From 7e5ff0b118bef21fefa543a6fd976daf6a5b3d17 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 13:20:35 +0200 Subject: [PATCH 01/94] 5564: Update composer dependencies --- composer.json | 2 +- composer.lock | 1755 ++++++++++++++++++++++++++++--------------------- 2 files changed, 1023 insertions(+), 734 deletions(-) diff --git a/composer.json b/composer.json index abe2133..bdd1081 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "api-platform/core": "^4.0", "composer/semver": "^3.4", "doctrine/dbal": "^4.0", - "doctrine/doctrine-bundle": "^2.13", + "doctrine/doctrine-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.0", "easycorp/easyadmin-bundle": "^4.0", diff --git a/composer.lock b/composer.lock index cbd5d07..32697ce 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "api-platform/core", - "version": "v4.1.17", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "e1191ca86e548d7610251bba2d8498b440cfd631" + "reference": "eb6396b0a2db169665d87af8883c69c1e6fd7d4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/e1191ca86e548d7610251bba2d8498b440cfd631", - "reference": "e1191ca86e548d7610251bba2d8498b440cfd631", + "url": "https://api.github.com/repos/api-platform/core/zipball/eb6396b0a2db169665d87af8883c69c1e6fd7d4b", + "reference": "eb6396b0a2db169665d87af8883c69c1e6fd7d4b", "shasum": "" }, "require": { @@ -32,7 +32,8 @@ "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", "symfony/translation-contracts": "^3.3", - "symfony/type-info": "^7.2", + "symfony/type-info": "^7.3 || 7.4.x-dev", + "symfony/validator": "^6.4 || ^7.1", "symfony/web-link": "^6.4 || ^7.1", "willdurand/negotiation": "^3.1" }, @@ -81,6 +82,7 @@ "doctrine/mongodb-odm-bundle": "^5.0", "doctrine/orm": "^2.17 || ^3.0", "elasticsearch/elasticsearch": "^7.17 || ^8.4 || ^9.0", + "ext-mongodb": "^1.21 || ^2.0", "friends-of-behat/mink-browserkit-driver": "^1.3.1", "friends-of-behat/mink-extension": "^2.2", "friends-of-behat/symfony-extension": "^2.1", @@ -92,17 +94,17 @@ "illuminate/pagination": "^11.0 || ^12.0", "illuminate/routing": "^11.0 || ^12.0", "illuminate/support": "^11.0 || ^12.0", - "jangregor/phpstan-prophecy": "^1.0", + "jangregor/phpstan-prophecy": "^2.1.11", "justinrainbow/json-schema": "^5.2.11", "laravel/framework": "^11.0 || ^12.0", "orchestra/testbench": "^9.1", "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", "phpstan/phpdoc-parser": "^1.29 || ^2.0", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-doctrine": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-symfony": "^2.0", "phpunit/phpunit": "11.5.x-dev", "psr/log": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", @@ -117,19 +119,21 @@ "symfony/console": "^6.4 || ^7.0", "symfony/css-selector": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/doctrine-bridge": "^6.4.2 || ^7.0.2", + "symfony/doctrine-bridge": "^6.4.2 || ^7.1", "symfony/dom-crawler": "^6.4 || ^7.0", "symfony/error-handler": "^6.4 || ^7.0", "symfony/event-dispatcher": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0", "symfony/finder": "^6.4 || ^7.0", "symfony/form": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || 7.4.x-dev", "symfony/http-client": "^6.4 || ^7.0", "symfony/intl": "^6.4 || ^7.0", + "symfony/json-streamer": "7.4.x-dev", "symfony/maker-bundle": "^1.24", "symfony/mercure-bundle": "*", "symfony/messenger": "^6.4 || ^7.0", + "symfony/object-mapper": "7.4.x-dev", "symfony/routing": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/security-core": "^6.4 || ^7.0", @@ -146,7 +150,6 @@ "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", "elasticsearch/elasticsearch": "To support Elasticsearch.", - "ocramius/package-versions": "To display the API Platform's version in the debug bar.", "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", "psr/cache-implementation": "To use metadata caching.", "ramsey/uuid": "To support Ramsey's UUID identifiers.", @@ -154,6 +157,7 @@ "symfony/config": "To load XML configuration files.", "symfony/expression-language": "To use authorization features.", "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/json-streamer": "To use the JSON Streamer component.", "symfony/messenger": "To support messenger integration.", "symfony/security": "To use authorization features.", "symfony/twig-bundle": "To use the Swagger UI integration.", @@ -179,7 +183,8 @@ "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-main": "4.2.x-dev" + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" } }, "autoload": { @@ -218,22 +223,22 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v4.1.17" + "source": "https://github.com/api-platform/core/tree/v4.2.2" }, - "time": "2025-06-19T10:14:20+00:00" + "time": "2025-10-09T08:35:20+00:00" }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -285,7 +290,7 @@ "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" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -295,13 +300,9 @@ { "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" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "doctrine/collections", @@ -391,34 +392,34 @@ }, { "name": "doctrine/dbal", - "version": "4.2.4", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b37d160498ea91a2382a2ebe825c4ea6254fc0ec" + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b37d160498ea91a2382a2ebe825c4ea6254fc0ec", - "reference": "b37d160498ea91a2382a2ebe825c4ea6254fc0ec", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3|^1", - "php": "^8.1", + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "13.0.0", + "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "2.1.17", - "phpstan/phpstan-phpunit": "2.0.6", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "10.5.46", - "slevomat/coding-standard": "8.16.2", - "squizlabs/php_codesniffer": "3.13.1", + "phpunit/phpunit": "11.5.23", + "slevomat/coding-standard": "8.24.0", + "squizlabs/php_codesniffer": "4.0.0", "symfony/cache": "^6.3.8|^7.0", "symfony/console": "^5.4|^6.3|^7.0" }, @@ -477,7 +478,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.2.4" + "source": "https://github.com/doctrine/dbal/tree/4.3.4" }, "funding": [ { @@ -493,7 +494,7 @@ "type": "tidelift" } ], - "time": "2025-06-15T23:15:01+00:00" + "time": "2025-10-09T09:11:36+00:00" }, { "name": "doctrine/deprecations", @@ -545,20 +546,21 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.15.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "d88294521a1bca943240adca65fa19ca8a7288c6" + "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/d88294521a1bca943240adca65fa19ca8a7288c6", - "reference": "d88294521a1bca943240adca65fa19ca8a7288c6", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/cd5d4da6a5f7cf3d8708e17211234657b5eb4e95", + "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95", "shasum": "" }, "require": { "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/deprecations": "^1.0", "doctrine/persistence": "^3.1 || ^4", "doctrine/sql-formatter": "^1.0.1", "php": "^8.1", @@ -566,7 +568,6 @@ "symfony/config": "^6.4 || ^7.0", "symfony/console": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/deprecation-contracts": "^2.1 || ^3", "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", "symfony/framework-bundle": "^6.4 || ^7.0", "symfony/service-contracts": "^2.5 || ^3" @@ -581,18 +582,17 @@ "require-dev": { "doctrine/annotations": "^1 || ^2", "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^13", - "doctrine/deprecations": "^1.0", + "doctrine/coding-standard": "^14", "doctrine/orm": "^2.17 || ^3.1", "friendsofphp/proxy-manager-lts": "^1.0", "phpstan/phpstan": "2.1.1", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^9.6.22", + "phpunit/phpunit": "^10.5.53 || ^12.3.10", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", "symfony/messenger": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^7.2", "symfony/property-info": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/stopwatch": "^6.4 || ^7.0", @@ -602,7 +602,7 @@ "symfony/var-exporter": "^6.4.1 || ^7.0.1", "symfony/web-profiler-bundle": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0", - "twig/twig": "^2.13 || ^3.0.4" + "twig/twig": "^2.14.7 || ^3.0.4" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -647,7 +647,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.15.0" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.0" }, "funding": [ { @@ -663,24 +663,24 @@ "type": "tidelift" } ], - "time": "2025-06-16T19:53:58+00:00" + "time": "2025-10-11T04:43:27+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.4.2", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" + "reference": "71c81279ca0e907c3edc718418b93fd63074856c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", - "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/71c81279ca0e907c3edc718418b93fd63074856c", + "reference": "71c81279ca0e907c3edc718418b93fd63074856c", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^2.4", + "doctrine/doctrine-bundle": "^2.4 || ^3.0", "doctrine/migrations": "^3.2", "php": "^7.2 || ^8.0", "symfony/deprecation-contracts": "^2.1 || ^3", @@ -688,7 +688,7 @@ }, "require-dev": { "composer/semver": "^3.0", - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^12 || ^14", "doctrine/orm": "^2.6 || ^3", "phpstan/phpstan": "^1.4 || ^2", "phpstan/phpstan-deprecation-rules": "^1 || ^2", @@ -732,7 +732,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.5.0" }, "funding": [ { @@ -748,7 +748,7 @@ "type": "tidelift" } ], - "time": "2025-03-11T17:36:26+00:00" + "time": "2025-10-12T17:06:40+00:00" }, { "name": "doctrine/event-manager", @@ -843,33 +843,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -914,7 +913,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -930,7 +929,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -1081,16 +1080,16 @@ }, { "name": "doctrine/migrations", - "version": "3.9.1", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "0f1e0c960ac29866d648a4f50142a74fe1cb6999" + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/0f1e0c960ac29866d648a4f50142a74fe1cb6999", - "reference": "0f1e0c960ac29866d648a4f50142a74fe1cb6999", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", "shasum": "" }, "require": { @@ -1108,18 +1107,18 @@ "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^13", "doctrine/orm": "^2.13 || ^3", "doctrine/persistence": "^2 || ^3 || ^4", "doctrine/sql-formatter": "^1.0", "ext-pdo_sqlite": "*", "fig/log-test": "^1", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-strict-rules": "^1.4", - "phpstan/phpstan-symfony": "^1.3", - "phpunit/phpunit": "^10.3", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^10.3 || ^11.0 || ^12.0", "symfony/cache": "^5.4 || ^6.0 || ^7.0", "symfony/process": "^5.4 || ^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0" @@ -1164,7 +1163,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.9.1" + "source": "https://github.com/doctrine/migrations/tree/3.9.4" }, "funding": [ { @@ -1180,20 +1179,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T07:19:23+00:00" + "time": "2025-08-19T06:41:07+00:00" }, { "name": "doctrine/orm", - "version": "3.4.3", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "ef607f26c2965fe460c55733cc7c031fb7e1f2fa" + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/ef607f26c2965fe460c55733cc7c031fb7e1f2fa", - "reference": "ef607f26c2965fe460c55733cc7c031fb7e1f2fa", + "url": "https://api.github.com/repos/doctrine/orm/zipball/5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", "shasum": "" }, "require": { @@ -1268,22 +1267,22 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.4.3" + "source": "https://github.com/doctrine/orm/tree/3.5.2" }, - "time": "2025-06-27T12:14:15+00:00" + "time": "2025-08-08T17:00:40+00:00" }, { "name": "doctrine/persistence", - "version": "4.0.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "45004aca79189474f113cbe3a53847c2115a55fa" + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/45004aca79189474f113cbe3a53847c2115a55fa", - "reference": "45004aca79189474f113cbe3a53847c2115a55fa", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/dcbdfe4b211ae09478e192289cae7ab0987b29a4", + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4", "shasum": "" }, "require": { @@ -1291,16 +1290,14 @@ "php": "^8.1", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, - "conflict": { - "doctrine/common": "<2.10" - }, "require-dev": { "doctrine/coding-standard": "^12", "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "^9.6", - "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -1349,7 +1346,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/4.0.0" + "source": "https://github.com/doctrine/persistence/tree/4.1.0" }, "funding": [ { @@ -1365,7 +1362,7 @@ "type": "tidelift" } ], - "time": "2024-11-01T21:49:07+00:00" + "time": "2025-08-21T16:00:31+00:00" }, { "name": "doctrine/sql-formatter", @@ -1424,16 +1421,16 @@ }, { "name": "easycorp/easyadmin-bundle", - "version": "v4.24.9", + "version": "v4.26.3", "source": { "type": "git", "url": "https://github.com/EasyCorp/EasyAdminBundle.git", - "reference": "1f1ab3a897107e5c62657a8cb04790e297b95a21" + "reference": "fed89754c7532ce5592fda43921d810b59e24c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/1f1ab3a897107e5c62657a8cb04790e297b95a21", - "reference": "1f1ab3a897107e5c62657a8cb04790e297b95a21", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/fed89754c7532ce5592fda43921d810b59e24c4c", + "reference": "fed89754c7532ce5592fda43921d810b59e24c4c", "shasum": "" }, "require": { @@ -1514,7 +1511,7 @@ ], "support": { "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", - "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.24.9" + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.26.3" }, "funding": [ { @@ -1522,7 +1519,7 @@ "type": "github" } ], - "time": "2025-06-17T18:10:27+00:00" + "time": "2025-10-08T18:21:48+00:00" }, { "name": "firebase/php-jwt", @@ -1589,22 +1586,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1695,7 +1692,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -1711,20 +1708,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -1732,7 +1729,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -1778,7 +1775,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -1794,20 +1791,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -1823,7 +1820,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1894,7 +1891,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -1910,20 +1907,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "itk-dev/openid-connect", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect.git", - "reference": "8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d" + "reference": "65951585ef2680432b33391fadffb1f87d210d27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d", - "reference": "8f3b8c0cc4abc7e91c1ee0f3e978deee6806da6d", + "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/65951585ef2680432b33391fadffb1f87d210d27", + "reference": "65951585ef2680432b33391fadffb1f87d210d27", "shasum": "" }, "require": { @@ -1971,9 +1968,9 @@ "description": "OpenID connect configuration package", "support": { "issues": "https://github.com/itk-dev/openid-connect/issues", - "source": "https://github.com/itk-dev/openid-connect/tree/4.0.1" + "source": "https://github.com/itk-dev/openid-connect/tree/4.0.2" }, - "time": "2025-01-13T09:01:13+00:00" + "time": "2025-10-06T07:25:31+00:00" }, { "name": "itk-dev/openid-connect-bundle", @@ -2212,16 +2209,16 @@ }, { "name": "masterminds/html5", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -2273,9 +2270,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2024-03-31T07:05:07+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "monolog/monolog", @@ -2639,16 +2636,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.2", + "version": "5.6.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", "shasum": "" }, "require": { @@ -2697,9 +2694,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" }, - "time": "2025-04-13T19:20:35+00:00" + "time": "2025-08-01T19:43:32+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -2761,16 +2758,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -2802,9 +2799,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "psr/cache", @@ -3411,16 +3408,16 @@ }, { "name": "symfony/amqp-messenger", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/amqp-messenger.git", - "reference": "635c859743bb8166d1b294e7c63a142a5a0ffc16" + "reference": "0ed5f72c1d9bbfcfc751b3832939a00a3246fe98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/635c859743bb8166d1b294e7c63a142a5a0ffc16", - "reference": "635c859743bb8166d1b294e7c63a142a5a0ffc16", + "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/0ed5f72c1d9bbfcfc751b3832939a00a3246fe98", + "reference": "0ed5f72c1d9bbfcfc751b3832939a00a3246fe98", "shasum": "" }, "require": { @@ -3460,7 +3457,7 @@ "description": "Symfony AMQP extension Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/amqp-messenger/tree/v7.3.0" + "source": "https://github.com/symfony/amqp-messenger/tree/v7.3.2" }, "funding": [ { @@ -3471,12 +3468,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-25T10:45:43+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/asset", @@ -3549,16 +3550,16 @@ }, { "name": "symfony/browser-kit", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "5384291845e74fd7d54f3d925c4a86ce12336593" + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/5384291845e74fd7d54f3d925c4a86ce12336593", - "reference": "5384291845e74fd7d54f3d925c4a86ce12336593", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f0b889b73a845cddef1d25fe207b37fd04cb5419", + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419", "shasum": "" }, "require": { @@ -3597,7 +3598,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.3.0" + "source": "https://github.com/symfony/browser-kit/tree/v7.3.2" }, "funding": [ { @@ -3608,25 +3609,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-05T10:15:41+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/cache", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e" + "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e", - "reference": "a7c6caa9d6113cebfb3020b427bcb021ebfdfc9e", + "url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", + "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", "shasum": "" }, "require": { @@ -3695,7 +3700,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.1" + "source": "https://github.com/symfony/cache/tree/v7.3.4" }, "funding": [ { @@ -3706,12 +3711,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/cache-contracts", @@ -3865,16 +3874,16 @@ }, { "name": "symfony/config", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "ba62ae565f1327c2f6366726312ed828c85853bc" + "reference": "8a09223170046d2cfda3d2e11af01df2c641e961" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/ba62ae565f1327c2f6366726312ed828c85853bc", - "reference": "ba62ae565f1327c2f6366726312ed828c85853bc", + "url": "https://api.github.com/repos/symfony/config/zipball/8a09223170046d2cfda3d2e11af01df2c641e961", + "reference": "8a09223170046d2cfda3d2e11af01df2c641e961", "shasum": "" }, "require": { @@ -3920,7 +3929,7 @@ "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/v7.3.0" + "source": "https://github.com/symfony/config/tree/v7.3.4" }, "funding": [ { @@ -3931,25 +3940,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-15T09:04:05+00:00" + "time": "2025-09-22T12:46:16+00:00" }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -4014,7 +4027,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -4025,25 +4038,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "8656c4848b48784c4bb8c4ae50d2b43f832cead8" + "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8656c4848b48784c4bb8c4ae50d2b43f832cead8", - "reference": "8656c4848b48784c4bb8c4ae50d2b43f832cead8", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/82119812ab0bf3425c1234d413efd1b19bb92ae4", + "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4", "shasum": "" }, "require": { @@ -4094,7 +4111,7 @@ "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/v7.3.1" + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.4" }, "funding": [ { @@ -4105,12 +4122,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4181,16 +4202,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "6c0acb248c46452ae2c15752dc71e72f3335403f" + "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/6c0acb248c46452ae2c15752dc71e72f3335403f", - "reference": "6c0acb248c46452ae2c15752dc71e72f3335403f", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/21cd48c34a47a0d0e303a590a67c3450fde55888", + "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888", "shasum": "" }, "require": { @@ -4239,7 +4260,7 @@ "symfony/security-core": "^6.4|^7.0", "symfony/stopwatch": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0" @@ -4270,7 +4291,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.1" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.4" }, "funding": [ { @@ -4281,25 +4302,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-26T13:02:59+00:00" + "time": "2025-09-24T09:56:23+00:00" }, { "name": "symfony/doctrine-messenger", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "099d9cd03f889c31c90d406fed07f25dc3732487" + "reference": "064159484ab330590b7b477f6c8835812f2e340f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/099d9cd03f889c31c90d406fed07f25dc3732487", - "reference": "099d9cd03f889c31c90d406fed07f25dc3732487", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/064159484ab330590b7b477f6c8835812f2e340f", + "reference": "064159484ab330590b7b477f6c8835812f2e340f", "shasum": "" }, "require": { @@ -4342,7 +4367,7 @@ "description": "Symfony Doctrine Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-messenger/tree/v7.3.0" + "source": "https://github.com/symfony/doctrine-messenger/tree/v7.3.4" }, "funding": [ { @@ -4353,25 +4378,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-26T11:30:13+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9" + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", "shasum": "" }, "require": { @@ -4409,7 +4438,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.3.1" + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" }, "funding": [ { @@ -4420,25 +4449,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-15T10:07:06+00:00" + "time": "2025-08-06T20:13:54+00:00" }, { "name": "symfony/dotenv", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "28347a897771d0c28e99b75166dd2689099f3045" + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/28347a897771d0c28e99b75166dd2689099f3045", - "reference": "28347a897771d0c28e99b75166dd2689099f3045", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/2192790a11f9e22cbcf9dc705a3ff22a5503923a", + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a", "shasum": "" }, "require": { @@ -4483,7 +4516,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v7.3.0" + "source": "https://github.com/symfony/dotenv/tree/v7.3.2" }, "funding": [ { @@ -4494,25 +4527,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-27T11:18:42+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/error-handler", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", "shasum": "" }, "require": { @@ -4560,7 +4597,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.1" + "source": "https://github.com/symfony/error-handler/tree/v7.3.4" }, "funding": [ { @@ -4571,25 +4608,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -4640,7 +4681,7 @@ "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/v7.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -4651,12 +4692,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4736,16 +4781,16 @@ }, { "name": "symfony/expression-language", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "26f4884a455e755e630a5fc372df124a3578da2e" + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/26f4884a455e755e630a5fc372df124a3578da2e", - "reference": "26f4884a455e755e630a5fc372df124a3578da2e", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/32d2d19c62e58767e6552166c32fb259975d2b23", + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23", "shasum": "" }, "require": { @@ -4780,7 +4825,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v7.3.0" + "source": "https://github.com/symfony/expression-language/tree/v7.3.2" }, "funding": [ { @@ -4791,25 +4836,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-15T11:52:45+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { @@ -4846,7 +4895,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -4857,25 +4906,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -4910,7 +4963,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -4921,25 +4974,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/flex", - "version": "v2.7.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "4ae50d368415a06820739e54d38a4a29d6df9155" + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/4ae50d368415a06820739e54d38a4a29d6df9155", - "reference": "4ae50d368415a06820739e54d38a4a29d6df9155", + "url": "https://api.github.com/repos/symfony/flex/zipball/f356aa35f3cf3d2f46c31d344c1098eb2d260426", + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426", "shasum": "" }, "require": { @@ -4978,7 +5035,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.7.1" + "source": "https://github.com/symfony/flex/tree/v2.8.2" }, "funding": [ { @@ -4989,25 +5046,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-28T14:22:54+00:00" + "time": "2025-08-22T07:17:23+00:00" }, { "name": "symfony/form", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "e06b02dd21b33b0cd7bb942c7e446ef7b22a2a5a" + "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/e06b02dd21b33b0cd7bb942c7e446ef7b22a2a5a", - "reference": "e06b02dd21b33b0cd7bb942c7e446ef7b22a2a5a", + "url": "https://api.github.com/repos/symfony/form/zipball/7b3eee0f4d4dfd1ff1be70a27474197330c61736", + "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736", "shasum": "" }, "require": { @@ -5075,7 +5136,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.3.1" + "source": "https://github.com/symfony/form/tree/v7.3.4" }, "funding": [ { @@ -5086,25 +5147,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "91905f22f26aa350a33b3b9690bdf94976b0d0ab" + "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/91905f22f26aa350a33b3b9690bdf94976b0d0ab", - "reference": "91905f22f26aa350a33b3b9690bdf94976b0d0ab", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b13e7cec5a144c8dba6f4233a2c53c00bc29e140", + "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140", "shasum": "" }, "require": { @@ -5194,7 +5259,7 @@ "symfony/string": "^6.4|^7.0", "symfony/translation": "^7.3", "symfony/twig-bundle": "^6.4|^7.0", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/web-link": "^6.4|^7.0", @@ -5229,7 +5294,7 @@ "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/v7.3.1" + "source": "https://github.com/symfony/framework-bundle/tree/v7.3.4" }, "funding": [ { @@ -5240,25 +5305,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-17T05:51:54+00:00" }, { "name": "symfony/http-client", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64", - "reference": "4403d87a2c16f33345dca93407a8714ee8c05a64", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { @@ -5266,6 +5335,7 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -5324,7 +5394,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.1" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -5335,12 +5405,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T07:58:39+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", @@ -5422,16 +5496,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6", + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6", "shasum": "" }, "require": { @@ -5481,7 +5555,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.4" }, "funding": [ { @@ -5492,25 +5566,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-23T15:07:14+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" + "reference": "b796dffea7821f035047235e076b60ca2446e3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf", + "reference": "b796dffea7821f035047235e076b60ca2446e3cf", "shasum": "" }, "require": { @@ -5595,7 +5673,7 @@ "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/v7.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.4" }, "funding": [ { @@ -5606,25 +5684,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T08:24:55+00:00" + "time": "2025-09-27T12:32:17+00:00" }, { "name": "symfony/intl", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "bd50940329ac1cfc4af0491cc4468f477d967e45" + "reference": "e6db84864655885d9dac676a9d7dde0d904fda54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/bd50940329ac1cfc4af0491cc4468f477d967e45", - "reference": "bd50940329ac1cfc4af0491cc4468f477d967e45", + "url": "https://api.github.com/repos/symfony/intl/zipball/e6db84864655885d9dac676a9d7dde0d904fda54", + "reference": "e6db84864655885d9dac676a9d7dde0d904fda54", "shasum": "" }, "require": { @@ -5681,7 +5763,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.3.1" + "source": "https://github.com/symfony/intl/tree/v7.3.4" }, "funding": [ { @@ -5692,25 +5774,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-06T16:10:07+00:00" + "time": "2025-09-08T14:11:30+00:00" }, { "name": "symfony/messenger", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "716c89b86ce58c4946d436d862694971c999d1aa" + "reference": "d9e04339404ba2dcd04c24172125516dc0e06c35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/716c89b86ce58c4946d436d862694971c999d1aa", - "reference": "716c89b86ce58c4946d436d862694971c999d1aa", + "url": "https://api.github.com/repos/symfony/messenger/zipball/d9e04339404ba2dcd04c24172125516dc0e06c35", + "reference": "d9e04339404ba2dcd04c24172125516dc0e06c35", "shasum": "" }, "require": { @@ -5770,7 +5856,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v7.3.1" + "source": "https://github.com/symfony/messenger/tree/v7.3.3" }, "funding": [ { @@ -5781,25 +5867,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", "shasum": "" }, "require": { @@ -5854,7 +5944,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.4" }, "funding": [ { @@ -5865,25 +5955,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "1b188c8abbbef25b111da878797514b7a8d33990" + "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/1b188c8abbbef25b111da878797514b7a8d33990", - "reference": "1b188c8abbbef25b111da878797514b7a8d33990", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/7acf2abe23e5019451399ba69fc8ed3d61d4d8f0", + "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0", "shasum": "" }, "require": { @@ -5932,7 +6026,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.0" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.4" }, "funding": [ { @@ -5943,12 +6037,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-21T12:17:46+00:00" + "time": "2025-09-24T16:45:39+00:00" }, { "name": "symfony/monolog-bundle", @@ -6033,16 +6131,16 @@ }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { @@ -6080,7 +6178,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -6091,12 +6189,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/password-hasher", @@ -6172,16 +6274,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -6230,7 +6332,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -6241,25 +6343,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "763d2a91fea5681509ca01acbc1c5e450d127811" + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/763d2a91fea5681509ca01acbc1c5e450d127811", - "reference": "763d2a91fea5681509ca01acbc1c5e450d127811", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", "shasum": "" }, "require": { @@ -6314,7 +6420,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.33.0" }, "funding": [ { @@ -6325,16 +6431,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-21T18:38:29+00:00" + "time": "2025-06-20T22:24:30+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -6397,7 +6507,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -6408,6 +6518,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -6417,7 +6531,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -6478,7 +6592,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -6489,6 +6603,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -6498,7 +6616,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -6559,7 +6677,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -6570,6 +6688,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -6579,16 +6701,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -6635,7 +6757,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -6646,25 +6768,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { @@ -6711,7 +6837,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -6722,16 +6848,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-20T12:04:08+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6790,7 +6920,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -6801,6 +6931,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -6810,16 +6944,16 @@ }, { "name": "symfony/property-access", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "518d15c8cca726ebe665dcd7154074584cf862e8" + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/518d15c8cca726ebe665dcd7154074584cf862e8", - "reference": "518d15c8cca726ebe665dcd7154074584cf862e8", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", "shasum": "" }, "require": { @@ -6866,7 +7000,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.3.1" + "source": "https://github.com/symfony/property-access/tree/v7.3.3" }, "funding": [ { @@ -6877,25 +7011,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-08-04T15:15:28+00:00" }, { "name": "symfony/property-info", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970" + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/90586acbf2a6dd13bee4f09f09111c8bd4773970", - "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace", + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace", "shasum": "" }, "require": { @@ -6952,7 +7090,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.3.1" + "source": "https://github.com/symfony/property-info/tree/v7.3.4" }, "funding": [ { @@ -6963,25 +7101,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-15T13:55:54+00:00" }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", "shasum": "" }, "require": { @@ -7033,7 +7175,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.3.4" }, "funding": [ { @@ -7044,25 +7186,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/runtime", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" + "reference": "3550e2711e30bfa5d808514781cd52d1cc1d9e9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", - "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", + "url": "https://api.github.com/repos/symfony/runtime/zipball/3550e2711e30bfa5d808514781cd52d1cc1d9e9f", + "reference": "3550e2711e30bfa5d808514781cd52d1cc1d9e9f", "shasum": "" }, "require": { @@ -7112,7 +7258,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.3.1" + "source": "https://github.com/symfony/runtime/tree/v7.3.4" }, "funding": [ { @@ -7123,25 +7269,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-09-11T15:31:28+00:00" }, { "name": "symfony/security-bundle", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "428a281fd66c8358adc2259c8578e6d81fbb7079" + "reference": "f750d9abccbeaa433c56f6a4eb2073166476a75a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/428a281fd66c8358adc2259c8578e6d81fbb7079", - "reference": "428a281fd66c8358adc2259c8578e6d81fbb7079", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/f750d9abccbeaa433c56f6a4eb2073166476a75a", + "reference": "f750d9abccbeaa433c56f6a4eb2073166476a75a", "shasum": "" }, "require": { @@ -7218,7 +7368,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v7.3.1" + "source": "https://github.com/symfony/security-bundle/tree/v7.3.4" }, "funding": [ { @@ -7229,25 +7379,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/security-core", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "fafab1003a31e51506e1a0a83e81c072211d81ba" + "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/fafab1003a31e51506e1a0a83e81c072211d81ba", - "reference": "fafab1003a31e51506e1a0a83e81c072211d81ba", + "url": "https://api.github.com/repos/symfony/security-core/zipball/68b9d3ca57615afde6152a1e1441fa035bea43f8", + "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8", "shasum": "" }, "require": { @@ -7305,7 +7459,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.3.1" + "source": "https://github.com/symfony/security-core/tree/v7.3.4" }, "funding": [ { @@ -7316,12 +7470,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-23T07:28:50+00:00" + "time": "2025-09-24T14:32:13+00:00" }, { "name": "symfony/security-csrf", @@ -7395,16 +7553,16 @@ }, { "name": "symfony/security-http", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "b7182ed0fd2359297f78ff6d407265168255ea84" + "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/b7182ed0fd2359297f78ff6d407265168255ea84", - "reference": "b7182ed0fd2359297f78ff6d407265168255ea84", + "url": "https://api.github.com/repos/symfony/security-http/zipball/1cf54d0648ebab23bf9b8972617b79f1995e13a9", + "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9", "shasum": "" }, "require": { @@ -7463,7 +7621,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.3.1" + "source": "https://github.com/symfony/security-http/tree/v7.3.4" }, "funding": [ { @@ -7474,31 +7632,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-09-09T17:06:44+00:00" }, { "name": "symfony/serializer", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "feaf837cedbbc8287986602223175d3fd639922d" + "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/feaf837cedbbc8287986602223175d3fd639922d", - "reference": "feaf837cedbbc8287986602223175d3fd639922d", + "url": "https://api.github.com/repos/symfony/serializer/zipball/0df5af266c6fe9a855af7db4fea86e13b9ca3ab1", + "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", @@ -7528,7 +7691,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -7561,7 +7724,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.3.1" + "source": "https://github.com/symfony/serializer/tree/v7.3.4" }, "funding": [ { @@ -7572,12 +7735,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-15T13:39:02+00:00" }, { "name": "symfony/service-contracts", @@ -7726,16 +7893,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -7750,7 +7917,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -7793,7 +7959,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -7804,25 +7970,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063" + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063", + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", "shasum": "" }, "require": { @@ -7889,7 +8059,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.1" + "source": "https://github.com/symfony/translation/tree/v7.3.4" }, "funding": [ { @@ -7900,12 +8070,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-07T11:39:36+00:00" }, { "name": "symfony/translation-contracts", @@ -7987,16 +8161,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "082eb15d8a4f9afee0acc4709fbe3aaf26d48891" + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/082eb15d8a4f9afee0acc4709fbe3aaf26d48891", - "reference": "082eb15d8a4f9afee0acc4709fbe3aaf26d48891", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/33558f013b7f6ed72805527c8405cae0062e47c5", + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5", "shasum": "" }, "require": { @@ -8078,7 +8252,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.3.0" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.3" }, "funding": [ { @@ -8089,25 +8263,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-19T13:28:56+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "bc23c11d9716fc2261ee26a32e654b0e8b1b1896" + "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/bc23c11d9716fc2261ee26a32e654b0e8b1b1896", - "reference": "bc23c11d9716fc2261ee26a32e654b0e8b1b1896", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/da5c778a8416fcce5318737c4d944f6fa2bb3f81", + "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81", "shasum": "" }, "require": { @@ -8162,7 +8340,7 @@ "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/v7.3.1" + "source": "https://github.com/symfony/twig-bundle/tree/v7.3.4" }, "funding": [ { @@ -8173,25 +8351,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-24T04:04:43+00:00" + "time": "2025-09-10T12:00:31+00:00" }, { "name": "symfony/type-info", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "5fa6e25e4195e73ce9e457b521ac5e61ec271150" + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/5fa6e25e4195e73ce9e457b521ac5e61ec271150", - "reference": "5fa6e25e4195e73ce9e457b521ac5e61ec271150", + "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b", "shasum": "" }, "require": { @@ -8241,7 +8423,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.3.1" + "source": "https://github.com/symfony/type-info/tree/v7.3.4" }, "funding": [ { @@ -8252,12 +8434,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-11T15:33:27+00:00" }, { "name": "symfony/uid", @@ -8335,38 +8521,38 @@ }, { "name": "symfony/ux-twig-component", - "version": "v2.27.0", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "0879cd53812b79e8b6a20e104b6e785a2ac2c013" + "reference": "2f445efda4d4400d4d1911ddf9710c366f339614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/0879cd53812b79e8b6a20e104b6e785a2ac2c013", - "reference": "0879cd53812b79e8b6a20e104b6e785a2ac2c013", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/2f445efda4d4400d4d1911ddf9710c366f339614", + "reference": "2f445efda4d4400d4d1911ddf9710c366f339614", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0|^8.0", "symfony/deprecation-contracts": "^2.2|^3.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0|^8.0", + "symfony/property-access": "^5.4|^6.0|^7.0|^8.0", "twig/twig": "^3.10.3" }, "conflict": { "symfony/config": "<5.4.0" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/phpunit-bridge": "^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0|^8.0", + "symfony/css-selector": "^5.4|^6.0|^7.0|^8.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0|^8.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.0|^7.0|^8.0", "symfony/stimulus-bundle": "^2.9.1", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/webpack-encore-bundle": "^1.15" + "symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.15|^2.3.0" }, "type": "symfony-bundle", "extra": { @@ -8398,7 +8584,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.27.0" + "source": "https://github.com/symfony/ux-twig-component/tree/v2.30.0" }, "funding": [ { @@ -8409,25 +8595,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-21T16:41:28+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/validator", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "e2f2497c869fc57446f735fbf00cff4de32ae8c3" + "reference": "5e29a348b5fac2227b6938a54db006d673bb813a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/e2f2497c869fc57446f735fbf00cff4de32ae8c3", - "reference": "e2f2497c869fc57446f735fbf00cff4de32ae8c3", + "url": "https://api.github.com/repos/symfony/validator/zipball/5e29a348b5fac2227b6938a54db006d673bb813a", + "reference": "5e29a348b5fac2227b6938a54db006d673bb813a", "shasum": "" }, "require": { @@ -8466,7 +8656,7 @@ "symfony/property-info": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", "symfony/translation": "^6.4.3|^7.0.3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/yaml": "^6.4|^7.0" }, "type": "library", @@ -8496,7 +8686,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.3.1" + "source": "https://github.com/symfony/validator/tree/v7.3.4" }, "funding": [ { @@ -8507,25 +8697,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-26T13:22:23+00:00" + "time": "2025-09-24T06:32:27+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", "shasum": "" }, "require": { @@ -8537,7 +8731,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -8580,7 +8773,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" }, "funding": [ { @@ -8591,25 +8784,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", - "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", "shasum": "" }, "require": { @@ -8657,7 +8854,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" }, "funding": [ { @@ -8668,12 +8865,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-15T09:04:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/web-link", @@ -8760,32 +8961,32 @@ }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c" + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/e335394b68a775a9b2bd173a8ba4fd2001f3870c", - "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/7ae70d44c24c3b913f308af8396169b5c6d9e0f5", + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/asset": "^5.4 || ^6.2 || ^7.0", - "symfony/config": "^5.4 || ^6.2 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0", + "symfony/asset": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/config": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0 || ^8.0", "symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0" }, "require-dev": { - "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0", - "symfony/http-client": "^5.4 || ^6.2 || ^7.0", - "symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0", - "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0", - "symfony/web-link": "^5.4 || ^6.2 || ^7.0" + "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-client": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/web-link": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "type": "symfony-bundle", "extra": { @@ -8812,7 +9013,7 @@ "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.2.0" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.3.0" }, "funding": [ { @@ -8823,25 +9024,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-02T07:27:19+00:00" + "time": "2025-08-05T11:43:32+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", "shasum": "" }, "require": { @@ -8884,7 +9089,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.3.3" }, "funding": [ { @@ -8895,12 +9100,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-03T06:57:57+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "twig/extra-bundle", @@ -9450,16 +9659,16 @@ }, { "name": "doctrine/data-fixtures", - "version": "2.0.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "f65b353922b7ac48f360428e19b22fcce5aba134" + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f65b353922b7ac48f360428e19b22fcce5aba134", - "reference": "f65b353922b7ac48f360428e19b22fcce5aba134", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f161e20f04ba5440a09330e156b40f04dd70d47f", + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f", "shasum": "" }, "require": { @@ -9513,7 +9722,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/2.0.3" + "source": "https://github.com/doctrine/data-fixtures/tree/2.1.0" }, "funding": [ { @@ -9529,20 +9738,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T19:59:58+00:00" + "time": "2025-07-08T17:48:20+00:00" }, { "name": "ergebnis/composer-normalize", - "version": "2.47.0", + "version": "2.48.2", "source": { "type": "git", "url": "https://github.com/ergebnis/composer-normalize.git", - "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544" + "reference": "86dc9731b8320f49e9be9ad6d8e4de9b8b0e9b8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/ed24b9f8901f8fbafeca98f662eaca39427f0544", - "reference": "ed24b9f8901f8fbafeca98f662eaca39427f0544", + "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/86dc9731b8320f49e9be9ad6d8e4de9b8b0e9b8b", + "reference": "86dc9731b8320f49e9be9ad6d8e4de9b8b0e9b8b", "shasum": "" }, "require": { @@ -9552,30 +9761,31 @@ "ergebnis/json-printer": "^3.7.0", "ext-json": "*", "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", - "localheinz/diff": "^1.2.0", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "localheinz/diff": "^1.3.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "composer/composer": "^2.8.3", - "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.46.0", - "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.53.0", + "ergebnis/phpstan-rules": "^2.11.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", "fakerphp/faker": "^1.24.1", "infection/infection": "~0.26.6", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.11", - "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", "phpunit/phpunit": "^9.6.20", - "rector/rector": "^2.0.11", + "rector/rector": "^2.1.4", "symfony/filesystem": "^5.4.41" }, "type": "composer-plugin", "extra": { "class": "Ergebnis\\Composer\\Normalize\\NormalizePlugin", "branch-alias": { - "dev-main": "2.44-dev" + "dev-main": "2.49-dev" }, "plugin-optional": true, "composer-normalize": { @@ -9612,43 +9822,48 @@ "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/composer-normalize" }, - "time": "2025-04-15T11:09:27+00:00" + "time": "2025-09-06T11:42:34+00:00" }, { "name": "ergebnis/json", - "version": "1.4.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/ergebnis/json.git", - "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69" + "reference": "7b56d2b5d9e897e75b43e2e753075a0904c921b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json/zipball/7656ac2aa6c2ca4408f96f599e9a17a22c464f69", - "reference": "7656ac2aa6c2ca4408f96f599e9a17a22c464f69", + "url": "https://api.github.com/repos/ergebnis/json/zipball/7b56d2b5d9e897e75b43e2e753075a0904c921b1", + "reference": "7b56d2b5d9e897e75b43e2e753075a0904c921b1", "shasum": "" }, "require": { "ext-json": "*", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", "ergebnis/data-provider": "^3.3.0", "ergebnis/license": "^2.5.0", "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpstan-rules": "^2.11.0", "ergebnis/phpunit-slow-test-detector": "^2.16.1", "fakerphp/faker": "^1.24.0", "infection/infection": "~0.26.6", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.10", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.1", - "phpunit/phpunit": "^9.6.18", - "rector/rector": "^1.2.10" + "phpstan/phpstan": "^2.1.22", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.24", + "rector/rector": "^2.1.4" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "1.7-dev" + }, "composer-normalize": { "indent-size": 2, "indent-style": "space" @@ -9680,20 +9895,20 @@ "security": "https://github.com/ergebnis/json/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json" }, - "time": "2024-11-17T11:51:22+00:00" + "time": "2025-09-06T09:08:45+00:00" }, { "name": "ergebnis/json-normalizer", - "version": "4.9.0", + "version": "4.10.1", "source": { "type": "git", "url": "https://github.com/ergebnis/json-normalizer.git", - "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1" + "reference": "77961faf2c651c3f05977b53c6c68e8434febf62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/cc4dcf3890448572a2d9bea97133c4d860e59fb1", - "reference": "cc4dcf3890448572a2d9bea97133c4d860e59fb1", + "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/77961faf2c651c3f05977b53c6c68e8434febf62", + "reference": "77961faf2c651c3f05977b53c6c68e8434febf62", "shasum": "" }, "require": { @@ -9703,7 +9918,7 @@ "ergebnis/json-schema-validator": "^4.2.0", "ext-json": "*", "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "composer/semver": "^3.4.3", @@ -9728,7 +9943,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.8-dev" + "dev-main": "4.11-dev" }, "composer-normalize": { "indent-size": 2, @@ -9762,24 +9977,24 @@ "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json-normalizer" }, - "time": "2025-04-10T13:13:04+00:00" + "time": "2025-09-06T09:18:13+00:00" }, { "name": "ergebnis/json-pointer", - "version": "3.6.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/ergebnis/json-pointer.git", - "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798" + "reference": "43bef355184e9542635e35dd2705910a3df4c236" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/4fc85d8edb74466d282119d8d9541ec7cffc0798", - "reference": "4fc85d8edb74466d282119d8d9541ec7cffc0798", + "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/43bef355184e9542635e35dd2705910a3df4c236", + "reference": "43bef355184e9542635e35dd2705910a3df4c236", "shasum": "" }, "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.43.0", @@ -9800,7 +10015,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.8-dev" }, "composer-normalize": { "indent-size": 2, @@ -9835,28 +10050,29 @@ "security": "https://github.com/ergebnis/json-pointer/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json-pointer" }, - "time": "2024-11-17T12:37:06+00:00" + "time": "2025-09-06T09:28:19+00:00" }, { "name": "ergebnis/json-printer", - "version": "3.7.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/ergebnis/json-printer.git", - "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82" + "reference": "211d73fc7ec6daf98568ee6ed6e6d133dee8503e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/ced41fce7854152f0e8f38793c2ffe59513cdd82", - "reference": "ced41fce7854152f0e8f38793c2ffe59513cdd82", + "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/211d73fc7ec6daf98568ee6ed6e6d133dee8503e", + "reference": "211d73fc7ec6daf98568ee6ed6e6d133dee8503e", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", "ergebnis/data-provider": "^3.3.0", "ergebnis/license": "^2.5.0", "ergebnis/php-cs-fixer-config": "^6.37.0", @@ -9872,6 +10088,15 @@ "rector/rector": "^1.2.10" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.9-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, "autoload": { "psr-4": { "Ergebnis\\Json\\Printer\\": "src/" @@ -9900,20 +10125,20 @@ "security": "https://github.com/ergebnis/json-printer/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json-printer" }, - "time": "2024-11-17T11:20:51+00:00" + "time": "2025-09-06T09:59:26+00:00" }, { "name": "ergebnis/json-schema-validator", - "version": "4.4.0", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/ergebnis/json-schema-validator.git", - "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec" + "reference": "b739527a480a9e3651360ad351ea77e7e9019df2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/85f90c81f718aebba1d738800af83eeb447dc7ec", - "reference": "85f90c81f718aebba1d738800af83eeb447dc7ec", + "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/b739527a480a9e3651360ad351ea77e7e9019df2", + "reference": "b739527a480a9e3651360ad351ea77e7e9019df2", "shasum": "" }, "require": { @@ -9921,7 +10146,7 @@ "ergebnis/json-pointer": "^3.4.0", "ext-json": "*", "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.44.0", @@ -9942,7 +10167,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.4-dev" + "dev-main": "4.6-dev" }, "composer-normalize": { "indent-size": 2, @@ -9977,7 +10202,7 @@ "security": "https://github.com/ergebnis/json-schema-validator/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/json-schema-validator" }, - "time": "2024-11-18T06:32:28+00:00" + "time": "2025-09-06T11:37:35+00:00" }, { "name": "evenement/evenement", @@ -10091,16 +10316,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -10110,10 +10335,10 @@ "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", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -10140,7 +10365,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -10148,63 +10373,62 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.76.0", + "version": "v3.88.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "0e3c484cef0ae9314b0f85986a36296087432c40" + "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/0e3c484cef0ae9314b0f85986a36296087432c40", - "reference": "0e3c484cef0ae9314b0f85986a36296087432c40", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99", + "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99", "shasum": "" }, "require": { - "clue/ndjson-react": "^1.0", + "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", + "fidry/cpu-core-counter": "^1.3", "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", + "react/event-loop": "^1.5", + "react/promise": "^3.3", + "react/socket": "^1.16", + "react/stream": "^1.4", "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", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7", + "infection/infection": "^0.31.0", + "justinrainbow/json-schema": "^6.5", "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" + "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", + "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2", + "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -10245,7 +10469,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.76.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2" }, "funding": [ { @@ -10253,7 +10477,7 @@ "type": "github" } ], - "time": "2025-06-30T14:15:06+00:00" + "time": "2025-09-27T00:24:15+00:00" }, { "name": "hautelook/alice-bundle", @@ -10331,16 +10555,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.4.2", + "version": "6.6.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", - "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/68ba7677532803cc0c5900dd5a4d730537f2b2f3", + "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3", "shasum": "" }, "require": { @@ -10350,7 +10574,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "1.2.0", + "json-schema/json-schema-test-suite": "^23.2", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -10400,26 +10624,26 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.0" }, - "time": "2025-06-03T18:27:04+00:00" + "time": "2025-10-10T11:34:09+00:00" }, { "name": "localheinz/diff", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/localheinz/diff.git", - "reference": "ec413943c2b518464865673fd5b38f7df867a010" + "reference": "33bd840935970cda6691c23fc7d94ae764c0734c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/localheinz/diff/zipball/ec413943c2b518464865673fd5b38f7df867a010", - "reference": "ec413943c2b518464865673fd5b38f7df867a010", + "url": "https://api.github.com/repos/localheinz/diff/zipball/33bd840935970cda6691c23fc7d94ae764c0734c", + "reference": "33bd840935970cda6691c23fc7d94ae764c0734c", "shasum": "" }, "require": { - "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { "phpunit/phpunit": "^7.5.0 || ^8.5.23", @@ -10455,22 +10679,22 @@ ], "support": { "issues": "https://github.com/localheinz/diff/issues", - "source": "https://github.com/localheinz/diff/tree/1.2.0" + "source": "https://github.com/localheinz/diff/tree/1.3.0" }, - "time": "2024-12-04T14:16:01+00:00" + "time": "2025-08-30T09:44:18+00:00" }, { "name": "marc-mabe/php-enum", - "version": "v4.7.1", + "version": "v4.7.2", "source": { "type": "git", "url": "https://github.com/marc-mabe/php-enum.git", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", "shasum": "" }, "require": { @@ -10528,22 +10752,22 @@ ], "support": { "issues": "https://github.com/marc-mabe/php-enum/issues", - "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" }, - "time": "2024-11-28T04:54:44+00:00" + "time": "2025-09-14T11:18:39+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -10582,7 +10806,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -10590,7 +10814,7 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nelmio/alice", @@ -10690,16 +10914,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -10718,7 +10942,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -10742,9 +10966,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -10914,16 +11138,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.17", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" - }, + "version": "2.1.31", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", - "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", "shasum": "" }, "require": { @@ -10968,20 +11187,20 @@ "type": "github" } ], - "time": "2025-05-21T20:55:28+00:00" + "time": "2025-10-10T14:14:11+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.3", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "4497663eb17b9d29211830df5aceaa3a4d256a35" + "reference": "5eaf37b87288474051469aee9f937fc9d862f330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4497663eb17b9d29211830df5aceaa3a4d256a35", - "reference": "4497663eb17b9d29211830df5aceaa3a4d256a35", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/5eaf37b87288474051469aee9f937fc9d862f330", + "reference": "5eaf37b87288474051469aee9f937fc9d862f330", "shasum": "" }, "require": { @@ -11015,7 +11234,8 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", - "symfony/cache": "^5.4" + "symfony/cache": "^5.4", + "symfony/uid": "^5.4 || ^6.4 || ^7.3" }, "type": "phpstan-extension", "extra": { @@ -11038,27 +11258,27 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.3" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.10" }, - "time": "2025-05-05T15:28:52+00:00" + "time": "2025-10-06T10:01:02+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", - "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9a9b161baee88a5f5c58d816943cff354ff233dc", + "reference": "9a9b161baee88a5f5c58d816943cff354ff233dc", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.18" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -11091,22 +11311,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.7" }, - "time": "2025-03-26T12:47:06+00:00" + "time": "2025-07-13T11:31:46+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.6", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "5005288e07583546ea00b52de4a9ac412eb869d7" + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/5005288e07583546ea00b52de4a9ac412eb869d7", - "reference": "5005288e07583546ea00b52de4a9ac412eb869d7", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/8820c22d785c235f69bb48da3d41e688bc8a1796", + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796", "shasum": "" }, "require": { @@ -11162,22 +11382,22 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.6" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.8" }, - "time": "2025-05-14T07:00:05+00:00" + "time": "2025-09-07T06:55:50+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.10", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", - "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { @@ -11234,7 +11454,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { @@ -11254,7 +11474,7 @@ "type": "tidelift" } ], - "time": "2025-06-18T08:56:18+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11503,16 +11723,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.25", + "version": "11.5.42", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "864ab32b3ff52058f917c5b19b3cef821e4a4f1b" + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/864ab32b3ff52058f917c5b19b3cef821e4a4f1b", - "reference": "864ab32b3ff52058f917c5b19b3cef821e4a4f1b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", "shasum": "" }, "require": { @@ -11522,24 +11742,24 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-code-coverage": "^11.0.11", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.1", + "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.0", + "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.2", + "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -11584,7 +11804,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42" }, "funding": [ { @@ -11608,7 +11828,7 @@ "type": "tidelift" } ], - "time": "2025-06-27T04:36:07+00:00" + "time": "2025-09-28T12:09:13+00:00" }, { "name": "react/cache", @@ -11907,23 +12127,23 @@ }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -11968,7 +12188,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -11976,7 +12196,7 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "react/socket", @@ -12138,21 +12358,21 @@ }, { "name": "rector/rector", - "version": "2.1.0", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "d513dea45a94394b660e15c155d1fa27826f8e30" + "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/d513dea45a94394b660e15c155d1fa27826f8e30", - "reference": "d513dea45a94394b660e15c155d1fa27826f8e30", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f", + "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.17" + "phpstan/phpstan": "^2.1.26" }, "conflict": { "rector/rector-doctrine": "*", @@ -12186,7 +12406,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.1.0" + "source": "https://github.com/rectorphp/rector/tree/2.2.3" }, "funding": [ { @@ -12194,7 +12414,7 @@ "type": "github" } ], - "time": "2025-06-24T20:26:57+00:00" + "time": "2025-10-11T21:50:23+00:00" }, { "name": "sebastian/cli-parser", @@ -12368,16 +12588,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.1", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { @@ -12436,15 +12656,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T06:57:01+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", @@ -12649,16 +12881,16 @@ }, { "name": "sebastian/exporter", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { @@ -12672,7 +12904,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -12715,15 +12947,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-12-05T09:17:50+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", @@ -12961,23 +13205,23 @@ }, { "name": "sebastian/recursion-context", - "version": "6.0.2", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -13013,28 +13257,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-07-03T05:10:34+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "5.1.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -13070,15 +13326,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:35:50+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", @@ -13253,16 +13521,16 @@ }, { "name": "symfony/debug-bundle", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "781acc90f31f5fe18915f9276890864ebbbe3da8" + "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/781acc90f31f5fe18915f9276890864ebbbe3da8", - "reference": "781acc90f31f5fe18915f9276890864ebbbe3da8", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/30f922edd53dd85238f1f26dbb68a044109f8f0e", + "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e", "shasum": "" }, "require": { @@ -13304,7 +13572,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v7.3.0" + "source": "https://github.com/symfony/debug-bundle/tree/v7.3.4" }, "funding": [ { @@ -13315,25 +13583,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-04T13:21:13+00:00" + "time": "2025-09-10T12:00:31+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.63.0", + "version": "v1.64.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "69478ab39bc303abfbe3293006a78b09a8512425" + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/69478ab39bc303abfbe3293006a78b09a8512425", - "reference": "69478ab39bc303abfbe3293006a78b09a8512425", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c86da84640b0586e92aee2b276ee3638ef2f425a", + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a", "shasum": "" }, "require": { @@ -13361,6 +13633,7 @@ "symfony/http-client": "^6.4|^7.0", "symfony/phpunit-bridge": "^6.4.1|^7.0", "symfony/security-core": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0", "twig/twig": "^3.0|^4.x-dev" }, @@ -13396,7 +13669,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.63.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.64.0" }, "funding": [ { @@ -13412,11 +13685,11 @@ "type": "tidelift" } ], - "time": "2025-04-26T01:41:37+00:00" + "time": "2025-06-23T16:12:08+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -13476,7 +13749,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -13487,6 +13760,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -13496,7 +13773,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -13552,7 +13829,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -13563,6 +13840,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -13572,16 +13853,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -13613,7 +13894,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -13624,25 +13905,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v7.3.1", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "47c994d8f08817122ffb48bf2ea4fb97b7e00d51" + "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/47c994d8f08817122ffb48bf2ea4fb97b7e00d51", - "reference": "47c994d8f08817122ffb48bf2ea4fb97b7e00d51", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f305fa4add690bb7d6b14ab61f37c3bd061a3dd7", + "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7", "shasum": "" }, "require": { @@ -13698,7 +13983,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.1" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.4" }, "funding": [ { @@ -13709,12 +13994,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-05T09:30:41+00:00" + "time": "2025-09-25T08:03:55+00:00" }, { "name": "theofidry/alice-data-fixtures", From aa24a1adc6f02385dd23291000ca642e8fb8684b Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 13:25:11 +0200 Subject: [PATCH 02/94] 5564: Update to phpunit 12 --- composer.json | 4 +- composer.lock | 534 +++++++++++++++++++++----------------------------- symfony.lock | 6 - 3 files changed, 222 insertions(+), 322 deletions(-) diff --git a/composer.json b/composer.json index bdd1081..8cbf299 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "api-platform/core": "^4.0", "composer/semver": "^3.4", "doctrine/dbal": "^4.0", - "doctrine/doctrine-bundle": "^3.0", + "doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.0", "easycorp/easyadmin-bundle": "^4.0", @@ -53,7 +53,7 @@ "phpstan/phpstan-doctrine": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-symfony": "^2.0", - "phpunit/phpunit": "^11.3", + "phpunit/phpunit": "^12.0", "rector/rector": "^2.0", "symfony/css-selector": "^7.2", "symfony/debug-bundle": "^7.2", diff --git a/composer.lock b/composer.lock index 32697ce..01d397c 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": "d347cd87a3a3926f9844ee4768154429", + "content-hash": "2dbe013a397c0321b36acd5b037eac54", "packages": [ { "name": "api-platform/core", @@ -11388,35 +11388,34 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.11", + "version": "12.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", - "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", - "php": ">=8.2", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-text-template": "^4.0.1", - "sebastian/code-unit-reverse-lookup": "^4.0.1", - "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", - "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.2", + "nikic/php-parser": "^5.6.1", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "phpunit/phpunit": "^12.3.7" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -11425,7 +11424,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0.x-dev" + "dev-main": "12.4.x-dev" } }, "autoload": { @@ -11454,7 +11453,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.4.0" }, "funding": [ { @@ -11474,32 +11473,32 @@ "type": "tidelift" } ], - "time": "2025-08-27T14:37:49+00:00" + "time": "2025-09-24T13:44:41+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -11527,7 +11526,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" }, "funding": [ { @@ -11535,28 +11534,28 @@ "type": "github" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2025-02-07T04:58:37+00:00" }, { "name": "phpunit/php-invoker", - "version": "5.0.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", - "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-pcntl": "*" @@ -11564,7 +11563,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -11591,7 +11590,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" }, "funding": [ { @@ -11599,32 +11598,32 @@ "type": "github" } ], - "time": "2024-07-03T05:07:44+00:00" + "time": "2025-02-07T04:58:58+00:00" }, { "name": "phpunit/php-text-template", - "version": "4.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", - "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -11651,7 +11650,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" }, "funding": [ { @@ -11659,32 +11658,32 @@ "type": "github" } ], - "time": "2024-07-03T05:08:43+00:00" + "time": "2025-02-07T04:59:16+00:00" }, { "name": "phpunit/php-timer", - "version": "7.0.1", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", - "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -11711,7 +11710,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" }, "funding": [ { @@ -11719,20 +11718,20 @@ "type": "github" } ], - "time": "2024-07-03T05:09:35+00:00" + "time": "2025-02-07T04:59:38+00:00" }, { "name": "phpunit/phpunit", - "version": "11.5.42", + "version": "12.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c" + "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", - "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", + "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", "shasum": "" }, "require": { @@ -11745,34 +11744,30 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.11", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-invoker": "^5.0.1", - "phpunit/php-text-template": "^4.0.1", - "phpunit/php-timer": "^7.0.1", - "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.2", - "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.2", - "sebastian/global-state": "^7.0.2", - "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.3", - "sebastian/version": "^5.0.2", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.4.0", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, "bin": [ "phpunit" ], "type": "library", "extra": { "branch-alias": { - "dev-main": "11.5-dev" + "dev-main": "12.4-dev" } }, "autoload": { @@ -11804,7 +11799,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.1" }, "funding": [ { @@ -11828,7 +11823,7 @@ "type": "tidelift" } ], - "time": "2025-09-28T12:09:13+00:00" + "time": "2025-10-09T14:08:29+00:00" }, { "name": "react/cache", @@ -12418,28 +12413,28 @@ }, { "name": "sebastian/cli-parser", - "version": "3.0.2", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", - "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.2-dev" } }, "autoload": { @@ -12463,152 +12458,51 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2024-07-03T04:41:36+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", - "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "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": "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", - "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" - }, - "funding": [ + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2025-03-19T07:56:08+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", - "reference": "183a9b2632194febd219bb9246eee421dad8d45e", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "phpunit/phpunit": "^11.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "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", - "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" - }, - "funding": [ + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" } ], - "time": "2024-07-03T04:45:54+00:00" + "time": "2025-09-14T09:36:45+00:00" }, { "name": "sebastian/comparator", - "version": "6.3.2", + "version": "7.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/diff": "^6.0", - "sebastian/exporter": "^6.0" + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^11.4" + "phpunit/phpunit": "^12.2" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -12616,7 +12510,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.3-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -12656,7 +12550,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" }, "funding": [ { @@ -12676,33 +12570,33 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:07:46+00:00" + "time": "2025-08-20T11:27:00+00:00" }, { "name": "sebastian/complexity", - "version": "4.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", - "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -12726,7 +12620,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" }, "funding": [ { @@ -12734,33 +12628,33 @@ "type": "github" } ], - "time": "2024-07-03T04:49:50+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { "name": "sebastian/diff", - "version": "6.0.2", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", - "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -12793,7 +12687,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" }, "funding": [ { @@ -12801,27 +12695,27 @@ "type": "github" } ], - "time": "2024-07-03T04:53:05+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "sebastian/environment", - "version": "7.2.1", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", - "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-posix": "*" @@ -12829,7 +12723,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.2-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -12857,7 +12751,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" }, "funding": [ { @@ -12877,34 +12771,34 @@ "type": "tidelift" } ], - "time": "2025-05-21T11:55:47+00:00" + "time": "2025-08-12T14:11:56+00:00" }, { "name": "sebastian/exporter", - "version": "6.3.2", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", - "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.2", - "sebastian/recursion-context": "^6.0" + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.3-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -12947,7 +12841,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" }, "funding": [ { @@ -12967,35 +12861,35 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:12:51+00:00" + "time": "2025-09-24T06:16:11+00:00" }, { "name": "sebastian/global-state", - "version": "7.0.2", + "version": "8.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", - "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -13021,41 +12915,53 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-07-03T04:57:36+00:00" + "time": "2025-08-29T11:29:25+00:00" }, { "name": "sebastian/lines-of-code", - "version": "3.0.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", - "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -13079,7 +12985,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" }, "funding": [ { @@ -13087,34 +12993,34 @@ "type": "github" } ], - "time": "2024-07-03T04:58:38+00:00" + "time": "2025-02-07T04:57:28+00:00" }, { "name": "sebastian/object-enumerator", - "version": "6.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", - "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "php": ">=8.2", - "sebastian/object-reflector": "^4.0", - "sebastian/recursion-context": "^6.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -13137,7 +13043,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" }, "funding": [ { @@ -13145,32 +13051,32 @@ "type": "github" } ], - "time": "2024-07-03T05:00:13+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { "name": "sebastian/object-reflector", - "version": "4.0.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", - "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -13193,7 +13099,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { @@ -13201,32 +13107,32 @@ "type": "github" } ], - "time": "2024-07-03T05:01:32+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { "name": "sebastian/recursion-context", - "version": "6.0.3", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", - "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -13257,7 +13163,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { @@ -13277,32 +13183,32 @@ "type": "tidelift" } ], - "time": "2025-08-13T04:42:22+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { "name": "sebastian/type", - "version": "5.1.3", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", - "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -13326,7 +13232,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" }, "funding": [ { @@ -13346,29 +13252,29 @@ "type": "tidelift" } ], - "time": "2025-08-09T06:55:48+00:00" + "time": "2025-08-09T06:57:12+00:00" }, { "name": "sebastian/version", - "version": "5.0.2", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", - "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -13392,7 +13298,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" }, "funding": [ { @@ -13400,7 +13306,7 @@ "type": "github" } ], - "time": "2024-10-09T05:16:32+00:00" + "time": "2025-02-07T05:00:38+00:00" }, { "name": "staabm/side-effects-detector", diff --git a/symfony.lock b/symfony.lock index 9cdfe30..e419996 100644 --- a/symfony.lock +++ b/symfony.lock @@ -277,12 +277,6 @@ "sebastian/cli-parser": { "version": "1.0.1" }, - "sebastian/code-unit": { - "version": "1.0.8" - }, - "sebastian/code-unit-reverse-lookup": { - "version": "2.0.3" - }, "sebastian/comparator": { "version": "4.0.6" }, From e83bf18b20d6786d54e64581f770115785a89932 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 14:32:07 +0200 Subject: [PATCH 03/94] 5564: Update itk docker template, update github actions setup --- .env | 2 + .github/workflows/apispec.yaml | 115 +++++++++ .github/workflows/changelog.yaml | 29 +++ .github/workflows/composer.yaml | 80 +++++++ .github/workflows/doctrine.yaml | 41 ++++ .github/workflows/github_build_release.yml | 2 +- .github/workflows/javascript.yaml | 37 +++ .github/workflows/markdown.yaml | 44 ++++ .github/workflows/php.yaml | 60 +++++ .github/workflows/pr.yaml | 263 ++------------------- .github/workflows/styles.yaml | 37 +++ .github/workflows/twig.yaml | 51 ++++ .github/workflows/yaml.yaml | 41 ++++ .markdownlint.jsonc | 22 ++ .markdownlintignore | 12 + .php-cs-fixer.dist.php | 33 +-- .prettierrc.yaml | 11 + .twig-cs-fixer.dist.php | 16 ++ docker-compose.dev.yml | 2 +- docker-compose.redirect.yml | 2 +- docker-compose.server.yml | 3 +- docker-compose.yml | 42 +++- 22 files changed, 673 insertions(+), 272 deletions(-) create mode 100644 .github/workflows/apispec.yaml create mode 100644 .github/workflows/changelog.yaml create mode 100644 .github/workflows/composer.yaml create mode 100644 .github/workflows/doctrine.yaml create mode 100644 .github/workflows/javascript.yaml create mode 100644 .github/workflows/markdown.yaml create mode 100644 .github/workflows/php.yaml create mode 100644 .github/workflows/styles.yaml create mode 100644 .github/workflows/twig.yaml create mode 100644 .github/workflows/yaml.yaml create mode 100644 .markdownlint.jsonc create mode 100644 .markdownlintignore create mode 100644 .prettierrc.yaml create mode 100644 .twig-cs-fixer.dist.php diff --git a/.env b/.env index 0a1d819..79e2667 100644 --- a/.env +++ b/.env @@ -2,6 +2,8 @@ COMPOSE_PROJECT_NAME=itksites #COMPOSE_DOMAIN=itksites.local.itkdev.dk COMPOSE_DOMAIN=sites.itkdev.dk +ITKDEV_TEMPLATE=symfony-6 + # In all environments, the following files are loaded if they exist, # the latter taking precedence over the former: # diff --git a/.github/workflows/apispec.yaml b/.github/workflows/apispec.yaml new file mode 100644 index 0000000..8487438 --- /dev/null +++ b/.github/workflows/apispec.yaml @@ -0,0 +1,115 @@ +name: API Spec review + +env: + COMPOSE_USER: root + +on: + pull_request: + +jobs: + api-spec-updated: + name: Ensure committed API specification is up to date + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 + + - name: Create docker network + run: | + docker network create frontend + + - name: Composer install + run: | + docker compose run --rm phpfpm composer install + + - name: Export API specification + run: | + docker compose run --rm phpfpm bin/console api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction + + - name: Check for changes in specification + id: git-diff-spec + continue-on-error: true + run: git diff --diff-filter=ACMRT --exit-code public/api-spec-v1.yaml + + - name: Comment PR + if: steps.git-diff-spec.outcome == 'failure' + env: + GH_TOKEN: ${{ github.token }} + run: | + echo '## 🛑 Exported API specification file not up to date' > var/comment.md + echo '' >> var/comment.md + echo 'Please run `task api:spec:export` to export the API specification. Then commit and push the changes.' >> var/comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file var/comment.md --create-if-none --edit-last + + - name: Fail job, api spec is not up to date + if: steps.git-diff-spec.outcome == 'failure' + run: | + exit 1 + + detect-breaking-changes: + name: Detect breaking changes in API specification + runs-on: ubuntu-latest + needs: [api-spec-updated] + steps: + - name: Check out BASE rev + uses: actions/checkout@v5 + with: + ref: ${{ github.base_ref }} + path: base + + - name: Check out HEAD rev + uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + path: head + + - name: Run OpenAPI Changed (from HEAD rev) + id: api-changed + continue-on-error: true + uses: docker://openapitools/openapi-diff:latest + with: + args: --fail-on-changed base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-changed.md + + - name: Run OpenAPI Incompatible (from HEAD rev) + id: api-incompatible + continue-on-error: true + uses: docker://openapitools/openapi-diff:latest + with: + args: --fail-on-incompatible base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-incompatible.md + + - name: Comment PR with no changes + if: steps.api-changed.outcome == 'success' && steps.api-incompatible.outcome == 'success' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr comment ${{ github.event.pull_request.number }} --body "✅ **No changes detected in API specification**" --create-if-none --edit-last + + - name: Comment PR with non-breaking changes + if: steps.api-changed.outcome == 'failure' && steps.api-incompatible.outcome == 'success' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "## ⚠️ Non-Breaking changes detected in API specification" > ../comment.md + echo "" >> ../comment.md + cat ../api-spec-changed.md >> ../comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last + + - name: Comment PR with breaking changes + if: steps.api-incompatible.outcome == 'failure' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "## 🛑 Breaking changes detected in API specification" > ../comment.md + echo "" >> ../comment.md + cat ../api-spec-incompatible.md >> ../comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last + + - name: Fail if breaking changes detected + if: steps.api-incompatible.outcome == 'failure' + run: | + exit 1 \ No newline at end of file diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml new file mode 100644 index 0000000..fead572 --- /dev/null +++ b/.github/workflows/changelog.yaml @@ -0,0 +1,29 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/changelog.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Changelog +### +### Checks that changelog has been updated + +name: Changelog + +on: + pull_request: + +jobs: + changelog: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 + + - name: Git fetch + run: git fetch + + - name: Check that changelog has been updated. + run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml new file mode 100644 index 0000000..a0e5a94 --- /dev/null +++ b/.github/workflows/composer.yaml @@ -0,0 +1,80 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/composer.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Composer +### +### Validates composer.json and checks that it's normalized. +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [ergebnis/composer-normalize](https://github.com/ergebnis/composer-normalize) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev ergebnis/composer-normalize +### ``` +### +### Normalize `composer.json` by running +### +### ``` shell +### docker compose run --rm phpfpm composer normalize +### ``` + +name: Composer + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + composer-validate: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer validate --strict + + composer-normalized: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm composer normalize --dry-run + + composer-audit: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer audit diff --git a/.github/workflows/doctrine.yaml b/.github/workflows/doctrine.yaml new file mode 100644 index 0000000..823c375 --- /dev/null +++ b/.github/workflows/doctrine.yaml @@ -0,0 +1,41 @@ +name: Doctrine + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + coding-standards: + name: Validate Schema + runs-on: ubuntu-latest + env: + APP_ENV: prod + + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - name: Run Composer Install + run: | + docker compose run --rm phpfpm composer install + + - name: Run Doctrine Migrations + run: | + docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Setup messenger "failed" doctrine transport to ensure db schema is updated + run: | + docker compose run --rm phpfpm bin/console messenger:setup-transports failed + + - name: Validate Doctrine schema + run: | + docker compose run --rm phpfpm bin/console doctrine:schema:validate \ No newline at end of file diff --git a/.github/workflows/github_build_release.yml b/.github/workflows/github_build_release.yml index 28f350e..3817c60 100644 --- a/.github/workflows/github_build_release.yml +++ b/.github/workflows/github_build_release.yml @@ -16,7 +16,7 @@ jobs: APP_ENV: prod steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Composer install run: | diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml new file mode 100644 index 0000000..44b976e --- /dev/null +++ b/.github/workflows/javascript.yaml @@ -0,0 +1,37 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/javascript.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony JavaScript (and TypeScript) +### +### Validates JavaScript files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. + +name: JavaScript + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + javascript-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm prettier 'assets/**/*.js' --check diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml new file mode 100644 index 0000000..ae83163 --- /dev/null +++ b/.github/workflows/markdown.yaml @@ -0,0 +1,44 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/markdown.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Markdown +### +### Lints Markdown files (`**/*.md`) in the project. +### +### [markdownlint-cli configuration +### files](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration), +### `.markdownlint.jsonc` and `.markdownlintignore`, control what is actually +### linted and how. +### +### #### Assumptions +### +### 1. A docker compose service named `markdownlint` for running `markdownlint` +### (from +### [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli)) +### exists. + +name: Markdown + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + markdown-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm markdownlint markdownlint '**/*.md' diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml new file mode 100644 index 0000000..b7a68dd --- /dev/null +++ b/.github/workflows/php.yaml @@ -0,0 +1,60 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/php.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony PHP +### +### Checks that PHP code adheres to the [Symfony coding +### standards](https://symfony.com/doc/current/contributing/code/standards.html). +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. 2. +### [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev friendsofphp/php-cs-fixer +### ``` +### +### Clean up and check code by running +### +### ``` shell +### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix +### docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff +### ``` +### +### > [!NOTE] The template adds `.php-cs-fixer.dist.php` as [a configuration +### > file for PHP CS +### > Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst) +### > and this makes it possible to override the actual configuration used in a +### > project by adding a more important configuration file, `.php-cs-fixer.php`. + +name: Symfony PHP + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + coding-standards: + name: PHP - Check Coding Standards + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer install + # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command + docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index eca4339..9119f5f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,241 +1,36 @@ -on: pull_request -name: Review -jobs: - test-composer-install: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ["8.4"] - name: Validate composer (PHP ${{ matrix.php}}) - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip - coverage: none - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Validate composer files - run: composer validate composer.json --strict - - - name: '[prod] Composer install with exported .env variables' - run: | - set -a && source .env && set +a - APP_ENV=prod composer install --no-dev -o - - - name: Reset composer install - run: rm -rf ./vendor - - - name: '[dev] Composer install with exported .env variables' - run: | - set -a && source .env && set +a - APP_ENV=dev composer install - - - name: Normalize composer files - run: composer normalize --dry-run - - validate-doctrine-schema: - runs-on: ubuntu-latest - env: - DATABASE_URL: mysql://db:db@127.0.0.1:3306/db?serverVersion=10.11.0-mariadb - strategy: - fail-fast: false - matrix: - php: ["8.4"] - name: Validate Doctrine Schema (PHP ${{ matrix.php}}) - services: - mariadb: - image: mariadb:10.11 - env: - MYSQL_USER: db - MYSQL_PASSWORD: db - MYSQL_DATABASE: db - MYSQL_ROOT_PASSWORD: db - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip - coverage: none - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer - - - name: 'Composer install with exported .env variables' - run: | - set -a && source .env && set +a - APP_ENV=prod composer install --no-dev -o - - - name: Run Doctrine Migrations - run: APP_ENV=prod php bin/console doctrine:migrations:migrate --no-interaction - - - name: Setup messenger "failed" doctrine transport to ensure db schema is updated - run: APP_ENV=prod php bin/console messenger:setup-transports failed - - - name: Validate Doctrine schema - run: APP_ENV=prod php bin/console doctrine:schema:validate - - php-cs-fixer: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ["8.4"] - name: PHP Coding Standards Fixer (PHP ${{ matrix.php }}) - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip - coverage: none - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer +name: PR Review - - name: Install Dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist +env: + COMPOSE_USER: root - - name: php-cs-fixer - run: composer coding-standards-check +on: pull_request +jobs: phpunit: + name: PHP Unit tests runs-on: ubuntu-latest - services: - mariadb: - image: mariadb:10.5 - ports: - - 3306 - env: - MYSQL_USER: db - MYSQL_PASSWORD: db - MYSQL_DATABASE: db_test - MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - strategy: - fail-fast: false - matrix: - php: ["8.4"] - name: PHP Unit tests (PHP ${{ matrix.php }}) - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip - coverage: none - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer - - - name: Install Dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - - - name: PHP Unit - env: - PORT: ${{ job.services.mariadb.ports[3306] }} - run: DATABASE_URL="mysql://db:db@127.0.0.1:$PORT/db?serverVersion=mariadb-10.5.13" composer run tests - - apispec: - runs-on: ubuntu-latest - name: API Specification validation - strategy: - fail-fast: false - matrix: - php: ["8.4"] steps: - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php}} - extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip - coverage: none - - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer + uses: actions/checkout@v5 - - name: Install Dependencies - run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - - - name: Export specifications (yaml) - run: bin/console api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - name: Check for changes in specifications (yaml) - run: git diff --diff-filter=ACMRT --exit-code public/api-spec-v1.yaml + - name: Run tests + run: | + docker compose exec phpfpm composer run tests fixtures: - runs-on: ubuntu-latest name: Load fixtures - strategy: - fail-fast: false + runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 @@ -252,10 +47,8 @@ jobs: docker compose exec phpfpm composer fixtures build-assets: - runs-on: ubuntu-latest name: Build assets - strategy: - fail-fast: false + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -268,19 +61,3 @@ jobs: docker compose run --rm node yarn install docker compose run --rm node yarn build - changelog: - runs-on: ubuntu-latest - name: Changelog should be updated - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Git fetch - run: git fetch - - - name: Check that changelog has been updated. - run: git diff --exit-code origin/develop -- CHANGELOG.md && exit 1 || exit 0 diff --git a/.github/workflows/styles.yaml b/.github/workflows/styles.yaml new file mode 100644 index 0000000..4c39d78 --- /dev/null +++ b/.github/workflows/styles.yaml @@ -0,0 +1,37 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/symfony/styles.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Symfony Styles (CSS and SCSS) +### +### Validates styles files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. + +name: Styles + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + styles-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm prettier 'assets/**/*.{css,scss}' --check diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml new file mode 100644 index 0000000..5909841 --- /dev/null +++ b/.github/workflows/twig.yaml @@ -0,0 +1,51 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/twig.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Twig +### +### Validates Twig files +### +### #### Assumptions +### +### 1. A docker compose service named `phpfpm` can be run and `composer` can be +### run inside the `phpfpm` service. +### 2. [vincentlanglet/twig-cs-fixer](https://github.com/VincentLanglet/Twig-CS-Fixer) +### is a dev requirement in `composer.json`: +### +### ``` shell +### docker compose run --rm phpfpm composer require --dev vincentlanglet/twig-cs-fixer +### ``` +### +### 3. A [Configuration +### file](https://github.com/VincentLanglet/Twig-CS-Fixer/blob/main/docs/configuration.md#configuration-file) +### in the root of the project defines which files to check and rules to use. + +name: Twig + +env: + COMPOSE_USER: root + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + twig-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml new file mode 100644 index 0000000..631e525 --- /dev/null +++ b/.github/workflows/yaml.yaml @@ -0,0 +1,41 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/yaml.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### YAML +### +### Validates YAML files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. +### +### #### Symfony YAML +### +### Symfony's YAML config files use 4 spaces for indentation and single quotes. +### Therefore we use a [Prettier configuration +### file](https://prettier.io/docs/configuration), `.prettierrc.yaml`, to make +### Prettier format YAML files in the `config/` folder like Symfony expects. + +name: YAML + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + yaml-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm prettier '**/*.{yml,yaml}' --check diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..0253096 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,22 @@ +// This file is copied from config/markdown/.markdownlint.jsonc in https://github.com/itk-dev/devops_itkdev-docker. +// Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +// markdownlint-cli configuration file (cf. https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration) +{ + "default": true, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false + }, + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + "no-duplicate-heading": { + "siblings_only": true + }, + // https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md033.md + "no-inline-html": { + "allowed_elements": ["details", "summary"] + } +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..d143ace --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,12 @@ +# This file is copied from config/markdown/.markdownlintignore in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#ignoring-files +vendor/ +node_modules/ +LICENSE.md +# Drupal +web/*.md +web/core/ +web/libraries/ +web/*/contrib/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index bb6c6e9..c23b927 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,19 +1,20 @@ in(__DIR__) - ->exclude('var') -; +// https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst -return (new PhpCsFixer\Config()) - ->setRiskyAllowed(true) - ->setRules([ - '@Symfony' => true, - '@Symfony:risky' => false, - 'phpdoc_align' => false, - 'no_superfluous_phpdoc_tags' => false, - 'array_syntax' => ['syntax' => 'short'], - 'declare_strict_types' => true - ]) - ->setFinder($finder) -; +$finder = new PhpCsFixer\Finder(); +// Check all files … +$finder->in(__DIR__); +// … that are not ignored by VCS +$finder->ignoreVCSIgnored(true); + +$config = new PhpCsFixer\Config(); +$config->setFinder($finder); + +$config->setRules([ + '@Symfony' => true, +]); + +return $config; diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..12e0898 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,11 @@ +# This file is copied from config/symfony/yaml/.prettierrc.yaml in https://github.com/itk-dev/devops_itkdev-docker. +# Feel free to edit the file, but consider making a pull request if you find a general issue with the file. + +# https://prettier.io/docs/configuration +overrides: + # Symfony config + - files: + - "config/**/*.{yml,yaml}" + options: + tabWidth: 4 + singleQuote: true diff --git a/.twig-cs-fixer.dist.php b/.twig-cs-fixer.dist.php new file mode 100644 index 0000000..8242555 --- /dev/null +++ b/.twig-cs-fixer.dist.php @@ -0,0 +1,16 @@ +in(__DIR__); +// … that are not ignored by VCS +$finder->ignoreVCSIgnored(true); + +$config = new TwigCsFixer\Config\Config(); +$config->setFinder($finder); + +return $config; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4ac6fe3..ca2f4d1 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,4 +1,4 @@ -# itk-version: 3.2.1 +# itk-version: 3.2.4 services: phpfpm: environment: diff --git a/docker-compose.redirect.yml b/docker-compose.redirect.yml index 66f26e9..e9ba157 100644 --- a/docker-compose.redirect.yml +++ b/docker-compose.redirect.yml @@ -1,4 +1,4 @@ -# itk-version: 3.2.1 +# itk-version: 3.2.4 services: nginx: labels: diff --git a/docker-compose.server.yml b/docker-compose.server.yml index f93d840..44c75cd 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -1,4 +1,4 @@ -# itk-version: 3.2.1 +# itk-version: 3.2.4 networks: frontend: external: true @@ -17,7 +17,6 @@ services: environment: - PHP_MAX_EXECUTION_TIME=30 - PHP_MEMORY_LIMIT=128M - - COMPOSER_VERSION=2 volumes: - .:/app diff --git a/docker-compose.yml b/docker-compose.yml index 88d602c..dc2ef97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -# itk-version: 3.2.1 +# itk-version: 3.2.4 networks: frontend: external: true @@ -12,7 +12,13 @@ services: networks: - app ports: - - '3306' + - "3306" + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 10s + interval: 10s + timeout: 5s + retries: 3 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_USER=db @@ -22,6 +28,7 @@ services: phpfpm: image: itkdev/php8.4-fpm:latest + user: ${COMPOSE_USER:-deploy} networks: - app extra_hosts: @@ -33,10 +40,10 @@ services: # Depending on the setup, you may have to remove --read-envelope-from from msmtp (cf. https://marlam.de/msmtp/msmtp.html) or use SMTP to send mail - PHP_SENDMAIL_PATH=/usr/bin/msmtp --host=mail --port=1025 --read-recipients --read-envelope-from - DOCKER_HOST_DOMAIN=${COMPOSE_DOMAIN} - - COMPOSER_VERSION=2 - PHP_IDE_CONFIG=serverName=localhost depends_on: - - mariadb + mariadb: + condition: service_healthy volumes: - .:/app @@ -48,7 +55,7 @@ services: depends_on: - phpfpm ports: - - '8080' + - "8080" volumes: - ./.docker/templates:/etc/nginx/templates:ro - .:/app @@ -61,9 +68,9 @@ services: - "traefik.enable=true" - "traefik.docker.network=frontend" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(`${COMPOSE_DOMAIN}`)" -# HTTPS config - uncomment to enable redirect from :80 to :443 -# - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.middlewares=redirect-to-https" -# - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + # HTTPS config - uncomment to enable redirect from :80 to :443 + # - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.middlewares=redirect-to-https" + # - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" mail: image: axllent/mailpit @@ -78,3 +85,22 @@ services: - "traefik.docker.network=frontend" - "traefik.http.routers.${COMPOSE_PROJECT_NAME}mail.rule=Host(`mail-${COMPOSE_DOMAIN}`)" - "traefik.http.services.${COMPOSE_PROJECT_NAME}mail.loadbalancer.server.port=8025" + + # Code checks tools + markdownlint: + image: itkdev/markdownlint + profiles: + - dev + volumes: + - ./:/md + + prettier: + # Prettier does not (yet, fcf. + # https://github.com/prettier/prettier/issues/15206) have an official + # docker image. + # https://hub.docker.com/r/jauderho/prettier is good candidate (cf. https://hub.docker.com/search?q=prettier&sort=updated_at&order=desc) + image: jauderho/prettier + profiles: + - dev + volumes: + - ./:/work From 060d7e46d4b9cf87d519b9c6b5acf1f79aff83a5 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 14:45:53 +0200 Subject: [PATCH 04/94] 5564: Update Changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0a0af..d25da1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#60](https://github.com/itk-dev/devops_itksites/pull/60) 5564: Dependency updates + - Update dependencies + - Update phpunit from 11 to 12 + - Update ITK docker template + - Update github actions workflows + - [#58](https://github.com/itk-dev/devops_itksites/pull/58) 5002: Added export to everything @@ -133,6 +139,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2022-09-15 [Unreleased]: https://github.com/itk-dev/devops_itksites/compare/1.6.0...HEAD +[1.8.10]: https://github.com/itk-dev/devops_itksites/compare/1.8.9...1.8.10 +[1.8.9]: https://github.com/itk-dev/devops_itksites/compare/1.8.8...1.8.9 +[1.8.8]: https://github.com/itk-dev/devops_itksites/compare/1.8.7...1.8.8 +[1.8.7]: https://github.com/itk-dev/devops_itksites/compare/1.8.6...1.8.7 +[1.8.6]: https://github.com/itk-dev/devops_itksites/compare/1.8.5...1.8.6 +[1.8.5]: https://github.com/itk-dev/devops_itksites/compare/1.8.4...1.8.5 +[1.8.4]: https://github.com/itk-dev/devops_itksites/compare/1.8.3...1.8.4 +[1.8.3]: https://github.com/itk-dev/devops_itksites/compare/1.8.2...1.8.3 +[1.8.2]: https://github.com/itk-dev/devops_itksites/compare/1.8.1...1.8.2 +[1.8.1]: https://github.com/itk-dev/devops_itksites/compare/1.8.0...1.8.1 +[1.8.0]: https://github.com/itk-dev/devops_itksites/compare/1.7.1...1.8.0 +[1.7.1]: https://github.com/itk-dev/devops_itksites/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/itk-dev/devops_itksites/compare/1.6.1...1.7.0 +[1.6.1]: https://github.com/itk-dev/devops_itksites/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/itk-dev/devops_itksites/compare/1.5.0...1.6.0 [1.5.0]: https://github.com/itk-dev/devops_itksites/compare/1.4.1...1.5.0 [1.4.1]: https://github.com/itk-dev/devops_itksites/compare/1.4.0...1.4.1 From a647b3d92bd5a4dace90163f72d5d77cc52b5284 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 14:46:09 +0200 Subject: [PATCH 05/94] 5564: Update exported api spec --- .github/workflows/apispec.yaml | 2 +- public/api-spec-v1.json | 63 +++------------------------------- public/api-spec-v1.yaml | 42 ++--------------------- 3 files changed, 9 insertions(+), 98 deletions(-) diff --git a/.github/workflows/apispec.yaml b/.github/workflows/apispec.yaml index 8487438..31627de 100644 --- a/.github/workflows/apispec.yaml +++ b/.github/workflows/apispec.yaml @@ -40,7 +40,7 @@ jobs: run: | echo '## 🛑 Exported API specification file not up to date' > var/comment.md echo '' >> var/comment.md - echo 'Please run `task api:spec:export` to export the API specification. Then commit and push the changes.' >> var/comment.md + echo 'Please run `composer update-api-spec` to export the API specification. Then commit and push the changes.' >> var/comment.md gh pr comment ${{ github.event.pull_request.number }} --body-file var/comment.md --create-if-none --edit-last - name: Fail job, api spec is not up to date diff --git a/public/api-spec-v1.json b/public/api-spec-v1.json index 1639d4b..34114b0 100644 --- a/public/api-spec-v1.json +++ b/public/api-spec-v1.json @@ -21,56 +21,14 @@ "responses": { "202": { "description": "DetectionResult resource created", - "content": { - "application/json": { - "schema": {} - }, - "application/ld+json": { - "schema": {} - } - }, "links": {} }, "400": { "description": "Invalid input", - "content": { - "application/ld+json": { - "schema": { - "$ref": "#/components/schemas/Error.jsonld" - } - }, - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, "links": {} }, "422": { "description": "An error occurred", - "content": { - "application/ld+json": { - "schema": { - "$ref": "#/components/schemas/ConstraintViolation.jsonld-jsonld" - } - }, - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/ConstraintViolation-json" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConstraintViolation-json" - } - } - }, "links": {} } }, @@ -92,21 +50,18 @@ } }, "required": true - }, - "deprecated": false + } } } }, "components": { "schemas": { - "ConstraintViolation-json": { + "ConstraintViolation": { "type": "object", "description": "Unprocessable entity", - "deprecated": false, "properties": { "status": { "default": 422, - "example": 422, "type": "integer" }, "violations": { @@ -149,14 +104,12 @@ } } }, - "ConstraintViolation.jsonld-jsonld": { + "ConstraintViolation.jsonld": { "type": "object", "description": "Unprocessable entity", - "deprecated": false, "properties": { "status": { "default": 422, - "example": 422, "type": "integer" }, "violations": { @@ -205,22 +158,17 @@ }, "DetectionResult-write": { "type": "object", - "description": "", - "deprecated": false, "properties": { "type": { "default": "", - "example": "", "type": "string" }, "rootDir": { "default": "", - "example": "", "type": "string" }, "data": { "default": "", - "example": "", "type": "string" } } @@ -228,7 +176,6 @@ "Error": { "type": "object", "description": "A representation of common errors.", - "deprecated": false, "properties": { "title": { "readOnly": true, @@ -271,7 +218,6 @@ "Error.jsonld": { "type": "object", "description": "A representation of common errors.", - "deprecated": false, "properties": { "title": { "readOnly": true, @@ -340,7 +286,8 @@ ], "tags": [ { - "name": "DetectionResult" + "name": "DetectionResult", + "description": "Resource 'DetectionResult' operations." } ], "webhooks": {} diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index c8a4009..e90525c 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -16,37 +16,12 @@ paths: responses: '202': description: 'DetectionResult resource created' - content: - application/json: - schema: { } - application/ld+json: - schema: { } links: { } '400': description: 'Invalid input' - content: - application/ld+json: - schema: - $ref: '#/components/schemas/Error.jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/Error' - application/json: - schema: - $ref: '#/components/schemas/Error' links: { } '422': description: 'An error occurred' - content: - application/ld+json: - schema: - $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - application/json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' links: { } summary: 'Creates a DetectionResult resource.' description: 'Creates a DetectionResult resource.' @@ -61,17 +36,14 @@ paths: schema: $ref: '#/components/schemas/DetectionResult-write' required: true - deprecated: false components: schemas: - ConstraintViolation-json: + ConstraintViolation: type: object description: 'Unprocessable entity' - deprecated: false properties: status: default: 422 - example: 422 type: integer violations: type: array @@ -100,14 +72,12 @@ components: type: - string - 'null' - ConstraintViolation.jsonld-jsonld: + ConstraintViolation.jsonld: type: object description: 'Unprocessable entity' - deprecated: false properties: status: default: 422 - example: 422 type: integer violations: type: array @@ -141,25 +111,19 @@ components: - 'null' DetectionResult-write: type: object - description: '' - deprecated: false properties: type: default: '' - example: '' type: string rootDir: default: '' - example: '' type: string data: default: '' - example: '' type: string Error: type: object description: 'A representation of common errors.' - deprecated: false properties: title: readOnly: true @@ -191,7 +155,6 @@ components: Error.jsonld: type: object description: 'A representation of common errors.' - deprecated: false properties: title: readOnly: true @@ -242,4 +205,5 @@ security: tags: - name: DetectionResult + description: "Resource 'DetectionResult' operations." webhooks: { } From 5efc33ae92c3d87856b470a76f5118ec0dc71e28 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 15:06:06 +0200 Subject: [PATCH 06/94] 5564: Lock api-platform to 4.1, bump composer dependencies --- composer.json | 106 ++++++++++++++++++++-------------------- composer.lock | 40 +++++++-------- public/api-spec-v1.json | 63 ++++++++++++++++++++++-- public/api-spec-v1.yaml | 42 ++++++++++++++-- 4 files changed, 168 insertions(+), 83 deletions(-) diff --git a/composer.json b/composer.json index 8cbf299..4807946 100644 --- a/composer.json +++ b/composer.json @@ -4,63 +4,63 @@ "license": "MIT", "type": "project", "require": { - "php": ">=8.3", + "php": ">=8.4", "ext-ctype": "*", "ext-iconv": "*", - "api-platform/core": "^4.0", - "composer/semver": "^3.4", - "doctrine/dbal": "^4.0", - "doctrine/doctrine-bundle": "^2.13", - "doctrine/doctrine-migrations-bundle": "^3.4", - "doctrine/orm": "^3.0", - "easycorp/easyadmin-bundle": "^4.0", - "itk-dev/openid-connect-bundle": "^4.0", - "itk-dev/vault-bundle": "^0.1.0", - "nelmio/cors-bundle": "^2.2", - "ocramius/doctrine-batch-utils": "^2.8", - "phpdocumentor/reflection-docblock": "^5.3", - "phpstan/phpdoc-parser": "^2.0", - "symfony/amqp-messenger": "^7.2", - "symfony/asset": "^7.2", - "symfony/browser-kit": "^7.2", - "symfony/console": "^7.2", - "symfony/doctrine-messenger": "^7.2", - "symfony/dotenv": "^7.2", - "symfony/expression-language": "^7.2", - "symfony/flex": "^2", - "symfony/framework-bundle": "^7.2", - "symfony/http-client": "^7.2", - "symfony/messenger": "^7.2", - "symfony/monolog-bundle": "^3.0", - "symfony/property-access": "^7.2", - "symfony/property-info": "^7.2", - "symfony/runtime": "^7.2", - "symfony/security-bundle": "^7.2", - "symfony/serializer": "^7.2", - "symfony/twig-bundle": "^7.2", - "symfony/uid": "^7.2", - "symfony/validator": "^7.2", - "symfony/webpack-encore-bundle": "^2.0", - "symfony/yaml": "^7.2" + "api-platform/core": "~4.1.25", + "composer/semver": "^3.4.4", + "doctrine/dbal": "^4.3.4", + "doctrine/doctrine-bundle": "^2.18", + "doctrine/doctrine-migrations-bundle": "^3.5", + "doctrine/orm": "^3.5.2", + "easycorp/easyadmin-bundle": "^4.26.3", + "itk-dev/openid-connect-bundle": "^4.0.1", + "itk-dev/vault-bundle": "^0.1.2", + "nelmio/cors-bundle": "^2.5", + "ocramius/doctrine-batch-utils": "^2.11", + "phpdocumentor/reflection-docblock": "^5.6.3", + "phpstan/phpdoc-parser": "^2.3", + "symfony/amqp-messenger": "^7.3.2", + "symfony/asset": "^7.3", + "symfony/browser-kit": "^7.3.2", + "symfony/console": "^7.3.4", + "symfony/doctrine-messenger": "^7.3.4", + "symfony/dotenv": "^7.3.2", + "symfony/expression-language": "^7.3.2", + "symfony/flex": "^2.8.2", + "symfony/framework-bundle": "^7.3.4", + "symfony/http-client": "^7.3.4", + "symfony/messenger": "^7.3.3", + "symfony/monolog-bundle": "^3.10", + "symfony/property-access": "^7.3.3", + "symfony/property-info": "^7.3.4", + "symfony/runtime": "^7.3.4", + "symfony/security-bundle": "^7.3.4", + "symfony/serializer": "^7.3.4", + "symfony/twig-bundle": "^7.3.4", + "symfony/uid": "^7.3.1", + "symfony/validator": "^7.3.4", + "symfony/webpack-encore-bundle": "^2.3", + "symfony/yaml": "^7.3.3" }, "require-dev": { - "ergebnis/composer-normalize": "^2.23", - "friendsofphp/php-cs-fixer": "^3.6", - "hautelook/alice-bundle": "^2.14", - "justinrainbow/json-schema": "^6.0", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-doctrine": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-symfony": "^2.0", - "phpunit/phpunit": "^12.0", - "rector/rector": "^2.0", - "symfony/css-selector": "^7.2", - "symfony/debug-bundle": "^7.2", - "symfony/maker-bundle": "^1.37", - "symfony/stopwatch": "^7.2", - "symfony/var-dumper": "^7.2", - "symfony/web-profiler-bundle": "^7.2" + "ergebnis/composer-normalize": "^2.48.2", + "friendsofphp/php-cs-fixer": "^3.88.2", + "hautelook/alice-bundle": "^2.15.1", + "justinrainbow/json-schema": "^6.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.31", + "phpstan/phpstan-doctrine": "^2.0.10", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-symfony": "^2.0.8", + "phpunit/phpunit": "^12.4.1", + "rector/rector": "^2.2.3", + "symfony/css-selector": "^7.3", + "symfony/debug-bundle": "^7.3.4", + "symfony/maker-bundle": "^1.64", + "symfony/stopwatch": "^7.3", + "symfony/var-dumper": "^7.3.4", + "symfony/web-profiler-bundle": "^7.3.4" }, "replace": { "symfony/polyfill-ctype": "*", diff --git a/composer.lock b/composer.lock index 01d397c..e69c996 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2dbe013a397c0321b36acd5b037eac54", + "content-hash": "6c30a7903f7732d356dec62a8b9b5f24", "packages": [ { "name": "api-platform/core", - "version": "v4.2.2", + "version": "v4.1.25", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "eb6396b0a2db169665d87af8883c69c1e6fd7d4b" + "reference": "5c25cd2876740cd25e24efdbbb349359d5c98fc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/eb6396b0a2db169665d87af8883c69c1e6fd7d4b", - "reference": "eb6396b0a2db169665d87af8883c69c1e6fd7d4b", + "url": "https://api.github.com/repos/api-platform/core/zipball/5c25cd2876740cd25e24efdbbb349359d5c98fc1", + "reference": "5c25cd2876740cd25e24efdbbb349359d5c98fc1", "shasum": "" }, "require": { @@ -32,7 +32,7 @@ "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", "symfony/translation-contracts": "^3.3", - "symfony/type-info": "^7.3 || 7.4.x-dev", + "symfony/type-info": "^7.2", "symfony/validator": "^6.4 || ^7.1", "symfony/web-link": "^6.4 || ^7.1", "willdurand/negotiation": "^3.1" @@ -82,7 +82,6 @@ "doctrine/mongodb-odm-bundle": "^5.0", "doctrine/orm": "^2.17 || ^3.0", "elasticsearch/elasticsearch": "^7.17 || ^8.4 || ^9.0", - "ext-mongodb": "^1.21 || ^2.0", "friends-of-behat/mink-browserkit-driver": "^1.3.1", "friends-of-behat/mink-extension": "^2.2", "friends-of-behat/symfony-extension": "^2.1", @@ -94,17 +93,17 @@ "illuminate/pagination": "^11.0 || ^12.0", "illuminate/routing": "^11.0 || ^12.0", "illuminate/support": "^11.0 || ^12.0", - "jangregor/phpstan-prophecy": "^2.1.11", + "jangregor/phpstan-prophecy": "^1.0", "justinrainbow/json-schema": "^5.2.11", "laravel/framework": "^11.0 || ^12.0", "orchestra/testbench": "^9.1", "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", "phpstan/phpdoc-parser": "^1.29 || ^2.0", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-doctrine": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-symfony": "^1.0", "phpunit/phpunit": "11.5.x-dev", "psr/log": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", @@ -119,21 +118,19 @@ "symfony/console": "^6.4 || ^7.0", "symfony/css-selector": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/doctrine-bridge": "^6.4.2 || ^7.1", + "symfony/doctrine-bridge": "^6.4.2 || ^7.0.2", "symfony/dom-crawler": "^6.4 || ^7.0", "symfony/error-handler": "^6.4 || ^7.0", "symfony/event-dispatcher": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0", "symfony/finder": "^6.4 || ^7.0", "symfony/form": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0 || 7.4.x-dev", + "symfony/framework-bundle": "^6.4 || ^7.0", "symfony/http-client": "^6.4 || ^7.0", "symfony/intl": "^6.4 || ^7.0", - "symfony/json-streamer": "7.4.x-dev", "symfony/maker-bundle": "^1.24", "symfony/mercure-bundle": "*", "symfony/messenger": "^6.4 || ^7.0", - "symfony/object-mapper": "7.4.x-dev", "symfony/routing": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/security-core": "^6.4 || ^7.0", @@ -150,6 +147,7 @@ "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", "elasticsearch/elasticsearch": "To support Elasticsearch.", + "ocramius/package-versions": "To display the API Platform's version in the debug bar.", "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", "psr/cache-implementation": "To use metadata caching.", "ramsey/uuid": "To support Ramsey's UUID identifiers.", @@ -157,7 +155,6 @@ "symfony/config": "To load XML configuration files.", "symfony/expression-language": "To use authorization features.", "symfony/http-client": "To use the HTTP cache invalidation system.", - "symfony/json-streamer": "To use the JSON Streamer component.", "symfony/messenger": "To support messenger integration.", "symfony/security": "To use authorization features.", "symfony/twig-bundle": "To use the Swagger UI integration.", @@ -183,8 +180,7 @@ "branch-alias": { "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev", - "dev-4.2": "4.2.x-dev", - "dev-main": "4.3.x-dev" + "dev-main": "4.2.x-dev" } }, "autoload": { @@ -223,9 +219,9 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v4.2.2" + "source": "https://github.com/api-platform/core/tree/v4.1.25" }, - "time": "2025-10-09T08:35:20+00:00" + "time": "2025-09-26T15:30:38+00:00" }, { "name": "composer/semver", @@ -14069,7 +14065,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.3", + "php": ">=8.4", "ext-ctype": "*", "ext-iconv": "*" }, diff --git a/public/api-spec-v1.json b/public/api-spec-v1.json index 34114b0..1639d4b 100644 --- a/public/api-spec-v1.json +++ b/public/api-spec-v1.json @@ -21,14 +21,56 @@ "responses": { "202": { "description": "DetectionResult resource created", + "content": { + "application/json": { + "schema": {} + }, + "application/ld+json": { + "schema": {} + } + }, "links": {} }, "400": { "description": "Invalid input", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/Error.jsonld" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, "links": {} }, "422": { "description": "An error occurred", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/ConstraintViolation.jsonld-jsonld" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ConstraintViolation-json" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConstraintViolation-json" + } + } + }, "links": {} } }, @@ -50,18 +92,21 @@ } }, "required": true - } + }, + "deprecated": false } } }, "components": { "schemas": { - "ConstraintViolation": { + "ConstraintViolation-json": { "type": "object", "description": "Unprocessable entity", + "deprecated": false, "properties": { "status": { "default": 422, + "example": 422, "type": "integer" }, "violations": { @@ -104,12 +149,14 @@ } } }, - "ConstraintViolation.jsonld": { + "ConstraintViolation.jsonld-jsonld": { "type": "object", "description": "Unprocessable entity", + "deprecated": false, "properties": { "status": { "default": 422, + "example": 422, "type": "integer" }, "violations": { @@ -158,17 +205,22 @@ }, "DetectionResult-write": { "type": "object", + "description": "", + "deprecated": false, "properties": { "type": { "default": "", + "example": "", "type": "string" }, "rootDir": { "default": "", + "example": "", "type": "string" }, "data": { "default": "", + "example": "", "type": "string" } } @@ -176,6 +228,7 @@ "Error": { "type": "object", "description": "A representation of common errors.", + "deprecated": false, "properties": { "title": { "readOnly": true, @@ -218,6 +271,7 @@ "Error.jsonld": { "type": "object", "description": "A representation of common errors.", + "deprecated": false, "properties": { "title": { "readOnly": true, @@ -286,8 +340,7 @@ ], "tags": [ { - "name": "DetectionResult", - "description": "Resource 'DetectionResult' operations." + "name": "DetectionResult" } ], "webhooks": {} diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index e90525c..c8a4009 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -16,12 +16,37 @@ paths: responses: '202': description: 'DetectionResult resource created' + content: + application/json: + schema: { } + application/ld+json: + schema: { } links: { } '400': description: 'Invalid input' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/Error.jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + application/json: + schema: + $ref: '#/components/schemas/Error' links: { } '422': description: 'An error occurred' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + application/json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' links: { } summary: 'Creates a DetectionResult resource.' description: 'Creates a DetectionResult resource.' @@ -36,14 +61,17 @@ paths: schema: $ref: '#/components/schemas/DetectionResult-write' required: true + deprecated: false components: schemas: - ConstraintViolation: + ConstraintViolation-json: type: object description: 'Unprocessable entity' + deprecated: false properties: status: default: 422 + example: 422 type: integer violations: type: array @@ -72,12 +100,14 @@ components: type: - string - 'null' - ConstraintViolation.jsonld: + ConstraintViolation.jsonld-jsonld: type: object description: 'Unprocessable entity' + deprecated: false properties: status: default: 422 + example: 422 type: integer violations: type: array @@ -111,19 +141,25 @@ components: - 'null' DetectionResult-write: type: object + description: '' + deprecated: false properties: type: default: '' + example: '' type: string rootDir: default: '' + example: '' type: string data: default: '' + example: '' type: string Error: type: object description: 'A representation of common errors.' + deprecated: false properties: title: readOnly: true @@ -155,6 +191,7 @@ components: Error.jsonld: type: object description: 'A representation of common errors.' + deprecated: false properties: title: readOnly: true @@ -205,5 +242,4 @@ security: tags: - name: DetectionResult - description: "Resource 'DetectionResult' operations." webhooks: { } From 338ccabd614271cf8f30b72da18477ca5a05a108 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 15:07:37 +0200 Subject: [PATCH 07/94] 5564: PHP code style fixes --- src/Command/ReplayDetectionResultsCommand.php | 12 ------------ src/Entity/Site.php | 2 -- src/Handler/DetectionResultHandlerInterface.php | 2 +- src/Handler/DirectoryHandler.php | 9 --------- src/Handler/DockerImageHandler.php | 11 ----------- src/Handler/DrupalHandler.php | 8 -------- src/Handler/GitHandler.php | 13 ------------- src/Handler/NginxHandler.php | 8 -------- src/Handler/SymfonyHandler.php | 7 ------- src/MessageHandler/DetectionResultHandler.php | 3 --- src/Repository/DetectionResultRepository.php | 14 ++------------ .../ServiceCertificate/ServiceRepository.php | 2 -- src/Repository/SiteRepository.php | 2 -- src/Security/ApiKeyAuthenticator.php | 9 --------- src/Security/AzureOIDCAuthenticator.php | 7 ------- src/Service/DomainFactory.php | 4 ---- src/Service/InstallationFactory.php | 2 -- 17 files changed, 3 insertions(+), 112 deletions(-) diff --git a/src/Command/ReplayDetectionResultsCommand.php b/src/Command/ReplayDetectionResultsCommand.php index 020ecb1..a17c05d 100644 --- a/src/Command/ReplayDetectionResultsCommand.php +++ b/src/Command/ReplayDetectionResultsCommand.php @@ -30,9 +30,6 @@ class ReplayDetectionResultsCommand extends Command { /** * ReplayDetectionResultsCommand constructor. - * - * @param EntityManagerInterface $entityManager - * @param MessageBusInterface $messageBus */ public function __construct( private readonly EntityManagerInterface $entityManager, @@ -68,7 +65,6 @@ protected function configure(): void ; } - /** {@inheritDoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -147,10 +143,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * Dispatch message to the message bus. * - * @param Ulid $detectionResultID - * - * @return Envelope - * * @throws \Throwable */ private function dispatch(Ulid $detectionResultID): Envelope @@ -172,10 +164,6 @@ private function dispatch(Ulid $detectionResultID): Envelope /** * Get a Ulid from a string with/without dashes. - * - * @param string $ulid - * - * @return Ulid */ private static function fromString(string $ulid): Ulid { diff --git a/src/Entity/Site.php b/src/Entity/Site.php index e02fb94..08d606a 100644 --- a/src/Entity/Site.php +++ b/src/Entity/Site.php @@ -167,8 +167,6 @@ public function getPrimaryDomain(): ?string * these domains the first is the primary: * - 360.aarhuskommune.dk * - 360.aarhuskommune.dk.srvitkphp74.itkdev.dk - * - * @return void */ private function updatePrimaryDomain(): void { diff --git a/src/Handler/DetectionResultHandlerInterface.php b/src/Handler/DetectionResultHandlerInterface.php index 31d4013..ee241f6 100644 --- a/src/Handler/DetectionResultHandlerInterface.php +++ b/src/Handler/DetectionResultHandlerInterface.php @@ -23,7 +23,7 @@ public function handleResult(DetectionResult $detectionResult): void; * Does the handler support the give detection result type. * * @param string $type - * The result type + * The result type */ public function supportsType(string $type): bool; } diff --git a/src/Handler/DirectoryHandler.php b/src/Handler/DirectoryHandler.php index 934e3cc..073d42e 100644 --- a/src/Handler/DirectoryHandler.php +++ b/src/Handler/DirectoryHandler.php @@ -20,9 +20,6 @@ { /** * DirectoryHandler constructor. - * - * @param SiteRepository $siteRepository - * @param InstallationFactory $installationFactory */ public function __construct( private SiteRepository $siteRepository, @@ -31,9 +28,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { $server = $detectionResult->getServer(); @@ -63,9 +57,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** - * {@inheritDoc} - */ public function supportsType(string $type): bool { return DetectionType::DIRECTORY === $type; diff --git a/src/Handler/DockerImageHandler.php b/src/Handler/DockerImageHandler.php index 195714a..0f4cc27 100644 --- a/src/Handler/DockerImageHandler.php +++ b/src/Handler/DockerImageHandler.php @@ -25,13 +25,6 @@ /** * DirectoryHandler constructor. - * - * @param DockerImageTagFactory $dockerImageTagFactory - * @param ModuleVersionFactory $moduleVersionFactory - * @param PackageVersionFactory $packageVersionFactory - * @param AdvisoryFactory $advisoryFactory - * @param SiteFactory $siteFactory - * @param DomainFactory $domainFactory */ public function __construct( private DockerImageTagFactory $dockerImageTagFactory, @@ -43,9 +36,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { if (empty($detectionResult->getData())) { @@ -86,7 +76,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** {@inheritDoc} */ public function supportsType(string $type): bool { return DetectionType::DOCKER === $type; diff --git a/src/Handler/DrupalHandler.php b/src/Handler/DrupalHandler.php index b789a48..33f1c14 100644 --- a/src/Handler/DrupalHandler.php +++ b/src/Handler/DrupalHandler.php @@ -18,10 +18,6 @@ { /** * DirectoryHandler constructor. - * - * @param InstallationFactory $installationFactory - * @param PackageVersionFactory $packageVersionFactory - * @param ModuleVersionFactory $moduleVersionFactory */ public function __construct( private InstallationFactory $installationFactory, @@ -30,9 +26,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { try { @@ -56,7 +49,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** {@inheritDoc} */ public function supportsType(string $type): bool { return DetectionType::DRUPAL === $type; diff --git a/src/Handler/GitHandler.php b/src/Handler/GitHandler.php index 2915558..da6bdd9 100644 --- a/src/Handler/GitHandler.php +++ b/src/Handler/GitHandler.php @@ -16,9 +16,6 @@ { /** * DirectoryHandler constructor. - * - * @param InstallationFactory $installationFactory - * @param GitTagFactory $gitCloneFactory */ public function __construct( private InstallationFactory $installationFactory, @@ -26,9 +23,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { try { @@ -47,9 +41,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** - * {@inheritDoc} - */ public function supportsType(string $type): bool { return DetectionType::GIT === $type; @@ -61,10 +52,6 @@ public function supportsType(string $type): bool * The git harvester will send an empty result even for * "fatal: not a git repository (or any parent up to mount point /)" * - * @param DetectionResult $result - * - * @return object|null - * * @throws \JsonException */ private function getData(DetectionResult $result): ?object diff --git a/src/Handler/NginxHandler.php b/src/Handler/NginxHandler.php index ac3c957..214c19a 100644 --- a/src/Handler/NginxHandler.php +++ b/src/Handler/NginxHandler.php @@ -20,10 +20,6 @@ /** * DirectoryHandler constructor. - * - * @param SiteFactory $siteFactory - * @param DomainFactory $domainFactory - * @param ValidatorInterface $validator */ public function __construct( private SiteFactory $siteFactory, @@ -32,9 +28,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { try { @@ -63,7 +56,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** {@inheritDoc} */ public function supportsType(string $type): bool { return DetectionType::NGINX === $type; diff --git a/src/Handler/SymfonyHandler.php b/src/Handler/SymfonyHandler.php index b92de3b..e33ccfc 100644 --- a/src/Handler/SymfonyHandler.php +++ b/src/Handler/SymfonyHandler.php @@ -17,9 +17,6 @@ { /** * DirectoryHandler constructor. - * - * @param PackageVersionFactory $packageVersionFactory - * @param InstallationFactory $installationFactory */ public function __construct( private PackageVersionFactory $packageVersionFactory, @@ -27,9 +24,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function handleResult(DetectionResult $detectionResult): void { try { @@ -57,7 +51,6 @@ public function handleResult(DetectionResult $detectionResult): void } } - /** {@inheritDoc} */ public function supportsType(string $type): bool { return DetectionType::SYMFONY === $type; diff --git a/src/MessageHandler/DetectionResultHandler.php b/src/MessageHandler/DetectionResultHandler.php index c162fa7..996e79f 100644 --- a/src/MessageHandler/DetectionResultHandler.php +++ b/src/MessageHandler/DetectionResultHandler.php @@ -16,9 +16,6 @@ { /** * DetectionResultHandler constructor. - * - * @param Security $security - * @param MessageBusInterface $messageBus */ public function __construct( private Security $security, diff --git a/src/Repository/DetectionResultRepository.php b/src/Repository/DetectionResultRepository.php index a4e9b15..beaa237 100644 --- a/src/Repository/DetectionResultRepository.php +++ b/src/Repository/DetectionResultRepository.php @@ -26,10 +26,10 @@ public function __construct(ManagerRegistry $registry) * Remove detection results base on last contact. * * @param \dateTime $date - * The date to delete before + * The date to delete before * * @return mixed - * Number of records removed or false + * Number of records removed or false */ public function remove(\DateTime $date): mixed { @@ -43,12 +43,6 @@ public function remove(\DateTime $date): mixed /** * Delete all except X similar detection results. - * - * @param DetectionResult $detectionResult - * @param int $keep - * @param bool $flush - * - * @return void */ public function cleanup(DetectionResult $detectionResult, int $keep = 5, bool $flush = false): void { @@ -78,10 +72,6 @@ public function cleanup(DetectionResult $detectionResult, int $keep = 5, bool $f /** * Delete all detection results for a given installation. - * - * @param Installation $installation - * - * @return void */ public function deleteByInstallation(Installation $installation): void { diff --git a/src/Repository/ServiceCertificate/ServiceRepository.php b/src/Repository/ServiceCertificate/ServiceRepository.php index cdff2e7..bfd5391 100644 --- a/src/Repository/ServiceCertificate/ServiceRepository.php +++ b/src/Repository/ServiceCertificate/ServiceRepository.php @@ -44,8 +44,6 @@ public function remove(Service $entity, bool $flush = false): void /** * Get unique types from existing services. - * - * @return array */ public function getTypes(): array { diff --git a/src/Repository/SiteRepository.php b/src/Repository/SiteRepository.php index 9a1ec09..5b7f841 100644 --- a/src/Repository/SiteRepository.php +++ b/src/Repository/SiteRepository.php @@ -37,8 +37,6 @@ public function findByRootDirAndServer(string $rootDir, Server $server): mixed /** * Get unique primary domains from existing sites. - * - * @return array */ public function getPrimaryDomains(): array { diff --git a/src/Security/ApiKeyAuthenticator.php b/src/Security/ApiKeyAuthenticator.php index d77b9a7..8232e27 100644 --- a/src/Security/ApiKeyAuthenticator.php +++ b/src/Security/ApiKeyAuthenticator.php @@ -31,9 +31,6 @@ public function supports(Request $request): ?bool && str_starts_with((string) $request->headers->get(self::AUTH_HEADER), self::AUTH_HEADER_PREFIX); } - /** - * {@inheritdoc} - */ public function authenticate(Request $request): Passport { $apiKey = substr((string) $request->headers->get(self::AUTH_HEADER), strlen(self::AUTH_HEADER_PREFIX)); @@ -46,18 +43,12 @@ public function authenticate(Request $request): Passport return new SelfValidatingPassport(new UserBadge($apiKey)); } - /** - * {@inheritdoc} - */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // on success, let the request continue return null; } - /** - * {@inheritdoc} - */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { $data = [ diff --git a/src/Security/AzureOIDCAuthenticator.php b/src/Security/AzureOIDCAuthenticator.php index c118bdc..459757d 100644 --- a/src/Security/AzureOIDCAuthenticator.php +++ b/src/Security/AzureOIDCAuthenticator.php @@ -28,10 +28,6 @@ class AzureOIDCAuthenticator extends OpenIdLoginAuthenticator /** * AzureOIDCAuthenticator constructor. - * - * @param EntityManagerInterface $entityManager - * @param UrlGeneratorInterface $router - * @param OpenIdConfigurationProviderManager $providerManager */ public function __construct( private readonly EntityManagerInterface $entityManager, @@ -41,7 +37,6 @@ public function __construct( parent::__construct($providerManager); } - /** {@inheritDoc} */ public function authenticate(Request $request): Passport { try { @@ -73,7 +68,6 @@ public function authenticate(Request $request): Passport } } - /** {@inheritDoc} */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { $targetUrl = $this->getTargetPath($request->getSession(), $firewallName) ?? $this->router->generate('admin'); @@ -81,7 +75,6 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token, return new RedirectResponse($targetUrl); } - /** {@inheritDoc} */ public function start(Request $request, ?AuthenticationException $authException = null): Response { return new RedirectResponse($this->router->generate('itkdev_openid_connect_login', [ diff --git a/src/Service/DomainFactory.php b/src/Service/DomainFactory.php index 341d2d3..6c06e12 100644 --- a/src/Service/DomainFactory.php +++ b/src/Service/DomainFactory.php @@ -23,10 +23,6 @@ public function __construct( } /** - * @param string $addressList - * @param Site $site - * @param DetectionResult $detectionResult - * * @return Collection */ public function getDomains(string $addressList, Site $site, DetectionResult $detectionResult): Collection diff --git a/src/Service/InstallationFactory.php b/src/Service/InstallationFactory.php index 51a02a0..b94f57c 100644 --- a/src/Service/InstallationFactory.php +++ b/src/Service/InstallationFactory.php @@ -23,8 +23,6 @@ public function __construct( /** * Get Installations from detection result. * - * @param DetectionResult $detectionResult - * * @return Collection */ public function getInstallations(DetectionResult $detectionResult): Collection From 0d207f91300f3d8025bc40a90bdd37946d8a4e5c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 15:11:06 +0200 Subject: [PATCH 08/94] 5564: Add vincentlanglet/twig-cs-fixer --- .gitignore | 4 +++ composer.json | 5 ++-- composer.lock | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++- symfony.lock | 9 ++++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2bf29f0..397042c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ yarn-error.log phpstan.neon ###< phpstan/phpstan ### .phpunit.cache + +###> vincentlanglet/twig-cs-fixer ### +/.twig-cs-fixer.cache +###< vincentlanglet/twig-cs-fixer ### diff --git a/composer.json b/composer.json index 4807946..a6dd8c9 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,8 @@ "symfony/maker-bundle": "^1.64", "symfony/stopwatch": "^7.3", "symfony/var-dumper": "^7.3.4", - "symfony/web-profiler-bundle": "^7.3.4" + "symfony/web-profiler-bundle": "^7.3.4", + "vincentlanglet/twig-cs-fixer": "^3.10" }, "replace": { "symfony/polyfill-ctype": "*", @@ -98,7 +99,7 @@ }, "extra": { "symfony": { - "allow-contrib": false, + "allow-contrib": true, "require": "7.3.*" } }, diff --git a/composer.lock b/composer.lock index e69c996..454da30 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": "6c30a7903f7732d356dec62a8b9b5f24", + "content-hash": "1a4ccfa6b381e151c19b48ed226e0b54", "packages": [ { "name": "api-platform/core", @@ -14057,6 +14057,83 @@ } ], "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vincentlanglet/twig-cs-fixer", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/VincentLanglet/Twig-CS-Fixer.git", + "reference": "ee9b6a31d2c2522b2773ecf31f5d29c2e26311a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/ee9b6a31d2c2522b2773ecf31f5d29c2e26311a6", + "reference": "ee9b6a31d2c2522b2773ecf31f5d29c2e26311a6", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "ext-ctype": "*", + "ext-json": "*", + "php": ">=8.0", + "symfony/console": "^5.4.9 || ^6.4 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/string": "^5.4.42 || ^6.4.10 || ~7.0.10 || ^7.1.3 || ^8.0", + "twig/twig": "^3.4", + "webmozart/assert": "^1.10" + }, + "require-dev": { + "composer/semver": "^3.2.0", + "dereuromark/composer-prefer-lowest": "^0.1.10", + "ergebnis/composer-normalize": "^2.29", + "friendsofphp/php-cs-fixer": "^3.13.0", + "infection/infection": "^0.26.16 || ^0.29.14", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpunit/phpunit": "^9.5.26 || ^11.5.18 || ^12.1.3", + "rector/rector": "^2.0.0", + "shipmonk/composer-dependency-analyser": "^1.6", + "symfony/process": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/ux-twig-component": "^2.2.0", + "twig/cache-extra": "^3.2" + }, + "bin": [ + "bin/twig-cs-fixer" + ], + "type": "coding-standard", + "autoload": { + "psr-4": { + "TwigCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Vincent Langlet" + } + ], + "description": "A tool to automatically fix Twig code style", + "homepage": "https://github.com/VincentLanglet/Twig-CS-Fixer", + "support": { + "issues": "https://github.com/VincentLanglet/Twig-CS-Fixer/issues", + "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/VincentLanglet", + "type": "github" + } + ], + "time": "2025-09-15T11:28:55+00:00" } ], "aliases": [], diff --git a/symfony.lock b/symfony.lock index e419996..cf36f78 100644 --- a/symfony.lock +++ b/symfony.lock @@ -678,6 +678,15 @@ "twig/twig": { "version": "v3.3.8" }, + "vincentlanglet/twig-cs-fixer": { + "version": "3.10", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "3.0", + "ref": "d42582ae1bce86fd43491d6264c738b0867f8ffe" + } + }, "webmozart/assert": { "version": "1.10.0" }, From 16211acd73e561d4eaa264b05d84e9b352df547c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 15:11:42 +0200 Subject: [PATCH 09/94] 5564: Lint twig files --- templates/EasyAdminBundle/Fields/code.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/config_file_path.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/db_version.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/domain.html.twig | 2 +- templates/EasyAdminBundle/Fields/eol.html.twig | 2 +- templates/EasyAdminBundle/Fields/hosting_provider.html.twig | 6 +++--- templates/EasyAdminBundle/Fields/root_dir.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/server_type.html.twig | 2 +- templates/EasyAdminBundle/Fields/site.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/sources.html.twig | 2 +- templates/EasyAdminBundle/Fields/ssh_link.html.twig | 2 +- templates/EasyAdminBundle/Fields/text_mono.html.twig | 4 ++-- templates/admin/domain.html.twig | 4 ++-- .../EasyAdminBundle/crud/field/association.html.twig | 4 ++-- 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/templates/EasyAdminBundle/Fields/code.html.twig b/templates/EasyAdminBundle/Fields/code.html.twig index c4fb385..28a08e7 100644 --- a/templates/EasyAdminBundle/Fields/code.html.twig +++ b/templates/EasyAdminBundle/Fields/code.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain = 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} {{ field.formattedValue }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/templates/EasyAdminBundle/Fields/config_file_path.html.twig b/templates/EasyAdminBundle/Fields/config_file_path.html.twig index 2639f2c..da9307a 100644 --- a/templates/EasyAdminBundle/Fields/config_file_path.html.twig +++ b/templates/EasyAdminBundle/Fields/config_file_path.html.twig @@ -4,5 +4,5 @@ {% if ea.crud.currentAction == 'detail' %} {{ field.formattedValue }} {% else %} - {{ field.formattedValue | replace({'/etc/nginx/sites-enabled/': './'}) }} -{% endif %} \ No newline at end of file + {{ field.formattedValue|replace({'/etc/nginx/sites-enabled/': './'}) }} +{% endif %} diff --git a/templates/EasyAdminBundle/Fields/db_version.html.twig b/templates/EasyAdminBundle/Fields/db_version.html.twig index 567ae8a..c192aeb 100644 --- a/templates/EasyAdminBundle/Fields/db_version.html.twig +++ b/templates/EasyAdminBundle/Fields/db_version.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain = 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} {{ field.formattedValue }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/templates/EasyAdminBundle/Fields/domain.html.twig b/templates/EasyAdminBundle/Fields/domain.html.twig index eba0e51..3dc99f1 100644 --- a/templates/EasyAdminBundle/Fields/domain.html.twig +++ b/templates/EasyAdminBundle/Fields/domain.html.twig @@ -9,4 +9,4 @@ {{ field.value }} {% else %} {{ field.formattedValue }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/templates/EasyAdminBundle/Fields/eol.html.twig b/templates/EasyAdminBundle/Fields/eol.html.twig index 6e1f433..9acd5bd 100644 --- a/templates/EasyAdminBundle/Fields/eol.html.twig +++ b/templates/EasyAdminBundle/Fields/eol.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} -{% set slicedDate = field.formattedValue | slice(0, 7) %} +{% set slicedDate = field.formattedValue|slice(0, 7) %} {% if ea.crud.currentAction == 'detail' %} {% set outputValue = field.formattedValue %} {% else %} diff --git a/templates/EasyAdminBundle/Fields/hosting_provider.html.twig b/templates/EasyAdminBundle/Fields/hosting_provider.html.twig index ad1e247..cc1d7ab 100644 --- a/templates/EasyAdminBundle/Fields/hosting_provider.html.twig +++ b/templates/EasyAdminBundle/Fields/hosting_provider.html.twig @@ -3,11 +3,11 @@ {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is empty %} ? -{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::AZURE') %} +{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::AZURE') %} {{ field.formattedValue }} -{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::IT_RELATION') %} +{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::IT_RELATION') %} {{ field.formattedValue }} -{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::DBC') %} +{% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::DBC') %} {{ field.formattedValue }} {% else %} {{ field.formattedValue }} diff --git a/templates/EasyAdminBundle/Fields/root_dir.html.twig b/templates/EasyAdminBundle/Fields/root_dir.html.twig index 93d3677..c31765c 100644 --- a/templates/EasyAdminBundle/Fields/root_dir.html.twig +++ b/templates/EasyAdminBundle/Fields/root_dir.html.twig @@ -7,9 +7,9 @@ {% if field.formattedValue starts with '/data/www/' %} - {{ field.formattedValue | replace({'/data/www/': './'}) }} + {{ field.formattedValue|replace({'/data/www/': './'}) }} {% elseif field.formattedValue starts with '/home/www/' %} - {{ field.formattedValue | replace({'/home/www/': './'}) }} + {{ field.formattedValue|replace({'/home/www/': './'}) }} {% else %} {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/server_type.html.twig b/templates/EasyAdminBundle/Fields/server_type.html.twig index a6eb2ee..52369c6 100644 --- a/templates/EasyAdminBundle/Fields/server_type.html.twig +++ b/templates/EasyAdminBundle/Fields/server_type.html.twig @@ -11,4 +11,4 @@ {{ field.formattedValue }} {% else %} {{ field.formattedValue }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/templates/EasyAdminBundle/Fields/site.html.twig b/templates/EasyAdminBundle/Fields/site.html.twig index 2c2fc21..6bc1a58 100644 --- a/templates/EasyAdminBundle/Fields/site.html.twig +++ b/templates/EasyAdminBundle/Fields/site.html.twig @@ -1,9 +1,9 @@ {# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} -{% if field.formattedValue == constant('App\\Types\\SiteType::NGINX') %} +{% if field.formattedValue == constant('App\\Types\\SiteType::NGINX') %} {{ field.formattedValue }} -{% elseif field.formattedValue == constant('App\\Types\\SiteType::DOCKER') %} +{% elseif field.formattedValue == constant('App\\Types\\SiteType::DOCKER') %} {{ field.formattedValue }} {% else %} {{ field.formattedValue }} diff --git a/templates/EasyAdminBundle/Fields/sources.html.twig b/templates/EasyAdminBundle/Fields/sources.html.twig index 5ae9bec..a4ba5ad 100644 --- a/templates/EasyAdminBundle/Fields/sources.html.twig +++ b/templates/EasyAdminBundle/Fields/sources.html.twig @@ -4,7 +4,7 @@ {% if ea.crud.currentAction == 'detail' %}
    {% for item in field.value %} -{#
  • {{ item }}
  • #} +{#
  • {{ item }}
  • #}
  • {{ item }}
  • {% endfor %}
diff --git a/templates/EasyAdminBundle/Fields/ssh_link.html.twig b/templates/EasyAdminBundle/Fields/ssh_link.html.twig index 0aaf499..11e692b 100644 --- a/templates/EasyAdminBundle/Fields/ssh_link.html.twig +++ b/templates/EasyAdminBundle/Fields/ssh_link.html.twig @@ -10,4 +10,4 @@
- \ No newline at end of file + diff --git a/templates/EasyAdminBundle/Fields/text_mono.html.twig b/templates/EasyAdminBundle/Fields/text_mono.html.twig index 20ddb24..dab826d 100644 --- a/templates/EasyAdminBundle/Fields/text_mono.html.twig +++ b/templates/EasyAdminBundle/Fields/text_mono.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain = 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} {{ field.formattedValue }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/templates/admin/domain.html.twig b/templates/admin/domain.html.twig index 1e63970..e9cc5a4 100644 --- a/templates/admin/domain.html.twig +++ b/templates/admin/domain.html.twig @@ -6,7 +6,7 @@ .set('filters', { primaryDomain: { comparison: '=', - value: field.value - } + value: field.value, + }, }) %} {{ field.value }} diff --git a/templates/bundles/EasyAdminBundle/crud/field/association.html.twig b/templates/bundles/EasyAdminBundle/crud/field/association.html.twig index 4ec629d..8bd21aa 100644 --- a/templates/bundles/EasyAdminBundle/crud/field/association.html.twig +++ b/templates/bundles/EasyAdminBundle/crud/field/association.html.twig @@ -4,8 +4,8 @@ {% if 'toMany' == field.customOptions.get('associationType') %} {% if field.value.count == 0 %} None - {% elseif not has_display(field.value[0]) and ea.crud.currentAction != 'detail'%} - {{ field.value | length }} + {% elseif not has_display(field.value[0]) and ea.crud.currentAction != 'detail' %} + {{ field.value|length }} {% else %} {% for value in field.value %} {% if ea.crud.currentAction == 'detail' %} From ac2a23af70541863a90ebff737651f36b28ff675 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 16:01:20 +0200 Subject: [PATCH 10/94] 5564: Use assets mapper for CSS --- .env | 3 +- .gitignore | 5 ++ assets/app.js | 9 ++ public/css/admin.css => assets/styles/app.css | 0 composer.json | 10 ++- composer.lock | 90 ++++++++++++++++++- config/packages/asset_mapper.yaml | 11 +++ importmap.php | 19 ++++ src/Controller/Admin/DashboardController.php | 4 +- symfony.lock | 15 ++++ templates/base.html.twig | 1 + 11 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 assets/app.js rename public/css/admin.css => assets/styles/app.css (100%) create mode 100644 config/packages/asset_mapper.yaml create mode 100644 importmap.php diff --git a/.env b/.env index 79e2667..a2f0c5a 100644 --- a/.env +++ b/.env @@ -1,6 +1,5 @@ COMPOSE_PROJECT_NAME=itksites -#COMPOSE_DOMAIN=itksites.local.itkdev.dk -COMPOSE_DOMAIN=sites.itkdev.dk +COMPOSE_DOMAIN=itksites.local.itkdev.dk ITKDEV_TEMPLATE=symfony-6 diff --git a/.gitignore b/.gitignore index 397042c..80f8c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ phpstan.neon ###> vincentlanglet/twig-cs-fixer ### /.twig-cs-fixer.cache ###< vincentlanglet/twig-cs-fixer ### + +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### diff --git a/assets/app.js b/assets/app.js new file mode 100644 index 0000000..6174cc6 --- /dev/null +++ b/assets/app.js @@ -0,0 +1,9 @@ +/* + * Welcome to your app's main JavaScript file! + * + * This file will be included onto the page via the importmap() Twig function, + * which should already be in your base.html.twig. + */ +import './styles/app.css'; + +console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/public/css/admin.css b/assets/styles/app.css similarity index 100% rename from public/css/admin.css rename to assets/styles/app.css diff --git a/composer.json b/composer.json index a6dd8c9..689d484 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "phpdocumentor/reflection-docblock": "^5.6.3", "phpstan/phpdoc-parser": "^2.3", "symfony/amqp-messenger": "^7.3.2", - "symfony/asset": "^7.3", + "symfony/asset": "7.3.*", + "symfony/asset-mapper": "7.3.*", "symfony/browser-kit": "^7.3.2", "symfony/console": "^7.3.4", "symfony/doctrine-messenger": "^7.3.4", @@ -41,7 +42,9 @@ "symfony/uid": "^7.3.1", "symfony/validator": "^7.3.4", "symfony/webpack-encore-bundle": "^2.3", - "symfony/yaml": "^7.3.3" + "symfony/yaml": "^7.3.3", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.48.2", @@ -112,7 +115,8 @@ ], "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" }, "coding-standards-apply": [ "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix" diff --git a/composer.lock b/composer.lock index 454da30..c7af645 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": "1a4ccfa6b381e151c19b48ed226e0b54", + "content-hash": "27b62ec66b20f82b1b953f9b698bd714", "packages": [ { "name": "api-platform/core", @@ -3544,6 +3544,90 @@ ], "time": "2025-03-05T10:15:41+00:00" }, + { + "name": "symfony/asset-mapper", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "0c40c579e27244616cf0fe4d1759aab2ceb99a9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/0c40c579e27244616cf0fe4d1759aab2ceb99a9a", + "reference": "0c40c579e27244616cf0fe4d1759aab2ceb99a9a", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^7.1", + "symfony/http-client": "^6.4|^7.0" + }, + "conflict": { + "symfony/framework-bundle": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "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 directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v7.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-22T15:31:00+00:00" + }, { "name": "symfony/browser-kit", "version": "v7.3.2", @@ -14138,7 +14222,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -14146,6 +14230,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/importmap.php b/importmap.php new file mode 100644 index 0000000..70ebf14 --- /dev/null +++ b/importmap.php @@ -0,0 +1,19 @@ + [ + 'path' => './assets/app.js', + 'entrypoint' => true, + ], +]; diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index e63ed4f..bccf631 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -52,7 +52,7 @@ public function index(): Response public function configureDashboard(): Dashboard { return Dashboard::new() - ->setTitle('ITK sites') + ->setTitle('ITK sites logo') ->setFaviconPath('img/favicon.ico') ->renderContentMaximized(); } @@ -95,6 +95,6 @@ public function configureCrud(): Crud #[\Override] public function configureAssets(): Assets { - return Assets::new()->addCssFile('css/admin.css'); + return parent::configureAssets()->addAssetMapperEntry('app'); } } diff --git a/symfony.lock b/symfony.lock index cf36f78..68d5966 100644 --- a/symfony.lock +++ b/symfony.lock @@ -316,6 +316,21 @@ "symfony/asset": { "version": "v6.0.3" }, + "symfony/asset-mapper": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/browser-kit": { "version": "v6.0.3" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index d4f83f7..a4f2bfe 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -10,6 +10,7 @@ {% endblock %} {% block javascripts %} + {% block importmap %}{{ importmap('app') }}{% endblock %} {{ encore_entry_script_tags('app') }} {% endblock %} From 193ade332161843d33e7704805230ae45704896e Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 16:06:23 +0200 Subject: [PATCH 11/94] 5564: Lint yml files --- .github/workflows/apispec.yaml | 216 +++++----- .github/workflows/changelog.yaml | 28 +- .github/workflows/composer.yaml | 80 ++-- .github/workflows/doctrine.yaml | 68 +-- .github/workflows/github_build_release.yml | 74 ++-- .github/workflows/javascript.yaml | 34 +- .github/workflows/markdown.yaml | 34 +- .github/workflows/php.yaml | 36 +- .github/workflows/pr.yaml | 111 +++-- .github/workflows/styles.yaml | 34 +- .github/workflows/twig.yaml | 38 +- .github/workflows/yaml.yaml | 28 +- .prettierrc.yaml | 12 +- .woodpecker/prod.yml | 74 ++-- .woodpecker/stg.yml | 60 +-- composer.json | 6 +- config/packages/api_platform.yaml | 2 +- config/packages/cache.yaml | 2 +- config/packages/debug.yaml | 2 +- config/packages/itkdev_openid_connect.yaml | 56 +-- config/packages/messenger.yaml | 2 +- config/packages/monolog.yaml | 12 +- config/packages/security.yaml | 2 +- config/packages/webpack_encore.yaml | 11 +- config/routes.yaml | 10 +- config/routes/easyadmin.yaml | 4 +- config/routes/itkdev_openid_connect.yaml | 4 +- config/services.yaml | 9 +- docker-compose.override.yml | 4 +- fixtures/oidc.yaml | 12 +- fixtures/server.yaml | 32 +- fixtures/service_certificates.yaml | 48 +-- fixtures/site.yaml | 144 +++---- fixtures/user.yaml | 6 +- public/api-spec-v1.yaml | 465 ++++++++++----------- 35 files changed, 877 insertions(+), 883 deletions(-) diff --git a/.github/workflows/apispec.yaml b/.github/workflows/apispec.yaml index 31627de..7d11a53 100644 --- a/.github/workflows/apispec.yaml +++ b/.github/workflows/apispec.yaml @@ -1,115 +1,115 @@ name: API Spec review env: - COMPOSE_USER: root + COMPOSE_USER: root on: - pull_request: + pull_request: jobs: - api-spec-updated: - name: Ensure committed API specification is up to date - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 - - - name: Create docker network - run: | - docker network create frontend - - - name: Composer install - run: | - docker compose run --rm phpfpm composer install - - - name: Export API specification - run: | - docker compose run --rm phpfpm bin/console api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction - - - name: Check for changes in specification - id: git-diff-spec - continue-on-error: true - run: git diff --diff-filter=ACMRT --exit-code public/api-spec-v1.yaml - - - name: Comment PR - if: steps.git-diff-spec.outcome == 'failure' - env: - GH_TOKEN: ${{ github.token }} - run: | - echo '## 🛑 Exported API specification file not up to date' > var/comment.md - echo '' >> var/comment.md - echo 'Please run `composer update-api-spec` to export the API specification. Then commit and push the changes.' >> var/comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file var/comment.md --create-if-none --edit-last - - - name: Fail job, api spec is not up to date - if: steps.git-diff-spec.outcome == 'failure' - run: | - exit 1 - - detect-breaking-changes: - name: Detect breaking changes in API specification - runs-on: ubuntu-latest - needs: [api-spec-updated] - steps: - - name: Check out BASE rev - uses: actions/checkout@v5 - with: - ref: ${{ github.base_ref }} - path: base - - - name: Check out HEAD rev - uses: actions/checkout@v5 - with: - ref: ${{ github.head_ref }} - path: head - - - name: Run OpenAPI Changed (from HEAD rev) - id: api-changed - continue-on-error: true - uses: docker://openapitools/openapi-diff:latest - with: - args: --fail-on-changed base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-changed.md - - - name: Run OpenAPI Incompatible (from HEAD rev) - id: api-incompatible - continue-on-error: true - uses: docker://openapitools/openapi-diff:latest - with: - args: --fail-on-incompatible base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-incompatible.md - - - name: Comment PR with no changes - if: steps.api-changed.outcome == 'success' && steps.api-incompatible.outcome == 'success' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr comment ${{ github.event.pull_request.number }} --body "✅ **No changes detected in API specification**" --create-if-none --edit-last - - - name: Comment PR with non-breaking changes - if: steps.api-changed.outcome == 'failure' && steps.api-incompatible.outcome == 'success' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "## ⚠️ Non-Breaking changes detected in API specification" > ../comment.md - echo "" >> ../comment.md - cat ../api-spec-changed.md >> ../comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last - - - name: Comment PR with breaking changes - if: steps.api-incompatible.outcome == 'failure' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "## 🛑 Breaking changes detected in API specification" > ../comment.md - echo "" >> ../comment.md - cat ../api-spec-incompatible.md >> ../comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last - - - name: Fail if breaking changes detected - if: steps.api-incompatible.outcome == 'failure' - run: | - exit 1 \ No newline at end of file + api-spec-updated: + name: Ensure committed API specification is up to date + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 + + - name: Create docker network + run: | + docker network create frontend + + - name: Composer install + run: | + docker compose run --rm phpfpm composer install + + - name: Export API specification + run: | + docker compose run --rm phpfpm bin/console api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction + + - name: Check for changes in specification + id: git-diff-spec + continue-on-error: true + run: git diff --diff-filter=ACMRT --exit-code public/api-spec-v1.yaml + + - name: Comment PR + if: steps.git-diff-spec.outcome == 'failure' + env: + GH_TOKEN: ${{ github.token }} + run: | + echo '## 🛑 Exported API specification file not up to date' > var/comment.md + echo '' >> var/comment.md + echo 'Please run `composer update-api-spec` to export the API specification. Then commit and push the changes.' >> var/comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file var/comment.md --create-if-none --edit-last + + - name: Fail job, api spec is not up to date + if: steps.git-diff-spec.outcome == 'failure' + run: | + exit 1 + + detect-breaking-changes: + name: Detect breaking changes in API specification + runs-on: ubuntu-latest + needs: [api-spec-updated] + steps: + - name: Check out BASE rev + uses: actions/checkout@v5 + with: + ref: ${{ github.base_ref }} + path: base + + - name: Check out HEAD rev + uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + path: head + + - name: Run OpenAPI Changed (from HEAD rev) + id: api-changed + continue-on-error: true + uses: docker://openapitools/openapi-diff:latest + with: + args: --fail-on-changed base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-changed.md + + - name: Run OpenAPI Incompatible (from HEAD rev) + id: api-incompatible + continue-on-error: true + uses: docker://openapitools/openapi-diff:latest + with: + args: --fail-on-incompatible base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-incompatible.md + + - name: Comment PR with no changes + if: steps.api-changed.outcome == 'success' && steps.api-incompatible.outcome == 'success' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr comment ${{ github.event.pull_request.number }} --body "✅ **No changes detected in API specification**" --create-if-none --edit-last + + - name: Comment PR with non-breaking changes + if: steps.api-changed.outcome == 'failure' && steps.api-incompatible.outcome == 'success' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "## ⚠️ Non-Breaking changes detected in API specification" > ../comment.md + echo "" >> ../comment.md + cat ../api-spec-changed.md >> ../comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last + + - name: Comment PR with breaking changes + if: steps.api-incompatible.outcome == 'failure' + working-directory: head + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "## 🛑 Breaking changes detected in API specification" > ../comment.md + echo "" >> ../comment.md + cat ../api-spec-incompatible.md >> ../comment.md + gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last + + - name: Fail if breaking changes detected + if: steps.api-incompatible.outcome == 'failure' + run: | + exit 1 diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index fead572..15297e3 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -9,21 +9,21 @@ name: Changelog on: - pull_request: + pull_request: jobs: - changelog: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 + changelog: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 - - name: Git fetch - run: git fetch + - name: Git fetch + run: git fetch - - name: Check that changelog has been updated. - run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0 + - name: Check that changelog has been updated. + run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index a0e5a94..9a56bd6 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -26,55 +26,55 @@ name: Composer env: - COMPOSE_USER: root + COMPOSE_USER: root on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - composer-validate: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - uses: actions/checkout@v5 + composer-validate: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm phpfpm composer validate --strict + - run: | + docker compose run --rm phpfpm composer validate --strict - composer-normalized: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - uses: actions/checkout@v5 + composer-normalized: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm phpfpm composer install - docker compose run --rm phpfpm composer normalize --dry-run + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm composer normalize --dry-run - composer-audit: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - uses: actions/checkout@v5 + composer-audit: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm phpfpm composer audit + - run: | + docker compose run --rm phpfpm composer audit diff --git a/.github/workflows/doctrine.yaml b/.github/workflows/doctrine.yaml index 823c375..696886d 100644 --- a/.github/workflows/doctrine.yaml +++ b/.github/workflows/doctrine.yaml @@ -1,41 +1,41 @@ name: Doctrine env: - COMPOSE_USER: root + COMPOSE_USER: root on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - coding-standards: - name: Validate Schema - runs-on: ubuntu-latest - env: - APP_ENV: prod - - steps: - - uses: actions/checkout@v5 - - - name: Create docker network - run: | - docker network create frontend - - - name: Run Composer Install - run: | - docker compose run --rm phpfpm composer install - - - name: Run Doctrine Migrations - run: | - docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction - - - name: Setup messenger "failed" doctrine transport to ensure db schema is updated - run: | - docker compose run --rm phpfpm bin/console messenger:setup-transports failed - - - name: Validate Doctrine schema - run: | - docker compose run --rm phpfpm bin/console doctrine:schema:validate \ No newline at end of file + coding-standards: + name: Validate Schema + runs-on: ubuntu-latest + env: + APP_ENV: prod + + steps: + - uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - name: Run Composer Install + run: | + docker compose run --rm phpfpm composer install + + - name: Run Doctrine Migrations + run: | + docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Setup messenger "failed" doctrine transport to ensure db schema is updated + run: | + docker compose run --rm phpfpm bin/console messenger:setup-transports failed + + - name: Validate Doctrine schema + run: | + docker compose run --rm phpfpm bin/console doctrine:schema:validate diff --git a/.github/workflows/github_build_release.yml b/.github/workflows/github_build_release.yml index 3817c60..b26cea6 100644 --- a/.github/workflows/github_build_release.yml +++ b/.github/workflows/github_build_release.yml @@ -1,45 +1,45 @@ on: - push: - tags: - - '*.*.*' + push: + tags: + - "*.*.*" name: Create Github Release permissions: - contents: write + contents: write jobs: - create-release: - runs-on: ubuntu-latest - env: - COMPOSER_ALLOW_SUPERUSER: 1 - APP_ENV: prod - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Composer install - run: | - docker network create frontend - docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer install --no-dev -o --classmap-authoritative - docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer clear-cache - docker compose run --rm node yarn install - docker compose run --rm node yarn build - - - name: Make assets dir - run: | - mkdir -p ../assets - - - name: Create archive - run: tar --exclude='.git' --exclude='node_modules' -zcf ../assets/${{ github.event.repository.name }}-${{ github.ref_name }}.tar.gz ./ - - - name: Create checksum - run: | - cd ../assets - sha256sum ${{ github.event.repository.name }}-${{ github.ref_name }}.tar.gz > ../assets/checksum.txt - - - name: Create a release in GitHub and uploads assets - run: gh release create ${{ github.ref_name }} --verify-tag --generate-notes ../assets/*.* + create-release: + runs-on: ubuntu-latest env: - GITHUB_TOKEN: ${{ github.TOKEN }} - shell: bash + COMPOSER_ALLOW_SUPERUSER: 1 + APP_ENV: prod + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Composer install + run: | + docker network create frontend + docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer install --no-dev -o --classmap-authoritative + docker compose run --rm --user=root --env APP_ENV=prod phpfpm composer clear-cache + docker compose run --rm node yarn install + docker compose run --rm node yarn build + + - name: Make assets dir + run: | + mkdir -p ../assets + + - name: Create archive + run: tar --exclude='.git' --exclude='node_modules' -zcf ../assets/${{ github.event.repository.name }}-${{ github.ref_name }}.tar.gz ./ + + - name: Create checksum + run: | + cd ../assets + sha256sum ${{ github.event.repository.name }}-${{ github.ref_name }}.tar.gz > ../assets/checksum.txt + + - name: Create a release in GitHub and uploads assets + run: gh release create ${{ github.ref_name }} --verify-tag --generate-notes ../assets/*.* + env: + GITHUB_TOKEN: ${{ github.TOKEN }} + shell: bash diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml index 44b976e..bafc128 100644 --- a/.github/workflows/javascript.yaml +++ b/.github/workflows/javascript.yaml @@ -14,24 +14,24 @@ name: JavaScript on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - javascript-lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v5 + javascript-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm prettier 'assets/**/*.js' --check + - run: | + docker compose run --rm prettier 'assets/**/*.js' --check diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index ae83163..cdd8b4b 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -21,24 +21,24 @@ name: Markdown on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - markdown-lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v5 + markdown-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm markdownlint markdownlint '**/*.md' + - run: | + docker compose run --rm markdownlint markdownlint '**/*.md' diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index b7a68dd..d8942c7 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -34,27 +34,27 @@ name: Symfony PHP env: - COMPOSE_USER: root + COMPOSE_USER: root on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - coding-standards: - name: PHP - Check Coding Standards - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 + coding-standards: + name: PHP - Check Coding Standards + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm phpfpm composer install - # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command - docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff + - run: | + docker compose run --rm phpfpm composer install + # https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/usage.rst#the-check-command + docker compose run --rm phpfpm vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 9119f5f..6dfe6ad 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,63 +1,62 @@ name: PR Review env: - COMPOSE_USER: root + COMPOSE_USER: root on: pull_request jobs: - phpunit: - name: PHP Unit tests - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - - name: Run tests - run: | - docker compose exec phpfpm composer run tests - - fixtures: - name: Load fixtures - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 - - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - - name: Load fixtures - run: | - docker compose exec phpfpm composer fixtures - - build-assets: - name: Build assets - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - name: Build assets - run: | - docker network create frontend - docker compose run --rm node yarn install - docker compose run --rm node yarn build - + phpunit: + name: PHP Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Run tests + run: | + docker compose exec phpfpm composer run tests + + fixtures: + name: Load fixtures + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 + + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Load fixtures + run: | + docker compose exec phpfpm composer fixtures + + build-assets: + name: Build assets + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: Build assets + run: | + docker network create frontend + docker compose run --rm node yarn install + docker compose run --rm node yarn build diff --git a/.github/workflows/styles.yaml b/.github/workflows/styles.yaml index 4c39d78..964cffb 100644 --- a/.github/workflows/styles.yaml +++ b/.github/workflows/styles.yaml @@ -14,24 +14,24 @@ name: Styles on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - styles-lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v5 + styles-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm prettier 'assets/**/*.{css,scss}' --check + - run: | + docker compose run --rm prettier 'assets/**/*.{css,scss}' --check diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml index 5909841..11b54ad 100644 --- a/.github/workflows/twig.yaml +++ b/.github/workflows/twig.yaml @@ -24,28 +24,28 @@ name: Twig env: - COMPOSE_USER: root + COMPOSE_USER: root on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - twig-lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v5 + twig-lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm phpfpm composer install - docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint + - run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/twig-cs-fixer lint diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml index 631e525..ba8ca52 100644 --- a/.github/workflows/yaml.yaml +++ b/.github/workflows/yaml.yaml @@ -21,21 +21,21 @@ name: YAML on: - pull_request: - push: - branches: - - main - - develop + pull_request: + push: + branches: + - main + - develop jobs: - yaml-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 + yaml-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - run: | - docker compose run --rm prettier '**/*.{yml,yaml}' --check + - run: | + docker compose run --rm prettier '**/*.{yml,yaml}' --check diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 12e0898..5b271ea 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -3,9 +3,9 @@ # https://prettier.io/docs/configuration overrides: - # Symfony config - - files: - - "config/**/*.{yml,yaml}" - options: - tabWidth: 4 - singleQuote: true + # Symfony config + - files: + - "config/**/*.{yml,yaml}" + options: + tabWidth: 4 + singleQuote: true diff --git a/.woodpecker/prod.yml b/.woodpecker/prod.yml index 9bf9692..e3c1cc2 100644 --- a/.woodpecker/prod.yml +++ b/.woodpecker/prod.yml @@ -1,45 +1,45 @@ when: - - event: release + - event: release skip_clone: true labels: - zone: CLOUD + zone: CLOUD steps: - - name: Ansible playbook - image: itkdev/ansible-plugin:1 - pull: true - settings: - id: - from_secret: id - secret: - from_secret: secret - host: - from_secret: prod_host - path: - from_secret: prod_path - user: - from_secret: user - keep: 4 - playbook: 'release' - pre_up: - - itkdev-docker-compose-server run phpfpm bin/console doctrine:migrations:migrate --no-interaction - - itkdev-docker-compose-server run phpfpm bin/console messenger:setup-transports + - name: Ansible playbook + image: itkdev/ansible-plugin:1 + pull: true + settings: + id: + from_secret: id + secret: + from_secret: secret + host: + from_secret: prod_host + path: + from_secret: prod_path + user: + from_secret: user + keep: 4 + playbook: "release" + pre_up: + - itkdev-docker-compose-server run phpfpm bin/console doctrine:migrations:migrate --no-interaction + - itkdev-docker-compose-server run phpfpm bin/console messenger:setup-transports - - name: Run post deploy - image: itkdev/ansible-plugin:1 - pull: true - settings: - id: - from_secret: id - secret: - from_secret: secret - host: - from_secret: prod_host - path: - from_secret: prod_path - user: - from_secret: user - actions: - - itkdev-docker-compose-server exec phpfpm bin/console cache:clear + - name: Run post deploy + image: itkdev/ansible-plugin:1 + pull: true + settings: + id: + from_secret: id + secret: + from_secret: secret + host: + from_secret: prod_host + path: + from_secret: prod_path + user: + from_secret: user + actions: + - itkdev-docker-compose-server exec phpfpm bin/console cache:clear diff --git a/.woodpecker/stg.yml b/.woodpecker/stg.yml index 64ded24..0a5749a 100644 --- a/.woodpecker/stg.yml +++ b/.woodpecker/stg.yml @@ -1,37 +1,37 @@ when: - - branch: release/* - event: push + - branch: release/* + event: push skip_clone: true labels: - zone: DMZ + zone: DMZ steps: - - name: Run stg site update - image: itkdev/ansible-plugin:1 - when: - branch: release/* - event: push - pull: true - settings: - id: - from_secret: id - secret: - from_secret: secret - host: - from_secret: stg_host - path: - from_secret: stg_path - user: - from_secret: user - actions: - - git reset --hard - - git fetch origin ${CI_COMMIT_BRANCH} - - git checkout ${CI_COMMIT_BRANCH} - - git pull - - itkdev-docker-compose-server up -d --force-recreate --remove-orphans - - itkdev-docker-compose-server exec phpfpm composer install -no-dev -o --classmap-authoritative - - itkdev-docker-compose-server exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - itkdev-docker-compose-server exec phpfpm bin/console messenger:setup-transports - - itkdev-docker-compose-server exec phpfpm bin/console cache:clear + - name: Run stg site update + image: itkdev/ansible-plugin:1 + when: + branch: release/* + event: push + pull: true + settings: + id: + from_secret: id + secret: + from_secret: secret + host: + from_secret: stg_host + path: + from_secret: stg_path + user: + from_secret: user + actions: + - git reset --hard + - git fetch origin ${CI_COMMIT_BRANCH} + - git checkout ${CI_COMMIT_BRANCH} + - git pull + - itkdev-docker-compose-server up -d --force-recreate --remove-orphans + - itkdev-docker-compose-server exec phpfpm composer install -no-dev -o --classmap-authoritative + - itkdev-docker-compose-server exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + - itkdev-docker-compose-server exec phpfpm bin/console messenger:setup-transports + - itkdev-docker-compose-server exec phpfpm bin/console cache:clear diff --git a/composer.json b/composer.json index 689d484..685da54 100644 --- a/composer.json +++ b/composer.json @@ -119,10 +119,12 @@ "importmap:install": "symfony-cmd" }, "coding-standards-apply": [ - "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix" + "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix", + "vendor/bin/twig-cs-fixer --fix" ], "coding-standards-check": [ - "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run" + "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run", + "vendor/bin/twig-cs-fixer" ], "fixtures-load": [ "bin/console hautelook:fixtures:load --no-interaction" diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 56a17a4..e6a1b80 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -10,7 +10,7 @@ api_platform: enable_swagger: true enable_swagger_ui: true swagger: - versions: [ 3 ] + versions: [3] api_keys: apiKey: name: Authorization diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml index 6899b72..c3eb53d 100644 --- a/config/packages/cache.yaml +++ b/config/packages/cache.yaml @@ -16,4 +16,4 @@ framework: # Namespaced pools use the above "app" backend by default #pools: - #my.dedicated.cache: null + #my.dedicated.cache: null diff --git a/config/packages/debug.yaml b/config/packages/debug.yaml index ad874af..54a4821 100644 --- a/config/packages/debug.yaml +++ b/config/packages/debug.yaml @@ -2,4 +2,4 @@ when@dev: debug: # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. # See the "server:dump" command to start a new server. - dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" + dump_destination: 'tcp://%env(VAR_DUMPER_SERVER)%' diff --git a/config/packages/itkdev_openid_connect.yaml b/config/packages/itkdev_openid_connect.yaml index 4afe5f1..854a6d3 100644 --- a/config/packages/itkdev_openid_connect.yaml +++ b/config/packages/itkdev_openid_connect.yaml @@ -1,31 +1,31 @@ itkdev_openid_connect: - cache_options: - cache_pool: 'cache.app' # Cache item pool for caching discovery document and CLI login tokens - cli_login_options: - route: '%env(OIDC_CLI_LOGIN_ROUTE)%' # Route for CLI login - user_provider: 'security.user.provider.concrete.app_user_provider' - openid_providers: - # Define one or more providers - # [providerKey]: - # options: - # metadata_url: … - # … - azure_az: - options: - metadata_url: '%env(string:AZURE_AZ_OIDC_METADATA_URL)%' - client_id: '%env(AZURE_AZ_OIDC_CLIENT_ID)%' - client_secret: '%env(AZURE_AZ_OIDC_CLIENT_SECRET)%' - # Specify redirect URI - redirect_uri: '%env(string:AZURE_AZ_OIDC_REDIRECT_URI)%' - allow_http: '%env(bool:AZURE_AZ_OIDC_ALLOW_HTTP)%' - # Optional: Specify leeway (seconds) to account for clock skew between provider and hosting - # Defaults to 10 - leeway: '%env(int:AZURE_AZ_OIDC_LEEWAY)%' + cache_options: + cache_pool: 'cache.app' # Cache item pool for caching discovery document and CLI login tokens + cli_login_options: + route: '%env(OIDC_CLI_LOGIN_ROUTE)%' # Route for CLI login + user_provider: 'security.user.provider.concrete.app_user_provider' + openid_providers: + # Define one or more providers + # [providerKey]: + # options: + # metadata_url: … + # … + azure_az: + options: + metadata_url: '%env(string:AZURE_AZ_OIDC_METADATA_URL)%' + client_id: '%env(AZURE_AZ_OIDC_CLIENT_ID)%' + client_secret: '%env(AZURE_AZ_OIDC_CLIENT_SECRET)%' + # Specify redirect URI + redirect_uri: '%env(string:AZURE_AZ_OIDC_REDIRECT_URI)%' + allow_http: '%env(bool:AZURE_AZ_OIDC_ALLOW_HTTP)%' + # Optional: Specify leeway (seconds) to account for clock skew between provider and hosting + # Defaults to 10 + leeway: '%env(int:AZURE_AZ_OIDC_LEEWAY)%' when@prod: - itkdev_openid_connect: - openid_providers: - azure_az: - options: - client_id: '%env(vault:AZURE_AZ_OIDC_CLIENT_ID)%' - client_secret: '%env(vault:AZURE_AZ_OIDC_CLIENT_SECRET)%' + itkdev_openid_connect: + openid_providers: + azure_az: + options: + client_id: '%env(vault:AZURE_AZ_OIDC_CLIENT_ID)%' + client_secret: '%env(vault:AZURE_AZ_OIDC_CLIENT_SECRET)%' diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 419a72f..8db527e 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -7,7 +7,7 @@ framework: # https://symfony.com/doc/current/messenger.html#transport-configuration sync: 'sync://' async: - dsn: "%env(MESSENGER_TRANSPORT_DSN)%" + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy: # 1, 3, 9, 27 max_retries: 4 diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 9db7d8a..13ae420 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -7,9 +7,9 @@ when@dev: handlers: main: type: stream - path: "%kernel.logs_dir%/%kernel.environment%.log" + path: '%kernel.logs_dir%/%kernel.environment%.log' level: debug - channels: ["!event"] + channels: ['!event'] # uncomment to get logging in your browser # you may have to allow bigger header sizes in your Web server configuration #firephp: @@ -21,7 +21,7 @@ when@dev: console: type: console process_psr_3_messages: false - channels: ["!event", "!doctrine", "!console"] + channels: ['!event', '!doctrine', '!console'] when@test: monolog: @@ -31,10 +31,10 @@ when@test: action_level: error handler: nested excluded_http_codes: [404, 405] - channels: ["!event"] + channels: ['!event'] nested: type: stream - path: "%kernel.logs_dir%/%kernel.environment%.log" + path: '%kernel.logs_dir%/%kernel.environment%.log' level: debug when@prod: @@ -54,7 +54,7 @@ when@prod: console: type: console process_psr_3_messages: false - channels: ["!event", "!doctrine"] + channels: ['!event', '!doctrine'] deprecation: type: stream channels: [deprecation] diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 67b283a..178fb0b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -42,7 +42,7 @@ security: access_control: - { path: ^/api/docs, roles: PUBLIC_ACCESS } - { path: ^/api, roles: ROLE_SERVER } - - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } when@test: diff --git a/config/packages/webpack_encore.yaml b/config/packages/webpack_encore.yaml index 4c009ee..d8b3909 100644 --- a/config/packages/webpack_encore.yaml +++ b/config/packages/webpack_encore.yaml @@ -11,8 +11,8 @@ webpack_encore: # https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change # 'data-turbo-track': reload # link_attributes: - # Uncomment if using Turbo Drive - # 'data-turbo-track': reload + # Uncomment if using Turbo Drive + # 'data-turbo-track': reload # If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials') # crossorigin: 'anonymous' @@ -25,15 +25,14 @@ webpack_encore: # If you have multiple builds: # builds: - # frontend: '%kernel.project_dir%/public/frontend/build' + # frontend: '%kernel.project_dir%/public/frontend/build' - # pass the build name as the 3rd argument to the Twig functions - # {{ encore_entry_script_tags('entry1', null, 'frontend') }} + # pass the build name as the 3rd argument to the Twig functions + # {{ encore_entry_script_tags('entry1', null, 'frontend') }} framework: assets: json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' - #when@prod: # webpack_encore: # # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes) diff --git a/config/routes.yaml b/config/routes.yaml index 2048965..1459988 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,9 +1,9 @@ homepage: - path: / - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction - defaults: - path: /admin - permanent: false + path: / + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction + defaults: + path: /admin + permanent: false controllers: resource: diff --git a/config/routes/easyadmin.yaml b/config/routes/easyadmin.yaml index a88552e..083ca15 100644 --- a/config/routes/easyadmin.yaml +++ b/config/routes/easyadmin.yaml @@ -1,3 +1,3 @@ easyadmin: - resource: . - type: easyadmin.routes \ No newline at end of file + resource: . + type: easyadmin.routes diff --git a/config/routes/itkdev_openid_connect.yaml b/config/routes/itkdev_openid_connect.yaml index 2628cd9..1c60325 100644 --- a/config/routes/itkdev_openid_connect.yaml +++ b/config/routes/itkdev_openid_connect.yaml @@ -1,3 +1,3 @@ itkdev_openid_connect: - resource: "@ItkDevOpenIdConnectBundle/src/Resources/config/routes.yaml" - prefix: "/openidconnect" # Prefix for bundle routes \ No newline at end of file + resource: '@ItkDevOpenIdConnectBundle/src/Resources/config/routes.yaml' + prefix: '/openidconnect' # Prefix for bundle routes diff --git a/config/services.yaml b/config/services.yaml index 8463e06..71d40c9 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -8,7 +8,7 @@ parameters: services: # default configuration for services in *this* file _defaults: - autowire: true # Automatically injects dependencies in your services. + autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. # makes classes in src/ available to be used as services @@ -25,13 +25,12 @@ services: _instanceof: App\Handler\DetectionResultHandlerInterface: - tags: [ app.handler.detection_result_handler ] + tags: [app.handler.detection_result_handler] App\EventListener\RemovedRelationsListener: tags: - - - name: 'doctrine.event_listener' - event: 'postFlush' + - name: 'doctrine.event_listener' + event: 'postFlush' App\MessageHandler\ProcessDetectionResultHandler: arguments: diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 76201fa..4208ae2 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,8 +2,8 @@ services: rabbit: image: rabbitmq:4-management-alpine networks: - - app - - frontend + - app + - frontend ports: - "15672" environment: diff --git a/fixtures/oidc.yaml b/fixtures/oidc.yaml index 3712104..26fcc88 100644 --- a/fixtures/oidc.yaml +++ b/fixtures/oidc.yaml @@ -1,8 +1,8 @@ # @see https://fakerphp.github.io/formatters/ App\Entity\OIDC: - oidcservice_certificate/dev.example.com: - domain: 'dev.example.com' - expirationTime: <(new DateTime('+2 years'))> - onePasswordUrl: 'https://example.com/1password' - usageDocumentationUrl: https://example.com/example.com/README.md#oidc - type: 'stg' + oidcservice_certificate/dev.example.com: + domain: "dev.example.com" + expirationTime: <(new DateTime('+2 years'))> + onePasswordUrl: "https://example.com/1password" + usageDocumentationUrl: https://example.com/example.com/README.md#oidc + type: "stg" diff --git a/fixtures/server.yaml b/fixtures/server.yaml index 8fb9bf3..c23095a 100644 --- a/fixtures/server.yaml +++ b/fixtures/server.yaml @@ -1,17 +1,17 @@ App\Entity\Server: - server_{1..10}: - # A server id is needed by App\Entity\DetectionResult::generateHash(). - id: '<(new Symfony\Component\Uid\Ulid())>' - apiKey: - name: - hostingProvider: - internalIp: - externalIp: - aarhusSsl: - letsEncryptSsl: - veeam: - azureBackup: - monitoring: - databaseVersion: - system: - type: + server_{1..10}: + # A server id is needed by App\Entity\DetectionResult::generateHash(). + id: '<(new Symfony\Component\Uid\Ulid())>' + apiKey: + name: + hostingProvider: + internalIp: + externalIp: + aarhusSsl: + letsEncryptSsl: + veeam: + azureBackup: + monitoring: + databaseVersion: + system: + type: diff --git a/fixtures/service_certificates.yaml b/fixtures/service_certificates.yaml index 76c0a0d..66c8cd6 100644 --- a/fixtures/service_certificates.yaml +++ b/fixtures/service_certificates.yaml @@ -1,30 +1,30 @@ # @see https://fakerphp.github.io/formatters/ App\Entity\ServiceCertificate: - service_certificate/dev.example.com: - domain: 'dev.example.com' - name: 'CPR-opslag' - description: | - Bruges til opslag af CPR-numre. - onePasswordUrl: 'https://example.com/1password' - expirationTime: <(new DateTime('+2 years'))> - usageDocumentationUrl: https://example.com/example.com/README.md#certificates + service_certificate/dev.example.com: + domain: "dev.example.com" + name: "CPR-opslag" + description: | + Bruges til opslag af CPR-numre. + onePasswordUrl: "https://example.com/1password" + expirationTime: <(new DateTime('+2 years'))> + usageDocumentationUrl: https://example.com/example.com/README.md#certificates - service_certificate/prod.example.com: - domain: 'prod.example.com' - name: 'CVR-opslag' - description: | - Bruges til opslag af CVR-numre. - onePasswordUrl: 'https://example.com/1password' - expirationTime: <(new DateTime('+4 years'))> - usageDocumentationUrl: https://example.com/example.com/README.md#certificates + service_certificate/prod.example.com: + domain: "prod.example.com" + name: "CVR-opslag" + description: | + Bruges til opslag af CVR-numre. + onePasswordUrl: "https://example.com/1password" + expirationTime: <(new DateTime('+4 years'))> + usageDocumentationUrl: https://example.com/example.com/README.md#certificates App\Entity\ServiceCertificate\Service: - service/dev.example.com/service_certificate/digital-post: - certificate: '@service_certificate/dev.example.com' - type: 'digital post' - onePasswordUrl: 'https://example.com/1password' + service/dev.example.com/service_certificate/digital-post: + certificate: "@service_certificate/dev.example.com" + type: "digital post" + onePasswordUrl: "https://example.com/1password" - service/dev.example.com/service_certificate/cpr-lookup: - certificate: '@service_certificate/dev.example.com' - type: 'cpr lookup' - onePasswordUrl: 'https://example.com/1password' + service/dev.example.com/service_certificate/cpr-lookup: + certificate: "@service_certificate/dev.example.com" + type: "cpr lookup" + onePasswordUrl: "https://example.com/1password" diff --git a/fixtures/site.yaml b/fixtures/site.yaml index 4c19d2d..1e321b1 100644 --- a/fixtures/site.yaml +++ b/fixtures/site.yaml @@ -1,83 +1,83 @@ App\Entity\DetectionResult: - detection_result/dev.example.com: - server: '@server_1' - type: 'nginx' - rootDir: '/data/www/dev.example.com/htdocs/public' - data: | - { - "domain": "dev.example.com", - "phpVersion": "8.2", - "config": "/etc/nginx/sites-enabled/dev.example.com" - } - __calls: - - generateHash: [] - - setLastContact: [] + detection_result/dev.example.com: + server: "@server_1" + type: "nginx" + rootDir: "/data/www/dev.example.com/htdocs/public" + data: | + { + "domain": "dev.example.com", + "phpVersion": "8.2", + "config": "/etc/nginx/sites-enabled/dev.example.com" + } + __calls: + - generateHash: [] + - setLastContact: [] - detection_result/test.example.com: - server: '@server_1' - type: 'nginx' - rootDir: '/data/www/test.example.com/htdocs/public' - data: | - { - "domain": "test.example.com", - "phpVersion": "8.2", - "config": "/etc/nginx/sites-enabled/test.example.com" - } - __calls: - - generateHash: [] - - setLastContact: [] + detection_result/test.example.com: + server: "@server_1" + type: "nginx" + rootDir: "/data/www/test.example.com/htdocs/public" + data: | + { + "domain": "test.example.com", + "phpVersion": "8.2", + "config": "/etc/nginx/sites-enabled/test.example.com" + } + __calls: + - generateHash: [] + - setLastContact: [] - detection_result/prod.example.com: - server: '@server_1' - type: 'nginx' - rootDir: '/data/www/prod.example.com/htdocs/public' - data: | - { - "domain": "prod.example.com", - "phpVersion": "8.2", - "config": "/etc/nginx/sites-enabled/prod.example.com" - } - __calls: - - generateHash: [] - - setLastContact: [] + detection_result/prod.example.com: + server: "@server_1" + type: "nginx" + rootDir: "/data/www/prod.example.com/htdocs/public" + data: | + { + "domain": "prod.example.com", + "phpVersion": "8.2", + "config": "/etc/nginx/sites-enabled/prod.example.com" + } + __calls: + - generateHash: [] + - setLastContact: [] App\Entity\Domain: - domain/dev.example.com: - rootDir: '/tmp' - address: 'dev.example.com' - detectionResult: '@detection_result/dev.example.com' - domain/test.example.com: - rootDir: '/tmp' - address: 'test.example.com' - detectionResult: '@detection_result/test.example.com' - domain/prod.example.com: - rootDir: '/tmp' - address: 'prod.example.com' - detectionResult: '@detection_result/prod.example.com' + domain/dev.example.com: + rootDir: "/tmp" + address: "dev.example.com" + detectionResult: "@detection_result/dev.example.com" + domain/test.example.com: + rootDir: "/tmp" + address: "test.example.com" + detectionResult: "@detection_result/test.example.com" + domain/prod.example.com: + rootDir: "/tmp" + address: "prod.example.com" + detectionResult: "@detection_result/prod.example.com" App\Entity\Installation: - installation/dev.example.com: - rootDir: '/tmp' - detectionResult: '@detection_result/dev.example.com' + installation/dev.example.com: + rootDir: "/tmp" + detectionResult: "@detection_result/dev.example.com" - installation/prod.example.com: - rootDir: '/tmp' - detectionResult: '@detection_result/prod.example.com' + installation/prod.example.com: + rootDir: "/tmp" + detectionResult: "@detection_result/prod.example.com" App\Entity\Site: - site/dev.example.com: - server: '@server_1' - detectionResult: '@detection_result/dev.example.com' - rootDir: '/data/www/dev.example.com/htdocs/public' - installation: '@installation/dev.example.com' - __calls: - - addDomain: ['@domain/dev.example.com'] + site/dev.example.com: + server: "@server_1" + detectionResult: "@detection_result/dev.example.com" + rootDir: "/data/www/dev.example.com/htdocs/public" + installation: "@installation/dev.example.com" + __calls: + - addDomain: ["@domain/dev.example.com"] - site/prod.example.com: - server: '@server_1' - detectionResult: '@detection_result/prod.example.com' - rootDir: '/data/www/prod.example.com/htdocs/public' - installation: '@installation/prod.example.com' - __calls: - - addDomain: ['@domain/prod.example.com'] - - addDomain: ['@domain/test.example.com'] + site/prod.example.com: + server: "@server_1" + detectionResult: "@detection_result/prod.example.com" + rootDir: "/data/www/prod.example.com/htdocs/public" + installation: "@installation/prod.example.com" + __calls: + - addDomain: ["@domain/prod.example.com"] + - addDomain: ["@domain/test.example.com"] diff --git a/fixtures/user.yaml b/fixtures/user.yaml index ec4cfd7..de04a82 100644 --- a/fixtures/user.yaml +++ b/fixtures/user.yaml @@ -1,5 +1,3 @@ App\Entity\User: - user_admin: - __construct: [ - 'admin@example.com', 'Admin', [ROLE_ADMIN] - ] + user_admin: + __construct: ["admin@example.com", "Admin", [ROLE_ADMIN]] diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index c8a4009..39587ec 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -1,245 +1,242 @@ openapi: 3.1.0 info: - title: '' - description: '' - version: 0.0.0 + title: "" + description: "" + version: 0.0.0 servers: - - - url: / - description: '' + - url: / + description: "" paths: - /api/detection_results: - post: - operationId: api_detection_results_post - tags: - - DetectionResult - responses: - '202': - description: 'DetectionResult resource created' - content: - application/json: - schema: { } - application/ld+json: - schema: { } - links: { } - '400': - description: 'Invalid input' - content: - application/ld+json: - schema: - $ref: '#/components/schemas/Error.jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/Error' - application/json: - schema: - $ref: '#/components/schemas/Error' - links: { } - '422': - description: 'An error occurred' - content: - application/ld+json: - schema: - $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - application/json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - links: { } - summary: 'Creates a DetectionResult resource.' - description: 'Creates a DetectionResult resource.' - parameters: [] - requestBody: - description: 'The new DetectionResult resource' - content: - application/json: - schema: - $ref: '#/components/schemas/DetectionResult-write' - application/ld+json: - schema: - $ref: '#/components/schemas/DetectionResult-write' - required: true - deprecated: false + /api/detection_results: + post: + operationId: api_detection_results_post + tags: + - DetectionResult + responses: + "202": + description: "DetectionResult resource created" + content: + application/json: + schema: {} + application/ld+json: + schema: {} + links: {} + "400": + description: "Invalid input" + content: + application/ld+json: + schema: + $ref: "#/components/schemas/Error.jsonld" + application/problem+json: + schema: + $ref: "#/components/schemas/Error" + application/json: + schema: + $ref: "#/components/schemas/Error" + links: {} + "422": + description: "An error occurred" + content: + application/ld+json: + schema: + $ref: "#/components/schemas/ConstraintViolation.jsonld-jsonld" + application/problem+json: + schema: + $ref: "#/components/schemas/ConstraintViolation-json" + application/json: + schema: + $ref: "#/components/schemas/ConstraintViolation-json" + links: {} + summary: "Creates a DetectionResult resource." + description: "Creates a DetectionResult resource." + parameters: [] + requestBody: + description: "The new DetectionResult resource" + content: + application/json: + schema: + $ref: "#/components/schemas/DetectionResult-write" + application/ld+json: + schema: + $ref: "#/components/schemas/DetectionResult-write" + required: true + deprecated: false components: - schemas: - ConstraintViolation-json: - type: object - description: 'Unprocessable entity' - deprecated: false - properties: - status: - default: 422 - example: 422 - type: integer - violations: - type: array - items: + schemas: + ConstraintViolation-json: type: object + description: "Unprocessable entity" + deprecated: false properties: - propertyPath: - type: string - description: 'The property path of the violation' - message: - type: string - description: 'The message associated with the violation' - detail: - readOnly: true - type: string - type: - readOnly: true - type: string - title: - readOnly: true - type: - - string - - 'null' - instance: - readOnly: true - type: - - string - - 'null' - ConstraintViolation.jsonld-jsonld: - type: object - description: 'Unprocessable entity' - deprecated: false - properties: - status: - default: 422 - example: 422 - type: integer - violations: - type: array - items: + status: + default: 422 + example: 422 + type: integer + violations: + type: array + items: + type: object + properties: + propertyPath: + type: string + description: "The property path of the violation" + message: + type: string + description: "The message associated with the violation" + detail: + readOnly: true + type: string + type: + readOnly: true + type: string + title: + readOnly: true + type: + - string + - "null" + instance: + readOnly: true + type: + - string + - "null" + ConstraintViolation.jsonld-jsonld: type: object + description: "Unprocessable entity" + deprecated: false properties: - propertyPath: - type: string - description: 'The property path of the violation' - message: - type: string - description: 'The message associated with the violation' - detail: - readOnly: true - type: string - description: - readOnly: true - type: string - type: - readOnly: true - type: string - title: - readOnly: true - type: - - string - - 'null' - instance: - readOnly: true - type: - - string - - 'null' - DetectionResult-write: - type: object - description: '' - deprecated: false - properties: - type: - default: '' - example: '' - type: string - rootDir: - default: '' - example: '' - type: string - data: - default: '' - example: '' - type: string - Error: - type: object - description: 'A representation of common errors.' - deprecated: false - properties: - title: - readOnly: true - description: 'A short, human-readable summary of the problem.' - type: - - string - - 'null' - detail: - readOnly: true - description: 'A human-readable explanation specific to this occurrence of the problem.' - type: - - string - - 'null' - status: - type: number - examples: - - 404 - default: 400 - instance: - readOnly: true - description: 'A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.' - type: - - string - - 'null' - type: - readOnly: true - description: 'A URI reference that identifies the problem type' - type: string - Error.jsonld: - type: object - description: 'A representation of common errors.' - deprecated: false - properties: - title: - readOnly: true - description: 'A short, human-readable summary of the problem.' - type: - - string - - 'null' - detail: - readOnly: true - description: 'A human-readable explanation specific to this occurrence of the problem.' - type: - - string - - 'null' - status: - type: number - examples: - - 404 - default: 400 - instance: - readOnly: true - description: 'A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.' - type: - - string - - 'null' - type: - readOnly: true - description: 'A URI reference that identifies the problem type' - type: string - description: - readOnly: true - type: - - string - - 'null' - responses: { } - parameters: { } - examples: { } - requestBodies: { } - headers: { } - securitySchemes: - apiKey: - type: apiKey - description: 'Value for the Authorization header parameter.' - name: Authorization - in: header + status: + default: 422 + example: 422 + type: integer + violations: + type: array + items: + type: object + properties: + propertyPath: + type: string + description: "The property path of the violation" + message: + type: string + description: "The message associated with the violation" + detail: + readOnly: true + type: string + description: + readOnly: true + type: string + type: + readOnly: true + type: string + title: + readOnly: true + type: + - string + - "null" + instance: + readOnly: true + type: + - string + - "null" + DetectionResult-write: + type: object + description: "" + deprecated: false + properties: + type: + default: "" + example: "" + type: string + rootDir: + default: "" + example: "" + type: string + data: + default: "" + example: "" + type: string + Error: + type: object + description: "A representation of common errors." + deprecated: false + properties: + title: + readOnly: true + description: "A short, human-readable summary of the problem." + type: + - string + - "null" + detail: + readOnly: true + description: "A human-readable explanation specific to this occurrence of the problem." + type: + - string + - "null" + status: + type: number + examples: + - 404 + default: 400 + instance: + readOnly: true + description: "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + type: + - string + - "null" + type: + readOnly: true + description: "A URI reference that identifies the problem type" + type: string + Error.jsonld: + type: object + description: "A representation of common errors." + deprecated: false + properties: + title: + readOnly: true + description: "A short, human-readable summary of the problem." + type: + - string + - "null" + detail: + readOnly: true + description: "A human-readable explanation specific to this occurrence of the problem." + type: + - string + - "null" + status: + type: number + examples: + - 404 + default: 400 + instance: + readOnly: true + description: "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." + type: + - string + - "null" + type: + readOnly: true + description: "A URI reference that identifies the problem type" + type: string + description: + readOnly: true + type: + - string + - "null" + responses: {} + parameters: {} + examples: {} + requestBodies: {} + headers: {} + securitySchemes: + apiKey: + type: apiKey + description: "Value for the Authorization header parameter." + name: Authorization + in: header security: - - - apiKey: [] + - apiKey: [] tags: - - - name: DetectionResult -webhooks: { } + - name: DetectionResult +webhooks: {} From 32b61437c7351560d74e1f63bb15f7ef768784a0 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 18:50:47 +0200 Subject: [PATCH 12/94] 5564: Add api spec to prettier ignore --- .prettierignore | 2 + public/api-spec-v1.yaml | 465 ++++++++++++++++++++-------------------- 2 files changed, 236 insertions(+), 231 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..bb35050 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# API spec +public/api-spec-v1.yaml diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index 39587ec..c8a4009 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -1,242 +1,245 @@ openapi: 3.1.0 info: - title: "" - description: "" - version: 0.0.0 + title: '' + description: '' + version: 0.0.0 servers: - - url: / - description: "" + - + url: / + description: '' paths: - /api/detection_results: - post: - operationId: api_detection_results_post - tags: - - DetectionResult - responses: - "202": - description: "DetectionResult resource created" - content: - application/json: - schema: {} - application/ld+json: - schema: {} - links: {} - "400": - description: "Invalid input" - content: - application/ld+json: - schema: - $ref: "#/components/schemas/Error.jsonld" - application/problem+json: - schema: - $ref: "#/components/schemas/Error" - application/json: - schema: - $ref: "#/components/schemas/Error" - links: {} - "422": - description: "An error occurred" - content: - application/ld+json: - schema: - $ref: "#/components/schemas/ConstraintViolation.jsonld-jsonld" - application/problem+json: - schema: - $ref: "#/components/schemas/ConstraintViolation-json" - application/json: - schema: - $ref: "#/components/schemas/ConstraintViolation-json" - links: {} - summary: "Creates a DetectionResult resource." - description: "Creates a DetectionResult resource." - parameters: [] - requestBody: - description: "The new DetectionResult resource" - content: - application/json: - schema: - $ref: "#/components/schemas/DetectionResult-write" - application/ld+json: - schema: - $ref: "#/components/schemas/DetectionResult-write" - required: true - deprecated: false + /api/detection_results: + post: + operationId: api_detection_results_post + tags: + - DetectionResult + responses: + '202': + description: 'DetectionResult resource created' + content: + application/json: + schema: { } + application/ld+json: + schema: { } + links: { } + '400': + description: 'Invalid input' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/Error.jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + application/json: + schema: + $ref: '#/components/schemas/Error' + links: { } + '422': + description: 'An error occurred' + content: + application/ld+json: + schema: + $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + application/json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + links: { } + summary: 'Creates a DetectionResult resource.' + description: 'Creates a DetectionResult resource.' + parameters: [] + requestBody: + description: 'The new DetectionResult resource' + content: + application/json: + schema: + $ref: '#/components/schemas/DetectionResult-write' + application/ld+json: + schema: + $ref: '#/components/schemas/DetectionResult-write' + required: true + deprecated: false components: - schemas: - ConstraintViolation-json: + schemas: + ConstraintViolation-json: + type: object + description: 'Unprocessable entity' + deprecated: false + properties: + status: + default: 422 + example: 422 + type: integer + violations: + type: array + items: type: object - description: "Unprocessable entity" - deprecated: false properties: - status: - default: 422 - example: 422 - type: integer - violations: - type: array - items: - type: object - properties: - propertyPath: - type: string - description: "The property path of the violation" - message: - type: string - description: "The message associated with the violation" - detail: - readOnly: true - type: string - type: - readOnly: true - type: string - title: - readOnly: true - type: - - string - - "null" - instance: - readOnly: true - type: - - string - - "null" - ConstraintViolation.jsonld-jsonld: + propertyPath: + type: string + description: 'The property path of the violation' + message: + type: string + description: 'The message associated with the violation' + detail: + readOnly: true + type: string + type: + readOnly: true + type: string + title: + readOnly: true + type: + - string + - 'null' + instance: + readOnly: true + type: + - string + - 'null' + ConstraintViolation.jsonld-jsonld: + type: object + description: 'Unprocessable entity' + deprecated: false + properties: + status: + default: 422 + example: 422 + type: integer + violations: + type: array + items: type: object - description: "Unprocessable entity" - deprecated: false properties: - status: - default: 422 - example: 422 - type: integer - violations: - type: array - items: - type: object - properties: - propertyPath: - type: string - description: "The property path of the violation" - message: - type: string - description: "The message associated with the violation" - detail: - readOnly: true - type: string - description: - readOnly: true - type: string - type: - readOnly: true - type: string - title: - readOnly: true - type: - - string - - "null" - instance: - readOnly: true - type: - - string - - "null" - DetectionResult-write: - type: object - description: "" - deprecated: false - properties: - type: - default: "" - example: "" - type: string - rootDir: - default: "" - example: "" - type: string - data: - default: "" - example: "" - type: string - Error: - type: object - description: "A representation of common errors." - deprecated: false - properties: - title: - readOnly: true - description: "A short, human-readable summary of the problem." - type: - - string - - "null" - detail: - readOnly: true - description: "A human-readable explanation specific to this occurrence of the problem." - type: - - string - - "null" - status: - type: number - examples: - - 404 - default: 400 - instance: - readOnly: true - description: "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." - type: - - string - - "null" - type: - readOnly: true - description: "A URI reference that identifies the problem type" - type: string - Error.jsonld: - type: object - description: "A representation of common errors." - deprecated: false - properties: - title: - readOnly: true - description: "A short, human-readable summary of the problem." - type: - - string - - "null" - detail: - readOnly: true - description: "A human-readable explanation specific to this occurrence of the problem." - type: - - string - - "null" - status: - type: number - examples: - - 404 - default: 400 - instance: - readOnly: true - description: "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced." - type: - - string - - "null" - type: - readOnly: true - description: "A URI reference that identifies the problem type" - type: string - description: - readOnly: true - type: - - string - - "null" - responses: {} - parameters: {} - examples: {} - requestBodies: {} - headers: {} - securitySchemes: - apiKey: - type: apiKey - description: "Value for the Authorization header parameter." - name: Authorization - in: header + propertyPath: + type: string + description: 'The property path of the violation' + message: + type: string + description: 'The message associated with the violation' + detail: + readOnly: true + type: string + description: + readOnly: true + type: string + type: + readOnly: true + type: string + title: + readOnly: true + type: + - string + - 'null' + instance: + readOnly: true + type: + - string + - 'null' + DetectionResult-write: + type: object + description: '' + deprecated: false + properties: + type: + default: '' + example: '' + type: string + rootDir: + default: '' + example: '' + type: string + data: + default: '' + example: '' + type: string + Error: + type: object + description: 'A representation of common errors.' + deprecated: false + properties: + title: + readOnly: true + description: 'A short, human-readable summary of the problem.' + type: + - string + - 'null' + detail: + readOnly: true + description: 'A human-readable explanation specific to this occurrence of the problem.' + type: + - string + - 'null' + status: + type: number + examples: + - 404 + default: 400 + instance: + readOnly: true + description: 'A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.' + type: + - string + - 'null' + type: + readOnly: true + description: 'A URI reference that identifies the problem type' + type: string + Error.jsonld: + type: object + description: 'A representation of common errors.' + deprecated: false + properties: + title: + readOnly: true + description: 'A short, human-readable summary of the problem.' + type: + - string + - 'null' + detail: + readOnly: true + description: 'A human-readable explanation specific to this occurrence of the problem.' + type: + - string + - 'null' + status: + type: number + examples: + - 404 + default: 400 + instance: + readOnly: true + description: 'A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.' + type: + - string + - 'null' + type: + readOnly: true + description: 'A URI reference that identifies the problem type' + type: string + description: + readOnly: true + type: + - string + - 'null' + responses: { } + parameters: { } + examples: { } + requestBodies: { } + headers: { } + securitySchemes: + apiKey: + type: apiKey + description: 'Value for the Authorization header parameter.' + name: Authorization + in: header security: - - apiKey: [] + - + apiKey: [] tags: - - name: DetectionResult -webhooks: {} + - + name: DetectionResult +webhooks: { } From f1548aad1e91c392b85368f20e61771dad36630c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 18:56:04 +0200 Subject: [PATCH 13/94] 5564: Composer normalize --- composer.json | 8 ++++---- composer.lock | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 685da54..b032c1b 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ "phpdocumentor/reflection-docblock": "^5.6.3", "phpstan/phpdoc-parser": "^2.3", "symfony/amqp-messenger": "^7.3.2", - "symfony/asset": "7.3.*", - "symfony/asset-mapper": "7.3.*", + "symfony/asset": "^7.3.0", + "symfony/asset-mapper": "^7.3.4", "symfony/browser-kit": "^7.3.2", "symfony/console": "^7.3.4", "symfony/doctrine-messenger": "^7.3.4", @@ -43,8 +43,8 @@ "symfony/validator": "^7.3.4", "symfony/webpack-encore-bundle": "^2.3", "symfony/yaml": "^7.3.3", - "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/extra-bundle": "^2.12 || ^3.21", + "twig/twig": "^2.12 || ^3.21.1" }, "require-dev": { "ergebnis/composer-normalize": "^2.48.2", diff --git a/composer.lock b/composer.lock index c7af645..ff3bcc3 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": "27b62ec66b20f82b1b953f9b698bd714", + "content-hash": "23e908fe58ef72041cd16db5e898981d", "packages": [ { "name": "api-platform/core", @@ -14222,7 +14222,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -14230,6 +14230,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } From 918deec8cb30fd5c93e800d57e8016740a574758 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 18:57:02 +0200 Subject: [PATCH 14/94] 5564: JS lint --- assets/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/app.js b/assets/app.js index 6174cc6..e2824a5 100644 --- a/assets/app.js +++ b/assets/app.js @@ -4,6 +4,6 @@ * This file will be included onto the page via the importmap() Twig function, * which should already be in your base.html.twig. */ -import './styles/app.css'; +import "./styles/app.css"; -console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); +console.log("This log comes from assets/app.js - welcome to AssetMapper! 🎉"); From 69528dc575622c3d3218964397d56dd97b416be2 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 18:59:26 +0200 Subject: [PATCH 15/94] 5564: Markdown lint --- CHANGELOG.md | 2 +- README.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d25da1f..b1bf9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,7 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade to: Symfony 7.2, Doctrine ORM 3.x / DBAL 4.x, Api-platform 4.0, PhpUnit 11 with dependencies - Switch to PHPStan - Added cleanup for detection results -- Refactor rootDir normalization to ensure values are always normalized, fix type errors, +- Refactor rootDir normalization to ensure values are always normalized, fix type errors, - Fix various values not being set ## [1.7.1] - 2024-11-08 diff --git a/README.md b/README.md index a1fc813..72b2dff 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ [![GitHub last commit](https://img.shields.io/github/last-commit/itk-dev/devops_itksites?style=flat-square)](https://github.com/itk-dev/devops_itksites/commits/develop/) [![GitHub License](https://img.shields.io/github/license/itk-dev/devops_itksites?style=flat-square)](https://github.com/itk-dev/devops_itksites/blob/develop/LICENSE) - This is our internal server and site registration tool. It works in tandem with our [ITK sites server harvester](https://github.com/itk-dev/devops_itkServerHarvest). The harvester is installed by default on all servers, and runs at intervals and collects @@ -15,6 +14,7 @@ information about sites and installations running on the server. These are sent `DetectionResults` to ITKsites where they are analysed and processed. This allows us to monitor + * What is installed and running * Which sites/domains we are hosting * What docker images we are running @@ -23,6 +23,7 @@ This allows us to monitor * What git repositories we are hosting Additionally we can register and document + * All OpenID Connect setups * All Services Certificates @@ -30,6 +31,7 @@ Servers, OpenID Connect setups, Services Certificates must be created and mainta All other information is kept up to date by analysing the DetectionResults. ## Architecture + This is a Symfony 6 project build with api-platform 3.x and EasyAdmin. Api-platform provides a simple REST api for POST'ing the DetectionResults. @@ -54,6 +56,7 @@ docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interact Then create a `.env.local` file to set secrets for your local setup. ### OpenID Connect + All users access is controlled by OpenID Connect. For local development you must add the following to your `.env.local` file: @@ -94,11 +97,13 @@ docker compose exec phpfpm bin/console itk-dev:openid-connect:login admin@exampl All processing of Detctionresults is done in a series of message handlers. To run these do either: + ```shell docker compose exec phpfpm composer queues ``` or + ```shell docker compose exec phpfpm bin/console messenger:consume async --failure-limit=1 -vvv ``` From ad0a2f4afaed3efcebe80aeacdac8c448b7fca84 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 14 Oct 2025 19:00:29 +0200 Subject: [PATCH 16/94] 5564: CSS lint --- assets/styles/app.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/styles/app.css b/assets/styles/app.css index 853c0f8..2e8af7a 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -2,14 +2,14 @@ --body-max-width: 100%; --sidebar-bg: #fff; /* make the base font size smaller */ - --button-primary-bg: rgb(0,123,166); - --pagination-active-bg: rgb(0,123,166); - --link-color: rgb(0,123,166); - --sidebar-menu-active-item-color: rgb(0,123,166); - --badge-boolean-true-bg: rgb(0,123,166); + --button-primary-bg: rgb(0, 123, 166); + --pagination-active-bg: rgb(0, 123, 166); + --link-color: rgb(0, 123, 166); + --sidebar-menu-active-item-color: rgb(0, 123, 166); + --badge-boolean-true-bg: rgb(0, 123, 166); --badge-boolean-false-bg: rgb(228, 73, 48); --badge-boolean-false-color: var(--white); - --sidebar-menu-color: rgb(66,66,66); - --text-color-dark: rgb(66,66,66); + --sidebar-menu-color: rgb(66, 66, 66); + --text-color-dark: rgb(66, 66, 66); --bs-danger-rgb: 228, 73, 48; -} \ No newline at end of file +} From 180faff23f477725947b30e965a9010c23ace70d Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 12:52:38 +0200 Subject: [PATCH 17/94] 5564: Remove redundant psalm config --- Taskfile.yml | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++ psalm.xml | 27 ------- 2 files changed, 203 insertions(+), 27 deletions(-) create mode 100644 Taskfile.yml delete mode 100644 psalm.xml diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..0785dc0 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,203 @@ +# https://taskfile.dev + +version: "3" + +# https://taskfile.dev/usage/#env-files +dotenv: [".env.local", ".env"] + +vars: + # https://taskfile.dev/reference/templating/ + DOCKER_COMPOSE: '{{.TASK_DOCKER_COMPOSE | default "docker compose"}}' + +tasks: + site:update: + desc: Update/install site + cmds: + - task: compose + vars: + COMPOSE_ARGS: pull + - task: compose + vars: + COMPOSE_ARGS: up --detach --wait + - task: composer + vars: + COMPOSER_ARGS: install + + fixtures:load: + desc: Load all fixtures + prompt: Really load all fixtures? + cmds: + - for: + - events + - organizations + - occurrences + - daily_occurrences + - tags + - vocabularies + - locations + task: compose + vars: + COMPOSE_ARGS: exec phpfpm bin/console app:fixtures:load {{.ITEM}} + # Loading some fixtures generates an 'Warning: Undefined array key "entityId"' error. + ignore_error: true + silent: true + + fixtures:load:test: + desc: Load local fixtures + prompt: Really load all local fixtures? + cmds: + - for: + - events + - organizations + - occurrences + - daily_occurrences + - tags + - vocabularies + - locations + task: compose + vars: + COMPOSE_ARGS: exec phpfpm bin/console app:fixtures:load {{.ITEM}} --url=file:///app/tests/resources/{{.ITEM}}.json + # Loading some fixtures generates an 'Warning: Undefined array key "entityId"' error. + ignore_error: true + + composer: + desc: "Run `composer` command. Example: task composer -- normalize" + cmds: + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm composer {{.COMPOSER_ARGS}} + + compose: + desc: "Run `docker compose` command. Example: task compose -- ps" + cmds: + # Run docker compose with both arguments passed via var plus any cli args. + - "{{.DOCKER_COMPOSE}} {{.COMPOSE_ARGS}} {{.CLI_ARGS}}" + + console: + desc: "Run Symfony console command. Example: task console -- cache:clear" + cmds: + # Run docker compose with both arguments passed via var plus any cli args. + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm bin/console {{.CONSOLE_ARGS}} + + coding-standards:apply: + desc: "Apply coding standards" + cmds: + - task: coding-standards:markdown:apply + - task: coding-standards:php:apply + - task: coding-standards:twig:apply + - task: coding-standards:yaml:apply + silent: true + + coding-standards:check: + desc: "Apply coding standards" + cmds: + - task: coding-standards:markdown:check + - task: coding-standards:php:check + - task: coding-standards:twig:check + - task: coding-standards:yaml:check + silent: true + + coding-standards:markdown:apply: + desc: "Apply coding standards for Markdown" + cmds: + - task: compose + vars: + COMPOSE_ARGS: run --rm markdownlint markdownlint '**/*.md' --fix + + coding-standards:markdown:check: + desc: "Apply and check coding standards for Markdown" + cmds: + - task: coding-standards:markdown:apply + - task: compose + vars: + COMPOSE_ARGS: run --rm markdownlint markdownlint '**/*.md' + + coding-standards:php:apply: + desc: "Apply coding standards for PHP" + cmds: + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm vendor/bin/php-cs-fixer fix + silent: true + + coding-standards:php:check: + desc: "Apply and check coding standards for PHP" + cmds: + - task: coding-standards:php:apply + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm vendor/bin/php-cs-fixer check + silent: true + + coding-standards:twig:apply: + desc: "Apply coding standards for Twig" + cmds: + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm vendor/bin/twig-cs-fixer fix + silent: true + + coding-standards:twig:check: + desc: "Apply and check coding standards for Twig" + cmds: + - task: coding-standards:twig:apply + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm vendor/bin/twig-cs-fixer check + silent: true + + coding-standards:yaml:apply: + desc: "Apply coding standards for YAML" + cmds: + - task: compose + vars: + COMPOSE_ARGS: run --rm prettier '**/*.{yml,yaml}' --write + silent: true + + coding-standards:yaml:check: + desc: "Apply and check coding standards for YAML" + cmds: + - task: coding-standards:yaml:apply + - task: compose + vars: + COMPOSE_ARGS: run --rm prettier '**/*.{yml,yaml}' --check + silent: true + + code-analysis: + desc: "Run code analysis" + cmds: + - task: code-analysis:phpstan + silent: true + + code-analysis:phpstan: + desc: "Run PHPStan" + cmds: + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm vendor/bin/phpstan + silent: true + + api:test: + desc: Run API tests + cmds: + - task: compose + vars: + COMPOSE_ARGS: exec phpfpm bin/phpunit + + api:spec:export: + desc: Export API spec + cmds: + - task: console + vars: + CONSOLE_ARGS: api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction + - task: console + vars: + CONSOLE_ARGS: api:openapi:export --json --output=public/api-spec-v1.json --no-interaction + silent: true + + default: + cmds: + - task --list + silent: true diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index bedf5ac..0000000 --- a/psalm.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - From e2a30c7b65db9ca61570689ebc1f0d2f209f3777 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 12:53:07 +0200 Subject: [PATCH 18/94] 5564: Add Taskfile --- Taskfile.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 0785dc0..e3b6974 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -22,6 +22,12 @@ tasks: - task: composer vars: COMPOSER_ARGS: install + - task: console + vars: + CONSOLE_ARGS: doctrine:migrations:migrate --no-interaction + - task: console + vars: + CONSOLE_ARGS: messenger:setup-transports fixtures:load: desc: Load all fixtures From b3dece71ffae94f441dcb4ad9be8bd3e948cd3ad Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 13:38:02 +0200 Subject: [PATCH 19/94] 5564: Move commands crom composer.json to taskfile, cleanup actions --- .github/workflows/changelog.yaml | 2 - .github/workflows/composer.yaml | 2 - .github/workflows/javascript.yaml | 2 - .github/workflows/markdown.yaml | 2 - .github/workflows/pr.yaml | 124 +++++++++++++++++------------- .github/workflows/styles.yaml | 2 - .github/workflows/twig.yaml | 2 - .github/workflows/yaml.yaml | 2 +- Taskfile.yml | 64 +++++++-------- composer.json | 26 +------ 10 files changed, 103 insertions(+), 125 deletions(-) diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 15297e3..9f5fead 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -14,8 +14,6 @@ on: jobs: changelog: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index 9a56bd6..314a11b 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -38,8 +38,6 @@ on: jobs: composer-validate: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml index bafc128..15f14b6 100644 --- a/.github/workflows/javascript.yaml +++ b/.github/workflows/javascript.yaml @@ -23,8 +23,6 @@ on: jobs: javascript-lint: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index cdd8b4b..cf4c011 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -30,8 +30,6 @@ on: jobs: markdown-lint: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 6dfe6ad..99b63a6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -6,57 +6,77 @@ env: on: pull_request jobs: - phpunit: - name: PHP Unit tests + phpstan: + name: PHPStan static analysis runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - - name: Run tests - run: | - docker compose exec phpfpm composer run tests - - fixtures: - name: Load fixtures - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 - - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - - name: Load fixtures - run: | - docker compose exec phpfpm composer fixtures - - build-assets: - name: Build assets - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - name: Build assets - run: | - docker network create frontend - docker compose run --rm node yarn install - docker compose run --rm node yarn build + phpstan: + name: PHPStan static analysis + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Create docker network + run: | + docker network create frontend + + - name: Run phpstan + run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/phpstan analyse src --no-progress --error-format=github --memory-limit=1G + + phpunit: + name: PHP Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Run tests + run: | + docker compose exec phpfpm composer run tests + + fixtures: + name: Load fixtures + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 + + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Load fixtures + run: | + docker compose exec phpfpm composer fixtures + + build-assets: + name: Build assets + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: Build assets + run: | + docker network create frontend + docker compose run --rm node yarn install + docker compose run --rm node yarn build diff --git a/.github/workflows/styles.yaml b/.github/workflows/styles.yaml index 964cffb..985e56f 100644 --- a/.github/workflows/styles.yaml +++ b/.github/workflows/styles.yaml @@ -23,8 +23,6 @@ on: jobs: styles-lint: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml index 11b54ad..680a42d 100644 --- a/.github/workflows/twig.yaml +++ b/.github/workflows/twig.yaml @@ -36,8 +36,6 @@ on: jobs: twig-lint: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml index ba8ca52..3115e31 100644 --- a/.github/workflows/yaml.yaml +++ b/.github/workflows/yaml.yaml @@ -14,7 +14,7 @@ ### #### Symfony YAML ### ### Symfony's YAML config files use 4 spaces for indentation and single quotes. -### Therefore we use a [Prettier configuration +### Therefore, we use a [Prettier configuration ### file](https://prettier.io/docs/configuration), `.prettierrc.yaml`, to make ### Prettier format YAML files in the `config/` folder like Symfony expects. diff --git a/Taskfile.yml b/Taskfile.yml index e3b6974..d4e3070 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -18,7 +18,7 @@ tasks: COMPOSE_ARGS: pull - task: compose vars: - COMPOSE_ARGS: up --detach --wait + COMPOSE_ARGS: up --detach - task: composer vars: COMPOSER_ARGS: install @@ -33,38 +33,10 @@ tasks: desc: Load all fixtures prompt: Really load all fixtures? cmds: - - for: - - events - - organizations - - occurrences - - daily_occurrences - - tags - - vocabularies - - locations - task: compose - vars: - COMPOSE_ARGS: exec phpfpm bin/console app:fixtures:load {{.ITEM}} - # Loading some fixtures generates an 'Warning: Undefined array key "entityId"' error. - ignore_error: true - silent: true - - fixtures:load:test: - desc: Load local fixtures - prompt: Really load all local fixtures? - cmds: - - for: - - events - - organizations - - occurrences - - daily_occurrences - - tags - - vocabularies - - locations - task: compose + - task: console vars: - COMPOSE_ARGS: exec phpfpm bin/console app:fixtures:load {{.ITEM}} --url=file:///app/tests/resources/{{.ITEM}}.json - # Loading some fixtures generates an 'Warning: Undefined array key "entityId"' error. - ignore_error: true + CONSOLE_ARGS: hautelook:fixtures:load --no-interaction + silent: true composer: desc: "Run `composer` command. Example: task composer -- normalize" @@ -185,15 +157,37 @@ tasks: COMPOSE_ARGS: exec phpfpm vendor/bin/phpstan silent: true - api:test: - desc: Run API tests + messenger:consume: + desc: "Run queue consumer" + cmds: + - task: console + vars: + CONSOLE_ARGS: bin/console messenger:consume async --failure-limit=1 -vvv + + test:setup: + desc: "Setup test environment" + env: + APP_ENV: test + cmds: + - task: console + vars: + CONSOLE_ARGS: doctrine:database:create --if-not-exists --no-interaction + - task: console + vars: + CONSOLE_ARGS: doctrine:database:create --no-interaction --if-not-exists --quiet + - task: console + vars: + CONSOLE_ARGS: doctrine:migrations:migrate --no-interaction --quiet + + test:run: + desc: "Run phpunit tests" cmds: - task: compose vars: COMPOSE_ARGS: exec phpfpm bin/phpunit api:spec:export: - desc: Export API spec + desc: "Export API spec" cmds: - task: console vars: diff --git a/composer.json b/composer.json index b032c1b..13ac7b1 100644 --- a/composer.json +++ b/composer.json @@ -117,30 +117,6 @@ "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", "importmap:install": "symfony-cmd" - }, - "coding-standards-apply": [ - "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix", - "vendor/bin/twig-cs-fixer --fix" - ], - "coding-standards-check": [ - "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run", - "vendor/bin/twig-cs-fixer" - ], - "fixtures-load": [ - "bin/console hautelook:fixtures:load --no-interaction" - ], - "queues": [ - "bin/console messenger:consume async --failure-limit=1 -vvv" - ], - "tests": [ - "bin/console --env=test doctrine:database:drop --if-exists --force --quiet", - "bin/console --env=test doctrine:database:create --no-interaction --if-not-exists --quiet", - "bin/console --env=test doctrine:migrations:migrate --no-interaction --quiet", - "vendor/bin/phpunit --stop-on-failure" - ], - "update-api-spec": [ - "bin/console api:openapi:export --output=public/api-spec-v1.yaml --yaml --no-interaction", - "bin/console api:openapi:export --output=public/api-spec-v1.json --no-interaction" - ] + } } } From 129ac60fc103153c1439a17ce0e903514f9a449d Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 13:41:02 +0200 Subject: [PATCH 20/94] 5564: Fix yaml indentation --- .github/workflows/pr.yaml | 116 ++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 99b63a6..54e7e79 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -10,73 +10,69 @@ jobs: name: PHPStan static analysis runs-on: ubuntu-latest steps: - phpstan: - name: PHPStan static analysis - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 + - name: Checkout + uses: actions/checkout@v5 - - name: Create docker network - run: | - docker network create frontend + - name: Create docker network + run: | + docker network create frontend - - name: Run phpstan - run: | - docker compose run --rm phpfpm composer install - docker compose run --rm phpfpm vendor/bin/phpstan analyse src --no-progress --error-format=github --memory-limit=1G + - name: Run phpstan + run: | + docker compose run --rm phpfpm composer install + docker compose run --rm phpfpm vendor/bin/phpstan analyse src --no-progress --error-format=github --memory-limit=1G - phpunit: - name: PHP Unit tests - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 + phpunit: + name: PHP Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - name: Run tests - run: | - docker compose exec phpfpm composer run tests + - name: Run tests + run: | + docker compose exec phpfpm composer run tests - fixtures: - name: Load fixtures - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 + fixtures: + name: Load fixtures + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 2 - - name: Install site - run: | - docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + - name: Install site + run: | + docker network create frontend + docker compose pull + docker compose up --detach + docker compose exec --user=root phpfpm composer install + docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - - name: Load fixtures - run: | - docker compose exec phpfpm composer fixtures + - name: Load fixtures + run: | + docker compose exec phpfpm composer fixtures - build-assets: - name: Build assets - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 2 + build-assets: + name: Build assets + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 - - name: Build assets - run: | - docker network create frontend - docker compose run --rm node yarn install - docker compose run --rm node yarn build + - name: Build assets + run: | + docker network create frontend + docker compose run --rm node yarn install + docker compose run --rm node yarn build From 23daf7a7cd06bec164a61aadd78fba6d4be28dea Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 13:43:18 +0200 Subject: [PATCH 21/94] 5564: Update checkout action --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 54e7e79..4e4ddde 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: fetch-depth: 2 From fe4eb24d94594cd42375d574b0609cf5da29650a Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 14:03:21 +0200 Subject: [PATCH 22/94] 5564: Update test action to report code coverage --- .github/workflows/pr.yaml | 73 ++++++++++++++++++++++++--------------- .gitignore | 1 + Taskfile.yml | 7 ++-- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 4e4ddde..628af19 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -6,57 +6,74 @@ env: on: pull_request jobs: - phpstan: - name: PHPStan static analysis + test-suite: + name: Test suite runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v5 + - uses: actions/checkout@v5 - - name: Create docker network + - name: Install Task + uses: go-task/setup-task@v1 + + - name: Setup docker network run: | docker network create frontend - - name: Run phpstan + - name: Install Site + run: | + task site:install + + - name: Test suite run: | - docker compose run --rm phpfpm composer install - docker compose run --rm phpfpm vendor/bin/phpstan analyse src --no-progress --error-format=github --memory-limit=1G + task test:setup + task test - phpunit: - name: PHP Unit tests + - name: Upload coverage to Codecov test + uses: codecov/codecov-action@v5 + with: + files: ./coverage/unit.xml + flags: unittests + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + phpstan: + name: PHPStan runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v5 + - uses: actions/checkout@v5 - - name: Install site + - name: Install Task + uses: go-task/setup-task@v1 + + - name: Start docker compose setup run: | docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + task compose -- up --detach + + - name: Install Dependencies + run: | + task composer -- install - - name: Run tests + - name: Run PHPStan run: | - docker compose exec phpfpm composer run tests + task code-analysis:phpstan fixtures: name: Load fixtures runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 + - uses: actions/checkout@v5 + + - name: Install Task + uses: go-task/setup-task@v1 - - name: Install site + - name: Setup docker network run: | docker network create frontend - docker compose pull - docker compose up --detach - docker compose exec --user=root phpfpm composer install - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Install Site + run: | + task site:install - name: Load fixtures run: | diff --git a/.gitignore b/.gitignore index 80f8c4d..4e7ed3e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ phpstan.neon /public/assets/ /assets/vendor/ ###< symfony/asset-mapper ### +coverage diff --git a/Taskfile.yml b/Taskfile.yml index d4e3070..9d08906 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -12,6 +12,7 @@ vars: tasks: site:update: desc: Update/install site + aliases: [site:install] cmds: - task: compose vars: @@ -179,12 +180,12 @@ tasks: vars: CONSOLE_ARGS: doctrine:migrations:migrate --no-interaction --quiet - test:run: - desc: "Run phpunit tests" + test: + desc: "Run tests" cmds: - task: compose vars: - COMPOSE_ARGS: exec phpfpm bin/phpunit + COMPOSE_ARGS: exec --env XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-clover=coverage/unit.xml api:spec:export: desc: "Export API spec" From a1c752af7b00d90227616c8f52389c7f913a8cc8 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 14:09:53 +0200 Subject: [PATCH 23/94] 5564: Fix PR review action --- .github/workflows/pr.yaml | 2 +- Taskfile.yml | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 628af19..466d7d4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -77,7 +77,7 @@ jobs: - name: Load fixtures run: | - docker compose exec phpfpm composer fixtures + task fixtures:load build-assets: name: Build assets diff --git a/Taskfile.yml b/Taskfile.yml index 9d08906..5b7cc25 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -167,18 +167,16 @@ tasks: test:setup: desc: "Setup test environment" - env: - APP_ENV: test cmds: - task: console vars: - CONSOLE_ARGS: doctrine:database:create --if-not-exists --no-interaction + CONSOLE_ARGS: --env=test doctrine:database:create --if-not-exists --no-interaction - task: console vars: - CONSOLE_ARGS: doctrine:database:create --no-interaction --if-not-exists --quiet + CONSOLE_ARGS: --env=test doctrine:database:create --no-interaction --if-not-exists --quiet - task: console vars: - CONSOLE_ARGS: doctrine:migrations:migrate --no-interaction --quiet + CONSOLE_ARGS: --env=test doctrine:migrations:migrate --no-interaction --quiet test: desc: "Run tests" From 0f57193b649660541113faf7821093f3939f094f Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 14:20:47 +0200 Subject: [PATCH 24/94] 5564: Fix PR fixtures action --- .github/workflows/pr.yaml | 2 +- config/packages/security.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 466d7d4..5f6e255 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -77,7 +77,7 @@ jobs: - name: Load fixtures run: | - task fixtures:load + task fixtures:load --yes build-assets: name: Build assets diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 178fb0b..67b283a 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -42,7 +42,7 @@ security: access_control: - { path: ^/api/docs, roles: PUBLIC_ACCESS } - { path: ^/api, roles: ROLE_SERVER } - # - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } when@test: From 39bc3f731a601c5b364f904998de2bd646f0ad11 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Fri, 17 Oct 2025 14:22:55 +0200 Subject: [PATCH 25/94] 5564: Lint yml files --- config/packages/security.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 67b283a..25bfb56 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -42,7 +42,7 @@ security: access_control: - { path: ^/api/docs, roles: PUBLIC_ACCESS } - { path: ^/api, roles: ROLE_SERVER } - - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } when@test: From 9b278ff1e172493f7332703bf4c5870b1d236cad Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 21 Oct 2025 13:42:27 +0200 Subject: [PATCH 26/94] 5566: Add project entity and Leantime sync --- .env | 2 + composer.json | 1 + composer.lock | 75 ++++++++++- config/packages/framework.yaml | 8 ++ config/services.yaml | 4 + migrations/Version20251021113753.php | 47 +++++++ src/Command/SyncLeantimeProjectsCommand.php | 56 ++++++++ src/Entity/Project.php | 120 ++++++++++++++++++ src/Exception/LeantimeApiException.php | 7 + src/Repository/ProjectRepository.php | 43 +++++++ src/Service/Leantime/ApiService.php | 95 ++++++++++++++ .../Leantime/LeantimeProjectUrlFactory.php | 17 +++ src/Service/Leantime/ProjectDto.php | 75 +++++++++++ src/Service/Leantime/ProjectSyncService.php | 76 +++++++++++ 14 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 migrations/Version20251021113753.php create mode 100644 src/Command/SyncLeantimeProjectsCommand.php create mode 100644 src/Entity/Project.php create mode 100644 src/Exception/LeantimeApiException.php create mode 100644 src/Repository/ProjectRepository.php create mode 100644 src/Service/Leantime/ApiService.php create mode 100644 src/Service/Leantime/LeantimeProjectUrlFactory.php create mode 100644 src/Service/Leantime/ProjectDto.php create mode 100644 src/Service/Leantime/ProjectSyncService.php diff --git a/.env b/.env index a2f0c5a..988bd7a 100644 --- a/.env +++ b/.env @@ -64,3 +64,5 @@ VAULT_SECRET_ID="CHANGE_ME_IN_LOCAL_ENV" # The number of old results for each server/result-type combination APP_KEEP_RESULTS=5 +APP_LEANTIME_URI=https://leantime.itkdev.dk +APP_LEANTIME_API_KEY="CHANGE_ME_IN_LOCAL_ENV" diff --git a/composer.json b/composer.json index 13ac7b1..a0105ff 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "symfony/http-client": "^7.3.4", "symfony/messenger": "^7.3.3", "symfony/monolog-bundle": "^3.10", + "symfony/object-mapper": "7.3.*", "symfony/property-access": "^7.3.3", "symfony/property-info": "^7.3.4", "symfony/runtime": "^7.3.4", diff --git a/composer.lock b/composer.lock index ff3bcc3..1b986e3 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": "23e908fe58ef72041cd16db5e898981d", + "content-hash": "de3752721523d5945ac78630b69f2b53", "packages": [ { "name": "api-platform/core", @@ -6209,6 +6209,79 @@ ], "time": "2023-11-06T17:08:13+00:00" }, + { + "name": "symfony/object-mapper", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/object-mapper.git", + "reference": "62752b89178d7654bb7ec97d3fa3dbd67e417dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/object-mapper/zipball/62752b89178d7654bb7ec97d3fa3dbd67e417dbe", + "reference": "62752b89178d7654bb7ec97d3fa3dbd67e417dbe", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^2.0" + }, + "conflict": { + "symfony/property-access": "<7.2" + }, + "require-dev": { + "symfony/property-access": "^7.2", + "symfony/var-exporter": "^7.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ObjectMapper\\": "" + }, + "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 way to map an object to another object", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/object-mapper/tree/v7.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-20T14:49:40+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.3.3", diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 2c9057c..1ad58d7 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -13,6 +13,14 @@ framework: #esi: true #fragments: true + http_client: + scoped_clients: + leantime.client: + base_uri: '%env(APP_LEANTIME_URI)%' + scope: '%env(APP_LEANTIME_URI)%' + headers: + x-api-key: '%env(APP_LEANTIME_API_KEY)%' + when@test: framework: test: true diff --git a/config/services.yaml b/config/services.yaml index 71d40c9..c98c423 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -36,3 +36,7 @@ services: arguments: $resultHandlers: !tagged_iterator app.handler.detection_result_handler $keepResults: '%env(int:APP_KEEP_RESULTS)%' + + App\Service\Leantime\LeantimeProjectUrlFactory: + arguments: + $baseUrl: '%env(string:APP_LEANTIME_URI)%' diff --git a/migrations/Version20251021113753.php b/migrations/Version20251021113753.php new file mode 100644 index 0000000..27b347d --- /dev/null +++ b/migrations/Version20251021113753.php @@ -0,0 +1,47 @@ +addSql(<<<'SQL' + CREATE TABLE project ( + id BINARY(16) NOT NULL, + created_at DATETIME NOT NULL, + modified_at DATETIME NOT NULL, + created_by VARCHAR(255) DEFAULT '' NOT NULL, + modified_by VARCHAR(255) DEFAULT '' NOT NULL, + leantime_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + leantime_url VARCHAR(255) DEFAULT NULL, + economics_url VARCHAR(255) DEFAULT NULL, + details LONGTEXT DEFAULT NULL, + leantime_modified_at DATETIME NOT NULL, + UNIQUE INDEX UNIQ_2FB3D0EE4B785F0C (leantime_id), + PRIMARY KEY (id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE project'); + } +} diff --git a/src/Command/SyncLeantimeProjectsCommand.php b/src/Command/SyncLeantimeProjectsCommand.php new file mode 100644 index 0000000..a7205a6 --- /dev/null +++ b/src/Command/SyncLeantimeProjectsCommand.php @@ -0,0 +1,56 @@ +addOption('project-id', null, InputOption::VALUE_REQUIRED, 'Leantime project ID'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + try { + if ($input->getOption('project-id')) { + $projectId = (int) $input->getOption('project-id'); + + $project = $this->projectSyncService->syncProject($projectId); + + $io->success(sprintf('Project: %s (%d) synced successfully.', $project->getName(), $project->getLeantimeId())); + } else { + $count = $this->projectSyncService->syncAllProjects(); + + $io->success(sprintf('Synced %d projects successfully.', $count)); + } + } catch (\Throwable $e) { + $io->error($e->getMessage()); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/Entity/Project.php b/src/Entity/Project.php new file mode 100644 index 0000000..33b4dc7 --- /dev/null +++ b/src/Entity/Project.php @@ -0,0 +1,120 @@ +leantimeId = $leantimeId; + $this->name = $name; + $this->details = $details; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getLeantimeUrl(): ?string + { + return $this->LeantimeUrl; + } + + public function setLeantimeUrl(string $LeantimeUrl): static + { + $this->LeantimeUrl = $LeantimeUrl; + + return $this; + } + + public function getEconomicsUrl(): ?string + { + return $this->EconomicsUrl; + } + + public function setEconomicsUrl(string $EconomicsUrl): static + { + $this->EconomicsUrl = $EconomicsUrl; + + return $this; + } + + public function getDetails(): ?string + { + return $this->details; + } + + public function setDetails(?string $details): static + { + $this->details = $details; + + return $this; + } + + public function getLeantimeId(): ?int + { + return $this->leantimeId; + } + + public function setLeantimeId(int $leantimeId): static + { + $this->leantimeId = $leantimeId; + + return $this; + } + + public function getLeantimeModifiedAt(): ?\DateTimeImmutable + { + return $this->leantimeModifiedAt; + } + + public function setLeantimeModifiedAt(\DateTimeImmutable $leantimeModifiedAt): static + { + $this->leantimeModifiedAt = $leantimeModifiedAt; + + return $this; + } + + public function getDetailsText(): ?string + { + return $this->detailsText; + } + + public function setDetailsText(string $detailsText): static + { + $this->detailsText = $detailsText; + + return $this; + } +} diff --git a/src/Exception/LeantimeApiException.php b/src/Exception/LeantimeApiException.php new file mode 100644 index 0000000..ba79156 --- /dev/null +++ b/src/Exception/LeantimeApiException.php @@ -0,0 +1,7 @@ + + */ +class ProjectRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Project::class); + } + + // /** + // * @return Project[] Returns an array of Project objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Project + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Service/Leantime/ApiService.php b/src/Service/Leantime/ApiService.php new file mode 100644 index 0000000..dd7390c --- /dev/null +++ b/src/Service/Leantime/ApiService.php @@ -0,0 +1,95 @@ + + * + * @throws LeantimeApiException + */ + public function getProjects(): array + { + $projects = []; + + $data = $this->request(self::API_PATH_JSONRPC, 'POST', 'leantime.rpc.projects.getAll', []); + + foreach ($data as $project) { + $projects[$project->id] = ProjectDto::fromStdClass($project); + } + + return $projects; + } + + /** + * Get project. + * + * @param $key + * A project key or id + * + * @throws LeantimeApiException + */ + public function getProject($key): ProjectDto + { + $data = $this->request(self::API_PATH_JSONRPC, 'POST', 'leantime.rpc.projects.getProject', ['id' => $key]); + + if (is_array($data) && false === $data[0]) { + throw new LeantimeApiException(sprintf('Project (id: %d) not found', $key), 404); + } + + return ProjectDto::fromStdClass($data); + } + + /** + * Get from Leantime. + * + * @throws LeantimeApiException + */ + private function request(string $path, string $type, string $method, array $params = []): mixed + { + try { + $response = $this->leantimeClient->request($type, $path, + ['json' => [ + 'jsonrpc' => '2.0', + 'method' => $method, + 'id' => new Ulid()->jsonSerialize(), + 'params' => $params, + ]] + ); + + $body = $response->getContent(); + + if ($body) { + $data = json_decode($body, null, 512, JSON_THROW_ON_ERROR); + + if (isset($data->error)) { + $message = $data->error->message; + if (isset($data->error->data)) { + $message .= ': '.(is_scalar($data->error->data) ? $data->error->data : json_encode($data->error->data)); + } + throw new LeantimeApiException($message, $data->error->code); + } + + return $data->result; + } + } catch (\Throwable $e) { + throw new LeantimeApiException('Error from Leantime API: '.$e->getMessage(), (int) $e->getCode(), $e); + } + + return null; + } +} diff --git a/src/Service/Leantime/LeantimeProjectUrlFactory.php b/src/Service/Leantime/LeantimeProjectUrlFactory.php new file mode 100644 index 0000000..5ed6f5d --- /dev/null +++ b/src/Service/Leantime/LeantimeProjectUrlFactory.php @@ -0,0 +1,17 @@ +baseUrl, $projectId); + } +} diff --git a/src/Service/Leantime/ProjectDto.php b/src/Service/Leantime/ProjectDto.php new file mode 100644 index 0000000..4ccf1cd --- /dev/null +++ b/src/Service/Leantime/ProjectDto.php @@ -0,0 +1,75 @@ +Udfyld beskrivelse

'; + + public function __construct( + #[Map(target: 'leantimeId')] + #[Map(target: 'leantimeUrl', transform: LeantimeProjectUrlFactory::class)] + public int $id, + #[Map(target: 'name')] + public string $name, + #[Map(target: 'details')] + public ?string $details, + public int $clientId, + public ?int $state, + public string $hourBudget, + public int $dollarBudget, + public string $menuType, + public string $type, + public ?int $parent, + #[Map(target: 'leantimeModifiedAt')] + public \DateTimeImmutable $modified, + public ?string $start, + public ?string $end, + public string $clientName, + public ?int $parentId, + public ?string $parentName, + public int $isFavorite, + ) { + } + + /** + * Create a LeantimeProjectDto from JSON data. + */ + public static function fromStdClass(\stdClass $data): self + { + $data->details = self::DETAILS_DEFAULT === $data->details ? null : $data->details; + $data->hourBudget = floatval($data->hourBudget); + $data->dollarBudget = floatval($data->dollarBudget); + $data->modified = \DateTimeImmutable::createFromFormat( + self::DATE_FORMAT, + $data->modified, + new \DateTimeZone(self::TIMEZONE) + ); + + return new self( + id: $data->id, + name: $data->name, + details: $data->details ?? null, + clientId: $data->clientId, + state: $data->state, + hourBudget: $data->hourBudget, + dollarBudget: $data->dollarBudget, + menuType: $data->menuType, + type: $data->type, + parent: $data->parent, + modified: $data->modified, + start: $data->start ?? null, + end: $data->end ?? null, + clientName: $data->clientName, + parentId: $data->parentId ?? null, + parentName: $data->parentName ?? null, + isFavorite: $data->isFavorite, + ); + } +} diff --git a/src/Service/Leantime/ProjectSyncService.php b/src/Service/Leantime/ProjectSyncService.php new file mode 100644 index 0000000..60f3ae8 --- /dev/null +++ b/src/Service/Leantime/ProjectSyncService.php @@ -0,0 +1,76 @@ +leantimeApiService->getProjects(); + $count = count($projectDtoArray); + + $projects = $this->entityManager->getRepository(Project::class)->findBy([], ['id' => 'ASC']); + + foreach ($projects as $project) { + if (isset($projectDtoArray[$project->getLeantimeId()])) { + $dto = $projectDtoArray[$project->getLeantimeId()]; + $this->setProperties($project, $dto); + + unset($projectDtoArray[$project->getLeantimeId()]); + } else { + // Delete projects that are not in Leantime + $this->entityManager->remove($project); + } + } + + foreach ($projectDtoArray as $id => $projectDto) { + $project = $this->objectMapper->map($projectDto, Project::class); + $this->setProperties($project, $projectDto); + $this->entityManager->persist($project); + } + + $this->entityManager->flush(); + + return $count; + } + + public function syncProject(int $leantimeId): Project + { + $projectDto = $this->leantimeApiService->getProject($leantimeId); + $project = $this->entityManager->getRepository(Project::class)->findOneBy(['leantimeId' => $leantimeId]); + + if (null === $project) { + $project = $this->objectMapper->map($projectDto, Project::class); + $this->entityManager->persist($project); + } + + $this->setProperties($project, $projectDto); + + $this->entityManager->flush(); + + return $project; + } + + private function setProperties(Project $project, ProjectDto $projectDto): void + { + $project->setLeantimeId($projectDto->id); + $project->setName($projectDto->name); + $project->setDetails($projectDto->details); + $project->setLeantimeModifiedAt($projectDto->modified); + + $url = $this->leantimeProjectUrlFactory->getProjectUrl($project->getLeantimeId()); + $project->setLeantimeUrl($url); + } +} From 06ca1c5a2009957c4009c69eb9c4c31958c4e3b7 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 22 Oct 2025 08:50:28 +0200 Subject: [PATCH 27/94] 5566: Add project crud controller, use 'disable' for actions, UI tweaks --- composer.json | 1 + composer.lock | 5 +- src/Command/SyncLeantimeProjectsCommand.php | 2 - src/Controller/Admin/DashboardController.php | 2 + .../Admin/DetectionResultCrudController.php | 8 +- .../Admin/DockerImageCrudController.php | 8 +- .../Admin/DockerImageTagCrudController.php | 8 +- src/Controller/Admin/DomainCrudController.php | 9 +- .../Admin/GitRepoCrudController.php | 8 +- src/Controller/Admin/GitTagCrudController.php | 8 +- .../Admin/InstallationCrudController.php | 8 +- src/Controller/Admin/ModuleCrudController.php | 8 +- .../Admin/ModuleVersionCrudController.php | 8 +- src/Controller/Admin/OIDCCrudController.php | 3 + .../Admin/PackageCrudController.php | 8 +- .../Admin/PackageVersionCrudController.php | 8 +- .../Admin/ProjectCrudController.php | 87 +++++++++++++++++++ src/Controller/Admin/ServerCrudController.php | 3 + .../ServiceCertificateCrudController.php | 6 +- src/Controller/Admin/SiteCrudController.php | 8 +- src/Entity/Project.php | 5 ++ src/Trait/ExportCrudControllerTrait.php | 2 +- 22 files changed, 134 insertions(+), 79 deletions(-) create mode 100644 src/Controller/Admin/ProjectCrudController.php diff --git a/composer.json b/composer.json index a0105ff..aa99043 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "php": ">=8.4", "ext-ctype": "*", "ext-iconv": "*", + "ext-mbstring": "*", "api-platform/core": "~4.1.25", "composer/semver": "^3.4.4", "doctrine/dbal": "^4.3.4", diff --git a/composer.lock b/composer.lock index 1b986e3..2e80675 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": "de3752721523d5945ac78630b69f2b53", + "content-hash": "60d7644f1ca92f78aa528dbb47634b49", "packages": [ { "name": "api-platform/core", @@ -14301,7 +14301,8 @@ "platform": { "php": ">=8.4", "ext-ctype": "*", - "ext-iconv": "*" + "ext-iconv": "*", + "ext-mbstring": "*" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/src/Command/SyncLeantimeProjectsCommand.php b/src/Command/SyncLeantimeProjectsCommand.php index a7205a6..dc66a49 100644 --- a/src/Command/SyncLeantimeProjectsCommand.php +++ b/src/Command/SyncLeantimeProjectsCommand.php @@ -2,7 +2,6 @@ namespace App\Command; -use App\Service\Leantime\ApiService; use App\Service\Leantime\ProjectSyncService; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -18,7 +17,6 @@ class SyncLeantimeProjectsCommand extends Command { public function __construct( - private readonly ApiService $leantimeService, private readonly ProjectSyncService $projectSyncService, ) { parent::__construct(); diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index bccf631..9624283 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -17,6 +17,7 @@ use App\Entity\OIDC; use App\Entity\Package; use App\Entity\PackageVersion; +use App\Entity\Project; use App\Entity\Server; use App\Entity\ServiceCertificate; use App\Entity\Site; @@ -62,6 +63,7 @@ public function configureMenuItems(): iterable { yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); yield MenuItem::linkToCrud('Servers', 'fas fa-server', Server::class); + yield MenuItem::linkToCrud('Projects', 'fas fa-folder', Project::class); yield MenuItem::linkToCrud('Installations', 'fas fa-folder', Installation::class); yield MenuItem::linkToCrud('Sites', 'fas fa-bookmark', Site::class); yield MenuItem::linkToCrud('Domains', 'fas fa-link', Domain::class); diff --git a/src/Controller/Admin/DetectionResultCrudController.php b/src/Controller/Admin/DetectionResultCrudController.php index 5027e68..a206930 100644 --- a/src/Controller/Admin/DetectionResultCrudController.php +++ b/src/Controller/Admin/DetectionResultCrudController.php @@ -36,12 +36,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/DockerImageCrudController.php b/src/Controller/Admin/DockerImageCrudController.php index 980c426..dec7b4f 100644 --- a/src/Controller/Admin/DockerImageCrudController.php +++ b/src/Controller/Admin/DockerImageCrudController.php @@ -32,12 +32,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/DockerImageTagCrudController.php b/src/Controller/Admin/DockerImageTagCrudController.php index 49f7855..779e484 100644 --- a/src/Controller/Admin/DockerImageTagCrudController.php +++ b/src/Controller/Admin/DockerImageTagCrudController.php @@ -31,12 +31,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/DomainCrudController.php b/src/Controller/Admin/DomainCrudController.php index 060e8ef..9387eb9 100644 --- a/src/Controller/Admin/DomainCrudController.php +++ b/src/Controller/Admin/DomainCrudController.php @@ -36,14 +36,9 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions + ->disable(Action::DELETE, Action::NEW, Action::EDIT) ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE) - ; + ->add(Crud::PAGE_INDEX, $this->createExportAction()); } #[\Override] diff --git a/src/Controller/Admin/GitRepoCrudController.php b/src/Controller/Admin/GitRepoCrudController.php index 7fa2d4e..562c8c9 100644 --- a/src/Controller/Admin/GitRepoCrudController.php +++ b/src/Controller/Admin/GitRepoCrudController.php @@ -35,12 +35,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/GitTagCrudController.php b/src/Controller/Admin/GitTagCrudController.php index eceec7e..8c2d592 100644 --- a/src/Controller/Admin/GitTagCrudController.php +++ b/src/Controller/Admin/GitTagCrudController.php @@ -33,12 +33,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/InstallationCrudController.php b/src/Controller/Admin/InstallationCrudController.php index e78a4cf..d36000e 100644 --- a/src/Controller/Admin/InstallationCrudController.php +++ b/src/Controller/Admin/InstallationCrudController.php @@ -41,13 +41,9 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions + ->disable(Action::DELETE, Action::NEW, Action::EDIT) ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->add(Crud::PAGE_INDEX, $this->createExportAction()); } #[\Override] diff --git a/src/Controller/Admin/ModuleCrudController.php b/src/Controller/Admin/ModuleCrudController.php index 9cc6a73..311d478 100644 --- a/src/Controller/Admin/ModuleCrudController.php +++ b/src/Controller/Admin/ModuleCrudController.php @@ -31,12 +31,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/ModuleVersionCrudController.php b/src/Controller/Admin/ModuleVersionCrudController.php index 1d3f3e5..e80ad8d 100644 --- a/src/Controller/Admin/ModuleVersionCrudController.php +++ b/src/Controller/Admin/ModuleVersionCrudController.php @@ -30,12 +30,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/OIDCCrudController.php b/src/Controller/Admin/OIDCCrudController.php index 176871e..48effce 100644 --- a/src/Controller/Admin/OIDCCrudController.php +++ b/src/Controller/Admin/OIDCCrudController.php @@ -44,6 +44,9 @@ public function configureActions(Actions $actions): Actions return $actions ->add(Crud::PAGE_INDEX, Action::DETAIL) ->add(Crud::PAGE_INDEX, $this->createExportAction()) + ->update(Crud::PAGE_INDEX, Action::NEW, + static fn (Action $action) => $action->setIcon('fa fa-plus') + ) ; } diff --git a/src/Controller/Admin/PackageCrudController.php b/src/Controller/Admin/PackageCrudController.php index 21fbd74..1e076b4 100644 --- a/src/Controller/Admin/PackageCrudController.php +++ b/src/Controller/Admin/PackageCrudController.php @@ -37,12 +37,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/PackageVersionCrudController.php b/src/Controller/Admin/PackageVersionCrudController.php index a015c2b..d36efc4 100644 --- a/src/Controller/Admin/PackageVersionCrudController.php +++ b/src/Controller/Admin/PackageVersionCrudController.php @@ -36,12 +36,8 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL); } #[\Override] diff --git a/src/Controller/Admin/ProjectCrudController.php b/src/Controller/Admin/ProjectCrudController.php new file mode 100644 index 0000000..88972f3 --- /dev/null +++ b/src/Controller/Admin/ProjectCrudController.php @@ -0,0 +1,87 @@ +setDefaultSort(['name' => 'ASC']) + ->setSearchFields(['name', 'details']) + ->showEntityActionsInlined() + ->setPageTitle(Crud::PAGE_INDEX, 'Leantime projects') + ->setHelp(Crud::PAGE_INDEX, 'Projects are synced from Leantime. Click on the "Sync all" button to update all projects.'); + } + + #[\Override] + public function configureActions(Actions $actions): Actions + { + return $actions + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, $this->createLeantimeAction()) + ->add(Crud::PAGE_DETAIL, $this->createLeantimeAction()) + ->add(Crud::PAGE_INDEX, Action::DETAIL) + ->add(Crud::PAGE_INDEX, $this->createExportAction()) + ->add(Crud::PAGE_INDEX, $this->createUpdateAllProjectsAction()); + } + + #[\Override] + public function configureFields(string $pageName): iterable + { + yield VersionField::new('LeantimeId')->setLabel('id'); + yield TextField::new('name'); + yield TextEditorField::new('details')->formatValue(fn ($value) => strip_tags($value)); + yield DateTimeField::new('createdAt')->hideOnIndex(); + } + + public function createLeantimeAction(): Action + { + return Action::new('leantime', $label ?? new TranslatableMessage('Leantime'), 'fa fa-link') + ->linkToUrl(function (Project $project) { + return $project->getLeantimeUrl(); + }); + } + + public function createUpdateAllProjectsAction(): Action + { + return Action::new('update', $label ?? new TranslatableMessage('Sync all'), 'fa fa-rotate') + ->createAsGlobalAction() + ->linkToCrudAction('updateAllProjects'); + } + + public function updateAllProjects(SiteRepository $siteRepository): RedirectResponse + { + $this->projectSyncService->syncAllProjects(); + + return $this->redirectToRoute('admin_project_index'); + } +} diff --git a/src/Controller/Admin/ServerCrudController.php b/src/Controller/Admin/ServerCrudController.php index a7bbcda..f0e3d80 100644 --- a/src/Controller/Admin/ServerCrudController.php +++ b/src/Controller/Admin/ServerCrudController.php @@ -62,6 +62,9 @@ public function configureActions(Actions $actions): Actions ->remove(Crud::PAGE_INDEX, Action::DELETE) ->add(Crud::PAGE_INDEX, Action::DETAIL) ->add(Crud::PAGE_INDEX, $this->createExportAction()) + ->update(Crud::PAGE_INDEX, Action::NEW, + static fn (Action $action) => $action->setIcon('fa fa-plus') + ) ; } diff --git a/src/Controller/Admin/ServiceCertificateCrudController.php b/src/Controller/Admin/ServiceCertificateCrudController.php index 8c68f7f..74267d5 100644 --- a/src/Controller/Admin/ServiceCertificateCrudController.php +++ b/src/Controller/Admin/ServiceCertificateCrudController.php @@ -52,7 +52,11 @@ public function configureActions(Actions $actions): Actions return $actions ->add(Crud::PAGE_INDEX, Action::DETAIL) ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->remove(Crud::PAGE_INDEX, Action::DELETE); + ->remove(Crud::PAGE_INDEX, Action::DELETE) + ->update(Crud::PAGE_INDEX, Action::NEW, + static fn (Action $action) => $action->setIcon('fa fa-plus') + ) + ; } #[\Override] diff --git a/src/Controller/Admin/SiteCrudController.php b/src/Controller/Admin/SiteCrudController.php index 3380dab..9a3af8e 100644 --- a/src/Controller/Admin/SiteCrudController.php +++ b/src/Controller/Admin/SiteCrudController.php @@ -44,13 +44,9 @@ public function configureCrud(Crud $crud): Crud public function configureActions(Actions $actions): Actions { return $actions + ->disable(Action::DELETE, Action::NEW, Action::EDIT) ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->remove(Crud::PAGE_INDEX, Action::NEW) - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->remove(Crud::PAGE_DETAIL, Action::EDIT) - ->remove(Crud::PAGE_DETAIL, Action::DELETE); + ->add(Crud::PAGE_INDEX, $this->createExportAction()); } #[\Override] diff --git a/src/Entity/Project.php b/src/Entity/Project.php index 33b4dc7..720f7d2 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -34,6 +34,11 @@ public function __construct(int $leantimeId, string $name, ?string $details = nu $this->details = $details; } + public function __toString(): string + { + return $this->name; + } + public function getName(): string { return $this->name; diff --git a/src/Trait/ExportCrudControllerTrait.php b/src/Trait/ExportCrudControllerTrait.php index 8880d58..4e9656c 100644 --- a/src/Trait/ExportCrudControllerTrait.php +++ b/src/Trait/ExportCrudControllerTrait.php @@ -34,7 +34,7 @@ public function setExporter(Exporter $exporter): void protected function createExportAction(string|TranslatableMessage|null $label = null): Action { - return Action::new('export', $label ?? new TranslatableMessage('Export')) + return Action::new('export', $label ?? new TranslatableMessage('Export'), 'fa fa-file-csv') ->createAsGlobalAction() ->linkToCrudAction('export'); } From 3cb35dc8576f07dd87d5886bf1c9a8dfd290bbba Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 23 Oct 2025 11:19:02 +0200 Subject: [PATCH 28/94] 5566: Add security contract entity and controller, refactor crud controller and styling --- assets/styles/app.css | 11 ++ ...21113753.php => Version20251023091529.php} | 30 +++- .../Admin/AbstractFullCrudController.php | 65 ++++++++ src/Controller/Admin/DashboardController.php | 26 ++- src/Controller/Admin/OIDCCrudController.php | 24 +-- .../Admin/ProjectCrudController.php | 10 +- .../Admin/SecurityContractCrudController.php | 66 ++++++++ src/Controller/Admin/ServerCrudController.php | 18 +-- .../ServiceCertificateCrudController.php | 31 +--- src/EasyAdmin/Config/AutoBadgeMenuItem.php | 28 ++++ .../Config/Menu/AutoBadgeCrudMenuItem.php | 38 +++++ src/Entity/Project.php | 20 +++ src/Entity/SecurityContract.php | 152 ++++++++++++++++++ src/Repository/OIDCRepository.php | 10 ++ src/Repository/SecurityContractRepository.php | 54 +++++++ .../ServiceCertificateRepository.php | 10 ++ .../EasyAdminBundle/crud/detail.html.twig | 43 +++++ 17 files changed, 564 insertions(+), 72 deletions(-) rename migrations/{Version20251021113753.php => Version20251023091529.php} (50%) create mode 100644 src/Controller/Admin/AbstractFullCrudController.php create mode 100644 src/Controller/Admin/SecurityContractCrudController.php create mode 100644 src/EasyAdmin/Config/AutoBadgeMenuItem.php create mode 100644 src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php create mode 100644 src/Entity/SecurityContract.php create mode 100644 src/Repository/SecurityContractRepository.php create mode 100644 templates/bundles/EasyAdminBundle/crud/detail.html.twig diff --git a/assets/styles/app.css b/assets/styles/app.css index 2e8af7a..3d9fd32 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -13,3 +13,14 @@ --text-color-dark: rgb(66, 66, 66); --bs-danger-rgb: 228, 73, 48; } + +/* Grouped dropdown group styling for index pages */ +.dropdown-menu { + .btn-danger i, .text-danger i { + color: var(--button-invisible-danger-color); + } + + a.btn-danger:hover , a.text-danger:hover { + background: var(--button-invisible-danger-hover-hover-bg); + } +} \ No newline at end of file diff --git a/migrations/Version20251021113753.php b/migrations/Version20251023091529.php similarity index 50% rename from migrations/Version20251021113753.php rename to migrations/Version20251023091529.php index 27b347d..f1a4fea 100644 --- a/migrations/Version20251021113753.php +++ b/migrations/Version20251023091529.php @@ -10,7 +10,7 @@ /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20251021113753 extends AbstractMigration +final class Version20251023091529 extends AbstractMigration { public function getDescription(): string { @@ -37,11 +37,39 @@ public function up(Schema $schema): void PRIMARY KEY (id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` SQL); + $this->addSql(<<<'SQL' + CREATE TABLE security_contract ( + id BINARY(16) NOT NULL, + created_at DATETIME NOT NULL, + modified_at DATETIME NOT NULL, + created_by VARCHAR(255) DEFAULT '' NOT NULL, + modified_by VARCHAR(255) DEFAULT '' NOT NULL, + economics_report_url VARCHAR(255) DEFAULT NULL, + operational_contract_url LONGTEXT DEFAULT NULL, + monthly_price DOUBLE PRECISION DEFAULT NULL, + quarterly_hours DOUBLE PRECISION DEFAULT NULL, + valid_from DATE NOT NULL, + valid_to DATE NOT NULL, + active TINYINT(1) NOT NULL, + notes LONGTEXT NOT NULL, + project_id BINARY(16) NOT NULL, + UNIQUE INDEX UNIQ_8AE4AF8B166D1F9C (project_id), + PRIMARY KEY (id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE + security_contract + ADD + CONSTRAINT FK_8AE4AF8B166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) + SQL); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE security_contract DROP FOREIGN KEY FK_8AE4AF8B166D1F9C'); $this->addSql('DROP TABLE project'); + $this->addSql('DROP TABLE security_contract'); } } diff --git a/src/Controller/Admin/AbstractFullCrudController.php b/src/Controller/Admin/AbstractFullCrudController.php new file mode 100644 index 0000000..268c544 --- /dev/null +++ b/src/Controller/Admin/AbstractFullCrudController.php @@ -0,0 +1,65 @@ +showEntityActionsInlined(); + } + + #[\Override] + public function configureActions(Actions $actions): Actions + { + // Remove default actions + $actions + ->remove(Crud::PAGE_INDEX, Action::EDIT) + ->remove(Crud::PAGE_INDEX, Action::DELETE); + + // Re-add default actions as grouped action. + $groupedDefaultActions = ActionGroup::new('default', 'Default') + ->addMainAction( + Action::new('show', 'Show') + ->linkToCrudAction(Action::DETAIL) + ) + ->addAction( + Action::new('edit', 'Edit') + ->linkToCrudAction(Action::EDIT) + ->setIcon('fa fa-edit') + ) + ->addDivider() + ->addAction( + Action::new('delete', 'Delete') + ->linkToCrudAction(Action::DELETE) + ->setIcon('fa fa-trash') + ->setCssClass('btn-danger text-danger') + ); + + return $actions + ->add(Crud::PAGE_INDEX, $groupedDefaultActions) + ->add(Crud::PAGE_INDEX, $this->createExportAction()) + ->update(Crud::PAGE_INDEX, Action::NEW, + static fn (Action $action) => $action->setIcon('fa fa-plus') + ) + ; + } + + #[\Override] + public function configureAssets(Assets $assets): Assets + { + return $assets + ->addWebpackEncoreEntry('easyadmin'); + } +} \ No newline at end of file diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 9624283..9e1e0e6 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -4,6 +4,7 @@ namespace App\Controller\Admin; +use App\EasyAdmin\Config\AutoBadgeMenuItem; use App\Entity\Advisory; use App\Entity\DetectionResult; use App\Entity\DockerImage; @@ -18,10 +19,14 @@ use App\Entity\Package; use App\Entity\PackageVersion; use App\Entity\Project; +use App\Entity\SecurityContract; use App\Entity\Server; use App\Entity\ServiceCertificate; use App\Entity\Site; use App\Repository\AdvisoryRepository; +use App\Repository\OIDCRepository; +use App\Repository\SecurityContractRepository; +use App\Repository\ServiceCertificateRepository; use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; @@ -35,6 +40,9 @@ class DashboardController extends AbstractDashboardController public function __construct( private readonly AdminUrlGenerator $adminUrlGenerator, private readonly AdvisoryRepository $advisoryRepository, + private readonly ServiceCertificateRepository $serviceCertificateRepository, + private readonly OIDCRepository $oidcRepository, + private readonly SecurityContractRepository $securityContractRepository, ) { } @@ -62,17 +70,27 @@ public function configureDashboard(): Dashboard public function configureMenuItems(): iterable { yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); - yield MenuItem::linkToCrud('Servers', 'fas fa-server', Server::class); + + yield MenuItem::section('Projects'); yield MenuItem::linkToCrud('Projects', 'fas fa-folder', Project::class); + yield AutoBadgeMenuItem::linkToCrud('Cyber Security', 'fas fa-file-contract', SecurityContract::class) + ->setBadge($this->securityContractRepository->countExpiredContracts(), 'danger'); + yield AutoBadgeMenuItem::linkToCrud('OIDC', 'fas fa-shield-halved', OIDC::class) + ->setBadge($this->oidcRepository->countExpiredCertificates(), 'danger'); + yield AutoBadgeMenuItem::linkToCrud('Service certificates', 'fas fa-passport', ServiceCertificate::class) + ->setBadge($this->serviceCertificateRepository->countExpiredCertificates(), 'danger'); + + yield MenuItem::section('Hosting'); + yield MenuItem::linkToCrud('Servers', 'fas fa-server', Server::class); yield MenuItem::linkToCrud('Installations', 'fas fa-folder', Installation::class); yield MenuItem::linkToCrud('Sites', 'fas fa-bookmark', Site::class); yield MenuItem::linkToCrud('Domains', 'fas fa-link', Domain::class); - yield MenuItem::linkToCrud('OIDC', 'fas fa-key', OIDC::class); - yield MenuItem::linkToCrud('Service certificates', 'fas fa-lock', ServiceCertificate::class); + yield MenuItem::section('Dependencies'); yield MenuItem::linkToCrud('Packages', 'fas fa-cube', Package::class); yield MenuItem::linkToCrud('Package Versions', 'fas fa-cubes', PackageVersion::class); - yield MenuItem::linkToCrud('Advisories', 'fas fa-skull-crossbones', Advisory::class)->setBadge($this->advisoryRepository->count([]), 'dark'); + yield AutoBadgeMenuItem::linkToCrud('Advisories', 'fas fa-skull-crossbones', Advisory::class) + ->setBadge($this->advisoryRepository->count([]), 'danger'); yield MenuItem::linkToCrud('Modules', 'fas fa-cube', Module::class); yield MenuItem::linkToCrud('Modules Versions', 'fas fa-cubes', ModuleVersion::class); yield MenuItem::linkToCrud('Docker Images', 'fas fa-cube', DockerImage::class); diff --git a/src/Controller/Admin/OIDCCrudController.php b/src/Controller/Admin/OIDCCrudController.php index 48effce..3ba6522 100644 --- a/src/Controller/Admin/OIDCCrudController.php +++ b/src/Controller/Admin/OIDCCrudController.php @@ -8,9 +8,9 @@ use App\Repository\SiteRepository; use App\Trait\ExportCrudControllerTrait; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; +use EasyCorp\Bundle\EasyAdminBundle\Config\ActionGroup; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; @@ -18,10 +18,8 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; use Symfony\Component\Translation\TranslatableMessage; -class OIDCCrudController extends AbstractCrudController +class OIDCCrudController extends AbstractFullCrudController { - use ExportCrudControllerTrait; - public function __construct( private readonly SiteRepository $siteRepository) { @@ -32,24 +30,6 @@ public static function getEntityFqcn(): string return OIDC::class; } - #[\Override] - public function configureCrud(Crud $crud): Crud - { - return $crud->showEntityActionsInlined(); - } - - #[\Override] - public function configureActions(Actions $actions): Actions - { - return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->update(Crud::PAGE_INDEX, Action::NEW, - static fn (Action $action) => $action->setIcon('fa fa-plus') - ) - ; - } - #[\Override] public function configureFields(string $pageName): iterable { diff --git a/src/Controller/Admin/ProjectCrudController.php b/src/Controller/Admin/ProjectCrudController.php index 88972f3..18af14a 100644 --- a/src/Controller/Admin/ProjectCrudController.php +++ b/src/Controller/Admin/ProjectCrudController.php @@ -38,7 +38,7 @@ public function configureCrud(Crud $crud): Crud ->setDefaultSort(['name' => 'ASC']) ->setSearchFields(['name', 'details']) ->showEntityActionsInlined() - ->setPageTitle(Crud::PAGE_INDEX, 'Leantime projects') + ->setPageTitle(Crud::PAGE_INDEX, 'Projects') ->setHelp(Crud::PAGE_INDEX, 'Projects are synced from Leantime. Click on the "Sync all" button to update all projects.'); } @@ -80,7 +80,13 @@ public function createUpdateAllProjectsAction(): Action public function updateAllProjects(SiteRepository $siteRepository): RedirectResponse { - $this->projectSyncService->syncAllProjects(); + try { + $this->projectSyncService->syncAllProjects(); + + $this->addFlash('info', 'All projects have been synced.', ); + } catch (\Throwable $e) { + $this->addFlash('error', 'An error occurred while syncing projects. Check the log for details.'); + } return $this->redirectToRoute('admin_project_index'); } diff --git a/src/Controller/Admin/SecurityContractCrudController.php b/src/Controller/Admin/SecurityContractCrudController.php new file mode 100644 index 0000000..80b372d --- /dev/null +++ b/src/Controller/Admin/SecurityContractCrudController.php @@ -0,0 +1,66 @@ +setDefaultSort(['project.name' => 'ASC']) + //->setSearchFields(['name', 'details']) + ->showEntityActionsInlined() + ->setPageTitle(Crud::PAGE_INDEX, 'Cyber Security Contracts'); + } + + #[\Override] + public function configureFields(string $pageName): iterable + { + yield FormField::addFieldset('Project'); + yield BooleanField::new('active')->renderAsSwitch(false)->setColumns(2); + yield AssociationField::new('project')->setColumns(10); + + yield FormField::addFieldset('Links'); + yield UrlField::new('project.leantimeUrl')->setLabel('Leantime URL')->setDisabled()->hideOnIndex()->hideWhenCreating(); + yield UrlField::new('economicsReportUrl')->setLabel('Economics URL')->hideOnIndex()->setColumns(12); + yield UrlField::new('operationalContractUrl')->setLabel('Contract URL')->hideOnIndex()->setColumns(12); + + yield FormField::addFieldset('Budget'); + yield NumberField::new('monthlyPrice')->setTextAlign('right')->setColumns(6); + yield NumberField::new('quarterlyHours')->setTextAlign('right')->setColumns(6); + + yield FormField::addFieldset('Validity'); + yield DateField::new('validFrom')->setColumns(6); + yield DateField::new('validTo')->setColumns(6); + + yield FormField::addFieldset('Notes'); + yield TextareaField::new('notes')->hideOnIndex()->setColumns(12); + } +} diff --git a/src/Controller/Admin/ServerCrudController.php b/src/Controller/Admin/ServerCrudController.php index f0e3d80..b15b4b5 100644 --- a/src/Controller/Admin/ServerCrudController.php +++ b/src/Controller/Admin/ServerCrudController.php @@ -28,10 +28,8 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use Symfony\Component\HttpFoundation\RequestStack; -class ServerCrudController extends AbstractCrudController +class ServerCrudController extends AbstractFullCrudController { - use ExportCrudControllerTrait; - public function __construct( private readonly RequestStack $requestStack, ) { @@ -54,20 +52,6 @@ public function configureCrud(Crud $crud): Crud return $crud; } - #[\Override] - public function configureActions(Actions $actions): Actions - { - return $actions - ->remove(Crud::PAGE_INDEX, Action::EDIT) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->update(Crud::PAGE_INDEX, Action::NEW, - static fn (Action $action) => $action->setIcon('fa fa-plus') - ) - ; - } - #[\Override] public function configureFields(string $pageName): iterable { diff --git a/src/Controller/Admin/ServiceCertificateCrudController.php b/src/Controller/Admin/ServiceCertificateCrudController.php index 74267d5..995bf76 100644 --- a/src/Controller/Admin/ServiceCertificateCrudController.php +++ b/src/Controller/Admin/ServiceCertificateCrudController.php @@ -21,12 +21,11 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; use Symfony\Component\Translation\TranslatableMessage; -class ServiceCertificateCrudController extends AbstractCrudController +class ServiceCertificateCrudController extends AbstractFullCrudController { - use ExportCrudControllerTrait; - - public function __construct(private readonly SiteRepository $siteRepository) - { + public function __construct( + private readonly SiteRepository $siteRepository + ) { } public static function getEntityFqcn(): string @@ -46,19 +45,6 @@ public function configureCrud(Crud $crud): Crud ->setSearchFields(['domain', 'name', 'description', 'services.type']); } - #[\Override] - public function configureActions(Actions $actions): Actions - { - return $actions - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->remove(Crud::PAGE_INDEX, Action::DELETE) - ->update(Crud::PAGE_INDEX, Action::NEW, - static fn (Action $action) => $action->setIcon('fa fa-plus') - ) - ; - } - #[\Override] public function configureFields(string $pageName): iterable { @@ -81,7 +67,7 @@ public function configureFields(string $pageName): iterable yield TextField::new('description')->onlyOnIndex() ->setHelp(new TranslatableMessage('Tell what this certificate is used for.'))->setMaxLength(33)->stripTags(); yield UrlField::new('onePasswordUrl') - ->setLabel(new TranslatableMessage('1Password url')); + ->setLabel(new TranslatableMessage('1Password url'))->hideOnIndex(); yield UrlField::new('usageDocumentationUrl')->hideOnIndex() ->setHelp(new TranslatableMessage('Tell where to find documentation on how the certificate is used on the site and how to configure the use.')); yield DateTimeField::new('expirationTime'); @@ -97,11 +83,4 @@ public function configureFields(string $pageName): iterable ->setTemplatePath('service_certificate/services.html.twig') ; } - - #[\Override] - public function configureAssets(Assets $assets): Assets - { - return $assets - ->addWebpackEncoreEntry('easyadmin'); - } } diff --git a/src/EasyAdmin/Config/AutoBadgeMenuItem.php b/src/EasyAdmin/Config/AutoBadgeMenuItem.php new file mode 100644 index 0000000..edc2fcd --- /dev/null +++ b/src/EasyAdmin/Config/AutoBadgeMenuItem.php @@ -0,0 +1,28 @@ +crudMenuItem = new CrudMenuItem(...$args); + } + + public function __call($name, $arguments) + { + return $this->crudMenuItem->$name(...$arguments); + } + + public static function __callStatic(string $name, array $arguments) + { + throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); + } + + public function setBadge(/* \Stringable|string|int|float|bool|null */ $content, string $style = 'secondary', array $htmlAttributes = []): self + { + if (!is_int($content)) { + throw new \InvalidArgumentException('The badge content must be an integer'); + } + + if ($content > 0) { + $this->crudMenuItem->setBadge($content, $style, $htmlAttributes); + } + + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/Project.php b/src/Entity/Project.php index 720f7d2..e1d3699 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -27,6 +27,9 @@ class Project extends AbstractBaseEntity implements \Stringable #[ORM\Column] private ?\DateTimeImmutable $leantimeModifiedAt = null; + #[ORM\OneToOne(mappedBy: 'project', cascade: ['persist', 'remove'])] + private ?SecurityContract $securityContract = null; + public function __construct(int $leantimeId, string $name, ?string $details = null) { $this->leantimeId = $leantimeId; @@ -122,4 +125,21 @@ public function setDetailsText(string $detailsText): static return $this; } + + public function getSecurityContract(): ?SecurityContract + { + return $this->securityContract; + } + + public function setSecurityContract(SecurityContract $securityContract): static + { + // set the owning side of the relation if necessary + if ($securityContract->getProject() !== $this) { + $securityContract->setProject($this); + } + + $this->securityContract = $securityContract; + + return $this; + } } diff --git a/src/Entity/SecurityContract.php b/src/Entity/SecurityContract.php new file mode 100644 index 0000000..97741e6 --- /dev/null +++ b/src/Entity/SecurityContract.php @@ -0,0 +1,152 @@ +project->getName(); + } + + public function getProject(): ?Project + { + return $this->project; + } + + public function setProject(Project $project): static + { + $this->project = $project; + + return $this; + } + + public function getEconomicsReportUrl(): ?string + { + return $this->economicsReportUrl; + } + + public function setEconomicsReportUrl(?string $economicsReportUrl): static + { + $this->economicsReportUrl = $economicsReportUrl; + + return $this; + } + + public function getOperationalContractUrl(): ?string + { + return $this->operationalContractUrl; + } + + public function setOperationalContractUrl(?string $operationalContractUrl): static + { + $this->operationalContractUrl = $operationalContractUrl; + + return $this; + } + + public function getMonthlyPrice(): ?float + { + return $this->monthlyPrice; + } + + public function setMonthlyPrice(?float $monthlyPrice): static + { + $this->monthlyPrice = $monthlyPrice; + + return $this; + } + + public function getQuarterlyHours(): ?float + { + return $this->quarterlyHours; + } + + public function setQuarterlyHours(float $quarterlyHours): static + { + $this->quarterlyHours = $quarterlyHours; + + return $this; + } + + public function getValidFrom(): ?\DateTimeImmutable + { + return $this->validFrom; + } + + public function setValidFrom(\DateTimeImmutable $validFrom): static + { + $this->validFrom = $validFrom; + + return $this; + } + + public function getValidTo(): ?\DateTimeImmutable + { + return $this->validTo; + } + + public function setValidTo(\DateTimeImmutable $validTo): static + { + $this->validTo = $validTo; + + return $this; + } + + public function isActive(): ?bool + { + return $this->active; + } + + public function setActive(bool $active): static + { + $this->active = $active; + + return $this; + } + + public function getNotes(): ?string + { + return $this->notes; + } + + public function setNotes(string $notes): static + { + $this->notes = $notes; + + return $this; + } +} diff --git a/src/Repository/OIDCRepository.php b/src/Repository/OIDCRepository.php index cfe5a56..09171d2 100644 --- a/src/Repository/OIDCRepository.php +++ b/src/Repository/OIDCRepository.php @@ -40,4 +40,14 @@ public function remove(OIDC $entity, bool $flush = false): void $this->getEntityManager()->flush(); } } + + public function countExpiredCertificates(): int + { + return $this->createQueryBuilder('o') + ->select('COUNT(o)') + ->where('o.expirationTime < :now') + ->setParameter('now', new \DateTime()) + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/src/Repository/SecurityContractRepository.php b/src/Repository/SecurityContractRepository.php new file mode 100644 index 0000000..d33baf8 --- /dev/null +++ b/src/Repository/SecurityContractRepository.php @@ -0,0 +1,54 @@ + + */ +class SecurityContractRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, SecurityContract::class); + } + + // /** + // * @return SecurityContract[] Returns an array of SecurityContract objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('s.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?SecurityContract + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } + + public function countExpiredContracts(): int + { + return $this->createQueryBuilder('c') + ->select('COUNT(c)') + ->where('c.validTo < :now') + ->andWhere('c.active = true') + ->setParameter('now', new \DateTime()) + ->getQuery() + ->getSingleScalarResult(); + } +} diff --git a/src/Repository/ServiceCertificateRepository.php b/src/Repository/ServiceCertificateRepository.php index 219da2b..bbd1370 100644 --- a/src/Repository/ServiceCertificateRepository.php +++ b/src/Repository/ServiceCertificateRepository.php @@ -40,4 +40,14 @@ public function remove(ServiceCertificate $entity, bool $flush = false): void $this->getEntityManager()->flush(); } } + + public function countExpiredCertificates(): int + { + return $this->createQueryBuilder('c') + ->select('COUNT(c)') + ->where('c.expirationTime < :now') + ->setParameter('now', new \DateTime()) + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/templates/bundles/EasyAdminBundle/crud/detail.html.twig b/templates/bundles/EasyAdminBundle/crud/detail.html.twig new file mode 100644 index 0000000..9abbc27 --- /dev/null +++ b/templates/bundles/EasyAdminBundle/crud/detail.html.twig @@ -0,0 +1,43 @@ +{% extends '@!EasyAdmin/crud/detail.html.twig' %} + +{% block main %} + {{ parent() }} + + +
+
+
+ + + + + Notes + + + +
+
+ +
+
+ + +
+
+ Notes +
+
+ +
+ + nfkngjkfdsnds + + +
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file From e11493eb33015268ca0193ed71fadf82ea467a91 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 23 Oct 2025 11:27:43 +0200 Subject: [PATCH 29/94] 5566: Normalize composer.json --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index aa99043..57405df 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/http-client": "^7.3.4", "symfony/messenger": "^7.3.3", "symfony/monolog-bundle": "^3.10", - "symfony/object-mapper": "7.3.*", + "symfony/object-mapper": "^7.3.0", "symfony/property-access": "^7.3.3", "symfony/property-info": "^7.3.4", "symfony/runtime": "^7.3.4", diff --git a/composer.lock b/composer.lock index 2e80675..c700676 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": "60d7644f1ca92f78aa528dbb47634b49", + "content-hash": "1e514f2fb03ce2e04966c86e6b5b2cc2", "packages": [ { "name": "api-platform/core", From c72a91610780cda6e6af323debdb0d4956541d39 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 23 Oct 2025 12:24:40 +0200 Subject: [PATCH 30/94] 5566: Code style and static analysis cleanup --- .../Admin/AbstractFullCrudController.php | 4 ++-- src/Controller/Admin/OIDCCrudController.php | 4 ---- .../Admin/ProjectCrudController.php | 6 +++--- .../Admin/SecurityContractCrudController.php | 13 +------------ src/Controller/Admin/ServerCrudController.php | 4 ---- .../ServiceCertificateCrudController.php | 7 +------ src/EasyAdmin/Config/AutoBadgeMenuItem.php | 5 +++-- .../Config/Menu/AutoBadgeCrudMenuItem.php | 19 +++++++++++++++---- src/Entity/Project.php | 12 ------------ src/Service/Leantime/ApiService.php | 2 +- src/Service/Leantime/ProjectDto.php | 4 ++-- .../EasyAdminBundle/crud/detail.html.twig | 5 +---- 12 files changed, 29 insertions(+), 56 deletions(-) diff --git a/src/Controller/Admin/AbstractFullCrudController.php b/src/Controller/Admin/AbstractFullCrudController.php index 268c544..10c4c47 100644 --- a/src/Controller/Admin/AbstractFullCrudController.php +++ b/src/Controller/Admin/AbstractFullCrudController.php @@ -53,7 +53,7 @@ public function configureActions(Actions $actions): Actions ->update(Crud::PAGE_INDEX, Action::NEW, static fn (Action $action) => $action->setIcon('fa fa-plus') ) - ; + ; } #[\Override] @@ -62,4 +62,4 @@ public function configureAssets(Assets $assets): Assets return $assets ->addWebpackEncoreEntry('easyadmin'); } -} \ No newline at end of file +} diff --git a/src/Controller/Admin/OIDCCrudController.php b/src/Controller/Admin/OIDCCrudController.php index 3ba6522..006c319 100644 --- a/src/Controller/Admin/OIDCCrudController.php +++ b/src/Controller/Admin/OIDCCrudController.php @@ -6,10 +6,6 @@ use App\Entity\OIDC; use App\Repository\SiteRepository; -use App\Trait\ExportCrudControllerTrait; -use EasyCorp\Bundle\EasyAdminBundle\Config\Action; -use EasyCorp\Bundle\EasyAdminBundle\Config\ActionGroup; -use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; diff --git a/src/Controller/Admin/ProjectCrudController.php b/src/Controller/Admin/ProjectCrudController.php index 18af14a..a435adf 100644 --- a/src/Controller/Admin/ProjectCrudController.php +++ b/src/Controller/Admin/ProjectCrudController.php @@ -63,7 +63,7 @@ public function configureFields(string $pageName): iterable yield DateTimeField::new('createdAt')->hideOnIndex(); } - public function createLeantimeAction(): Action + public function createLeantimeAction(?string $label = null): Action { return Action::new('leantime', $label ?? new TranslatableMessage('Leantime'), 'fa fa-link') ->linkToUrl(function (Project $project) { @@ -71,7 +71,7 @@ public function createLeantimeAction(): Action }); } - public function createUpdateAllProjectsAction(): Action + public function createUpdateAllProjectsAction(?string $label = null): Action { return Action::new('update', $label ?? new TranslatableMessage('Sync all'), 'fa fa-rotate') ->createAsGlobalAction() @@ -83,7 +83,7 @@ public function updateAllProjects(SiteRepository $siteRepository): RedirectRespo try { $this->projectSyncService->syncAllProjects(); - $this->addFlash('info', 'All projects have been synced.', ); + $this->addFlash('info', 'All projects have been synced.'); } catch (\Throwable $e) { $this->addFlash('error', 'An error occurred while syncing projects. Check the log for details.'); } diff --git a/src/Controller/Admin/SecurityContractCrudController.php b/src/Controller/Admin/SecurityContractCrudController.php index 80b372d..c95ccad 100644 --- a/src/Controller/Admin/SecurityContractCrudController.php +++ b/src/Controller/Admin/SecurityContractCrudController.php @@ -3,10 +3,7 @@ namespace App\Controller\Admin; use App\Entity\SecurityContract; -use App\Repository\SecurityContractRepository; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; -use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; @@ -14,17 +11,9 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Response; class SecurityContractCrudController extends AbstractFullCrudController { - public function __construct( - private readonly SecurityContractRepository $securityContractRepository, - ) - { - } - public static function getEntityFqcn(): string { return SecurityContract::class; @@ -35,7 +24,7 @@ public function configureCrud(Crud $crud): Crud { return $crud ->setDefaultSort(['project.name' => 'ASC']) - //->setSearchFields(['name', 'details']) + // ->setSearchFields(['name', 'details']) ->showEntityActionsInlined() ->setPageTitle(Crud::PAGE_INDEX, 'Cyber Security Contracts'); } diff --git a/src/Controller/Admin/ServerCrudController.php b/src/Controller/Admin/ServerCrudController.php index b15b4b5..e56522d 100644 --- a/src/Controller/Admin/ServerCrudController.php +++ b/src/Controller/Admin/ServerCrudController.php @@ -9,16 +9,12 @@ use App\Form\Type\Admin\MariaDbVersionFilter; use App\Form\Type\Admin\ServerTypeFilter; use App\Form\Type\Admin\SystemFilter; -use App\Trait\ExportCrudControllerTrait; use App\Types\DatabaseVersionType; use App\Types\HostingProviderType; use App\Types\ServerTypeType; use App\Types\SystemType; -use EasyCorp\Bundle\EasyAdminBundle\Config\Action; -use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; -use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; diff --git a/src/Controller/Admin/ServiceCertificateCrudController.php b/src/Controller/Admin/ServiceCertificateCrudController.php index 995bf76..526dbe0 100644 --- a/src/Controller/Admin/ServiceCertificateCrudController.php +++ b/src/Controller/Admin/ServiceCertificateCrudController.php @@ -7,12 +7,7 @@ use App\Entity\ServiceCertificate; use App\Form\Type\ServiceCertificate\ServiceType; use App\Repository\SiteRepository; -use App\Trait\ExportCrudControllerTrait; -use EasyCorp\Bundle\EasyAdminBundle\Config\Action; -use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; -use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; @@ -24,7 +19,7 @@ class ServiceCertificateCrudController extends AbstractFullCrudController { public function __construct( - private readonly SiteRepository $siteRepository + private readonly SiteRepository $siteRepository, ) { } diff --git a/src/EasyAdmin/Config/AutoBadgeMenuItem.php b/src/EasyAdmin/Config/AutoBadgeMenuItem.php index edc2fcd..9ba8ca1 100644 --- a/src/EasyAdmin/Config/AutoBadgeMenuItem.php +++ b/src/EasyAdmin/Config/AutoBadgeMenuItem.php @@ -3,16 +3,17 @@ namespace App\EasyAdmin\Config; use App\EasyAdmin\Config\Menu\AutoBadgeCrudMenuItem; -use EasyCorp\Bundle\EasyAdminBundle\Config\Menu\CrudMenuItem; use Symfony\Contracts\Translation\TranslatableInterface; class AutoBadgeMenuItem { + /** @phpstan-ignore missingType.return */ public function __call(string $name, array $arguments) { throw new \BadMethodCallException(sprintf('Method %s not implemented', $name)); } + /** @phpstan-ignore missingType.return */ public static function __callStatic(string $name, array $arguments) { throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); @@ -25,4 +26,4 @@ public static function linkToCrud(TranslatableInterface|string $label, ?string $ { return new AutoBadgeCrudMenuItem($label, $icon, $entityFqcn); } -} \ No newline at end of file +} diff --git a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php index be37436..97a8ecf 100644 --- a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php +++ b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php @@ -3,26 +3,32 @@ namespace App\EasyAdmin\Config\Menu; use EasyCorp\Bundle\EasyAdminBundle\Config\Menu\CrudMenuItem; +use EasyCorp\Bundle\EasyAdminBundle\Contracts\Menu\MenuItemInterface; +use EasyCorp\Bundle\EasyAdminBundle\Dto\MenuItemDto; +use Symfony\Contracts\Translation\TranslatableInterface; -class AutoBadgeCrudMenuItem +class AutoBadgeCrudMenuItem implements MenuItemInterface { private CrudMenuItem $crudMenuItem; - public function __construct(...$args) + public function __construct(TranslatableInterface|string $label, ?string $icon, string $entityFqcn) { - $this->crudMenuItem = new CrudMenuItem(...$args); + $this->crudMenuItem = new CrudMenuItem($label, $icon, $entityFqcn); } + /** @phpstan-ignore missingType.return, missingType.parameter, missingType.parameter */ public function __call($name, $arguments) { return $this->crudMenuItem->$name(...$arguments); } + /** @phpstan-ignore missingType.return */ public static function __callStatic(string $name, array $arguments) { throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); } + /** @phpstan-ignore missingType.parameter */ public function setBadge(/* \Stringable|string|int|float|bool|null */ $content, string $style = 'secondary', array $htmlAttributes = []): self { if (!is_int($content)) { @@ -35,4 +41,9 @@ public function setBadge(/* \Stringable|string|int|float|bool|null */ $content, return $this; } -} \ No newline at end of file + + public function getAsDto(): MenuItemDto + { + return $this->crudMenuItem->getAsDto(); + } +} diff --git a/src/Entity/Project.php b/src/Entity/Project.php index e1d3699..a1841ed 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -114,18 +114,6 @@ public function setLeantimeModifiedAt(\DateTimeImmutable $leantimeModifiedAt): s return $this; } - public function getDetailsText(): ?string - { - return $this->detailsText; - } - - public function setDetailsText(string $detailsText): static - { - $this->detailsText = $detailsText; - - return $this; - } - public function getSecurityContract(): ?SecurityContract { return $this->securityContract; diff --git a/src/Service/Leantime/ApiService.php b/src/Service/Leantime/ApiService.php index dd7390c..83a3ea6 100644 --- a/src/Service/Leantime/ApiService.php +++ b/src/Service/Leantime/ApiService.php @@ -43,7 +43,7 @@ public function getProjects(): array * * @throws LeantimeApiException */ - public function getProject($key): ProjectDto + public function getProject(int $key): ProjectDto { $data = $this->request(self::API_PATH_JSONRPC, 'POST', 'leantime.rpc.projects.getProject', ['id' => $key]); diff --git a/src/Service/Leantime/ProjectDto.php b/src/Service/Leantime/ProjectDto.php index 4ccf1cd..986f051 100644 --- a/src/Service/Leantime/ProjectDto.php +++ b/src/Service/Leantime/ProjectDto.php @@ -22,8 +22,8 @@ public function __construct( public ?string $details, public int $clientId, public ?int $state, - public string $hourBudget, - public int $dollarBudget, + public float $hourBudget, + public float $dollarBudget, public string $menuType, public string $type, public ?int $parent, diff --git a/templates/bundles/EasyAdminBundle/crud/detail.html.twig b/templates/bundles/EasyAdminBundle/crud/detail.html.twig index 9abbc27..9596b80 100644 --- a/templates/bundles/EasyAdminBundle/crud/detail.html.twig +++ b/templates/bundles/EasyAdminBundle/crud/detail.html.twig @@ -3,14 +3,12 @@ {% block main %} {{ parent() }} -
- Notes @@ -21,7 +19,6 @@
-
Notes @@ -40,4 +37,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} From a884b2694fa9cfa5b6004678f43824ac49cd2940 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 23 Oct 2025 12:30:01 +0200 Subject: [PATCH 31/94] 5566: Add CSS linting to taskfile, lint CSS files --- Taskfile.yml | 18 ++++++++++++++++++ assets/styles/app.css | 8 +++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 5b7cc25..cd73982 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -67,6 +67,7 @@ tasks: - task: coding-standards:php:apply - task: coding-standards:twig:apply - task: coding-standards:yaml:apply + - task: coding-standards:css:apply silent: true coding-standards:check: @@ -76,6 +77,7 @@ tasks: - task: coding-standards:php:check - task: coding-standards:twig:check - task: coding-standards:yaml:check + - task: coding-standards:css:check silent: true coding-standards:markdown:apply: @@ -142,6 +144,22 @@ tasks: - task: compose vars: COMPOSE_ARGS: run --rm prettier '**/*.{yml,yaml}' --check + + coding-standards:css:apply: + desc: "Apply coding standards for YAML" + cmds: + - task: compose + vars: + COMPOSE_ARGS: run --rm prettier 'assets/**/*.{css,scss}' --write + silent: true + + coding-standards:css:check: + desc: "Apply and check coding standards for YAML" + cmds: + - task: coding-standards:yaml:apply + - task: compose + vars: + COMPOSE_ARGS: run --rm prettier 'assets/**/*.{css,scss}' --check silent: true code-analysis: diff --git a/assets/styles/app.css b/assets/styles/app.css index 3d9fd32..8218dc7 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -16,11 +16,13 @@ /* Grouped dropdown group styling for index pages */ .dropdown-menu { - .btn-danger i, .text-danger i { + .btn-danger i, + .text-danger i { color: var(--button-invisible-danger-color); } - a.btn-danger:hover , a.text-danger:hover { + a.btn-danger:hover, + a.text-danger:hover { background: var(--button-invisible-danger-hover-hover-bg); } -} \ No newline at end of file +} From 01cf02ee306891a3680a6735c1b0045c3cab030f Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 18 May 2026 13:25:11 +0200 Subject: [PATCH 32/94] Added composer lock and fixed dependency issues --- composer.json | 2 + composer.lock | 14235 +++++++++++++++++ config/reference.php | 2 +- src/Controller/Admin/DashboardController.php | 3 - 4 files changed, 14238 insertions(+), 4 deletions(-) create mode 100644 composer.lock diff --git a/composer.json b/composer.json index 64f9396..b977eab 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", + "symfony/asset-mapper": "8.0.*", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", @@ -31,6 +32,7 @@ "symfony/http-client": "^8.0", "symfony/messenger": "^8.0", "symfony/monolog-bundle": "^4.0", + "symfony/object-mapper": "8.0.*", "symfony/property-access": "^8.0", "symfony/property-info": "^8.0", "symfony/runtime": "^8.0", diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c908354 --- /dev/null +++ b/composer.lock @@ -0,0 +1,14235 @@ +{ + "_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": "d4a07229eeefbd930b295a103ea17748", + "packages": [ + { + "name": "api-platform/core", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/api-platform/core.git", + "reference": "ad027fbbe1b64294174b962c9d9f02d0a3bca7a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/core/zipball/ad027fbbe1b64294174b962c9d9f02d0a3bca7a7", + "reference": "ad027fbbe1b64294174b962c9d9f02d0a3bca7a7", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "doctrine/inflector": "^2.0", + "php": ">=8.2", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^3.1", + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/translation-contracts": "^3.3", + "symfony/type-info": "^7.4 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0", + "willdurand/negotiation": "^3.1" + }, + "conflict": { + "doctrine/common": "<3.2.2", + "doctrine/dbal": "<2.10", + "doctrine/mongodb-odm": "<2.4", + "doctrine/orm": "<2.14.0 || 3.0.0", + "doctrine/persistence": "<1.3", + "phpspec/prophecy": "<1.15", + "phpunit/phpunit": "<9.5", + "symfony/framework-bundle": "6.4.6 || 7.0.6", + "symfony/object-mapper": "<7.3.4", + "symfony/var-exporter": "<6.1.1" + }, + "replace": { + "api-platform/doctrine-common": "self.version", + "api-platform/doctrine-odm": "self.version", + "api-platform/doctrine-orm": "self.version", + "api-platform/documentation": "self.version", + "api-platform/elasticsearch": "self.version", + "api-platform/graphql": "self.version", + "api-platform/hal": "self.version", + "api-platform/http-cache": "self.version", + "api-platform/hydra": "self.version", + "api-platform/json-api": "self.version", + "api-platform/json-schema": "self.version", + "api-platform/jsonld": "self.version", + "api-platform/laravel": "self.version", + "api-platform/mcp": "self.version", + "api-platform/metadata": "self.version", + "api-platform/openapi": "self.version", + "api-platform/parameter-validator": "self.version", + "api-platform/ramsey-uuid": "self.version", + "api-platform/serializer": "self.version", + "api-platform/state": "self.version", + "api-platform/symfony": "self.version", + "api-platform/validator": "self.version" + }, + "require-dev": { + "behat/behat": "^3.11", + "behat/mink": "^1.9", + "doctrine/common": "^3.2.2", + "doctrine/dbal": "^4.0", + "doctrine/doctrine-bundle": "^2.11 || ^3.1", + "doctrine/orm": "^2.17 || ^3.0", + "elasticsearch/elasticsearch": "^7.17 || ^8.4 || ^9.0", + "friends-of-behat/mink-browserkit-driver": "^1.3.1", + "friends-of-behat/mink-extension": "^2.2", + "friends-of-behat/symfony-extension": "^2.1", + "friendsofphp/php-cs-fixer": "^3.93", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "illuminate/config": "^11.0 || ^12.0 || ^13.0", + "illuminate/contracts": "^11.0 || ^12.0 || ^13.0", + "illuminate/database": "^11.0 || ^12.0 || ^13.0", + "illuminate/http": "^11.0 || ^12.0 || ^13.0", + "illuminate/pagination": "^11.0 || ^12.0 || ^13.0", + "illuminate/routing": "^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^11.0 || ^12.0 || ^13.0", + "jangregor/phpstan-prophecy": "^2.1.11", + "justinrainbow/json-schema": "^6.5.2", + "laravel/framework": "^11.0 || ^12.0 || ^13.0", + "mcp/sdk": ">=0.4 <1.0", + "orchestra/testbench": "^10.9 || ^11.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpdoc-parser": "^1.29 || ^2.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^11.5 || ^12.2", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^4.7", + "ramsey/uuid-doctrine": "^2.0", + "soyuka/contexts": "^3.3.10", + "soyuka/pmu": "^0.2.0", + "soyuka/stubs-mongodb": "^1.0", + "symfony/asset": "^6.4 || ^7.0 || ^8.0", + "symfony/browser-kit": "^6.4 || ^7.0 || ^8.0", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^6.4.2 || ^7.1 || ^8.0", + "symfony/dom-crawler": "^6.4 || ^7.0 || ^8.0", + "symfony/error-handler": "^6.4 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/form": "^6.4 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/intl": "^6.4 || ^7.0 || ^8.0", + "symfony/json-streamer": "^7.4 || ^8.0", + "symfony/maker-bundle": "^1.24", + "symfony/mcp-bundle": "dev-main", + "symfony/mercure-bundle": "*", + "symfony/messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/object-mapper": "^7.4 || ^8.0", + "symfony/routing": "^6.4 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/security-core": "^6.4 || ^7.0 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/uid": "^6.4 || ^7.0 || ^8.0", + "symfony/var-exporter": "^7.4 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "twig/twig": "^1.42.3 || ^2.12 || ^3.0", + "webonyx/graphql-php": "^15.0" + }, + "suggest": { + "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", + "elasticsearch/elasticsearch": "To support Elasticsearch.", + "opensearch-project/opensearch-php": "To support OpenSearch (^2.5).", + "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", + "psr/cache-implementation": "To use metadata caching.", + "ramsey/uuid": "To support Ramsey's UUID identifiers.", + "symfony/cache": "To have metadata caching when using Symfony integration.", + "symfony/config": "To load XML configuration files.", + "symfony/expression-language": "To use authorization features.", + "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/json-streamer": "To use the JSON Streamer component.", + "symfony/messenger": "To support messenger integration.", + "symfony/security": "To use authorization features.", + "symfony/twig-bundle": "To use the Swagger UI integration.", + "symfony/uid": "To support Symfony UUID/ULID identifiers.", + "symfony/web-profiler-bundle": "To use the data collector.", + "webonyx/graphql-php": "To support GraphQL." + }, + "type": "library", + "extra": { + "pmu": { + "projects": [ + "./src/*/composer.json", + "src/Doctrine/*/composer.json" + ] + }, + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.1 || ^8.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/JsonLd/HydraContext.php" + ], + "psr-4": { + "ApiPlatform\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr" + } + ], + "description": "Build a fully-featured hypermedia or GraphQL API in minutes!", + "homepage": "https://api-platform.com", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "laravel", + "openapi", + "rest", + "swagger", + "symfony" + ], + "support": { + "issues": "https://github.com/api-platform/core/issues", + "source": "https://github.com/api-platform/core/tree/v4.3.5" + }, + "time": "2026-05-11T12:20:22+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "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.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "doctrine/collections", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/7713da39d8e237f28411d6a616a3dce5e20d5de2", + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1", + "symfony/polyfill-php84": "^1.30" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "ext-json": "*", + "phpstan/phpstan": "^2.1.30", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.6.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%2Fcollections", + "type": "tidelift" + } + ], + "time": "2026-01-15T10:01:58+00:00" + }, + { + "name": "doctrine/dbal", + "version": "4.4.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "61e730f1658814821a85f2402c945f3883407dec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61e730f1658814821a85f2402c945f3883407dec", + "reference": "61e730f1658814821a85f2402c945f3883407dec", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "14.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "11.5.50", + "slevomat/coding-standard": "8.27.1", + "squizlabs/php_codesniffer": "4.0.1", + "symfony/cache": "^6.3.8|^7.0|^8.0", + "symfony/console": "^5.4|^6.3|^7.0|^8.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/4.4.3" + }, + "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%2Fdbal", + "type": "tidelift" + } + ], + "time": "2026-03-20T08:52:12+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/af84173db6978c3d2688ea3bcf3a91720b0704ce", + "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^4.0", + "doctrine/deprecations": "^1.0", + "doctrine/persistence": "^4", + "doctrine/sql-formatter": "^1.0.1", + "php": "^8.4", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/service-contracts": "^3" + }, + "conflict": { + "doctrine/orm": "<3.0 || >=4.0", + "twig/twig": "<3.0.4" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "doctrine/orm": "^3.4.4", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^12.3.10", + "psr/log": "^3.0", + "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4 || ^7.0 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "twig/twig": "^3.21.1" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/3.2.2" + }, + "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%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2025-12-24T12:24:29+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/20505da78735744fb4a42a3bb9a416b345ad6f7c", + "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^4", + "doctrine/doctrine-bundle": "^3", + "doctrine/migrations": "^3.2", + "php": "^8.4", + "psr/log": "^3", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^3", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/service-contracts": "^3.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/coding-standard": "^14", + "doctrine/orm": "^3", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^12.5", + "symfony/var-exporter": "^6.4 || ^7 || ^8" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/4.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%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "time": "2025-12-05T08:14:38+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.1.1" + }, + "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%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2026-01-29T07:11:08+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.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%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", + "shasum": "" + }, + "require": { + "php": "^8.4" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" + }, + "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.1.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": "2026-01-05T06:47:08+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "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%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "doctrine/migrations", + "version": "3.9.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.6 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.2 || ^7.0 || ^8.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "fig/log-test": "^1", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^10.3 || ^11.0 || ^12.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.9.7" + }, + "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%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2026-04-23T19:33:20+00:00" + }, + { + "name": "doctrine/orm", + "version": "3.6.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "7e88b416153dceeb563352ca2b12465f09eea173" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/7e88b416153dceeb563352ca2b12465f09eea173", + "reference": "7e88b416153dceeb563352ca2b12465f09eea173", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1 || ^4", + "ext-ctype": "*", + "php": "^8.1", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^14.0", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "2.1.23", + "phpstan/phpstan-deprecation-rules": "^2", + "phpunit/phpunit": "^10.5.0 || ^11.5", + "psr/log": "^1 || ^2 || ^3", + "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/3.6.5" + }, + "time": "2026-05-11T06:47:19+00:00" + }, + { + "name": "doctrine/persistence", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "doctrine/event-manager": "^1 || ^2", + "php": "^8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.58 || ^12", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/4.2.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%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2026-04-26T12:12:52+00:00" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.5.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "9563949f5cd3bd12a17d12fb980528bc141c5806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/9563949f5cd3bd12a17d12fb980528bc141c5806", + "reference": "9563949f5cd3bd12a17d12fb980528bc141c5806", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "ergebnis/phpunit-slow-test-detector": "^2.20", + "phpstan/phpstan": "^2.1.31", + "phpunit/phpunit": "^10.5.58" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.4" + }, + "time": "2026-02-08T16:21:46+00:00" + }, + { + "name": "easycorp/easyadmin-bundle", + "version": "v5.0.8", + "source": { + "type": "git", + "url": "https://github.com/EasyCorp/EasyAdminBundle.git", + "reference": "0feafc61cd7a2b990c2d7280fd7a450696a7fe92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/0feafc61cd7a2b990c2d7280fd7a450696a7fe92", + "reference": "0feafc61cd7a2b990c2d7280fd7a450696a7fe92", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.10|^4.4", + "doctrine/doctrine-bundle": "^2.18|^3.2", + "doctrine/orm": "^2.20|^3.6", + "php": ">=8.2", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/cache": "^6.4.33|^7.0|^8.0", + "symfony/config": "^6.4.32|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.32|^7.0|^8.0", + "symfony/deprecation-contracts": "^3.0", + "symfony/doctrine-bridge": "^6.4.32|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4.32|^7.0|^8.0", + "symfony/filesystem": "^6.4.30|^7.0|^8.0", + "symfony/form": "^6.4.32|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.33|^7.0|^8.0", + "symfony/http-foundation": "^6.4.33|^7.0|^8.0", + "symfony/http-kernel": "^6.4.33|^7.0|^8.0", + "symfony/intl": "^6.4.32|^7.0|^8.0", + "symfony/property-access": "^6.4.32|^7.0|^8.0", + "symfony/security-bundle": "^6.4.32|^7.0|^8.0", + "symfony/string": "^6.4.30|^7.0|^8.0", + "symfony/translation": "^6.4.32|^7.0|^8.0", + "symfony/twig-bridge": "^6.4.32|^7.1.9|^7.2|^8.0", + "symfony/twig-bundle": "^6.4.32|^7.0|^8.0", + "symfony/uid": "^6.4.32|^7.0|^8.0", + "symfony/ux-twig-component": "^2.32|^3.0", + "symfony/validator": "^6.4.33|^7.0|^8.0", + "twig/extra-bundle": "^3.23", + "twig/html-extra": "^3.23", + "twig/twig": "^3.23" + }, + "require-dev": { + "dama/doctrine-test-bundle": "^8.2", + "doctrine/doctrine-fixtures-bundle": "^3.4|3.5.x-dev|^4.0", + "league/flysystem": "^3.10", + "moneyphp/money": "^4.8", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4.32|^7.0|^8.0", + "symfony/css-selector": "^6.4.24|^7.0|^8.0", + "symfony/debug-bundle": "^6.4.27|^7.0|^8.0", + "symfony/dom-crawler": "^6.4.32|^7.0|^8.0", + "symfony/expression-language": "^6.4.32|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4.26|^7.0|^8.0", + "symfony/process": "^6.4.33|^7.0|^8.0", + "symfony/web-link": "^6.4.32|^7.0|^8.0", + "vincentlanglet/twig-cs-fixer": "^3.10", + "zenstruck/foundry": "^2.3" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "EasyCorp\\Bundle\\EasyAdminBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Project Contributors", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle/graphs/contributors" + } + ], + "description": "Admin generator for Symfony applications", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle", + "keywords": [ + "admin", + "backend", + "generator" + ], + "support": { + "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v5.0.8" + }, + "funding": [ + { + "url": "https://github.com/javiereguiluz", + "type": "github" + } + ], + "time": "2026-05-13T17:18:33+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v7.0.5", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + }, + "time": "2026-04-01T20:38:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "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.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "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.10.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": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "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.3.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": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "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.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "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.9.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": "2026-03-10T16:41:02+00:00" + }, + { + "name": "itk-dev/openid-connect", + "version": "4.1.2", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/openid-connect.git", + "reference": "f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5", + "reference": "f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "^7.0", + "league/oauth2-client": "^2.6", + "php": "^8.3", + "psr/cache": "^2.0 || ^3.0", + "psr/http-client": "^1.0", + "robrichards/xmlseclibs": "^3.1.5" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.50", + "friendsofphp/php-cs-fixer": "^3.75", + "mockery/mockery": "^1.6.12", + "phpstan/phpstan": "^2.1.40", + "phpunit/php-code-coverage": "^12", + "phpunit/phpunit": "^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "ItkDev\\OpenIdConnect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeppe Kuhlmann Andersen", + "email": "jekua@aarhus.dk" + }, + { + "name": "Ture Gjørup", + "email": "tug@aarhus.dk" + }, + { + "name": "Lars Steen Risom", + "email": "lats@aarhus.dk" + } + ], + "description": "OpenID connect configuration package", + "support": { + "issues": "https://github.com/itk-dev/openid-connect/issues", + "source": "https://github.com/itk-dev/openid-connect/tree/4.1.2" + }, + "time": "2026-05-11T12:03:06+00:00" + }, + { + "name": "itk-dev/openid-connect-bundle", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/openid-connect-bundle.git", + "reference": "7964acbd630ba0a6b4a362f714bfed255ad88e05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/7964acbd630ba0a6b4a362f714bfed255ad88e05", + "reference": "7964acbd630ba0a6b4a362f714bfed255ad88e05", + "shasum": "" + }, + "require": { + "doctrine/orm": "^2.8 || ^3.0", + "ext-json": "*", + "ext-openssl": "*", + "itk-dev/openid-connect": "^4.0", + "php": "^8.3", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.4.13 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4.13 || ^7.0 || ^8.0", + "symfony/uid": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.28", + "friendsofphp/php-cs-fixer": "^3.11", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^12.0", + "rector/rector": "^2.0", + "symfony/runtime": "^6.4.13 || ^7.0 || ^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "ItkDev\\OpenIdConnectBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeppe Kuhlmann Andersen", + "email": "jekua@aarhus.dk" + }, + { + "name": "Ture Gjørup", + "email": "tug@aarhus.dk" + } + ], + "description": "Symfony bundle for openid-connect", + "support": { + "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/4.2.0" + }, + "time": "2026-05-11T12:11:09+00:00" + }, + { + "name": "itk-dev/vault", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/vault-library.git", + "reference": "d5c4fbd58817dba495bd07dcd9a3b9865e7bc69b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/vault-library/zipball/d5c4fbd58817dba495bd07dcd9a3b9865e7bc69b", + "reference": "d5c4fbd58817dba495bd07dcd9a3b9865e7bc69b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.1", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "ItkDev\\Vault\\": "src/Vault" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jesper Kristensen", + "email": "cableman@linuxdev.dk", + "homepage": "https://linuxdev.dk", + "role": "Developer" + } + ], + "description": "Library to communicate with Hashicorp Vault", + "homepage": "https://github.com/itk-dev", + "keywords": [ + "approle", + "e", + "hashicorp", + "vault" + ], + "support": { + "issues": "https://github.com/itk-dev/vault-library/issues", + "source": "https://github.com/itk-dev/vault-library/tree/0.1.0" + }, + "time": "2024-10-21T07:16:27+00:00" + }, + { + "name": "itk-dev/vault-bundle", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/itk-dev/vault-bundle.git", + "reference": "9ce87b4098b3213f8744557593772bf6473c7fa9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itk-dev/vault-bundle/zipball/9ce87b4098b3213f8744557593772bf6473c7fa9", + "reference": "9ce87b4098b3213f8744557593772bf6473c7fa9", + "shasum": "" + }, + "require": { + "itk-dev/vault": "^0.1.0", + "nyholm/psr7": "^1.8", + "php": "^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "ItkDev\\VaultBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jesper Kristensen", + "email": "cableman@linuxdev.dk", + "homepage": "https://linuxdev.dk", + "role": "Developer" + } + ], + "description": "Symfony bundle for HashiCorp Vault", + "support": { + "issues": "https://github.com/itk-dev/vault-bundle/issues", + "source": "https://github.com/itk-dev/vault-bundle/tree/1.0.0" + }, + "time": "2026-03-25T09:34:40+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.0" + }, + "time": "2025-11-25T22:17:17+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nelmio/cors-bundle", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.5", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-symfony": "^1.4.4", + "phpunit/phpunit": "^8" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.1" + }, + "time": "2026-01-12T15:59:08+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "ocramius/doctrine-batch-utils", + "version": "2.12.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/DoctrineBatchUtils.git", + "reference": "a0a3a3b09f5c52088712398354b75e251cab90cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/DoctrineBatchUtils/zipball/a0a3a3b09f5c52088712398354b75e251cab90cc", + "reference": "a0a3a3b09f5c52088712398354b75e251cab90cc", + "shasum": "" + }, + "require": { + "doctrine/orm": "^3.5.8", + "doctrine/persistence": "^4.1.1", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "doctrine/coding-standard": "^14.0.0", + "doctrine/dbal": "^4.4.1", + "phpunit/phpunit": "^12.5.3", + "psalm/plugin-phpunit": "^0.19.5", + "roave/infection-static-analysis-plugin": "^1.42.0", + "vimeo/psalm": "^6.14.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "DoctrineBatchUtils\\": "src/DoctrineBatchUtils" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" + } + ], + "description": "A set of utilities to operate with Doctrine ORM's batch processing functionality", + "homepage": "https://github.com/Ocramius/DoctrineBatchUtils", + "keywords": [ + "batch processing", + "doctrine", + "doctrine2", + "orm" + ], + "support": { + "issues": "https://github.com/Ocramius/DoctrineBatchUtils/issues", + "source": "https://github.com/Ocramius/DoctrineBatchUtils/tree/2.12.0" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + } + ], + "time": "2025-12-12T14:45:53+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+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/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+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.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "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" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "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": "PSR-17: 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" + }, + "time": "2024-04-15T12:06:14+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/link", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/link.git", + "reference": "84b159194ecfd7eaa472280213976e96415433f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/link/zipball/84b159194ecfd7eaa472280213976e96415433f7", + "reference": "84b159194ecfd7eaa472280213976e96415433f7", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "suggest": { + "fig/link-util": "Provides some useful PSR-13 utilities" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Link\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for HTTP links", + "homepage": "https://github.com/php-fig/link", + "keywords": [ + "http", + "http-link", + "link", + "psr", + "psr-13", + "rest" + ], + "support": { + "source": "https://github.com/php-fig/link/tree/2.0.1" + }, + "time": "2021-03-11T23:00:27+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "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.2" + }, + "time": "2024-09-11T13:17:53+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": "robrichards/xmlseclibs", + "version": "3.1.5", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92", + "reference": "03062be78178cbb5e8f605cd255dc32a14981f92", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ], + "support": { + "issues": "https://github.com/robrichards/xmlseclibs/issues", + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5" + }, + "time": "2026-03-13T10:31:56+00:00" + }, + { + "name": "symfony/amqp-messenger", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/amqp-messenger.git", + "reference": "26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e", + "reference": "26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e", + "shasum": "" + }, + "require": { + "ext-amqp": "*", + "php": ">=8.4", + "symfony/messenger": "^7.4|^8.0" + }, + "require-dev": { + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "symfony-messenger-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\Bridge\\Amqp\\": "" + }, + "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": "Symfony AMQP extension Messenger Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/amqp-messenger/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/asset", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "72eca261f3af1bef741c48bb2c91a4e619dca03a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/72eca261f3af1bef741c48bb2c91a4e619dca03a", + "reference": "72eca261f3af1bef741c48bb2c91a4e619dca03a", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "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": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/asset-mapper", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", + "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.4", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "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 directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "f5a28fca785416cf489dd579011e74c831100cc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f5a28fca785416cf489dd579011e74c831100cc3", + "reference": "f5a28fca785416cf489dd579011e74c831100cc3", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/dom-crawler": "^7.4|^8.0" + }, + "require-dev": { + "symfony/css-selector": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/cache", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "8ff96cde73684bfa32b702f5cff1eb83b1fac429" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/8ff96cde73684bfa32b702f5cff1eb83b1fac429", + "reference": "8ff96cde73684bfa32b702f5cff1eb83b1fac429", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^7.4|^8.0" + }, + "conflict": { + "ext-redis": "<6.1", + "ext-relay": "<0.12.1" + }, + "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": "^4.3", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.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/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-05T08:24:00+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "225e8a254166bd3442e370c6f50145465db63831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", + "reference": "225e8a254166bd3442e370c6f50145465db63831", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-05T15:33:14+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b55a638b189a6faa875e0ccdb00908fb87af95b3", + "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/config", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "de665e669412ec2effe004d90298dbbdaf6e7e8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/de665e669412ec2effe004d90298dbbdaf6e7e8b", + "reference": "de665e669412ec2effe004d90298dbbdaf6e7e8b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.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/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-04T13:41:39+00:00" + }, + { + "name": "symfony/console", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3156577f46a38aa1b9323aad223de7a9cd426782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3156577f46a38aa1b9323aad223de7a9cd426782", + "reference": "3156577f46a38aa1b9323aad223de7a9cd426782", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4|^8.0" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "6fc374dae45a7633a5865da7fc2908baf29d4900" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6fc374dae45a7633a5865da7fc2908baf29d4900", + "reference": "6fc374dae45a7633a5865da7fc2908baf29d4900", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^7.4|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.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/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-06T11:55:35+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-13T15:52:40+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "dfe3dddc9c22756b9b145785fb5fd4b0445cd06e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/dfe3dddc9c22756b9b145785fb5fd4b0445cd06e", + "reference": "dfe3dddc9c22756b9b145785fb5fd4b0445cd06e", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/collections": "<1.8", + "doctrine/dbal": "<4.3", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<3.4", + "symfony/property-info": "<8.0" + }, + "require-dev": { + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^4.3", + "doctrine/orm": "^3.4", + "psr/log": "^1|^2|^3", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/doctrine-messenger": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "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 Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/doctrine-messenger", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-messenger.git", + "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/88329a3faba5023cfb569b3fc5b8a771336c4a88", + "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^4.3", + "php": ">=8.4", + "symfony/messenger": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "symfony-messenger-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\": "" + }, + "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": "Symfony Doctrine Messenger Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-messenger/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-20T07:51:53+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "284ace90732b445b027728b5e0eec6418a17a364" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/284ace90732b445b027728b5e0eec6418a17a364", + "reference": "284ace90732b445b027728b5e0eec6418a17a364", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "symfony/css-selector": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe", + "reference": "82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "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": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-11T13:06:45+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1119fe8dcfc3825ec74ec061b96ef0c8f281517", + "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0c3c1a17604c4dbbec4b93fe162c538482096e1f", + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.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": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.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/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-18T13:51:42+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b", + "reference": "b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/cache": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "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 an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/224db910898ce1317b892a9a1338f1f8f17eb7c7", + "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "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/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-11T16:39:47+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8da41214757b87d97f181e3d14a4179286151007" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", + "reference": "8da41214757b87d97f181e3d14a4179286151007", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/9cd384775973eabbf6e8b05784dda279fc67c28d", + "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.1" + }, + "conflict": { + "composer/semver": "<1.7.2", + "symfony/dotenv": "<5.4" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^6.4|^7.4|^8.0", + "symfony/filesystem": "^6.4|^7.4|^8.0", + "symfony/phpunit-bridge": "^6.4|^7.4|^8.0", + "symfony/process": "^6.4|^7.4|^8.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-16T09:38:19+00:00" + }, + { + "name": "symfony/form", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/form.git", + "reference": "dd9f73dd3b92e657c97aeeca1f47e981c635ea91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/form/zipball/dd9f73dd3b92e657c97aeeca1f47e981c635ea91", + "reference": "dd9f73dd3b92e657c97aeeca1f47e981c635ea91", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/options-resolver": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/polyfill-mbstring": "^1.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/intl": "<7.4", + "symfony/translation-contracts": "<2.5", + "symfony/validator": "<7.4" + }, + "require-dev": { + "doctrine/collections": "^1.0|^2.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Form\\": "" + }, + "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 to easily create, process and reuse HTML forms", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/form/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "c0d53dba8de800f5dd1e9dac79683d8c59934d34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c0d53dba8de800f5dd1e9dac79683d8c59934d34", + "reference": "c0d53dba8de800f5dd1e9dac79683d8c59934d34", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.4", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4.4|^8.0.4", + "symfony/dependency-injection": "^7.4.4|^8.0.4", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/routing": "^7.4|^8.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/console": "<7.4", + "symfony/form": "<7.4", + "symfony/json-streamer": "<7.4", + "symfony/messenger": "<7.4", + "symfony/mime": "<7.4.9|>=8.0,<8.0.9", + "symfony/security-csrf": "<7.4", + "symfony/serializer": "<7.4", + "symfony/translation": "<7.4", + "symfony/webhook": "<7.4", + "symfony/workflow": "<7.4" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/dotenv": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/json-streamer": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/mailer": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4.9|^8.0.9", + "symfony/notifier": "^7.4|^8.0", + "symfony/object-mapper": "^7.4.9|^8.0.9", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/scheduler": "^7.4|^8.0", + "symfony/security-bundle": "^7.4|^8.0", + "symfony/semaphore": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "symfony/type-info": "^7.4.1|^8.0.1", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/webhook": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.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/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/http-client", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "537c7f164078975b800f3f1c56810791024e4c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/537c7f164078975b800f3f1c56810791024e4c77", + "reference": "537c7f164078975b800f3f1c56810791024e4c77", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/log": "^1|^2|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<3", + "php-http/discovery": "<1.15" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^5.3.2", + "amphp/http-tunnel": "^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "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 powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "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 HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:17:50+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "02656f7ebeae5c155d659e946f6b3a33df24051b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/02656f7ebeae5c155d659e946f6b3a33df24051b", + "reference": "02656f7ebeae5c155d659e946f6b3a33df24051b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "20d3680373f4b791903c09e74b45402b4aeda71c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/20d3680373f4b791903c09e74b45402b4aeda71c", + "reference": "20d3680373f4b791903c09e74b45402b4aeda71c", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/log": "^1|^2|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/flex": "<2.10", + "symfony/http-client-contracts": "<2.5", + "symfony/translation-contracts": "<2.5", + "twig/twig": "<3.21" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21" + }, + "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/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T18:07:14+00:00" + }, + { + "name": "symfony/intl", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/intl.git", + "reference": "604a1dbbd67471e885e93274379cadd80dc33535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/intl/zipball/604a1dbbd67471e885e93274379cadd80dc33535", + "reference": "604a1dbbd67471e885e93274379cadd80dc33535", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "conflict": { + "symfony/string": "<7.4" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/data/" + ] + }, + "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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/messenger", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/messenger.git", + "reference": "c451c175724fc781c777783aaec3b7999ceb0621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/messenger/zipball/c451c175724fc781c777783aaec3b7999ceb0621", + "reference": "c451c175724fc781c777783aaec3b7999ceb0621", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/log": "^1|^2|^3", + "symfony/clock": "^7.4|^8.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/event-dispatcher-contracts": "<2.5", + "symfony/lock": "<7.4", + "symfony/serializer": "<7.4.4|>=8.0,<8.0.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4.4|^8.0.4", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Samuel Roze", + "email": "samuel.roze@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps applications send and receive messages to/from other applications or via message queues", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/messenger/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/mime", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "a9fcb293650c054b62a5b406f4e92e7b711ea333" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/a9fcb293650c054b62a5b406f4e92e7b711ea333", + "reference": "a9fcb293650c054b62a5b406f4e92e7b711ea333", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "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 manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/monolog-bridge", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "4b7249b1520773ad325e99231b08443017729297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/4b7249b1520773ad325e99231b08443017729297", + "reference": "4b7249b1520773ad325e99231b08443017729297", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3", + "php": ">=8.4", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/mailer": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "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 Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v4.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "c012c6aba13129eb02aa7dd61e66e720911d8598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/c012c6aba13129eb02aa7dd61e66e720911d8598", + "reference": "c012c6aba13129eb02aa7dd61e66e720911d8598", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "monolog/monolog": "^3.5", + "php": ">=8.2", + "symfony/config": "^7.3 || ^8.0", + "symfony/dependency-injection": "^7.3 || ^8.0", + "symfony/http-kernel": "^7.3 || ^8.0", + "symfony/monolog-bridge": "^7.3 || ^8.0", + "symfony/polyfill-php84": "^1.30" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.41 || ^12.3", + "symfony/console": "^7.3 || ^8.0", + "symfony/yaml": "^7.3 || ^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "src" + } + }, + "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": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v4.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-02T18:27:21+00:00" + }, + { + "name": "symfony/object-mapper", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/object-mapper.git", + "reference": "2296aac81e50ba1ac5d83389207150a424cf1f5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/object-mapper/zipball/2296aac81e50ba1ac5d83389207150a424cf1f5a", + "reference": "2296aac81e50ba1ac5d83389207150a424cf1f5a", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^2.0" + }, + "conflict": { + "symfony/property-access": "<7.2" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ObjectMapper\\": "" + }, + "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 way to map an object to another object", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/object-mapper/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8", + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/password-hasher", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "57ee968d3c38301ed3e5b838f850a10f2d06a7f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/57ee968d3c38301ed3e5b838f850a10f2d06a7f6", + "reference": "57ee968d3c38301ed3e5b838f850a10f2d06a7f6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PasswordHasher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides password hashing utilities", + "homepage": "https://symfony.com", + "keywords": [ + "hashing", + "password" + ], + "support": { + "source": "https://github.com/symfony/password-hasher/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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 intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-26T13:13:48+00:00" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "3510b63d07376b04e57e27e82607d468bb134f78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/3510b63d07376b04e57e27e82607d468bb134f78", + "reference": "3510b63d07376b04e57e27e82607d468bb134f78", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "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": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:50:15+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "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 for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T17:25:58+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "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.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T18:47:49+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "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.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-26T13:10:57+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/property-access", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "704c7808116fcdd67327db7b17de56b8ef6169e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/704c7808116fcdd67327db7b17de56b8ef6169e4", + "reference": "704c7808116fcdd67327db7b17de56b8ef6169e4", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/property-info": "^7.4.4|^8.0.4" + }, + "require-dev": { + "symfony/cache": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "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 functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/property-info", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "c21711980653360d6ef5c26d0f9ca6f58a1135c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/c21711980653360d6ef5c26d0f9ca6f58a1135c6", + "reference": "c21711980653360d6ef5c26d0f9ca6f58a1135c6", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/string": "^7.4|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/routing", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "75d1bd8e5da3424e4db2fc3ff0222cb4d0c73038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/75d1bd8e5da3424e4db2fc3ff0222cb4d0c73038", + "reference": "75d1bd8e5da3424e4db2fc3ff0222cb4d0c73038", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.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/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/runtime", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "30884f00e017a26100fcd9aa281082ebf9a87dce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/30884f00e017a26100fcd9aa281082ebf9a87dce", + "reference": "30884f00e017a26100fcd9aa281082ebf9a87dce", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.4" + }, + "conflict": { + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^7.4|^8.0", + "symfony/dotenv": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/security-bundle", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-bundle.git", + "reference": "c7ae56efdc872704101a55b86f950b111aea9a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/c7ae56efdc872704101a55b86f950b111aea9a06", + "reference": "c7ae56efdc872704101a55b86f950b111aea9a06", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.4", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/password-hasher": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/ldap": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SecurityBundle\\": "" + }, + "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 the Security component into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-bundle/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/security-core", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "7aa47b511c07734bbb3490046ca8cdff1bf4fbef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/7aa47b511c07734bbb3490046ca8cdff1bf4fbef", + "reference": "7aa47b511c07734bbb3490046ca8cdff1bf4fbef", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "psr/container": "^1.1|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/ldap": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Core\\": "" + }, + "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": "Symfony Security Component - Core Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-core/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/security-csrf", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-csrf.git", + "reference": "83c8f60ef8d385c05ea863093c9efabe74800883" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/83c8f60ef8d385c05ea863093c9efabe74800883", + "reference": "83c8f60ef8d385c05ea863093c9efabe74800883", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/security-core": "^7.4|^8.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Csrf\\": "" + }, + "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": "Symfony Security Component - CSRF Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-csrf/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/security-http", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-http.git", + "reference": "3c29d0118c6bc5919ce6f2ef4e9ead24503ca819" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-http/zipball/3c29d0118c6bc5919ce6f2ef4e9ead24503ca819", + "reference": "3c29d0118c6bc5919ce6f2ef4e9ead24503ca819", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Http\\": "" + }, + "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": "Symfony Security Component - HTTP Integration", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-http/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/serializer", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "72ed7e1475790714f07c3a59bd01fd32cd022fdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/72ed7e1475790714f07c3a59bd01fd32cd022fdf", + "reference": "72ed7e1475790714f07c3a59bd01fd32cd022fdf", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/property-access": "<7.4.2|>=8.0,<8.0.2", + "symfony/property-info": "<7.4", + "symfony/type-info": "<7.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^7.4.2|^8.0.2", + "symfony/property-info": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "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": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-04T13:41:39+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-28T09:44:51+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3", + "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "39be2ad058a3c0bd558edca23e65f009865d75ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/39be2ad058a3c0bd558edca23e65f009865d75ff", + "reference": "39be2ad058a3c0bd558edca23e65f009865d75ff", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "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 an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/f63e9342e12646a57c91ef8a366a4f9d8e557b67", + "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/http-client-contracts": "<2.5", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-06T11:30:54+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "a892d0b7f3d5d51b35895467e48aafbd1f2612a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/a892d0b7f3d5d51b35895467e48aafbd1f2612a0", + "reference": "a892d0b7f3d5d51b35895467e48aafbd1f2612a0", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.21" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/form": "<7.4.4|>8.0,<8.0.4", + "symfony/mime": "<7.4.8|>8.0,<8.0.8" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4.4|^8.0.4", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4.8|^8.0.8", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1", + "reference": "f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/type-info", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/08723aceb8c3271e8cb3db8b2565728b0c88e866", + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-29T15:02:55+00:00" + }, + { + "name": "symfony/uid", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "4d9d6510bbe88ebb4608b7200d18606cdf80825c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/4d9d6510bbe88ebb4608b7200d18606cdf80825c", + "reference": "4d9d6510bbe88ebb4608b7200d18606cdf80825c", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-30T16:10:06+00:00" + }, + { + "name": "symfony/ux-twig-component", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-twig-component.git", + "reference": "dea344e320238234c94f63b152d65821c80cd103" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/dea344e320238234c94f63b152d65821c80cd103", + "reference": "dea344e320238234c94f63b152d65821c80cd103", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.2|^3.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "twig/twig": "^3.10.3" + }, + "conflict": { + "symfony/config": "<6.4" + }, + "require-dev": { + "phpunit/phpunit": "^11.1|^12.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/stimulus-bundle": "^2.9.1|^3.0", + "symfony/twig-bundle": "^7.4|^8.0", + "twig/extra-bundle": "^3.10.3", + "twig/html-extra": "^3.10.3" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\TwigComponent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Twig components for Symfony", + "homepage": "https://symfony.com", + "keywords": [ + "components", + "symfony-ux", + "twig" + ], + "support": { + "source": "https://github.com/symfony/ux-twig-component/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-09T22:56:44+00:00" + }, + { + "name": "symfony/validator", + "version": "v8.0.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "12bb4be483a8626bd1b2f46f5d44c9449cf4361f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/12bb4be483a8626bd1b2f46f5d44c9449cf4361f", + "reference": "12bb4be483a8626bd1b2f46f5d44c9449cf4361f", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/lexer": "<1.1", + "symfony/doctrine-bridge": "<7.4", + "symfony/expression-language": "<7.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/bin/" + ] + }, + "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 validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v8.0.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-05T16:03:11+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", + "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12" + }, + "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/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-31T07:15:36+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "24cf67be4dd0926e4413635418682f4fff831412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/24cf67be4dd0926e4413635418682f4fff831412", + "reference": "24cf67be4dd0926e4413635418682f4fff831412", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.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/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-18T13:51:42+00:00" + }, + { + "name": "symfony/web-link", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-link.git", + "reference": "f76065fd8d59284332c8beaf2d7e1449f8c532c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-link/zipball/f76065fd8d59284332c8beaf2d7e1449f8c532c3", + "reference": "f76065fd8d59284332c8beaf2d7e1449f8c532c3", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/link": "^1.1|^2.0" + }, + "provide": { + "psr/link-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/http-kernel": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\WebLink\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages links between resources", + "homepage": "https://symfony.com", + "keywords": [ + "dns-prefetch", + "http", + "http2", + "link", + "performance", + "prefetch", + "preload", + "prerender", + "psr13", + "push" + ], + "support": { + "source": "https://github.com/symfony/web-link/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/webpack-encore-bundle", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/webpack-encore-bundle.git", + "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", + "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/asset": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/config": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0" + }, + "require-dev": { + "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-client": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/web-link": "^5.4 || ^6.2 || ^7.0 || ^8.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/webpack-encore", + "name": "symfony/webpack-encore" + } + }, + "autoload": { + "psr-4": { + "Symfony\\WebpackEncoreBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Integration of your Symfony app with Webpack Encore", + "support": { + "issues": "https://github.com/symfony/webpack-encore-bundle/issues", + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:41:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "48046fbd5567bd1717f278eaa2cfc3131f489984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/48046fbd5567bd1717f278eaa2cfc3131f489984", + "reference": "48046fbd5567bd1717f278eaa2cfc3131f489984", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^2.7", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^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.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-02-07T08:07:38+00:00" + }, + { + "name": "twig/html-extra", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/html-extra.git", + "reference": "313900fb98b371b006a55b1a29241a192634be13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/313900fb98b371b006a55b1a29241a192634be13", + "reference": "313900fb98b371b006a55b1a29241a192634be13", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/mime": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Html\\": "" + }, + "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 HTML", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/html-extra/tree/v3.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-03-17T07:24:08+00:00" + }, + { + "name": "twig/twig", + "version": "v3.25.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "0dade995be754556af4dcbf8721d45cb3271f9b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0dade995be754556af4dcbf8721d45cb3271f9b4", + "reference": "0dade995be754556af4dcbf8721d45cb3271f9b4", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "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.25.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-05-17T07:41:26+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" + } + ], + "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/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": "doctrine/data-fixtures", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/data-fixtures.git", + "reference": "bf7ac3a050b54b261cedfb3d0a44733819062275" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/bf7ac3a050b54b261cedfb3d0a44733819062275", + "reference": "bf7ac3a050b54b261cedfb3d0a44733819062275", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3.1 || ^4.0", + "php": "^8.1", + "psr/log": "^1.1 || ^2 || ^3" + }, + "conflict": { + "doctrine/dbal": "<3.5 || >=5", + "doctrine/orm": "<2.14 || >=4", + "doctrine/phpcr-odm": "<1.3.0" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "doctrine/dbal": "^3.5 || ^4", + "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", + "doctrine/orm": "^2.14 || ^3", + "doctrine/phpcr-odm": "^1.8 || ^2.0", + "ext-sqlite3": "*", + "fig/log-test": "^1", + "jackalope/jackalope-fs": "*", + "phpstan/phpstan": "2.1.46", + "phpunit/phpunit": "10.5.63 || 12.5.12", + "symfony/cache": "^6.4 || ^7 || ^8", + "symfony/var-exporter": "^6.4 || ^7 || ^8" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", + "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures", + "doctrine/orm": "For loading ORM fixtures", + "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\DataFixtures\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Data Fixtures for all Doctrine Object Managers", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database" + ], + "support": { + "issues": "https://github.com/doctrine/data-fixtures/issues", + "source": "https://github.com/doctrine/data-fixtures/tree/2.2.1" + }, + "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%2Fdata-fixtures", + "type": "tidelift" + } + ], + "time": "2026-04-01T13:56:01+00:00" + }, + { + "name": "ergebnis/agent-detector", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\AgentDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", + "support": { + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" + }, + "time": "2026-05-07T08:19:07+00:00" + }, + { + "name": "ergebnis/composer-normalize", + "version": "2.52.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/composer-normalize.git", + "reference": "988f83f5e51a42cdd2337e5fcd935432f8dfa33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/composer-normalize/zipball/988f83f5e51a42cdd2337e5fcd935432f8dfa33c", + "reference": "988f83f5e51a42cdd2337e5fcd935432f8dfa33c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "ergebnis/json": "^1.4.0", + "ergebnis/json-normalizer": "^4.9.0", + "ergebnis/json-printer": "^3.7.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "localheinz/diff": "^1.3.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "composer/composer": "^2.9.8", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.62.1", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.11", + "phpunit/phpunit": "^9.6.33", + "rector/rector": "^2.4.3", + "symfony/filesystem": "^5.4.41" + }, + "type": "composer-plugin", + "extra": { + "class": "Ergebnis\\Composer\\Normalize\\NormalizePlugin", + "branch-alias": { + "dev-main": "2.52-dev" + }, + "plugin-optional": true, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Composer\\Normalize\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a composer plugin for normalizing composer.json.", + "homepage": "https://github.com/ergebnis/composer-normalize", + "keywords": [ + "composer", + "normalize", + "normalizer", + "plugin" + ], + "support": { + "issues": "https://github.com/ergebnis/composer-normalize/issues", + "security": "https://github.com/ergebnis/composer-normalize/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/composer-normalize" + }, + "time": "2026-05-15T15:39:24+00:00" + }, + { + "name": "ergebnis/json", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json.git", + "reference": "7b56d2b5d9e897e75b43e2e753075a0904c921b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json/zipball/7b56d2b5d9e897e75b43e2e753075a0904c921b1", + "reference": "7b56d2b5d9e897e75b43e2e753075a0904c921b1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpstan-rules": "^2.11.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.24", + "rector/rector": "^2.1.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.7-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a Json value object for representing a valid JSON string.", + "homepage": "https://github.com/ergebnis/json", + "keywords": [ + "json" + ], + "support": { + "issues": "https://github.com/ergebnis/json/issues", + "security": "https://github.com/ergebnis/json/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json" + }, + "time": "2025-09-06T09:08:45+00:00" + }, + { + "name": "ergebnis/json-normalizer", + "version": "4.10.1", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-normalizer.git", + "reference": "77961faf2c651c3f05977b53c6c68e8434febf62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/77961faf2c651c3f05977b53c6c68e8434febf62", + "reference": "77961faf2c651c3f05977b53c6c68e8434febf62", + "shasum": "" + }, + "require": { + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ergebnis/json-printer": "^3.5.0", + "ergebnis/json-schema-validator": "^4.2.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "composer/semver": "^3.4.3", + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.19", + "rector/rector": "^1.2.10" + }, + "suggest": { + "composer/semver": "If you want to use ComposerJsonNormalizer or VersionConstraintNormalizer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.11-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Normalizer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides generic and vendor-specific normalizers for normalizing JSON documents.", + "homepage": "https://github.com/ergebnis/json-normalizer", + "keywords": [ + "json", + "normalizer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-normalizer/issues", + "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-normalizer" + }, + "time": "2025-09-06T09:18:13+00:00" + }, + { + "name": "ergebnis/json-pointer", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-pointer.git", + "reference": "b58c3c468a7ff109fdf9a255f17de29ecbe5276c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/b58c3c468a7ff109fdf9a255f17de29ecbe5276c", + "reference": "b58c3c468a7ff109fdf9a255f17de29ecbe5276c", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.50.0", + "ergebnis/data-provider": "^3.6.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.16.0", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.46", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.8-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Pointer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides an abstraction of a JSON pointer.", + "homepage": "https://github.com/ergebnis/json-pointer", + "keywords": [ + "RFC6901", + "json", + "pointer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-pointer/issues", + "security": "https://github.com/ergebnis/json-pointer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-pointer" + }, + "time": "2026-04-07T14:52:13+00:00" + }, + { + "name": "ergebnis/json-printer", + "version": "3.8.1", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-printer.git", + "reference": "211d73fc7ec6daf98568ee6ed6e6d133dee8503e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/211d73fc7ec6daf98568ee6ed6e6d133dee8503e", + "reference": "211d73fc7ec6daf98568ee6ed6e6d133dee8503e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.1", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.21", + "rector/rector": "^1.2.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.9-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\Printer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a JSON printer, allowing for flexible indentation.", + "homepage": "https://github.com/ergebnis/json-printer", + "keywords": [ + "formatter", + "json", + "printer" + ], + "support": { + "issues": "https://github.com/ergebnis/json-printer/issues", + "security": "https://github.com/ergebnis/json-printer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-printer" + }, + "time": "2025-09-06T09:59:26+00:00" + }, + { + "name": "ergebnis/json-schema-validator", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/json-schema-validator.git", + "reference": "b739527a480a9e3651360ad351ea77e7e9019df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/b739527a480a9e3651360ad351ea77e7e9019df2", + "reference": "b739527a480a9e3651360ad351ea77e7e9019df2", + "shasum": "" + }, + "require": { + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/data-provider": "^3.3.0", + "ergebnis/license": "^2.5.0", + "ergebnis/php-cs-fixer-config": "^6.37.0", + "ergebnis/phpunit-slow-test-detector": "^2.16.1", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.10", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^9.6.20", + "rector/rector": "^1.2.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.6-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\SchemaValidator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a JSON schema validator, building on top of justinrainbow/json-schema.", + "homepage": "https://github.com/ergebnis/json-schema-validator", + "keywords": [ + "json", + "schema", + "validator" + ], + "support": { + "issues": "https://github.com/ergebnis/json-schema-validator/issues", + "security": "https://github.com/ergebnis/json-schema-validator/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-schema-validator" + }, + "time": "2025-09-06T11:37:35+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": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "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": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "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.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.95.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "a28d88a5e172b27e78d0816992b15a9df3da20f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a28d88a5e172b27e78d0816992b15a9df3da20f1", + "reference": "a28d88a5e172b27e78d0816992b15a9df3da20f1", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.1.1", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.11.0", + "infection/infection": "^0.32.7", + "justinrainbow/json-schema": "^6.8.0", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.8", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.8" + }, + "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/**/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.95.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-05-15T09:20:44+00:00" + }, + { + "name": "hautelook/alice-bundle", + "version": "2.17.3", + "source": { + "type": "git", + "url": "https://github.com/theofidry/AliceBundle.git", + "reference": "3f3ec5248587d0b59b753f90f3f5a0891dd89d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/AliceBundle/zipball/3f3ec5248587d0b59b753f90f3f5a0891dd89d71", + "reference": "3f3ec5248587d0b59b753f90f3f5a0891dd89d71", + "shasum": "" + }, + "require": { + "doctrine/data-fixtures": "^1.7 || ^2.0", + "doctrine/doctrine-bundle": "^2.18.2 || ^3.0", + "doctrine/orm": "^3.1", + "doctrine/persistence": "^3.3.1 || ^4.0", + "php": "^8.4", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/doctrine-bridge": "^7.4 || ^8.0", + "symfony/finder": "^7.4 || ^8.0", + "symfony/framework-bundle": "^7.4 || ^8.0", + "theofidry/alice-data-fixtures": "^1.10" + }, + "require-dev": { + "monolog/monolog": "^3.5", + "php-cs-fixer/shim": "^3.94", + "phpspec/prophecy": "^1.14.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^11.5.46", + "symfony/runtime": "*" + }, + "type": "symfony-bundle", + "extra": { + "bin-dir": "bin", + "branch-alias": { + "dev-master": "2.x-dev" + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "Hautelook\\AliceBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Baldur Rensch", + "email": "brensch@gmail.com" + }, + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com", + "homepage": "https://github.com/theofidry" + } + ], + "description": "Symfony bundle to manage fixtures with Alice and Faker.", + "keywords": [ + "Fixture", + "alice", + "faker", + "orm", + "symfony" + ], + "support": { + "issues": "https://github.com/theofidry/AliceBundle/issues", + "source": "https://github.com/theofidry/AliceBundle/tree/2.17.3" + }, + "time": "2026-03-21T21:21:40+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.8.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "dev-main", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" + }, + "time": "2026-05-05T05:39:01+00:00" + }, + { + "name": "localheinz/diff", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/localheinz/diff.git", + "reference": "33bd840935970cda6691c23fc7d94ae764c0734c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/localheinz/diff/zipball/33bd840935970cda6691c23fc7d94ae764c0734c", + "reference": "33bd840935970cda6691c23fc7d94ae764c0734c", + "shasum": "" + }, + "require": { + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5.0 || ^8.5.23", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "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": "Fork of sebastian/diff for use with ergebnis/composer-normalize", + "homepage": "https://github.com/localheinz/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/localheinz/diff/issues", + "source": "https://github.com/localheinz/diff/tree/1.3.0" + }, + "time": "2025-08-30T09:44:18+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "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", + "phpspec/prophecy": "^1.10", + "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.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nelmio/alice", + "version": "3.17.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/alice.git", + "reference": "eb4db8bc50b0a5a9177aadd9eef4a930b6eaee95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/alice/zipball/eb4db8bc50b0a5a9177aadd9eef4a930b6eaee95", + "reference": "eb4db8bc50b0a5a9177aadd9eef4a930b6eaee95", + "shasum": "" + }, + "require": { + "fakerphp/faker": "^1.10", + "myclabs/deep-copy": "^1.10", + "php": "^8.2", + "sebastian/comparator": "^6.0 || ^7.0 || ^8.0", + "symfony/polyfill-php84": "^1.31", + "symfony/property-access": "^7.4 || ^8.0", + "symfony/yaml": "^7.4 || ^8.0" + }, + "conflict": { + "symfony/framework-bundle": "<7.4.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "phpspec/prophecy": "^1.6", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^11 || ^12.5 || ^13", + "symfony/config": "^7.4 || ^8.0", + "symfony/dependency-injection": "^7.4 || ^8.0", + "symfony/finder": "^7.4 || ^8.0", + "symfony/http-kernel": "^7.4 || ^8.0", + "symfony/var-dumper": "^7.4 || ^8.0" + }, + "suggest": { + "theofidry/alice-data-fixtures": "Wrapper for Alice to provide a persistence layer." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "files": [ + "src/deep_clone.php" + ], + "psr-4": { + "Nelmio\\Alice\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + }, + { + "name": "Tim Shelburne", + "email": "shelburt02@gmail.com" + }, + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Expressive fixtures generator", + "keywords": [ + "Fixture", + "data", + "faker", + "test" + ], + "support": { + "issues": "https://github.com/nelmio/alice/issues", + "source": "https://github.com/nelmio/alice/tree/3.17.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2026-02-24T16:58:28+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-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/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+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": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.54", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-04-29T13:31:09+00:00" + }, + { + "name": "phpstan/phpstan-doctrine", + "version": "2.0.22", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-doctrine.git", + "reference": "e87516b034749432d51653c0147e053e476e8c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e87516b034749432d51653c0147e053e476e8c53", + "reference": "e87516b034749432d51653c0147e053e476e8c53", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.34" + }, + "conflict": { + "doctrine/collections": "<1.0", + "doctrine/common": "<2.7", + "doctrine/mongodb-odm": "<1.2", + "doctrine/orm": "<2.5", + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "cache/array-adapter": "^1.1", + "composer/semver": "^3.3.2", + "cweagans/composer-patches": "^1.7.3", + "doctrine/annotations": "^2.0", + "doctrine/collections": "^1.6 || ^2.1", + "doctrine/common": "^2.7 || ^3.0", + "doctrine/dbal": "^3.3.8", + "doctrine/lexer": "^2.0 || ^3.0", + "doctrine/mongodb-odm": "^2.4.3", + "doctrine/orm": "^2.16.0", + "doctrine/persistence": "^2.2.1 || ^3.4.3", + "gedmo/doctrine-extensions": "^3.8", + "nesbot/carbon": "^2.49", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0.2", + "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6.20", + "ramsey/uuid": "^4.2", + "shipmonk/name-collision-detector": "^2.1", + "symfony/cache": "^5.4", + "symfony/uid": "^5.4 || ^6.4 || ^7.3" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Doctrine extensions for PHPStan", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-doctrine/issues", + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.22" + }, + "time": "2026-05-09T08:10:48+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "2.0.16", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.32" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^5", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" + }, + "time": "2026-02-14T09:05:21+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "2.0.17", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "fdd0cb5f08d1980c612d6f259d825ea644ed03f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/fdd0cb5f08d1980c612d6f259d825ea644ed03f4", + "reference": "fdd0cb5f08d1980c612d6f259d825ea644ed03f4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.13" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "psr/container": "1.1.2", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.17" + }, + "time": "2026-05-10T08:14:07+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "14.1.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "655533a65696bbc4231cd8027af150dadc40ec88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/655533a65696bbc4231cd8027af150dadc40ec88", + "reference": "655533a65696bbc4231cd8027af150dadc40ec88", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.4", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.2", + "sebastian/git-state": "^1.0", + "sebastian/lines-of-code": "^5.0", + "sebastian/version": "^7.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^13.1" + }, + "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-main": "14.1.x-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", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.1.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-05-16T05:16:14+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:33:26+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:34:47+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:36:37+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "9.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.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", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:37:53+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "13.1.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "38959098d3c10660a189afaa35a94290c1de67bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38959098d3c10660a189afaa35a94290c1de67bb", + "reference": "38959098d3c10660a189afaa35a94290c1de67bb", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^14.1.8", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.1.3", + "sebastian/diff": "^8.3.0", + "sebastian/environment": "^9.3.0", + "sebastian/exporter": "^8.0.2", + "sebastian/git-state": "^1.0", + "sebastian/global-state": "^9.0.0", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.0", + "sebastian/version": "^7.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "13.1-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/13.1.10" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-05-15T08:03:56+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.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "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.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "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.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "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.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 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.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "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.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+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": "rector/rector", + "version": "2.4.3", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "891824c6c59f02a56a5dd58ea8edc44e6c0ece29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/891824c6c59f02a56a5dd58ea8edc44e6c0ece29", + "reference": "891824c6c59f02a56a5dd58ea8edc44e6c0ece29", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.48" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.4.3" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-05-12T11:17:24+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:39:44+00:00" + }, + { + "name": "sebastian/comparator", + "version": "8.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1edd557042bf4ff9978ec125d8131b147d5c8224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1edd557042bf4ff9978ec125d8131b147d5c8224", + "reference": "1edd557042bf4ff9978ec125d8131b147d5c8224", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/diff": "^8.3", + "sebastian/exporter": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.1-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", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/8.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-05-15T08:30:51+00:00" + }, + { + "name": "sebastian/complexity", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:41:32+00:00" + }, + { + "name": "sebastian/diff", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b36d33b6e796513de7cb7df053afb3f55eefcd47", + "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.3-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", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/8.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" + } + ], + "time": "2026-05-15T04:58:09+00:00" + }, + { + "name": "sebastian/environment", + "version": "9.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "6767059a30e4277ac95ee034809e793528464768" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6767059a30e4277ac95ee034809e793528464768", + "reference": "6767059a30e4277ac95ee034809e793528464768", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.3-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": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/9.3.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-04-15T12:14:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "9cee180ebe62259e3ed48df2212d1fc8cfd971bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/9cee180ebe62259e3ed48df2212d1fc8cfd971bb", + "reference": "9cee180ebe62259e3ed48df2212d1fc8cfd971bb", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-04-15T12:38:05+00:00" + }, + { + "name": "sebastian/git-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/git-state.git", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/git-state/zipball/792a952e0eba55b6960a48aeceb9f371aad1f76b", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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 describing the state of a Git checkout", + "homepage": "https://github.com/sebastianbergmann/git-state", + "support": { + "issues": "https://github.com/sebastianbergmann/git-state/issues", + "security": "https://github.com/sebastianbergmann/git-state/security/policy", + "source": "https://github.com/sebastianbergmann/git-state/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/git-state", + "type": "tidelift" + } + ], + "time": "2026-03-21T12:54:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "9.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.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": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:45:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "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", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:45:54+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:46:36+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:47:13+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:51:28+00:00" + }, + { + "name": "sebastian/type", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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 types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:52:09+00:00" + }, + { + "name": "sebastian/version", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:52:52+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "3665cfade90565430909b906394c73c8739e57d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/3665cfade90565430909b906394c73c8739e57d0", + "reference": "3665cfade90565430909b906394c73c8739e57d0", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v8.0.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-18T13:51:42+00:00" + }, + { + "name": "symfony/debug-bundle", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "d9d127d61fb2aa7f7f324a8b1928767e2211d1bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/d9d127d61fb2aa7f7f324a8b1928767e2211d1bc", + "reference": "d9d127d61fb2aa7f7f324a8b1928767e2211d1bc", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "require-dev": { + "symfony/web-profiler-bundle": "^7.4|^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\DebugBundle\\": "" + }, + "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 the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug-bundle/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.67.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6ce8b313845f16bcf385ee3cb31d8b24e30d5516", + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1", + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^5.0", + "php": ">=8.1", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/doctrine-bundle": "<2.10", + "doctrine/orm": "<2.15" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.10|^3.0", + "doctrine/orm": "^2.15|^3", + "doctrine/persistence": "^3.1|^4.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-http": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "twig/twig": "^3.0|^4.x-dev" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MakerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "keywords": [ + "code generator", + "dev", + "generator", + "scaffold", + "scaffolding" + ], + "support": { + "issues": "https://github.com/symfony/maker-bundle/issues", + "source": "https://github.com/symfony/maker-bundle/tree/v1.67.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-18T13:39:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "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.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "26d89e459f037d2873300605d0a07e7a8ef84db0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/26d89e459f037d2873300605d0a07e7a8ef84db0", + "reference": "26d89e459f037d2873300605d0a07e7a8ef84db0", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-11T16:56:32+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "5b80ded3fc1c76c6587be3f7b74bd0f8d9e6d747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/5b80ded3fc1c76c6587be3f7b74bd0f8d9e6d747", + "reference": "5b80ded3fc1c76c6587be3f7b74bd0f8d9e6d747", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0" + }, + "conflict": { + "symfony/serializer": "<7.4", + "symfony/workflow": "<7.4" + }, + "require-dev": { + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\WebProfilerBundle\\": "" + }, + "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 development tool that gives detailed information about the execution of any request", + "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], + "support": { + "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-11T13:06:45+00:00" + }, + { + "name": "theofidry/alice-data-fixtures", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/AliceDataFixtures.git", + "reference": "011a482252ab416f2e4681731e954554d250695e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/AliceDataFixtures/zipball/011a482252ab416f2e4681731e954554d250695e", + "reference": "011a482252ab416f2e4681731e954554d250695e", + "shasum": "" + }, + "require": { + "nelmio/alice": "^3.10", + "php": "^8.4", + "psr/log": "^1 || ^2 || ^3", + "webmozart/assert": "^1.10 || ^2.0" + }, + "conflict": { + "doctrine/data-fixtures": "<1.7.0", + "doctrine/dbal": "<3.0", + "doctrine/doctrine-bundle": "<2.11.0", + "doctrine/mongodb-odm": "<2.6.0", + "doctrine/mongodb-odm-bundle": "<5.1.0", + "doctrine/orm": "<3.3.2", + "doctrine/persistence": "<2.5.7 || >=3.0 <3.4.0", + "doctrine/phpcr-bundle": "<3.0", + "doctrine/phpcr-odm": "<2.0.0", + "illuminate/database": "<12.0", + "ocramius/proxy-manager": "<2.1", + "symfony/framework-bundle": "<7.4.0", + "zendframework/zend-code": "<3.3.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpspec/prophecy": "^1.20.0", + "phpspec/prophecy-phpunit": "^2.3.0", + "phpunit/phpunit": "^11.5.12", + "symfony/runtime": "*" + }, + "suggest": { + "doctrine/data-fixtures": "To use Doctrine", + "doctrine/dbal": "To use Doctrine with the PHPCR flavour", + "doctrine/mongodb": "To use Doctrine with the MongoDB flavour", + "doctrine/mongodb-odm": "To use Doctrine with the MongoDB flavour", + "doctrine/orm": "To use Doctrine ORM", + "doctrine/phpcr-odm": "To use Doctrine with the PHPCR flavour", + "illuminate/database": "To use Eloquent", + "jackalope/jackalope-doctrine-dbal": "To use Doctrine with the PHPCR flavour" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fidry\\AliceDataFixtures\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com", + "homepage": "https://github.com/theofidry" + } + ], + "description": "Nelmio alice extension to persist the loaded fixtures.", + "keywords": [ + "Fixture", + "alice", + "data", + "faker", + "orm", + "tests" + ], + "support": { + "issues": "https://github.com/theofidry/AliceDataFixtures/issues", + "source": "https://github.com/theofidry/AliceDataFixtures/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-12-18T23:35:24+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^8.1" + }, + "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/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-12-08T11:19:18+00:00" + }, + { + "name": "vincentlanglet/twig-cs-fixer", + "version": "3.14.0", + "source": { + "type": "git", + "url": "https://github.com/VincentLanglet/Twig-CS-Fixer.git", + "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/599f110f192c31af5deb5736d6c1a970afdf51f3", + "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "ext-ctype": "*", + "php": ">=8.1", + "symfony/console": "^5.4.9 || ^6.4 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/string": "^5.4.42 || ^6.4.10 || ~7.0.10 || ^7.1.3 || ^8.0", + "twig/twig": "^3.4", + "webmozart/assert": "^1.10 || ^2.0" + }, + "require-dev": { + "composer/semver": "^3.2.0", + "dereuromark/composer-prefer-lowest": "^0.1.10", + "ergebnis/composer-normalize": "^2.29", + "friendsofphp/php-cs-fixer": "^3.13.0", + "infection/infection": "^0.26.16 || ^0.32.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpunit/phpunit": "^9.5.26 || ^11.5.18 || ^12.1.3", + "rector/rector": "^2.0.0", + "shipmonk/composer-dependency-analyser": "^1.6", + "symfony/process": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^5.4 || ^6.4 || ^7.0 || ^8.0", + "symfony/ux-twig-component": "^2.2.0", + "twig/cache-extra": "^3.2" + }, + "bin": [ + "bin/twig-cs-fixer" + ], + "type": "coding-standard", + "autoload": { + "psr-4": { + "TwigCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Vincent Langlet" + } + ], + "description": "A tool to automatically fix Twig code style", + "homepage": "https://github.com/VincentLanglet/Twig-CS-Fixer", + "support": { + "issues": "https://github.com/VincentLanglet/Twig-CS-Fixer/issues", + "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.14.0" + }, + "funding": [ + { + "url": "https://github.com/VincentLanglet", + "type": "github" + } + ], + "time": "2026-02-23T13:21:35+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.3.0" + }, + "time": "2026-04-11T10:33:05+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.4", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config/reference.php b/config/reference.php index a632abf..563c307 100644 --- a/config/reference.php +++ b/config/reference.php @@ -280,7 +280,7 @@ * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * paths?: string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 513d22d..4302345 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -20,9 +20,6 @@ class DashboardController extends AbstractDashboardController public function __construct( private readonly AdminUrlGenerator $adminUrlGenerator, private readonly AdvisoryRepository $advisoryRepository, - private readonly ServiceCertificateRepository $serviceCertificateRepository, - private readonly OIDCRepository $oidcRepository, - private readonly SecurityContractRepository $securityContractRepository, ) { } From ba9c1122c980ef018e9b9015359bd2bbc1c415c2 Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 18 May 2026 13:59:24 +0200 Subject: [PATCH 33/94] Added menu link --- src/Controller/Admin/DashboardController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 4302345..975630c 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -46,6 +46,7 @@ public function configureDashboard(): Dashboard public function configureMenuItems(): iterable { yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); + yield MenuItem::linkTo(SecurityContractCrudController::class, 'Hosting', 'fas fa-file-contract'); yield MenuItem::linkTo(ServerCrudController::class, 'Servers', 'fas fa-server'); yield MenuItem::linkTo(InstallationCrudController::class, 'Installations', 'fas fa-folder'); yield MenuItem::linkTo(SiteCrudController::class, 'Sites', 'fas fa-bookmark'); From 514260b572130c31e9e6affef6765ef83d42b940 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 10:32:54 +0200 Subject: [PATCH 34/94] Removed deprecated leantime service, and project entity --- .env | 7 +- composer.json | 1 - composer.lock | 75 +---- config/packages/framework.yaml | 8 +- config/services.yaml | 4 - migrations/Version20260519061123.php | 39 +++ src/Command/SyncLeantimeProjectsCommand.php | 54 ---- src/Command/SyncServiceAgreementsCommand.php | 40 +++ src/Controller/Admin/DashboardController.php | 2 +- .../Admin/ProjectCrudController.php | 93 ------- .../Admin/SecurityContractCrudController.php | 86 +++++- src/Entity/Project.php | 133 --------- src/Entity/SecurityContract.php | 263 +++++++++++++++--- src/Exception/LeantimeApiException.php | 7 - src/Repository/ProjectRepository.php | 43 --- src/Service/Leantime/ApiService.php | 95 ------- .../Leantime/LeantimeProjectUrlFactory.php | 17 -- src/Service/Leantime/ProjectDto.php | 75 ----- src/Service/Leantime/ProjectSyncService.php | 76 ----- src/Service/ServiceAgreementSyncService.php | 133 +++++++++ 20 files changed, 519 insertions(+), 732 deletions(-) create mode 100644 migrations/Version20260519061123.php delete mode 100644 src/Command/SyncLeantimeProjectsCommand.php create mode 100644 src/Command/SyncServiceAgreementsCommand.php delete mode 100644 src/Controller/Admin/ProjectCrudController.php delete mode 100644 src/Entity/Project.php delete mode 100644 src/Exception/LeantimeApiException.php delete mode 100644 src/Repository/ProjectRepository.php delete mode 100644 src/Service/Leantime/ApiService.php delete mode 100644 src/Service/Leantime/LeantimeProjectUrlFactory.php delete mode 100644 src/Service/Leantime/ProjectDto.php delete mode 100644 src/Service/Leantime/ProjectSyncService.php create mode 100644 src/Service/ServiceAgreementSyncService.php diff --git a/.env b/.env index 6d9f616..0fa1f32 100644 --- a/.env +++ b/.env @@ -64,5 +64,8 @@ VAULT_SECRET_ID="CHANGE_ME_IN_LOCAL_ENV" # The number of old results for each server/result-type combination APP_KEEP_RESULTS=5 -APP_LEANTIME_URI=https://leantime.itkdev.dk -APP_LEANTIME_API_KEY="CHANGE_ME_IN_LOCAL_ENV" + +###> economics ### +APP_ECONOMICS_URI=https://economics.itkdev.dk +APP_ECONOMICS_API_KEY=changeme +###< economics ### diff --git a/composer.json b/composer.json index b977eab..81d744f 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,6 @@ "symfony/http-client": "^8.0", "symfony/messenger": "^8.0", "symfony/monolog-bundle": "^4.0", - "symfony/object-mapper": "8.0.*", "symfony/property-access": "^8.0", "symfony/property-info": "^8.0", "symfony/runtime": "^8.0", diff --git a/composer.lock b/composer.lock index c908354..d938250 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": "d4a07229eeefbd930b295a103ea17748", + "content-hash": "b8846cfbafdf8854299f8b6dc8761bc9", "packages": [ { "name": "api-platform/core", @@ -5911,79 +5911,6 @@ ], "time": "2026-04-02T18:27:21+00:00" }, - { - "name": "symfony/object-mapper", - "version": "v8.0.9", - "source": { - "type": "git", - "url": "https://github.com/symfony/object-mapper.git", - "reference": "2296aac81e50ba1ac5d83389207150a424cf1f5a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/object-mapper/zipball/2296aac81e50ba1ac5d83389207150a424cf1f5a", - "reference": "2296aac81e50ba1ac5d83389207150a424cf1f5a", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "psr/container": "^2.0" - }, - "conflict": { - "symfony/property-access": "<7.2" - }, - "require-dev": { - "symfony/property-access": "^7.4|^8.0", - "symfony/var-exporter": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ObjectMapper\\": "" - }, - "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 way to map an object to another object", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/object-mapper/tree/v8.0.9" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-29T15:02:55+00:00" - }, { "name": "symfony/options-resolver", "version": "v8.0.8", diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 1ad58d7..1aa06cb 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -15,11 +15,11 @@ framework: http_client: scoped_clients: - leantime.client: - base_uri: '%env(APP_LEANTIME_URI)%' - scope: '%env(APP_LEANTIME_URI)%' + economics.client: + base_uri: '%env(APP_ECONOMICS_URI)%' + scope: '%env(APP_ECONOMICS_URI)%' headers: - x-api-key: '%env(APP_LEANTIME_API_KEY)%' + x-api-key: '%env(APP_ECONOMICS_API_KEY)%' when@test: framework: diff --git a/config/services.yaml b/config/services.yaml index c98c423..71d40c9 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -36,7 +36,3 @@ services: arguments: $resultHandlers: !tagged_iterator app.handler.detection_result_handler $keepResults: '%env(int:APP_KEEP_RESULTS)%' - - App\Service\Leantime\LeantimeProjectUrlFactory: - arguments: - $baseUrl: '%env(string:APP_LEANTIME_URI)%' diff --git a/migrations/Version20260519061123.php b/migrations/Version20260519061123.php new file mode 100644 index 0000000..4c04db0 --- /dev/null +++ b/migrations/Version20260519061123.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE security_contract DROP FOREIGN KEY `FK_8AE4AF8B166D1F9C`'); + $this->addSql('DROP TABLE project'); + $this->addSql('DROP INDEX UNIQ_8AE4AF8B166D1F9C ON security_contract'); + $this->addSql('ALTER TABLE security_contract ADD economics_id INT NOT NULL, ADD project_name VARCHAR(255) NOT NULL, ADD hosting_provider VARCHAR(255) DEFAULT NULL, ADD document_url VARCHAR(255) DEFAULT NULL, ADD eol TINYINT NOT NULL, ADD leantime_url VARCHAR(255) DEFAULT NULL, ADD client_contact_name VARCHAR(255) DEFAULT NULL, ADD client_contact_email VARCHAR(255) DEFAULT NULL, ADD dedicated_server TINYINT NOT NULL, ADD server_size VARCHAR(255) DEFAULT NULL, ADD system_owner_notices JSON DEFAULT NULL, ADD project_tracker_key VARCHAR(255) DEFAULT NULL, ADD cybersecurity_price DOUBLE PRECISION DEFAULT NULL, ADD cybersecurity_note LONGTEXT DEFAULT NULL, DROP notes, DROP project_id, CHANGE valid_from valid_from DATE DEFAULT NULL, CHANGE valid_to valid_to DATE DEFAULT NULL, CHANGE economics_report_url client_name VARCHAR(255) DEFAULT NULL, CHANGE operational_contract_url git_repos LONGTEXT DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8AE4AF8B4416F7E8 ON security_contract (economics_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE project (id BINARY(16) NOT NULL, created_at DATETIME NOT NULL, modified_at DATETIME NOT NULL, created_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT \'\' NOT NULL COLLATE `utf8mb4_unicode_ci`, modified_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT \'\' NOT NULL COLLATE `utf8mb4_unicode_ci`, leantime_id INT NOT NULL, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, leantime_url VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, economics_url VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, details LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, leantime_modified_at DATETIME NOT NULL, UNIQUE INDEX UNIQ_2FB3D0EE4B785F0C (leantime_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' '); + $this->addSql('DROP INDEX UNIQ_8AE4AF8B4416F7E8 ON security_contract'); + $this->addSql('ALTER TABLE security_contract ADD economics_report_url VARCHAR(255) DEFAULT NULL, ADD operational_contract_url LONGTEXT DEFAULT NULL, ADD notes LONGTEXT NOT NULL, ADD project_id BINARY(16) NOT NULL, DROP economics_id, DROP project_name, DROP client_name, DROP hosting_provider, DROP document_url, DROP eol, DROP leantime_url, DROP client_contact_name, DROP client_contact_email, DROP dedicated_server, DROP server_size, DROP git_repos, DROP system_owner_notices, DROP project_tracker_key, DROP cybersecurity_price, DROP cybersecurity_note, CHANGE valid_from valid_from DATE NOT NULL, CHANGE valid_to valid_to DATE NOT NULL'); + $this->addSql('ALTER TABLE security_contract ADD CONSTRAINT `FK_8AE4AF8B166D1F9C` FOREIGN KEY (project_id) REFERENCES project (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8AE4AF8B166D1F9C ON security_contract (project_id)'); + } +} diff --git a/src/Command/SyncLeantimeProjectsCommand.php b/src/Command/SyncLeantimeProjectsCommand.php deleted file mode 100644 index dc66a49..0000000 --- a/src/Command/SyncLeantimeProjectsCommand.php +++ /dev/null @@ -1,54 +0,0 @@ -addOption('project-id', null, InputOption::VALUE_REQUIRED, 'Leantime project ID'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - - try { - if ($input->getOption('project-id')) { - $projectId = (int) $input->getOption('project-id'); - - $project = $this->projectSyncService->syncProject($projectId); - - $io->success(sprintf('Project: %s (%d) synced successfully.', $project->getName(), $project->getLeantimeId())); - } else { - $count = $this->projectSyncService->syncAllProjects(); - - $io->success(sprintf('Synced %d projects successfully.', $count)); - } - } catch (\Throwable $e) { - $io->error($e->getMessage()); - - return Command::FAILURE; - } - - return Command::SUCCESS; - } -} diff --git a/src/Command/SyncServiceAgreementsCommand.php b/src/Command/SyncServiceAgreementsCommand.php new file mode 100644 index 0000000..55fcb64 --- /dev/null +++ b/src/Command/SyncServiceAgreementsCommand.php @@ -0,0 +1,40 @@ +syncService->syncAll(); + + $io->success(sprintf('Synced %d service agreements successfully.', $count)); + } catch (\Throwable $e) { + $io->error($e->getMessage()); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 975630c..883b425 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -46,13 +46,13 @@ public function configureDashboard(): Dashboard public function configureMenuItems(): iterable { yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); - yield MenuItem::linkTo(SecurityContractCrudController::class, 'Hosting', 'fas fa-file-contract'); yield MenuItem::linkTo(ServerCrudController::class, 'Servers', 'fas fa-server'); yield MenuItem::linkTo(InstallationCrudController::class, 'Installations', 'fas fa-folder'); yield MenuItem::linkTo(SiteCrudController::class, 'Sites', 'fas fa-bookmark'); yield MenuItem::linkTo(DomainCrudController::class, 'Domains', 'fas fa-link'); yield MenuItem::linkTo(OIDCCrudController::class, 'OIDC', 'fas fa-key'); yield MenuItem::linkTo(ServiceCertificateCrudController::class, 'Service certificates', 'fas fa-lock'); + yield MenuItem::linkTo(SecurityContractCrudController::class, 'Service Agreements', 'fas fa-file-contract'); yield MenuItem::section('Dependencies'); yield MenuItem::linkTo(PackageCrudController::class, 'Packages', 'fas fa-cube'); yield MenuItem::linkTo(PackageVersionCrudController::class, 'Package Versions', 'fas fa-cubes'); diff --git a/src/Controller/Admin/ProjectCrudController.php b/src/Controller/Admin/ProjectCrudController.php deleted file mode 100644 index a435adf..0000000 --- a/src/Controller/Admin/ProjectCrudController.php +++ /dev/null @@ -1,93 +0,0 @@ -setDefaultSort(['name' => 'ASC']) - ->setSearchFields(['name', 'details']) - ->showEntityActionsInlined() - ->setPageTitle(Crud::PAGE_INDEX, 'Projects') - ->setHelp(Crud::PAGE_INDEX, 'Projects are synced from Leantime. Click on the "Sync all" button to update all projects.'); - } - - #[\Override] - public function configureActions(Actions $actions): Actions - { - return $actions - ->disable(Action::DELETE, Action::NEW, Action::EDIT) - ->add(Crud::PAGE_INDEX, $this->createLeantimeAction()) - ->add(Crud::PAGE_DETAIL, $this->createLeantimeAction()) - ->add(Crud::PAGE_INDEX, Action::DETAIL) - ->add(Crud::PAGE_INDEX, $this->createExportAction()) - ->add(Crud::PAGE_INDEX, $this->createUpdateAllProjectsAction()); - } - - #[\Override] - public function configureFields(string $pageName): iterable - { - yield VersionField::new('LeantimeId')->setLabel('id'); - yield TextField::new('name'); - yield TextEditorField::new('details')->formatValue(fn ($value) => strip_tags($value)); - yield DateTimeField::new('createdAt')->hideOnIndex(); - } - - public function createLeantimeAction(?string $label = null): Action - { - return Action::new('leantime', $label ?? new TranslatableMessage('Leantime'), 'fa fa-link') - ->linkToUrl(function (Project $project) { - return $project->getLeantimeUrl(); - }); - } - - public function createUpdateAllProjectsAction(?string $label = null): Action - { - return Action::new('update', $label ?? new TranslatableMessage('Sync all'), 'fa fa-rotate') - ->createAsGlobalAction() - ->linkToCrudAction('updateAllProjects'); - } - - public function updateAllProjects(SiteRepository $siteRepository): RedirectResponse - { - try { - $this->projectSyncService->syncAllProjects(); - - $this->addFlash('info', 'All projects have been synced.'); - } catch (\Throwable $e) { - $this->addFlash('error', 'An error occurred while syncing projects. Check the log for details.'); - } - - return $this->redirectToRoute('admin_project_index'); - } -} diff --git a/src/Controller/Admin/SecurityContractCrudController.php b/src/Controller/Admin/SecurityContractCrudController.php index c95ccad..758851d 100644 --- a/src/Controller/Admin/SecurityContractCrudController.php +++ b/src/Controller/Admin/SecurityContractCrudController.php @@ -3,17 +3,31 @@ namespace App\Controller\Admin; use App\Entity\SecurityContract; +use App\Service\ServiceAgreementSyncService; +use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute; +use EasyCorp\Bundle\EasyAdminBundle\Config\Action; +use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; +use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; +use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; +use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Translation\TranslatableMessage; -class SecurityContractCrudController extends AbstractFullCrudController +class SecurityContractCrudController extends AbstractCrudController { + public function __construct( + private readonly ServiceAgreementSyncService $syncService, + private readonly AdminUrlGenerator $adminUrlGenerator, + ) { + } + public static function getEntityFqcn(): string { return SecurityContract::class; @@ -23,10 +37,20 @@ public static function getEntityFqcn(): string public function configureCrud(Crud $crud): Crud { return $crud - ->setDefaultSort(['project.name' => 'ASC']) - // ->setSearchFields(['name', 'details']) + ->setDefaultSort(['projectName' => 'ASC']) + ->setSearchFields(['projectName', 'clientName', 'hostingProvider']) ->showEntityActionsInlined() - ->setPageTitle(Crud::PAGE_INDEX, 'Cyber Security Contracts'); + ->setPageTitle(Crud::PAGE_INDEX, 'Service Agreements') + ->setHelp(Crud::PAGE_INDEX, 'Service agreements are synced from Economics. Click "Sync all" to update.'); + } + + #[\Override] + public function configureActions(Actions $actions): Actions + { + return $actions + ->disable(Action::DELETE, Action::NEW, Action::EDIT) + ->add(Crud::PAGE_INDEX, Action::DETAIL) + ->add(Crud::PAGE_INDEX, $this->createSyncAllAction()); } #[\Override] @@ -34,22 +58,62 @@ public function configureFields(string $pageName): iterable { yield FormField::addFieldset('Project'); yield BooleanField::new('active')->renderAsSwitch(false)->setColumns(2); - yield AssociationField::new('project')->setColumns(10); + yield BooleanField::new('eol')->setLabel('EOL')->renderAsSwitch(false)->setColumns(2); + yield TextField::new('projectName')->setColumns(8); + yield TextField::new('clientName')->hideOnIndex(); + yield TextField::new('hostingProvider'); yield FormField::addFieldset('Links'); - yield UrlField::new('project.leantimeUrl')->setLabel('Leantime URL')->setDisabled()->hideOnIndex()->hideWhenCreating(); - yield UrlField::new('economicsReportUrl')->setLabel('Economics URL')->hideOnIndex()->setColumns(12); - yield UrlField::new('operationalContractUrl')->setLabel('Contract URL')->hideOnIndex()->setColumns(12); + yield UrlField::new('leantimeUrl')->setLabel('Leantime URL')->hideOnIndex(); + yield UrlField::new('documentUrl')->setLabel('Document URL')->hideOnIndex(); + + yield FormField::addFieldset('Contact'); + yield TextField::new('clientContactName')->hideOnIndex(); + yield TextField::new('clientContactEmail')->hideOnIndex(); yield FormField::addFieldset('Budget'); yield NumberField::new('monthlyPrice')->setTextAlign('right')->setColumns(6); yield NumberField::new('quarterlyHours')->setTextAlign('right')->setColumns(6); + yield NumberField::new('cybersecurityPrice')->setTextAlign('right')->hideOnIndex()->setColumns(6); + yield TextareaField::new('cybersecurityNote')->hideOnIndex()->setColumns(12); + + yield FormField::addFieldset('Infrastructure'); + yield BooleanField::new('dedicatedServer')->renderAsSwitch(false)->hideOnIndex(); + yield TextField::new('serverSize')->hideOnIndex(); + yield TextareaField::new('gitRepos')->hideOnIndex(); + yield TextField::new('projectTrackerKey')->hideOnIndex(); yield FormField::addFieldset('Validity'); yield DateField::new('validFrom')->setColumns(6); yield DateField::new('validTo')->setColumns(6); + } + + #[AdminRoute] + public function syncAll(): RedirectResponse + { + try { + $count = $this->syncService->syncAll(); + + $this->addFlash('info', sprintf('Synced %d service agreements.', $count)); + } catch (\Throwable $e) { + $this->addFlash('error', sprintf('An error occurred while syncing: %s', $e->getMessage())); + } - yield FormField::addFieldset('Notes'); - yield TextareaField::new('notes')->hideOnIndex()->setColumns(12); + return $this->redirect( + $this->adminUrlGenerator + ->setController(static::class) + ->setAction(Crud::PAGE_INDEX) + ->generateUrl() + ); + } + + private function createSyncAllAction(): Action + { + return Action::new('syncAll', new TranslatableMessage('Sync all'), 'fa fa-rotate') + ->createAsGlobalAction() + ->linkToCrudAction('syncAll') + ->setHtmlAttributes([ + 'onclick' => "const i=this.querySelector('i');if(i){i.classList.add('fa-spin')}this.style.pointerEvents='none';this.style.opacity='0.6'", + ]); } } diff --git a/src/Entity/Project.php b/src/Entity/Project.php deleted file mode 100644 index a1841ed..0000000 --- a/src/Entity/Project.php +++ /dev/null @@ -1,133 +0,0 @@ -leantimeId = $leantimeId; - $this->name = $name; - $this->details = $details; - } - - public function __toString(): string - { - return $this->name; - } - - public function getName(): string - { - return $this->name; - } - - public function setName(string $name): static - { - $this->name = $name; - - return $this; - } - - public function getLeantimeUrl(): ?string - { - return $this->LeantimeUrl; - } - - public function setLeantimeUrl(string $LeantimeUrl): static - { - $this->LeantimeUrl = $LeantimeUrl; - - return $this; - } - - public function getEconomicsUrl(): ?string - { - return $this->EconomicsUrl; - } - - public function setEconomicsUrl(string $EconomicsUrl): static - { - $this->EconomicsUrl = $EconomicsUrl; - - return $this; - } - - public function getDetails(): ?string - { - return $this->details; - } - - public function setDetails(?string $details): static - { - $this->details = $details; - - return $this; - } - - public function getLeantimeId(): ?int - { - return $this->leantimeId; - } - - public function setLeantimeId(int $leantimeId): static - { - $this->leantimeId = $leantimeId; - - return $this; - } - - public function getLeantimeModifiedAt(): ?\DateTimeImmutable - { - return $this->leantimeModifiedAt; - } - - public function setLeantimeModifiedAt(\DateTimeImmutable $leantimeModifiedAt): static - { - $this->leantimeModifiedAt = $leantimeModifiedAt; - - return $this; - } - - public function getSecurityContract(): ?SecurityContract - { - return $this->securityContract; - } - - public function setSecurityContract(SecurityContract $securityContract): static - { - // set the owning side of the relation if necessary - if ($securityContract->getProject() !== $this) { - $securityContract->setProject($this); - } - - $this->securityContract = $securityContract; - - return $this; - } -} diff --git a/src/Entity/SecurityContract.php b/src/Entity/SecurityContract.php index 97741e6..390cdd7 100644 --- a/src/Entity/SecurityContract.php +++ b/src/Entity/SecurityContract.php @@ -9,95 +9,142 @@ #[ORM\Entity(repositoryClass: SecurityContractRepository::class)] class SecurityContract extends AbstractBaseEntity implements \Stringable { - #[ORM\OneToOne(inversedBy: 'securityContract')] - #[ORM\JoinColumn(nullable: false)] - private ?Project $project = null; + #[ORM\Column(unique: true)] + private ?int $economicsId = null; + + #[ORM\Column(length: 255)] + private string $projectName = ''; #[ORM\Column(length: 255, nullable: true)] - private ?string $economicsReportUrl = null; + private ?string $clientName = null; - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $operationalContractUrl = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $hostingProvider = null; - #[ORM\Column(nullable: true)] - private ?float $monthlyPrice = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $documentUrl = null; #[ORM\Column(nullable: true)] - private ?float $quarterlyHours = null; + private ?float $monthlyPrice = null; - #[ORM\Column(type: Types::DATE_IMMUTABLE)] + #[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)] private ?\DateTimeImmutable $validFrom = null; - #[ORM\Column(type: Types::DATE_IMMUTABLE)] + #[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)] private ?\DateTimeImmutable $validTo = null; #[ORM\Column] - private ?bool $active = null; + private bool $active = false; + + #[ORM\Column] + private bool $eol = false; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $leantimeUrl = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $clientContactName = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $clientContactEmail = null; + + #[ORM\Column] + private bool $dedicatedServer = false; - #[ORM\Column(type: Types::TEXT)] - private string $notes = ''; + #[ORM\Column(length: 255, nullable: true)] + private ?string $serverSize = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $gitRepos = null; + + #[ORM\Column(type: Types::JSON, nullable: true)] + private ?array $systemOwnerNotices = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $projectTrackerKey = null; + + #[ORM\Column(nullable: true)] + private ?float $quarterlyHours = null; + + #[ORM\Column(nullable: true)] + private ?float $cybersecurityPrice = null; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $cybersecurityNote = null; public function __toString(): string { - return $this->project->getName(); + return $this->projectName; } - public function getProject(): ?Project + public function getEconomicsId(): ?int { - return $this->project; + return $this->economicsId; } - public function setProject(Project $project): static + public function setEconomicsId(int $economicsId): static { - $this->project = $project; + $this->economicsId = $economicsId; return $this; } - public function getEconomicsReportUrl(): ?string + public function getProjectName(): string { - return $this->economicsReportUrl; + return $this->projectName; } - public function setEconomicsReportUrl(?string $economicsReportUrl): static + public function setProjectName(string $projectName): static { - $this->economicsReportUrl = $economicsReportUrl; + $this->projectName = $projectName; return $this; } - public function getOperationalContractUrl(): ?string + public function getClientName(): ?string { - return $this->operationalContractUrl; + return $this->clientName; } - public function setOperationalContractUrl(?string $operationalContractUrl): static + public function setClientName(?string $clientName): static { - $this->operationalContractUrl = $operationalContractUrl; + $this->clientName = $clientName; return $this; } - public function getMonthlyPrice(): ?float + public function getHostingProvider(): ?string { - return $this->monthlyPrice; + return $this->hostingProvider; } - public function setMonthlyPrice(?float $monthlyPrice): static + public function setHostingProvider(?string $hostingProvider): static { - $this->monthlyPrice = $monthlyPrice; + $this->hostingProvider = $hostingProvider; return $this; } - public function getQuarterlyHours(): ?float + public function getDocumentUrl(): ?string { - return $this->quarterlyHours; + return $this->documentUrl; } - public function setQuarterlyHours(float $quarterlyHours): static + public function setDocumentUrl(?string $documentUrl): static { - $this->quarterlyHours = $quarterlyHours; + $this->documentUrl = $documentUrl; + + return $this; + } + + public function getMonthlyPrice(): ?float + { + return $this->monthlyPrice; + } + + public function setMonthlyPrice(?float $monthlyPrice): static + { + $this->monthlyPrice = $monthlyPrice; return $this; } @@ -107,7 +154,7 @@ public function getValidFrom(): ?\DateTimeImmutable return $this->validFrom; } - public function setValidFrom(\DateTimeImmutable $validFrom): static + public function setValidFrom(?\DateTimeImmutable $validFrom): static { $this->validFrom = $validFrom; @@ -119,14 +166,14 @@ public function getValidTo(): ?\DateTimeImmutable return $this->validTo; } - public function setValidTo(\DateTimeImmutable $validTo): static + public function setValidTo(?\DateTimeImmutable $validTo): static { $this->validTo = $validTo; return $this; } - public function isActive(): ?bool + public function isActive(): bool { return $this->active; } @@ -138,14 +185,146 @@ public function setActive(bool $active): static return $this; } - public function getNotes(): ?string + public function isEol(): bool + { + return $this->eol; + } + + public function setEol(bool $eol): static + { + $this->eol = $eol; + + return $this; + } + + public function getLeantimeUrl(): ?string + { + return $this->leantimeUrl; + } + + public function setLeantimeUrl(?string $leantimeUrl): static + { + $this->leantimeUrl = $leantimeUrl; + + return $this; + } + + public function getClientContactName(): ?string + { + return $this->clientContactName; + } + + public function setClientContactName(?string $clientContactName): static + { + $this->clientContactName = $clientContactName; + + return $this; + } + + public function getClientContactEmail(): ?string + { + return $this->clientContactEmail; + } + + public function setClientContactEmail(?string $clientContactEmail): static + { + $this->clientContactEmail = $clientContactEmail; + + return $this; + } + + public function isDedicatedServer(): bool + { + return $this->dedicatedServer; + } + + public function setDedicatedServer(bool $dedicatedServer): static + { + $this->dedicatedServer = $dedicatedServer; + + return $this; + } + + public function getServerSize(): ?string + { + return $this->serverSize; + } + + public function setServerSize(?string $serverSize): static + { + $this->serverSize = $serverSize; + + return $this; + } + + public function getGitRepos(): ?string + { + return $this->gitRepos; + } + + public function setGitRepos(?string $gitRepos): static + { + $this->gitRepos = $gitRepos; + + return $this; + } + + public function getSystemOwnerNotices(): ?array + { + return $this->systemOwnerNotices; + } + + public function setSystemOwnerNotices(?array $systemOwnerNotices): static + { + $this->systemOwnerNotices = $systemOwnerNotices; + + return $this; + } + + public function getProjectTrackerKey(): ?string + { + return $this->projectTrackerKey; + } + + public function setProjectTrackerKey(?string $projectTrackerKey): static + { + $this->projectTrackerKey = $projectTrackerKey; + + return $this; + } + + public function getQuarterlyHours(): ?float + { + return $this->quarterlyHours; + } + + public function setQuarterlyHours(?float $quarterlyHours): static + { + $this->quarterlyHours = $quarterlyHours; + + return $this; + } + + public function getCybersecurityPrice(): ?float + { + return $this->cybersecurityPrice; + } + + public function setCybersecurityPrice(?float $cybersecurityPrice): static + { + $this->cybersecurityPrice = $cybersecurityPrice; + + return $this; + } + + public function getCybersecurityNote(): ?string { - return $this->notes; + return $this->cybersecurityNote; } - public function setNotes(string $notes): static + public function setCybersecurityNote(?string $cybersecurityNote): static { - $this->notes = $notes; + $this->cybersecurityNote = $cybersecurityNote; return $this; } diff --git a/src/Exception/LeantimeApiException.php b/src/Exception/LeantimeApiException.php deleted file mode 100644 index ba79156..0000000 --- a/src/Exception/LeantimeApiException.php +++ /dev/null @@ -1,7 +0,0 @@ - - */ -class ProjectRepository extends ServiceEntityRepository -{ - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Project::class); - } - - // /** - // * @return Project[] Returns an array of Project objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('p') - // ->andWhere('p.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('p.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } - - // public function findOneBySomeField($value): ?Project - // { - // return $this->createQueryBuilder('p') - // ->andWhere('p.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } -} diff --git a/src/Service/Leantime/ApiService.php b/src/Service/Leantime/ApiService.php deleted file mode 100644 index 83a3ea6..0000000 --- a/src/Service/Leantime/ApiService.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * @throws LeantimeApiException - */ - public function getProjects(): array - { - $projects = []; - - $data = $this->request(self::API_PATH_JSONRPC, 'POST', 'leantime.rpc.projects.getAll', []); - - foreach ($data as $project) { - $projects[$project->id] = ProjectDto::fromStdClass($project); - } - - return $projects; - } - - /** - * Get project. - * - * @param $key - * A project key or id - * - * @throws LeantimeApiException - */ - public function getProject(int $key): ProjectDto - { - $data = $this->request(self::API_PATH_JSONRPC, 'POST', 'leantime.rpc.projects.getProject', ['id' => $key]); - - if (is_array($data) && false === $data[0]) { - throw new LeantimeApiException(sprintf('Project (id: %d) not found', $key), 404); - } - - return ProjectDto::fromStdClass($data); - } - - /** - * Get from Leantime. - * - * @throws LeantimeApiException - */ - private function request(string $path, string $type, string $method, array $params = []): mixed - { - try { - $response = $this->leantimeClient->request($type, $path, - ['json' => [ - 'jsonrpc' => '2.0', - 'method' => $method, - 'id' => new Ulid()->jsonSerialize(), - 'params' => $params, - ]] - ); - - $body = $response->getContent(); - - if ($body) { - $data = json_decode($body, null, 512, JSON_THROW_ON_ERROR); - - if (isset($data->error)) { - $message = $data->error->message; - if (isset($data->error->data)) { - $message .= ': '.(is_scalar($data->error->data) ? $data->error->data : json_encode($data->error->data)); - } - throw new LeantimeApiException($message, $data->error->code); - } - - return $data->result; - } - } catch (\Throwable $e) { - throw new LeantimeApiException('Error from Leantime API: '.$e->getMessage(), (int) $e->getCode(), $e); - } - - return null; - } -} diff --git a/src/Service/Leantime/LeantimeProjectUrlFactory.php b/src/Service/Leantime/LeantimeProjectUrlFactory.php deleted file mode 100644 index 5ed6f5d..0000000 --- a/src/Service/Leantime/LeantimeProjectUrlFactory.php +++ /dev/null @@ -1,17 +0,0 @@ -baseUrl, $projectId); - } -} diff --git a/src/Service/Leantime/ProjectDto.php b/src/Service/Leantime/ProjectDto.php deleted file mode 100644 index 986f051..0000000 --- a/src/Service/Leantime/ProjectDto.php +++ /dev/null @@ -1,75 +0,0 @@ -Udfyld beskrivelse

'; - - public function __construct( - #[Map(target: 'leantimeId')] - #[Map(target: 'leantimeUrl', transform: LeantimeProjectUrlFactory::class)] - public int $id, - #[Map(target: 'name')] - public string $name, - #[Map(target: 'details')] - public ?string $details, - public int $clientId, - public ?int $state, - public float $hourBudget, - public float $dollarBudget, - public string $menuType, - public string $type, - public ?int $parent, - #[Map(target: 'leantimeModifiedAt')] - public \DateTimeImmutable $modified, - public ?string $start, - public ?string $end, - public string $clientName, - public ?int $parentId, - public ?string $parentName, - public int $isFavorite, - ) { - } - - /** - * Create a LeantimeProjectDto from JSON data. - */ - public static function fromStdClass(\stdClass $data): self - { - $data->details = self::DETAILS_DEFAULT === $data->details ? null : $data->details; - $data->hourBudget = floatval($data->hourBudget); - $data->dollarBudget = floatval($data->dollarBudget); - $data->modified = \DateTimeImmutable::createFromFormat( - self::DATE_FORMAT, - $data->modified, - new \DateTimeZone(self::TIMEZONE) - ); - - return new self( - id: $data->id, - name: $data->name, - details: $data->details ?? null, - clientId: $data->clientId, - state: $data->state, - hourBudget: $data->hourBudget, - dollarBudget: $data->dollarBudget, - menuType: $data->menuType, - type: $data->type, - parent: $data->parent, - modified: $data->modified, - start: $data->start ?? null, - end: $data->end ?? null, - clientName: $data->clientName, - parentId: $data->parentId ?? null, - parentName: $data->parentName ?? null, - isFavorite: $data->isFavorite, - ); - } -} diff --git a/src/Service/Leantime/ProjectSyncService.php b/src/Service/Leantime/ProjectSyncService.php deleted file mode 100644 index 60f3ae8..0000000 --- a/src/Service/Leantime/ProjectSyncService.php +++ /dev/null @@ -1,76 +0,0 @@ -leantimeApiService->getProjects(); - $count = count($projectDtoArray); - - $projects = $this->entityManager->getRepository(Project::class)->findBy([], ['id' => 'ASC']); - - foreach ($projects as $project) { - if (isset($projectDtoArray[$project->getLeantimeId()])) { - $dto = $projectDtoArray[$project->getLeantimeId()]; - $this->setProperties($project, $dto); - - unset($projectDtoArray[$project->getLeantimeId()]); - } else { - // Delete projects that are not in Leantime - $this->entityManager->remove($project); - } - } - - foreach ($projectDtoArray as $id => $projectDto) { - $project = $this->objectMapper->map($projectDto, Project::class); - $this->setProperties($project, $projectDto); - $this->entityManager->persist($project); - } - - $this->entityManager->flush(); - - return $count; - } - - public function syncProject(int $leantimeId): Project - { - $projectDto = $this->leantimeApiService->getProject($leantimeId); - $project = $this->entityManager->getRepository(Project::class)->findOneBy(['leantimeId' => $leantimeId]); - - if (null === $project) { - $project = $this->objectMapper->map($projectDto, Project::class); - $this->entityManager->persist($project); - } - - $this->setProperties($project, $projectDto); - - $this->entityManager->flush(); - - return $project; - } - - private function setProperties(Project $project, ProjectDto $projectDto): void - { - $project->setLeantimeId($projectDto->id); - $project->setName($projectDto->name); - $project->setDetails($projectDto->details); - $project->setLeantimeModifiedAt($projectDto->modified); - - $url = $this->leantimeProjectUrlFactory->getProjectUrl($project->getLeantimeId()); - $project->setLeantimeUrl($url); - } -} diff --git a/src/Service/ServiceAgreementSyncService.php b/src/Service/ServiceAgreementSyncService.php new file mode 100644 index 0000000..6b19dc2 --- /dev/null +++ b/src/Service/ServiceAgreementSyncService.php @@ -0,0 +1,133 @@ +economicsClient->request('GET', '/api/serviceagreements'); + $agreements = $response->toArray(); + } catch (ExceptionInterface $e) { + throw new \RuntimeException('Failed to fetch service agreements from Economics API: '.$e->getMessage(), 0, $e); + } + + $existingContracts = $this->entityManager->getRepository(SecurityContract::class)->findAll(); + $existingByEconomicsId = []; + foreach ($existingContracts as $contract) { + $existingByEconomicsId[$contract->getEconomicsId()] = $contract; + } + + $seenIds = []; + + foreach ($agreements as $data) { + $economicsId = $data['id']; + $seenIds[] = $economicsId; + + $contract = $existingByEconomicsId[$economicsId] ?? new SecurityContract(); + + $this->mapDataToContract($contract, $data); + + if (null === $contract->getEconomicsId()) { + $contract->setEconomicsId($economicsId); + } + + $this->entityManager->persist($contract); + } + + foreach ($existingContracts as $contract) { + if (!in_array($contract->getEconomicsId(), $seenIds, true)) { + $this->entityManager->remove($contract); + } + } + + $this->entityManager->flush(); + + return count($agreements); + } + + /** + * Map API response data onto a SecurityContract entity. + * + * @param array $data a single service agreement from the API + * @throws \Exception + */ + private function mapDataToContract(SecurityContract $contract, array $data): void + { + $contract->setEconomicsId($data['id']); + $contract->setProjectName($data['projectName'] ?? ''); + $contract->setClientName($data['clientName'] ?? null); + $contract->setHostingProvider($data['hostingProvider'] ?? null); + $contract->setDocumentUrl($data['documentUrl'] ?? null); + $contract->setMonthlyPrice(isset($data['price']) ? (float) $data['price'] : null); + $contract->setValidFrom($this->parseDate($data['validFrom'] ?? null)); + $contract->setValidTo($this->parseDate($data['validTo'] ?? null)); + $contract->setActive($data['isActive'] ?? false); + $contract->setEol($data['isEol'] ?? false); + $contract->setLeantimeUrl($data['leantimeUrl'] ?? null); + $contract->setClientContactName($data['clientContactName'] ?? null); + $contract->setClientContactEmail($data['clientContactEmail'] ?? null); + $contract->setDedicatedServer($data['dedicatedServer'] ?? false); + $contract->setServerSize($data['serverSize'] ?? null); + $contract->setGitRepos($data['gitRepos'] ?? null); + $contract->setSystemOwnerNotices($data['systemOwnerNotices'] ?? null); + $contract->setProjectTrackerKey($data['projectTrackerKey'] ?? null); + + $cyber = $data['cybersecurityAgreement'] ?? null; + if (is_array($cyber)) { + $contract->setQuarterlyHours(isset($cyber['quarterlyHours']) ? (float) $cyber['quarterlyHours'] : null); + $contract->setCybersecurityPrice(isset($cyber['price']) ? (float) $cyber['price'] : null); + $contract->setCybersecurityNote($cyber['note'] ?? null); + } + } + + /** + * Parse the Economics API date format ({date, timezone_type, timezone}) into a DateTimeImmutable. + * + * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData raw date object from the API + * + * @return \DateTimeImmutable|null the parsed date, or null if no valid date data was provided + * + * @throws \Exception if the date string cannot be parsed + */ + private function parseDate(?array $dateData): ?\DateTimeImmutable + { + if (null === $dateData || !isset($dateData['date'])) { + return null; + } + + $timezone = new \DateTimeZone($dateData['timezone'] ?? 'UTC'); + + return new \DateTimeImmutable($dateData['date'], $timezone); + } +} From 4368891b308de96dfae619966ef4ecc75e02837f Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 10:53:39 +0200 Subject: [PATCH 35/94] Updated changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c0ecd..aa5131b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#61](https://github.com/itk-dev/devops_itksites/pull/61) 5566: Service agreements + - Remove project entity + - Remove leantime service + - Add security contract entity with crud controller + - Add Abstract full crud controller and extend on it in some cases + - Add economics service and sync action/command for service agreement synchronization + - [#60](https://github.com/itk-dev/devops_itksites/pull/60) 5564: Dependency updates - Update dependencies - Update phpunit from 11 to 12 From 22a53c84cc95ad43b27257db297234918f717958 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:02:20 +0200 Subject: [PATCH 36/94] Applied coding standards --- .github/workflows/pr.yaml | 50 ++++++++++----------- composer.json | 2 +- src/Service/ServiceAgreementSyncService.php | 5 ++- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 07a7f6b..8fd2df4 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -8,31 +8,31 @@ jobs: name: Test suite runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - - name: Install Task - uses: go-task/setup-task@v1 - - - name: Setup docker network - run: | - docker network create frontend - - - name: Install Site - run: | - task site:install - - - name: Test suite - run: | - task test:setup - task test - - - name: Upload coverage to Codecov test - uses: codecov/codecov-action@v5 - with: - files: ./coverage/unit.xml - flags: unittests - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - uses: actions/checkout@v5 + + - name: Install Task + uses: go-task/setup-task@v1 + + - name: Setup docker network + run: | + docker network create frontend + + - name: Install Site + run: | + task site:install + + - name: Test suite + run: | + task test:setup + task test + + - name: Upload coverage to Codecov test + uses: codecov/codecov-action@v5 + with: + files: ./coverage/unit.xml + flags: unittests + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} validate-doctrine-schema: runs-on: ubuntu-latest diff --git a/composer.json b/composer.json index c55667f..f997dad 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", - "symfony/asset-mapper": "8.0.*", + "symfony/asset-mapper": "~8.0.0", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", diff --git a/src/Service/ServiceAgreementSyncService.php b/src/Service/ServiceAgreementSyncService.php index 6b19dc2..4c7be95 100644 --- a/src/Service/ServiceAgreementSyncService.php +++ b/src/Service/ServiceAgreementSyncService.php @@ -14,12 +14,12 @@ readonly class ServiceAgreementSyncService { /** - * @param EntityManagerInterface $entityManager Doctrine entity manager for persisting contracts + * @param EntityManagerInterface $entityManager Doctrine entity manager for persisting contracts * @param HttpClientInterface $economicsClient scoped HTTP client for the Economics API */ public function __construct( private EntityManagerInterface $entityManager, - private HttpClientInterface $economicsClient, + private HttpClientInterface $economicsClient, ) { } @@ -80,6 +80,7 @@ public function syncAll(): int * Map API response data onto a SecurityContract entity. * * @param array $data a single service agreement from the API + * * @throws \Exception */ private function mapDataToContract(SecurityContract $contract, array $data): void From 81923afc310116d34d8972556547adba8c811c01 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:16:12 +0200 Subject: [PATCH 37/94] Updated test suite github action --- .github/workflows/pr.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 8fd2df4..462ff7b 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -19,7 +19,9 @@ jobs: - name: Install Site run: | - task site:install + task site:update + task site:migrate + task fixtures:load - name: Test suite run: | From 1e3d94fbca4d5e12a06e1f358df8eb19b807a3fc Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:22:30 +0200 Subject: [PATCH 38/94] Updated PR.yml --- .github/workflows/pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 462ff7b..ebfb966 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -21,7 +21,7 @@ jobs: run: | task site:update task site:migrate - task fixtures:load + task fixtures:load -y - name: Test suite run: | From 7293af444f4097a4283077ff0af03de5b353f511 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:29:08 +0200 Subject: [PATCH 39/94] Updated PR --- .github/workflows/pr.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index ebfb966..7bb6354 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -25,8 +25,7 @@ jobs: - name: Test suite run: | - task test:setup - task test + task test:matrix - name: Upload coverage to Codecov test uses: codecov/codecov-action@v5 From 369f78d4817a7b919179469dbe1a58483783e91b Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:31:01 +0200 Subject: [PATCH 40/94] Updated composer.lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index fe179fa..277f9e3 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": "b8846cfbafdf8854299f8b6dc8761bc9", + "content-hash": "2a1b30ad222f086b612ec724b183c9eb", "packages": [ { "name": "api-platform/core", From aa32aaa0f2a3bd8465ddeff386703ea07c6b1220 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:43:07 +0200 Subject: [PATCH 41/94] Normalized composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f997dad..efe1ca0 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", - "symfony/asset-mapper": "~8.0.0", + "symfony/asset-mapper": "~8.0.0", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", From 904b7599c8a858d941d7adc8ba924c301781d8e7 Mon Sep 17 00:00:00 2001 From: martinyde Date: Tue, 19 May 2026 12:56:02 +0200 Subject: [PATCH 42/94] Modified changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa5131b..096dad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- [#61](https://github.com/itk-dev/devops_itksites/pull/61) 5566: Service agreements +- [#90](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - Remove project entity - Remove leantime service - Add security contract entity with crud controller From 27837bc97970f32faeea06e4fbeb96dc5c44f22f Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:13:49 +0200 Subject: [PATCH 43/94] feat: migrate to Symfony Asset Mapper, drop psalm.xml and admin.css - Add symfony/asset-mapper bundle with importmap and recipe files - Remove unused psalm.xml (project relies on PHPStan) - Remove obsolete public/css/admin.css Carved out of the closed PR #60 dependency-update scope as a focused PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 6 +++ CHANGELOG.md | 3 ++ assets/app.js | 9 ++++ assets/styles/app.css | 28 +++++++++++ composer.json | 4 +- composer.lock | 83 ++++++++++++++++++++++++++++++- config/packages/asset_mapper.yaml | 11 ++++ config/reference.php | 2 +- importmap.php | 19 +++++++ psalm.xml | 27 ---------- public/css/admin.css | 15 ------ symfony.lock | 15 ++++++ 12 files changed, 177 insertions(+), 45 deletions(-) create mode 100644 assets/app.js create mode 100644 assets/styles/app.css create mode 100644 config/packages/asset_mapper.yaml create mode 100644 importmap.php delete mode 100644 psalm.xml delete mode 100644 public/css/admin.css diff --git a/.gitignore b/.gitignore index 64c6627..5fb8656 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,11 @@ yarn-error.log phpstan.neon ###< phpstan/phpstan ### .phpunit.cache + +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### + .twig-cs-fixer.cache .playwright-mcp diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee3096..1d3c760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#81](https://github.com/itk-dev/devops_itksites/pull/81) 5564: Asset Mapper migration + - Add Symfony Asset Mapper bundle and importmap + ## [1.11.0] - 2026-05-19 - [#78](https://github.com/itk-dev/devops_itksites/pull/78) diff --git a/assets/app.js b/assets/app.js new file mode 100644 index 0000000..e2824a5 --- /dev/null +++ b/assets/app.js @@ -0,0 +1,9 @@ +/* + * Welcome to your app's main JavaScript file! + * + * This file will be included onto the page via the importmap() Twig function, + * which should already be in your base.html.twig. + */ +import "./styles/app.css"; + +console.log("This log comes from assets/app.js - welcome to AssetMapper! 🎉"); diff --git a/assets/styles/app.css b/assets/styles/app.css new file mode 100644 index 0000000..8218dc7 --- /dev/null +++ b/assets/styles/app.css @@ -0,0 +1,28 @@ +:root { + --body-max-width: 100%; + --sidebar-bg: #fff; + /* make the base font size smaller */ + --button-primary-bg: rgb(0, 123, 166); + --pagination-active-bg: rgb(0, 123, 166); + --link-color: rgb(0, 123, 166); + --sidebar-menu-active-item-color: rgb(0, 123, 166); + --badge-boolean-true-bg: rgb(0, 123, 166); + --badge-boolean-false-bg: rgb(228, 73, 48); + --badge-boolean-false-color: var(--white); + --sidebar-menu-color: rgb(66, 66, 66); + --text-color-dark: rgb(66, 66, 66); + --bs-danger-rgb: 228, 73, 48; +} + +/* Grouped dropdown group styling for index pages */ +.dropdown-menu { + .btn-danger i, + .text-danger i { + color: var(--button-invisible-danger-color); + } + + a.btn-danger:hover, + a.text-danger:hover { + background: var(--button-invisible-danger-hover-hover-bg); + } +} diff --git a/composer.json b/composer.json index 1737658..efe1ca0 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", + "symfony/asset-mapper": "~8.0.0", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", @@ -111,7 +112,8 @@ ], "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" }, "coding-standards-apply": [ "vendor/bin/php-cs-fixer fix" diff --git a/composer.lock b/composer.lock index 9392a9d..277f9e3 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": "d3d28912efc62de1dcb6b25446712071", + "content-hash": "2a1b30ad222f086b612ec724b183c9eb", "packages": [ { "name": "api-platform/core", @@ -3317,6 +3317,87 @@ ], "time": "2026-03-30T15:14:47+00:00" }, + { + "name": "symfony/asset-mapper", + "version": "v8.0.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", + "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.4", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0" + }, + "require-dev": { + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "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 directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v8.0.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-13T12:07:53+00:00" + }, { "name": "symfony/browser-kit", "version": "v8.0.8", diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/reference.php b/config/reference.php index a632abf..563c307 100644 --- a/config/reference.php +++ b/config/reference.php @@ -280,7 +280,7 @@ * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * paths?: string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true diff --git a/importmap.php b/importmap.php new file mode 100644 index 0000000..70ebf14 --- /dev/null +++ b/importmap.php @@ -0,0 +1,19 @@ + [ + 'path' => './assets/app.js', + 'entrypoint' => true, + ], +]; diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index bedf5ac..0000000 --- a/psalm.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/public/css/admin.css b/public/css/admin.css deleted file mode 100644 index 853c0f8..0000000 --- a/public/css/admin.css +++ /dev/null @@ -1,15 +0,0 @@ -:root { - --body-max-width: 100%; - --sidebar-bg: #fff; - /* make the base font size smaller */ - --button-primary-bg: rgb(0,123,166); - --pagination-active-bg: rgb(0,123,166); - --link-color: rgb(0,123,166); - --sidebar-menu-active-item-color: rgb(0,123,166); - --badge-boolean-true-bg: rgb(0,123,166); - --badge-boolean-false-bg: rgb(228, 73, 48); - --badge-boolean-false-color: var(--white); - --sidebar-menu-color: rgb(66,66,66); - --text-color-dark: rgb(66,66,66); - --bs-danger-rgb: 228, 73, 48; -} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 1033474..4e95b38 100644 --- a/symfony.lock +++ b/symfony.lock @@ -307,6 +307,21 @@ "symfony/asset": { "version": "v6.0.3" }, + "symfony/asset-mapper": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/browser-kit": { "version": "v6.0.3" }, From f6db7534799cc993f3b755ddcbab2b4f5ddb53bd Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:26:58 +0200 Subject: [PATCH 44/94] chore: scope PR down to maintenance contract feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop asset-mapper migration files (now in PR #81) - Drop duplicate workflows/apispec.yaml (develop has api-spec.yaml) - Consolidate doctrine validation + fixtures into doctrine.yaml, remove duplicates from pr.yaml - Update doctrine.yaml to actions/checkout@v6 and rename job to validate-doctrine-schema - Revert cosmetic changes to .gitignore, .prettierignore, .php-cs-fixer.dist.php, webpack_encore.yaml, .env - Restore psalm.xml and public/css/admin.css (their removal moved to PR #81) - Trim CHANGELOG: drop PR #60 row, fix #90 → #80 link, keep only feature bullets Co-Authored-By: Claude Opus 4.7 (1M context) --- .env | 1 - .github/workflows/apispec.yaml | 115 ---------------------------- .github/workflows/doctrine.yaml | 29 ++++++- .github/workflows/pr.yaml | 69 +---------------- .gitignore | 12 --- .php-cs-fixer.dist.php | 1 - .prettierignore | 3 - CHANGELOG.md | 10 +-- assets/app.js | 9 --- assets/styles/app.css | 28 ------- composer.json | 4 +- composer.lock | 83 +------------------- config/packages/asset_mapper.yaml | 11 --- config/packages/webpack_encore.yaml | 1 + config/reference.php | 2 +- importmap.php | 19 ----- psalm.xml | 27 +++++++ public/css/admin.css | 15 ++++ symfony.lock | 15 ---- 19 files changed, 74 insertions(+), 380 deletions(-) delete mode 100644 .github/workflows/apispec.yaml delete mode 100644 assets/app.js delete mode 100644 assets/styles/app.css delete mode 100644 config/packages/asset_mapper.yaml delete mode 100644 importmap.php create mode 100644 psalm.xml create mode 100644 public/css/admin.css diff --git a/.env b/.env index 0fa1f32..0b6b01c 100644 --- a/.env +++ b/.env @@ -2,7 +2,6 @@ COMPOSE_PROJECT_NAME=itksites COMPOSE_DOMAIN=itksites.local.itkdev.dk ITKDEV_TEMPLATE=symfony-8 - # In all environments, the following files are loaded if they exist, # the latter taking precedence over the former: # diff --git a/.github/workflows/apispec.yaml b/.github/workflows/apispec.yaml deleted file mode 100644 index 7d11a53..0000000 --- a/.github/workflows/apispec.yaml +++ /dev/null @@ -1,115 +0,0 @@ -name: API Spec review - -env: - COMPOSE_USER: root - -on: - pull_request: - -jobs: - api-spec-updated: - name: Ensure committed API specification is up to date - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 2 - - - name: Create docker network - run: | - docker network create frontend - - - name: Composer install - run: | - docker compose run --rm phpfpm composer install - - - name: Export API specification - run: | - docker compose run --rm phpfpm bin/console api:openapi:export --yaml --output=public/api-spec-v1.yaml --no-interaction - - - name: Check for changes in specification - id: git-diff-spec - continue-on-error: true - run: git diff --diff-filter=ACMRT --exit-code public/api-spec-v1.yaml - - - name: Comment PR - if: steps.git-diff-spec.outcome == 'failure' - env: - GH_TOKEN: ${{ github.token }} - run: | - echo '## 🛑 Exported API specification file not up to date' > var/comment.md - echo '' >> var/comment.md - echo 'Please run `composer update-api-spec` to export the API specification. Then commit and push the changes.' >> var/comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file var/comment.md --create-if-none --edit-last - - - name: Fail job, api spec is not up to date - if: steps.git-diff-spec.outcome == 'failure' - run: | - exit 1 - - detect-breaking-changes: - name: Detect breaking changes in API specification - runs-on: ubuntu-latest - needs: [api-spec-updated] - steps: - - name: Check out BASE rev - uses: actions/checkout@v5 - with: - ref: ${{ github.base_ref }} - path: base - - - name: Check out HEAD rev - uses: actions/checkout@v5 - with: - ref: ${{ github.head_ref }} - path: head - - - name: Run OpenAPI Changed (from HEAD rev) - id: api-changed - continue-on-error: true - uses: docker://openapitools/openapi-diff:latest - with: - args: --fail-on-changed base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-changed.md - - - name: Run OpenAPI Incompatible (from HEAD rev) - id: api-incompatible - continue-on-error: true - uses: docker://openapitools/openapi-diff:latest - with: - args: --fail-on-incompatible base/public/api-spec-v1.yaml head/public/api-spec-v1.yaml --markdown api-spec-incompatible.md - - - name: Comment PR with no changes - if: steps.api-changed.outcome == 'success' && steps.api-incompatible.outcome == 'success' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr comment ${{ github.event.pull_request.number }} --body "✅ **No changes detected in API specification**" --create-if-none --edit-last - - - name: Comment PR with non-breaking changes - if: steps.api-changed.outcome == 'failure' && steps.api-incompatible.outcome == 'success' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "## ⚠️ Non-Breaking changes detected in API specification" > ../comment.md - echo "" >> ../comment.md - cat ../api-spec-changed.md >> ../comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last - - - name: Comment PR with breaking changes - if: steps.api-incompatible.outcome == 'failure' - working-directory: head - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "## 🛑 Breaking changes detected in API specification" > ../comment.md - echo "" >> ../comment.md - cat ../api-spec-incompatible.md >> ../comment.md - gh pr comment ${{ github.event.pull_request.number }} --body-file ../comment.md --create-if-none --edit-last - - - name: Fail if breaking changes detected - if: steps.api-incompatible.outcome == 'failure' - run: | - exit 1 diff --git a/.github/workflows/doctrine.yaml b/.github/workflows/doctrine.yaml index 696886d..38276a6 100644 --- a/.github/workflows/doctrine.yaml +++ b/.github/workflows/doctrine.yaml @@ -11,14 +11,14 @@ on: - develop jobs: - coding-standards: - name: Validate Schema + validate-doctrine-schema: + name: Validate Doctrine Schema runs-on: ubuntu-latest env: APP_ENV: prod steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | @@ -39,3 +39,26 @@ jobs: - name: Validate Doctrine schema run: | docker compose run --rm phpfpm bin/console doctrine:schema:validate + + load-fixtures: + name: Load Doctrine fixtures + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Create docker network + run: | + docker network create frontend + + - name: Run Composer Install + run: | + docker compose run --rm phpfpm composer install --no-interaction + + - name: Run Doctrine Migrations + run: | + docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Load fixtures + run: | + docker compose run --rm phpfpm composer fixtures diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 7bb6354..5b24bf2 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,58 +1,8 @@ +on: pull_request name: Review env: COMPOSE_USER: runner -on: pull_request - jobs: - test-suite: - name: Test suite - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Install Task - uses: go-task/setup-task@v1 - - - name: Setup docker network - run: | - docker network create frontend - - - name: Install Site - run: | - task site:update - task site:migrate - task fixtures:load -y - - - name: Test suite - run: | - task test:matrix - - - name: Upload coverage to Codecov test - uses: codecov/codecov-action@v5 - with: - files: ./coverage/unit.xml - flags: unittests - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - validate-doctrine-schema: - runs-on: ubuntu-latest - name: Validate Doctrine Schema - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Create docker network - run: docker network create frontend - - - name: Install and validate - run: | - docker compose up --detach - docker compose exec phpfpm composer install --no-interaction - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - docker compose exec phpfpm bin/console messenger:setup-transports failed - docker compose exec phpfpm bin/console doctrine:schema:validate - phpstan: runs-on: ubuntu-latest name: PHPStan @@ -95,23 +45,6 @@ jobs: fail_ci_if_error: true flags: unittests - fixtures: - runs-on: ubuntu-latest - name: Load fixtures - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Create docker network - run: docker network create frontend - - - name: Load fixtures - run: | - docker compose up --detach - docker compose exec phpfpm composer install --no-interaction - docker compose exec phpfpm bin/console doctrine:migrations:migrate --no-interaction - docker compose exec phpfpm composer fixtures - build-assets: runs-on: ubuntu-latest name: Build assets diff --git a/.gitignore b/.gitignore index 3638608..64c6627 100644 --- a/.gitignore +++ b/.gitignore @@ -30,17 +30,5 @@ yarn-error.log phpstan.neon ###< phpstan/phpstan ### .phpunit.cache - -###> vincentlanglet/twig-cs-fixer ### -/.twig-cs-fixer.cache -###< vincentlanglet/twig-cs-fixer ### - -###> symfony/asset-mapper ### -/public/assets/ -/assets/vendor/ -###< symfony/asset-mapper ### -coverage - .twig-cs-fixer.cache .playwright-mcp - diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 84ac3b4..9acdc60 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -9,7 +9,6 @@ $finder->in(__DIR__); // … that are not ignored by VCS $finder->ignoreVCSIgnored(true); - // Exclude generated files $finder->notPath('config/reference.php'); diff --git a/.prettierignore b/.prettierignore index 4ec364e..41e20a0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,3 @@ -# API spec - # Generated API specification files public/api-spec-v1.yaml public/api-spec-v1.json - diff --git a/CHANGELOG.md b/CHANGELOG.md index 096dad2..18f3b2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,19 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- [#90](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - - Remove project entity - - Remove leantime service +- [#80](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases - Add economics service and sync action/command for service agreement synchronization -- [#60](https://github.com/itk-dev/devops_itksites/pull/60) 5564: Dependency updates - - Update dependencies - - Update phpunit from 11 to 12 - - Update ITK docker template - - Update github actions workflows - ## [1.11.0] - 2026-05-19 - [#78](https://github.com/itk-dev/devops_itksites/pull/78) diff --git a/assets/app.js b/assets/app.js deleted file mode 100644 index e2824a5..0000000 --- a/assets/app.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Welcome to your app's main JavaScript file! - * - * This file will be included onto the page via the importmap() Twig function, - * which should already be in your base.html.twig. - */ -import "./styles/app.css"; - -console.log("This log comes from assets/app.js - welcome to AssetMapper! 🎉"); diff --git a/assets/styles/app.css b/assets/styles/app.css deleted file mode 100644 index 8218dc7..0000000 --- a/assets/styles/app.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - --body-max-width: 100%; - --sidebar-bg: #fff; - /* make the base font size smaller */ - --button-primary-bg: rgb(0, 123, 166); - --pagination-active-bg: rgb(0, 123, 166); - --link-color: rgb(0, 123, 166); - --sidebar-menu-active-item-color: rgb(0, 123, 166); - --badge-boolean-true-bg: rgb(0, 123, 166); - --badge-boolean-false-bg: rgb(228, 73, 48); - --badge-boolean-false-color: var(--white); - --sidebar-menu-color: rgb(66, 66, 66); - --text-color-dark: rgb(66, 66, 66); - --bs-danger-rgb: 228, 73, 48; -} - -/* Grouped dropdown group styling for index pages */ -.dropdown-menu { - .btn-danger i, - .text-danger i { - color: var(--button-invisible-danger-color); - } - - a.btn-danger:hover, - a.text-danger:hover { - background: var(--button-invisible-danger-hover-hover-bg); - } -} diff --git a/composer.json b/composer.json index efe1ca0..1737658 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", - "symfony/asset-mapper": "~8.0.0", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", @@ -112,8 +111,7 @@ ], "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd", - "importmap:install": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd" }, "coding-standards-apply": [ "vendor/bin/php-cs-fixer fix" diff --git a/composer.lock b/composer.lock index 277f9e3..9392a9d 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": "2a1b30ad222f086b612ec724b183c9eb", + "content-hash": "d3d28912efc62de1dcb6b25446712071", "packages": [ { "name": "api-platform/core", @@ -3317,87 +3317,6 @@ ], "time": "2026-03-30T15:14:47+00:00" }, - { - "name": "symfony/asset-mapper", - "version": "v8.0.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/asset-mapper.git", - "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", - "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", - "shasum": "" - }, - "require": { - "composer/semver": "^3.0", - "php": ">=8.4", - "symfony/filesystem": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0" - }, - "require-dev": { - "symfony/asset": "^7.4|^8.0", - "symfony/browser-kit": "^7.4|^8.0", - "symfony/console": "^7.4|^8.0", - "symfony/event-dispatcher-contracts": "^3.0", - "symfony/finder": "^7.4|^8.0", - "symfony/framework-bundle": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/runtime": "^7.4|^8.0", - "symfony/web-link": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\AssetMapper\\": "" - }, - "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 directories of assets & makes them available in a public directory with versioned filenames.", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/asset-mapper/tree/v8.0.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-13T12:07:53+00:00" - }, { "name": "symfony/browser-kit", "version": "v8.0.8", diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml deleted file mode 100644 index f7653e9..0000000 --- a/config/packages/asset_mapper.yaml +++ /dev/null @@ -1,11 +0,0 @@ -framework: - asset_mapper: - # The paths to make available to the asset mapper. - paths: - - assets/ - missing_import_mode: strict - -when@prod: - framework: - asset_mapper: - missing_import_mode: warn diff --git a/config/packages/webpack_encore.yaml b/config/packages/webpack_encore.yaml index 0627dee..6583378 100644 --- a/config/packages/webpack_encore.yaml +++ b/config/packages/webpack_encore.yaml @@ -33,6 +33,7 @@ webpack_encore: framework: assets: json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' + #when@prod: # webpack_encore: # # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes) diff --git a/config/reference.php b/config/reference.php index 563c307..a632abf 100644 --- a/config/reference.php +++ b/config/reference.php @@ -280,7 +280,7 @@ * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: true + * enabled?: bool|Param, // Default: false * paths?: string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true diff --git a/importmap.php b/importmap.php deleted file mode 100644 index 70ebf14..0000000 --- a/importmap.php +++ /dev/null @@ -1,19 +0,0 @@ - [ - 'path' => './assets/app.js', - 'entrypoint' => true, - ], -]; diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..bedf5ac --- /dev/null +++ b/psalm.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/css/admin.css b/public/css/admin.css new file mode 100644 index 0000000..853c0f8 --- /dev/null +++ b/public/css/admin.css @@ -0,0 +1,15 @@ +:root { + --body-max-width: 100%; + --sidebar-bg: #fff; + /* make the base font size smaller */ + --button-primary-bg: rgb(0,123,166); + --pagination-active-bg: rgb(0,123,166); + --link-color: rgb(0,123,166); + --sidebar-menu-active-item-color: rgb(0,123,166); + --badge-boolean-true-bg: rgb(0,123,166); + --badge-boolean-false-bg: rgb(228, 73, 48); + --badge-boolean-false-color: var(--white); + --sidebar-menu-color: rgb(66,66,66); + --text-color-dark: rgb(66,66,66); + --bs-danger-rgb: 228, 73, 48; +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 4e95b38..1033474 100644 --- a/symfony.lock +++ b/symfony.lock @@ -307,21 +307,6 @@ "symfony/asset": { "version": "v6.0.3" }, - "symfony/asset-mapper": { - "version": "7.3", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "6.4", - "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" - }, - "files": [ - "assets/app.js", - "assets/styles/app.css", - "config/packages/asset_mapper.yaml", - "importmap.php" - ] - }, "symfony/browser-kit": { "version": "v6.0.3" }, From 08c24437a146977c03c933bf5b7e2c9b03952801 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:31:59 +0200 Subject: [PATCH 45/94] chore: drop phpstan-ignore on AutoBadge files, require ignore comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AutoBadgeMenuItem::__call/__callStatic always throw, so return type is `never` — type the magic-method parameters too, ignore unnecessary - AutoBadgeCrudMenuItem::__call delegates to the wrapped CrudMenuItem with an arbitrary method name, so return type is `mixed` - AutoBadgeCrudMenuItem::__callStatic always throws, so return type is `never` - AutoBadgeCrudMenuItem::setBadge uses the wrapped CrudMenuItem's documented union (\Stringable|string|int|float|bool|null); the runtime is_int check is unchanged - Enable reportIgnoresWithoutComments in phpstan.dist.neon so any future @phpstan-ignore must justify itself in a parenthesised comment Co-Authored-By: Claude Opus 4.7 (1M context) --- phpstan.dist.neon | 3 +++ src/EasyAdmin/Config/AutoBadgeMenuItem.php | 6 ++---- src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php | 9 +++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/phpstan.dist.neon b/phpstan.dist.neon index fdc5dbc..e1103b1 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,5 +1,8 @@ parameters: level: 6 + # Require a reason on every @phpstan-ignore so the next reader knows + # why a check was suppressed. https://phpstan.org/user-guide/ignoring-errors#requiring-comments-for-@phpstan-ignore + reportIgnoresWithoutComments: true paths: - bin/ - config/ diff --git a/src/EasyAdmin/Config/AutoBadgeMenuItem.php b/src/EasyAdmin/Config/AutoBadgeMenuItem.php index 9ba8ca1..a5d28ae 100644 --- a/src/EasyAdmin/Config/AutoBadgeMenuItem.php +++ b/src/EasyAdmin/Config/AutoBadgeMenuItem.php @@ -7,14 +7,12 @@ class AutoBadgeMenuItem { - /** @phpstan-ignore missingType.return */ - public function __call(string $name, array $arguments) + public function __call(string $name, array $arguments): never { throw new \BadMethodCallException(sprintf('Method %s not implemented', $name)); } - /** @phpstan-ignore missingType.return */ - public static function __callStatic(string $name, array $arguments) + public static function __callStatic(string $name, array $arguments): never { throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); } diff --git a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php index 97a8ecf..dcd8a83 100644 --- a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php +++ b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php @@ -16,20 +16,17 @@ public function __construct(TranslatableInterface|string $label, ?string $icon, $this->crudMenuItem = new CrudMenuItem($label, $icon, $entityFqcn); } - /** @phpstan-ignore missingType.return, missingType.parameter, missingType.parameter */ - public function __call($name, $arguments) + public function __call(string $name, array $arguments): mixed { return $this->crudMenuItem->$name(...$arguments); } - /** @phpstan-ignore missingType.return */ - public static function __callStatic(string $name, array $arguments) + public static function __callStatic(string $name, array $arguments): never { throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); } - /** @phpstan-ignore missingType.parameter */ - public function setBadge(/* \Stringable|string|int|float|bool|null */ $content, string $style = 'secondary', array $htmlAttributes = []): self + public function setBadge(\Stringable|string|int|float|bool|null $content, string $style = 'secondary', array $htmlAttributes = []): self { if (!is_int($content)) { throw new \InvalidArgumentException('The badge content must be an integer'); From 7c773f26ede8ed0c7f9fcd8d99f7b4b8d4a21cef Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:33:50 +0200 Subject: [PATCH 46/94] docs: add class-level docblocks to AutoBadge menu helpers Co-Authored-By: Claude Opus 4.7 (1M context) --- src/EasyAdmin/Config/AutoBadgeMenuItem.php | 4 ++++ src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/EasyAdmin/Config/AutoBadgeMenuItem.php b/src/EasyAdmin/Config/AutoBadgeMenuItem.php index a5d28ae..fec38b3 100644 --- a/src/EasyAdmin/Config/AutoBadgeMenuItem.php +++ b/src/EasyAdmin/Config/AutoBadgeMenuItem.php @@ -5,6 +5,10 @@ use App\EasyAdmin\Config\Menu\AutoBadgeCrudMenuItem; use Symfony\Contracts\Translation\TranslatableInterface; +/** + * Factory that mirrors EasyAdmin's MenuItem static API but produces + * AutoBadgeCrudMenuItem so dashboard entries hide their badge when the count is zero. + */ class AutoBadgeMenuItem { public function __call(string $name, array $arguments): never diff --git a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php index dcd8a83..640fe4a 100644 --- a/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php +++ b/src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php @@ -7,6 +7,10 @@ use EasyCorp\Bundle\EasyAdminBundle\Dto\MenuItemDto; use Symfony\Contracts\Translation\TranslatableInterface; +/** + * Drop-in replacement for EasyAdmin's CrudMenuItem that suppresses the badge + * when its integer count is zero or negative; all other calls pass through. + */ class AutoBadgeCrudMenuItem implements MenuItemInterface { private CrudMenuItem $crudMenuItem; From 1403daf3444d93d134ac3604c1f6f3435f6a76be Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:36:50 +0200 Subject: [PATCH 47/94] fix: restore Encore-based asset wiring, drop stranded AssetMapper calls The AssetMapper migration moved to PR #81 but the dashboard and base template still referenced AssetMapper, causing every admin smoke test to 500 with "AssetMapper component is not installed". - DashboardController::configureAssets() goes back to addCssFile('css/admin.css') - base.html.twig drops the {{ importmap('app') }} block Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Controller/Admin/DashboardController.php | 2 +- templates/base.html.twig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 883b425..7044546 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -81,6 +81,6 @@ public function configureCrud(): Crud #[\Override] public function configureAssets(): Assets { - return parent::configureAssets()->addAssetMapperEntry('app'); + return Assets::new()->addCssFile('css/admin.css'); } } diff --git a/templates/base.html.twig b/templates/base.html.twig index a4f2bfe..d4f83f7 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -10,7 +10,6 @@ {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} {{ encore_entry_script_tags('app') }} {% endblock %} From e16aaea444b358a726159d4576d50e9925862233 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 20 May 2026 10:44:23 +0200 Subject: [PATCH 48/94] chore: drop scratch detail.html.twig override The template was a global EasyAdmin override that rendered a hardcoded "Notes" fieldset with placeholder text on every entity's detail page. Clearly leftover from local exploration; if notes belong on the SecurityContract detail, add the field via configureFields() instead. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../EasyAdminBundle/crud/detail.html.twig | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 templates/bundles/EasyAdminBundle/crud/detail.html.twig diff --git a/templates/bundles/EasyAdminBundle/crud/detail.html.twig b/templates/bundles/EasyAdminBundle/crud/detail.html.twig deleted file mode 100644 index 9596b80..0000000 --- a/templates/bundles/EasyAdminBundle/crud/detail.html.twig +++ /dev/null @@ -1,40 +0,0 @@ -{% extends '@!EasyAdmin/crud/detail.html.twig' %} - -{% block main %} - {{ parent() }} - -
-
-
- - - - Notes - - - -
-
- -
-
- -
-
- Notes -
-
- -
- - nfkngjkfdsnds - - -
-
- -
-
-
- -{% endblock %} From 263f31f6181cbec7a580aa1940e2219537578e8e Mon Sep 17 00:00:00 2001 From: martinyde Date: Wed, 20 May 2026 12:18:37 +0200 Subject: [PATCH 49/94] Merged migrations into a new single migration with just secority contract entity --- config/reference.php | 2 +- migrations/Version20251023091529.php | 75 ---------------------------- migrations/Version20260519061123.php | 39 --------------- migrations/Version20260520101548.php | 31 ++++++++++++ 4 files changed, 32 insertions(+), 115 deletions(-) delete mode 100644 migrations/Version20251023091529.php delete mode 100644 migrations/Version20260519061123.php create mode 100644 migrations/Version20260520101548.php diff --git a/config/reference.php b/config/reference.php index a632abf..563c307 100644 --- a/config/reference.php +++ b/config/reference.php @@ -280,7 +280,7 @@ * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * paths?: string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true diff --git a/migrations/Version20251023091529.php b/migrations/Version20251023091529.php deleted file mode 100644 index f1a4fea..0000000 --- a/migrations/Version20251023091529.php +++ /dev/null @@ -1,75 +0,0 @@ -addSql(<<<'SQL' - CREATE TABLE project ( - id BINARY(16) NOT NULL, - created_at DATETIME NOT NULL, - modified_at DATETIME NOT NULL, - created_by VARCHAR(255) DEFAULT '' NOT NULL, - modified_by VARCHAR(255) DEFAULT '' NOT NULL, - leantime_id INT NOT NULL, - name VARCHAR(255) NOT NULL, - leantime_url VARCHAR(255) DEFAULT NULL, - economics_url VARCHAR(255) DEFAULT NULL, - details LONGTEXT DEFAULT NULL, - leantime_modified_at DATETIME NOT NULL, - UNIQUE INDEX UNIQ_2FB3D0EE4B785F0C (leantime_id), - PRIMARY KEY (id) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` - SQL); - $this->addSql(<<<'SQL' - CREATE TABLE security_contract ( - id BINARY(16) NOT NULL, - created_at DATETIME NOT NULL, - modified_at DATETIME NOT NULL, - created_by VARCHAR(255) DEFAULT '' NOT NULL, - modified_by VARCHAR(255) DEFAULT '' NOT NULL, - economics_report_url VARCHAR(255) DEFAULT NULL, - operational_contract_url LONGTEXT DEFAULT NULL, - monthly_price DOUBLE PRECISION DEFAULT NULL, - quarterly_hours DOUBLE PRECISION DEFAULT NULL, - valid_from DATE NOT NULL, - valid_to DATE NOT NULL, - active TINYINT(1) NOT NULL, - notes LONGTEXT NOT NULL, - project_id BINARY(16) NOT NULL, - UNIQUE INDEX UNIQ_8AE4AF8B166D1F9C (project_id), - PRIMARY KEY (id) - ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` - SQL); - $this->addSql(<<<'SQL' - ALTER TABLE - security_contract - ADD - CONSTRAINT FK_8AE4AF8B166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) - SQL); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE security_contract DROP FOREIGN KEY FK_8AE4AF8B166D1F9C'); - $this->addSql('DROP TABLE project'); - $this->addSql('DROP TABLE security_contract'); - } -} diff --git a/migrations/Version20260519061123.php b/migrations/Version20260519061123.php deleted file mode 100644 index 4c04db0..0000000 --- a/migrations/Version20260519061123.php +++ /dev/null @@ -1,39 +0,0 @@ -addSql('ALTER TABLE security_contract DROP FOREIGN KEY `FK_8AE4AF8B166D1F9C`'); - $this->addSql('DROP TABLE project'); - $this->addSql('DROP INDEX UNIQ_8AE4AF8B166D1F9C ON security_contract'); - $this->addSql('ALTER TABLE security_contract ADD economics_id INT NOT NULL, ADD project_name VARCHAR(255) NOT NULL, ADD hosting_provider VARCHAR(255) DEFAULT NULL, ADD document_url VARCHAR(255) DEFAULT NULL, ADD eol TINYINT NOT NULL, ADD leantime_url VARCHAR(255) DEFAULT NULL, ADD client_contact_name VARCHAR(255) DEFAULT NULL, ADD client_contact_email VARCHAR(255) DEFAULT NULL, ADD dedicated_server TINYINT NOT NULL, ADD server_size VARCHAR(255) DEFAULT NULL, ADD system_owner_notices JSON DEFAULT NULL, ADD project_tracker_key VARCHAR(255) DEFAULT NULL, ADD cybersecurity_price DOUBLE PRECISION DEFAULT NULL, ADD cybersecurity_note LONGTEXT DEFAULT NULL, DROP notes, DROP project_id, CHANGE valid_from valid_from DATE DEFAULT NULL, CHANGE valid_to valid_to DATE DEFAULT NULL, CHANGE economics_report_url client_name VARCHAR(255) DEFAULT NULL, CHANGE operational_contract_url git_repos LONGTEXT DEFAULT NULL'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8AE4AF8B4416F7E8 ON security_contract (economics_id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE project (id BINARY(16) NOT NULL, created_at DATETIME NOT NULL, modified_at DATETIME NOT NULL, created_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT \'\' NOT NULL COLLATE `utf8mb4_unicode_ci`, modified_by VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT \'\' NOT NULL COLLATE `utf8mb4_unicode_ci`, leantime_id INT NOT NULL, name VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, leantime_url VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, economics_url VARCHAR(255) CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, details LONGTEXT CHARACTER SET utf8mb4 DEFAULT NULL COLLATE `utf8mb4_unicode_ci`, leantime_modified_at DATETIME NOT NULL, UNIQUE INDEX UNIQ_2FB3D0EE4B785F0C (leantime_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'\' '); - $this->addSql('DROP INDEX UNIQ_8AE4AF8B4416F7E8 ON security_contract'); - $this->addSql('ALTER TABLE security_contract ADD economics_report_url VARCHAR(255) DEFAULT NULL, ADD operational_contract_url LONGTEXT DEFAULT NULL, ADD notes LONGTEXT NOT NULL, ADD project_id BINARY(16) NOT NULL, DROP economics_id, DROP project_name, DROP client_name, DROP hosting_provider, DROP document_url, DROP eol, DROP leantime_url, DROP client_contact_name, DROP client_contact_email, DROP dedicated_server, DROP server_size, DROP git_repos, DROP system_owner_notices, DROP project_tracker_key, DROP cybersecurity_price, DROP cybersecurity_note, CHANGE valid_from valid_from DATE NOT NULL, CHANGE valid_to valid_to DATE NOT NULL'); - $this->addSql('ALTER TABLE security_contract ADD CONSTRAINT `FK_8AE4AF8B166D1F9C` FOREIGN KEY (project_id) REFERENCES project (id)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8AE4AF8B166D1F9C ON security_contract (project_id)'); - } -} diff --git a/migrations/Version20260520101548.php b/migrations/Version20260520101548.php new file mode 100644 index 0000000..1a0e66f --- /dev/null +++ b/migrations/Version20260520101548.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE security_contract (id BINARY(16) NOT NULL, created_at DATETIME NOT NULL, modified_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT \'\' NOT NULL, modified_by VARCHAR(255) DEFAULT \'\' NOT NULL, economics_id INT NOT NULL, project_name VARCHAR(255) NOT NULL, client_name VARCHAR(255) DEFAULT NULL, hosting_provider VARCHAR(255) DEFAULT NULL, document_url VARCHAR(255) DEFAULT NULL, monthly_price DOUBLE PRECISION DEFAULT NULL, valid_from DATE DEFAULT NULL, valid_to DATE DEFAULT NULL, active TINYINT NOT NULL, eol TINYINT NOT NULL, leantime_url VARCHAR(255) DEFAULT NULL, client_contact_name VARCHAR(255) DEFAULT NULL, client_contact_email VARCHAR(255) DEFAULT NULL, dedicated_server TINYINT NOT NULL, server_size VARCHAR(255) DEFAULT NULL, git_repos LONGTEXT DEFAULT NULL, system_owner_notices JSON DEFAULT NULL, project_tracker_key VARCHAR(255) DEFAULT NULL, quarterly_hours DOUBLE PRECISION DEFAULT NULL, cybersecurity_price DOUBLE PRECISION DEFAULT NULL, cybersecurity_note LONGTEXT DEFAULT NULL, UNIQUE INDEX UNIQ_8AE4AF8B4416F7E8 (economics_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE security_contract'); + } +} From 32895668c105529986ee51c1436e236097ca1bb8 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 21 May 2026 11:49:39 +0200 Subject: [PATCH 50/94] feat: add Renovate auto-patch and auto-release pipeline Phase 1 fork validation before promoting to upstream itk-dev/devops_itksites. Patches/security target main as hotfixes with a 7-day soak (0 for CVE alerts); minor/major target develop with Dependency Dashboard approval. After a Renovate merge to main, the [Unreleased] CHANGELOG section is promoted to the next patch version, tagged from main, released by github_build_release.yml, and main is back-merged into develop. See RENOVATE_PLAN.md for the full design and exit criteria. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/scripts/renovate-changelog.sh | 43 ++++ .github/workflows/auto-release.yaml | 117 ++++++++++ .github/workflows/renovate.yaml | 62 +++++ CHANGELOG.md | 1 + RENOVATE_PLAN.md | 314 ++++++++++++++++++++++++++ renovate.json | 134 +++++++++++ 6 files changed, 671 insertions(+) create mode 100755 .github/scripts/renovate-changelog.sh create mode 100644 .github/workflows/auto-release.yaml create mode 100644 .github/workflows/renovate.yaml create mode 100644 RENOVATE_PLAN.md create mode 100644 renovate.json diff --git a/.github/scripts/renovate-changelog.sh b/.github/scripts/renovate-changelog.sh new file mode 100755 index 0000000..5f4ab6d --- /dev/null +++ b/.github/scripts/renovate-changelog.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Called by Renovate postUpgradeTasks. Appends a single bullet under the +# `## [Unreleased]` section of CHANGELOG.md so the auto-release workflow has +# content to promote into a tagged release after the Renovate PR merges to main. +set -euo pipefail + +BRANCH="${1:-renovate/unknown}" +TITLE="${2:-Update dependencies}" + +# Skip if CHANGELOG.md was already touched this run (grouped PRs fire the hook +# more than once; we only want one bullet per PR). +if git diff --cached --name-only | grep -qx CHANGELOG.md; then + exit 0 +fi +if git diff --name-only | grep -qx CHANGELOG.md; then + exit 0 +fi + +ENTRY="- Renovate: ${TITLE} (\`${BRANCH}\`)" + +python3 - "$ENTRY" <<'PY' +import re, sys, pathlib +entry = sys.argv[1] +p = pathlib.Path("CHANGELOG.md") +text = p.read_text(encoding="utf-8") + +pattern = re.compile(r"(## \[Unreleased\]\n)(.*?)(\n## \[)", re.DOTALL) +m = pattern.search(text) +if not m: + # No [Unreleased] section yet — insert one after the intro paragraph + # (the first blank line after the H1 header). + head, _, rest = text.partition("\n\n") + text = f"{head}\n\n## [Unreleased]\n\n{entry}\n\n{rest}" +else: + body = m.group(2).rstrip("\n") + sep = "\n" if body else "" + text = pattern.sub( + lambda _m: f"{_m.group(1)}{body}{sep}{entry}\n{_m.group(3)}", + text, count=1, + ) + +p.write_text(text, encoding="utf-8") +PY diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml new file mode 100644 index 0000000..59c472b --- /dev/null +++ b/.github/workflows/auto-release.yaml @@ -0,0 +1,117 @@ +# After a Renovate PR merges into `main`, promote the `[Unreleased]` section +# of CHANGELOG.md to the next patch version, commit, tag, push, and back-merge +# main into develop so the branches stay in sync. +# +# Required secrets: +# RELEASE_TOKEN PAT or GitHub App token with contents:write + workflows. +# Needed for the tag push to trigger github_build_release.yml +# (the default GITHUB_TOKEN cannot trigger other workflows) +# and for the back-merge push to `develop`. +# +# Gating: the existing pr.yaml is required by branch protection, so by the time +# a PR is merged into main, CI has already passed. We do not re-wait for checks +# on the merge commit (it has no associated check runs after a squash-merge). + +name: Auto Release on Renovate Merge + +on: + pull_request: + types: [closed] + branches: [main] + +permissions: + contents: read + +jobs: + release: + if: >- + github.event.pull_request.merged == true && + github.event.pull_request.user.login == 'renovate[bot]' + runs-on: ubuntu-latest + steps: + - name: Checkout main with full history + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN }} + + - name: Compute next patch version + id: version + run: | + latest=$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1) + : "${latest:=0.0.0}" + IFS=. read -r maj min pat <<<"$latest" + next="${maj}.${min}.$((pat + 1))" + { + echo "current=$latest" + echo "next=$next" + echo "date=$(date -u +%Y-%m-%d)" + } >>"$GITHUB_OUTPUT" + echo "Will release: $latest -> $next" + + - name: Bail if [Unreleased] is empty + id: gate + run: | + body=$(awk '/^## \[Unreleased\]/{f=1;next} /^## \[/{f=0} f' CHANGELOG.md \ + | grep -v '^[[:space:]]*$' || true) + if [ -z "$body" ]; then + echo "Nothing under [Unreleased]; skipping release." + echo "skip=true" >>"$GITHUB_OUTPUT" + else + echo "skip=false" >>"$GITHUB_OUTPUT" + fi + + - name: Promote [Unreleased] -> ${{ steps.version.outputs.next }} + if: steps.gate.outputs.skip != 'true' + env: + VER: ${{ steps.version.outputs.next }} + DATE: ${{ steps.version.outputs.date }} + run: | + python3 - <<'PY' + import os, re, pathlib + ver = os.environ["VER"] + date = os.environ["DATE"] + p = pathlib.Path("CHANGELOG.md") + text = p.read_text(encoding="utf-8") + text = re.sub( + r"## \[Unreleased\]\n", + f"## [Unreleased]\n\n## [{ver}] - {date}\n", + text, count=1, + ) + p.write_text(text, encoding="utf-8") + PY + + - name: Commit on main, tag, push + if: steps.gate.outputs.skip != 'true' + env: + VER: ${{ steps.version.outputs.next }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add CHANGELOG.md + git commit -m "release: ${VER}" + git tag -a "${VER}" -m "Release ${VER}" + git push origin main + git push origin "${VER}" + + - name: Back-merge main into develop + if: steps.gate.outputs.skip != 'true' + env: + VER: ${{ steps.version.outputs.next }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + git fetch origin develop + git checkout -B develop origin/develop + if git merge --no-ff main -m "chore: back-merge hotfix ${VER} into develop"; then + git push origin develop + echo "Back-merged ${VER} to develop." + else + git merge --abort || true + gh pr create \ + --repo "${GITHUB_REPOSITORY}" \ + --base develop \ + --head main \ + --title "Back-merge hotfix ${VER} into develop" \ + --body "Automated back-merge from \`main\` conflicted with \`develop\`. Resolve and merge to keep branches in sync after release ${VER}." + fi diff --git a/.github/workflows/renovate.yaml b/.github/workflows/renovate.yaml new file mode 100644 index 0000000..7bc78fe --- /dev/null +++ b/.github/workflows/renovate.yaml @@ -0,0 +1,62 @@ +# Self-hosted Renovate runner. Runs on a schedule and on demand. +# Self-hosted is required because postUpgradeTasks (which writes CHANGELOG.md +# inline with the dep bump) is disabled on the Mend Renovate SaaS app. +# +# Required secrets: +# RENOVATE_TOKEN PAT or GitHub App token with repo write + workflows. +# Pushes by the default GITHUB_TOKEN don't re-trigger Actions, +# so a PAT/App token is needed to make CI re-run on +# Renovate's branch updates. + +name: Renovate + +on: + schedule: + - cron: "0 5 * * 1-5" # weekdays 05:00 UTC ~= 06/07:00 Aarhus + workflow_dispatch: + inputs: + logLevel: + description: Renovate log level + type: choice + default: info + options: [debug, info, warn, error] + dryRun: + description: Dry-run (no PRs) + type: choice + default: "null" + options: ["null", "full"] + +permissions: + contents: read + +concurrency: + group: renovate + cancel-in-progress: false + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # PHP/Composer are needed so Renovate can resolve composer.lock + # updates inside its container. + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + tools: composer:v2 + coverage: none + + - name: Run Renovate + uses: renovatebot/github-action@v43 + with: + configurationFile: renovate.json + token: ${{ secrets.RENOVATE_TOKEN }} + renovate-version: latest + env: + LOG_LEVEL: ${{ inputs.logLevel || 'info' }} + RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'null' }} + RENOVATE_REPOSITORIES: ${{ github.repository }} + # Allow the changelog script to run from postUpgradeTasks. + RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^bash \\.github/scripts/renovate-changelog\\.sh"]' + RENOVATE_ALLOW_POST_UPGRADE_COMMAND_TEMPLATING: "true" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3c760..83a4725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#81](https://github.com/itk-dev/devops_itksites/pull/81) 5564: Asset Mapper migration - Add Symfony Asset Mapper bundle and importmap +- Add Renovate auto-patch + auto-release pipeline (Phase 1 fork validation) ## [1.11.0] - 2026-05-19 diff --git a/RENOVATE_PLAN.md b/RENOVATE_PLAN.md new file mode 100644 index 0000000..89ade1e --- /dev/null +++ b/RENOVATE_PLAN.md @@ -0,0 +1,314 @@ +# Plan: Renovate auto-patch + auto-release — fork test, then upstream + +## Objective + +Validate a Renovate + auto-release pipeline on the **fork** (`turegjorup/devops_itksites`), then re-implement the proven setup on **upstream** (`itk-dev/devops_itksites`). + +When complete, the pipeline will: + +1. Open grouped patch-update PRs (Composer/npm/GitHub Actions/Docker) **against `main` as hotfixes**, run the existing `pr.yaml` checks, and **auto-merge** when green. +2. Open security-advisory PRs (the `composer audit` set) against `main` with **0-day soak** so they ship as soon as CI passes. +3. After each merge to `main`, promote the `[Unreleased]` section of `CHANGELOG.md` to the next **patch** version, tag from `main`, let `github_build_release.yml` build the GitHub Release, then **back-merge `main` → `develop`** so the branches stay in sync. +4. Continue to route **minor/major** updates against `develop` with Dependency Dashboard approval (no auto-merge). + +## Why a fork test first + +- The pipeline writes commits, opens PRs, creates tags, publishes releases, and force-syncs `main` ↔ `develop`. Each of those is observable to the upstream team and to anyone subscribed to upstream releases. +- The repo already has a real git-flow with `hotfix/*`, `release/*` and `main`/`develop`. Renovate needs to slot into it cleanly — any misconfiguration produces tag noise, spurious PRs, or broken back-merges that are painful to clean up in a shared repo. +- The fork is a near-identical copy of upstream (same `pr.yaml`, same `changelog.yaml`, same `github_build_release.yml`, same composer.lock), so behavior should transfer 1:1 with only secret/owner substitutions. + +## Branch state (verified) + +| Branch | Origin (fork) | Upstream | HEAD | +| --- | --- | --- | --- | +| `develop` | ✅ | ✅ | `614c76e` (fork is ahead with renovate lock-file maintenance PRs) | +| `main` | ✅ (just pushed) | ✅ | `4402754` (Merge `release/1.11.0`) | + +Highest semver tag on upstream: **`1.11.0`**. First auto-release on the fork should produce `1.11.1`. + +## Context (why this is worth doing now) + +Running `composer audit` against `composer.lock` flags the May 20, 2026 coordinated Symfony + Twig disclosure: + +| Package | Locked | Fixed in | +| --- | --- | --- | +| `symfony/http-kernel`, `security-http`, `runtime`, etc. (v8.0.x) | v8.0.8–8.0.11 | **8.0.12** | +| `twig/twig` | v3.25.0 | **3.26.0** | + +Relevant CVEs: `CVE-2026-45075`, `CVE-2026-45065`, `CVE-2026-46626`, `CVE-2026-46633`, `CVE-2026-24425`, plus the HtmlSanitizer / Mailer / Notifier batch. All patch-level — exactly the class this pipeline auto-merges. + +## Architecture decisions (apply to both fork and upstream) + +- **Self-hosted Renovate** via `renovatebot/github-action` on a schedule. Self-hosted is required because `postUpgradeTasks` (the bit that writes `CHANGELOG.md` in the same commit as the dep bump) is disabled on Mend's hosted app. +- **Two base branches.** `baseBranches: ["main", "develop"]`. Patches/security/digest/lockFileMaintenance route to `main`; minor/major route to `develop`. `matchBaseBranches` in `packageRules` enforces the split. +- **Patch-only auto-merge.** Minor and major bumps open PRs against `develop` but require Dependency Dashboard approval. `php` package never auto-bumps. +- **CHANGELOG gate.** The repo's `changelog.yaml` workflow fails any PR that doesn't touch `CHANGELOG.md`. Rather than fork that workflow, Renovate appends an `[Unreleased]` bullet via `postUpgradeTasks` so every Renovate PR satisfies the gate. +- **Two PATs** so pushes re-trigger Actions (the default `GITHUB_TOKEN` doesn't): `RENOVATE_TOKEN` (Renovate's pushes to `renovate/*` branches) and `RELEASE_TOKEN` (the tag push that fires `github_build_release.yml`, plus the back-merge to `develop`). +- **Soak time:** 7 days for normal patches, 0 days for security advisories. The conservative window catches yanked releases and follow-up advisories; CVE-flagged patches still ship immediately via the vuln-alert path. + +## Deliverables (same files in both phases) + +``` +renovate.json +.github/workflows/renovate.yaml +.github/workflows/auto-release.yaml +.github/scripts/renovate-changelog.sh +``` + +--- + +# Phase 1 — Validate on the fork (`turegjorup/devops_itksites`) + +Goal: run the full pipeline against the fork's own `main` and `develop` until a clean Renovate PR → auto-merge → tag → GitHub Release → back-merge cycle completes end-to-end, twice (once for a regular patch, once for a security advisory). + +## Step 1.1 — Branch & baseline (already partly done) + +`main` has been fetched from upstream and pushed to origin (`4402754`). Now: + +```bash +git checkout develop && git pull origin develop +git checkout -b feat/renovate-auto-patch +``` + +Confirm no existing `renovate.json`, `.renovaterc*`, or `dependabot.yml` on develop. If `dependabot.yml` exists, delete it in this PR — Renovate replaces it. + +## Step 1.2 — Create `renovate.json` at repo root + +Key sections: + +- `extends`: `config:recommended`, `:dependencyDashboard`, `:semanticCommitsDisabled`, `:maintainLockFilesWeekly`, `schedule:weekdays`. +- `baseBranches: ["main", "develop"]`, `timezone: "Europe/Copenhagen"`. +- `vulnerabilityAlerts`: `automerge: true`, `minimumReleaseAge: "0 days"`, `prPriority: 10`, `commitMessagePrefix: "security:"`, `matchBaseBranches: ["main"]`. Also enable `osvVulnerabilityAlerts: true`. Security advisories always land on `main` so they ship as a hotfix release. +- `lockFileMaintenance`: weekly Monday morning, auto-merge, `matchBaseBranches: ["main"]`. +- `packageRules`: + - **Patch/pin/digest/lockFileMaintenance on `main`** → `matchBaseBranches: ["main"]`, `matchUpdateTypes: ["patch", "pin", "digest", "lockFileMaintenance"]`, `automerge: true`, `minimumReleaseAge: "7 days"`. The hotfix path. + - **Minor/major on `develop`** → `matchBaseBranches: ["develop"]`, `matchUpdateTypes: ["minor", "major"]`, `dependencyDashboardApproval: true`, `automerge: false`. The normal release path. + - **Suppression rules** so updates don't open against the wrong base: + - `matchBaseBranches: ["main"]` + `matchUpdateTypes: ["minor", "major"]` → `enabled: false`. + - `matchBaseBranches: ["develop"]` + `matchUpdateTypes: ["patch", "pin", "digest", "lockFileMaintenance"]` → `enabled: false` (patches reach develop via the back-merge). + - Group `^symfony/`, `^doctrine/`, `^api-platform/` patches each into one PR (apply on `main`). + - `php` package → `matchBaseBranches: ["develop"]`, `rangeStrategy: "in-range-only"`, no auto-merge. + - GitHub Actions → pin digests, auto-merge digest/patch on `main`. +- `postUpgradeTasks`: `bash .github/scripts/renovate-changelog.sh {{{branchName}}} {{{prTitle}}}`, `fileFilters: ["CHANGELOG.md"]`, `executionMode: "branch"`. + +## Step 1.3 — Create `.github/scripts/renovate-changelog.sh` + +Idempotent script that appends one bullet under `## [Unreleased]`: + +- `set -euo pipefail`. +- Exit 0 if `CHANGELOG.md` is already staged or modified in the working tree (prevents duplicate bullets when `postUpgradeTasks` fires multiple times for a grouped PR). +- Python heredoc splices `- Renovate: (\`\`)` into the `[Unreleased]` section. If no `[Unreleased]` section exists, create one after the intro paragraph. + +```bash +chmod +x .github/scripts/renovate-changelog.sh +``` + +Local validation: + +```bash +.github/scripts/renovate-changelog.sh renovate/symfony-patch "Update symfony (patch)" +git diff CHANGELOG.md # expect one new bullet under [Unreleased] +git checkout -- CHANGELOG.md +``` + +## Step 1.4 — Create `.github/workflows/renovate.yaml` + +- Triggers: `schedule` (weekdays 05:00 UTC) + `workflow_dispatch` with `logLevel` and `dryRun` inputs. +- `concurrency.group: renovate`, `cancel-in-progress: false`. +- Set up PHP 8.4 + Composer (needed so `composer update` works inside Renovate's container). +- `renovatebot/github-action@v43` with `token: ${{ secrets.RENOVATE_TOKEN }}`. +- Critical env vars for `postUpgradeTasks`: + - `RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^bash \\.github/scripts/renovate-changelog\\.sh"]'` + - `RENOVATE_ALLOW_POST_UPGRADE_COMMAND_TEMPLATING: "true"` +- `RENOVATE_REPOSITORIES: ${{ github.repository }}` — this means the same workflow file works unmodified on both fork and upstream; it always operates on its own repo. + +## Step 1.5 — Create `.github/workflows/auto-release.yaml` + +Trigger: `pull_request.closed` on `main`, gated by `github.event.pull_request.merged == true && github.event.pull_request.user.login == 'renovate[bot]'`. + +Steps: + +1. `actions/checkout@v6` with `ref: main`, `fetch-depth: 0`, `token: ${{ secrets.RELEASE_TOKEN }}`. +2. `lewagon/wait-on-check-action@v1.4.0` waiting on the merge commit's required checks (`PHP Unit tests`, `PHPStan`, `Validate Doctrine Schema`, `Load fixtures`, `Build assets`). +3. Compute next patch: `git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1`, bump the patch component. On the fork, the first run produces `1.11.1`. +4. Gate: if `[Unreleased]` is empty (`awk` extraction returns nothing), `skip=true` and stop. +5. Promote `[Unreleased]` → `[] - ` via Python heredoc. +6. `git commit -m "release: "`, `git tag -a `, push both to `main`. `github_build_release.yml` fires on the `*.*.*` tag. +7. **Back-merge `main` → `develop`:** + - `git fetch origin develop && git checkout develop` + - `git merge --no-ff main -m "chore: back-merge hotfix into develop"` + - On conflict: abort, then `gh pr create --base develop --head main --title "Back-merge hotfix "`. Do not force-push. + - Otherwise push develop with `RELEASE_TOKEN`. + +## Step 1.6 — Local validation before pushing + +```bash +python3 -c "import json; json.load(open('renovate.json'))" +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/renovate.yaml'))" +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/auto-release.yaml'))" +bash -n .github/scripts/renovate-changelog.sh +``` + +If Taskfile defines them, also: + +```bash +task lint +``` + +## Step 1.7 — Fork-side manual setup + +Done by **you (turegjorup)** as the fork owner before the pipeline runs: + +1. **Repository secrets on `turegjorup/devops_itksites`:** + - `RENOVATE_TOKEN` — a PAT on your account scoped to the fork with `contents:write`, `pull-requests:write`, `workflows`. For the test phase this can be a classic PAT or fine-grained PAT; a GitHub App is overkill. + - `RELEASE_TOKEN` — PAT with `contents:write`, `workflows` for the tag push + back-merge. +2. **Branch protection on `main` and `develop` (fork)** — match what you'll later configure on upstream: + - Required status checks: `PHP Unit tests`, `PHPStan`, `Validate Doctrine Schema`, `Load fixtures`, `Build assets`, `Changelog`, `composer-validate`, `composer-normalized`, `composer-audit`. + - Require branches up to date before merging. + - **Allow auto-merge** at the repo level. + - Allow `RELEASE_TOKEN` identity to direct-push (for back-merges); fallback to PR if blocked. +3. **(Optional) CODEOWNERS exemption** for `renovate[bot]` if you mirror upstream's CODEOWNERS. + +## Step 1.8 — Commit & open PR against the fork's `develop` + +```bash +git add renovate.json .github/workflows/renovate.yaml .github/workflows/auto-release.yaml .github/scripts/renovate-changelog.sh +# changelog.yaml will gate this PR — add a bullet to [Unreleased] +$EDITOR CHANGELOG.md +git add CHANGELOG.md +git commit -m "Add Renovate auto-patch + auto-release pipeline" +git push -u origin feat/renovate-auto-patch +gh pr create --repo turegjorup/devops_itksites --base develop --fill +``` + +Merge it once the existing `pr.yaml` checks pass. (No release fires from this — it lands on `develop`, not `main`.) + +## Step 1.9 — Promote the pipeline files to `main` on the fork + +The pipeline only takes effect for hotfix PRs once the workflow files and `renovate.json` exist on `main`. Two options: + +- **Quick:** cherry-pick the four pipeline files from develop to main with a manual PR (`gh pr create --base main --head feat/renovate-pipeline-main`). Safe because these are tooling files; no app behavior changes. +- **Slow:** wait for the next `release/*` → `main` cut to carry them across. + +Use the quick path for the fork test so the validation cycle isn't blocked on a release. + +## Step 1.10 — First-run verification on the fork + +1. **Dry-run.** Actions → Renovate → Run workflow with `dryRun: full`, `logLevel: debug`. Confirm logs show: + - Config parses cleanly. + - Patch candidates for `symfony/*` (8.0.8/9/10/11 → 8.0.12) and `twig/twig` (3.25.0 → 3.26.0) are scoped to `main`. + - Minor/major candidates (if any) scoped to `develop`. + - `postUpgradeTasks` listed as allowed. +2. **Live run.** Re-run with `dryRun: null`. Dependency Dashboard issue appears; first PRs open within minutes. +3. **First patch PR (against `main`):** + - `CHANGELOG.md` has exactly one new bullet. + - `pr.yaml`, `composer-audit`, `Changelog` all pass. + - PR auto-merges after `minimumReleaseAge` (security PRs immediately, regular patches after 7 days — use a security PR for the first test to skip the soak). +4. **Auto-release cycle:** + - `auto-release.yaml` runs after merge, computes `1.11.1`, promotes `[Unreleased]`, tags from `main`, pushes. + - `github_build_release.yml` builds the GitHub Release on the fork. +5. **Back-merge:** verify the release commit reached `develop` (push or PR). + +## Step 1.11 — Exit criteria for Phase 1 + +Move to Phase 2 only when **all of these** are observed on the fork: + +- One **security** PR (e.g. a Symfony 8.0.x patch) → auto-merged with 0-day soak → released → back-merged. +- One **regular patch** PR → auto-merged after 7-day soak → released → back-merged. (To avoid blocking the Phase 1 sign-off on a real 7-day wait, temporarily set `minimumReleaseAge: "0 days"` for this single test run, confirm the merge fires, then restore `"7 days"` before going to upstream.) +- One **grouped patch** PR (Symfony or Doctrine cluster) → auto-merged correctly without partial-update artifacts. +- One **minor or major** candidate appeared on the Dependency Dashboard against `develop` and did **not** auto-merge. +- A **back-merge conflict** scenario has been exercised at least once (synthesize one if it doesn't happen naturally) and the PR-fallback worked. +- The CHANGELOG ended each cycle correctly: `[Unreleased]` empty, `[1.11.x] - ` populated. + +Document any deviations or required tweaks in `RENOVATE_TEST_NOTES.md` (created during this phase, not promoted to upstream). + +## Step 1.12 — Cleanup before Phase 2 + +After the fork test is signed off: + +- Optionally delete the test releases (`1.11.1`, `1.11.2`, …) from the fork's Releases page to keep the fork's tag history tidy. The tags themselves can stay. +- Keep the `Renovate` Actions workflow enabled on the fork so it continues to track patches as a low-stakes early-warning system. + +--- + +# Phase 2 — Re-implement on upstream (`itk-dev/devops_itksites`) + +Goal: port the validated files to upstream with the minimum possible delta. The smaller the diff between fork and upstream, the cheaper the review and the lower the risk. + +## Step 2.1 — Sync fork's pipeline state to a fresh branch + +```bash +git fetch upstream develop +git checkout -b feat/renovate-auto-patch upstream/develop +git checkout feat/renovate-auto-patch -- \ + renovate.json \ + .github/workflows/renovate.yaml \ + .github/workflows/auto-release.yaml \ + .github/scripts/renovate-changelog.sh +# OR, if working from the merged develop on the fork: +# git checkout feat/renovate-auto-patch-fork -- +``` + +No content changes should be needed — `RENOVATE_REPOSITORIES: ${{ github.repository }}` makes the workflow portable. + +## Step 2.2 — Upstream-only manual setup (request from itk-dev admins) + +Mirror the fork's manual setup, but on upstream: + +1. **Repository secrets on `itk-dev/devops_itksites`:** + - `RENOVATE_TOKEN` — a **GitHub App** installation token. Create a dedicated app (e.g. `renovate-itkdev`) on the itk-dev org with `contents:write`, `pull-requests:write`, `workflows:write` permissions; install it on `devops_itksites`; store the App ID + private key as `RENOVATE_APP_ID` / `RENOVATE_APP_PRIVATE_KEY` secrets; the workflow exchanges them for an installation token at runtime via `actions/create-github-app-token`. The App identity (e.g. `renovate-itkdev[bot]`) is what shows up in commits and audit logs — no personnel coupling. + - `RELEASE_TOKEN` — same GitHub App token, or a separate App if you want auditing separation between Renovate pushes and release pushes. Needs `contents:write`, `workflows:write`. +2. **Branch protection on `main` and `develop`** — identical rule sets to the fork (status checks, up-to-date, auto-merge enabled, RELEASE_TOKEN allowed to push). +3. **CODEOWNERS** — if upstream has CODEOWNERS, either exempt `renovate[bot]` or accept that each PR needs a human approval before auto-merge. + +## Step 2.3 — PR + handoff + +```bash +$EDITOR CHANGELOG.md # add bullet under [Unreleased] +git add -A +git commit -m "Add Renovate auto-patch + auto-release pipeline" +git push -u upstream feat/renovate-auto-patch +gh pr create --repo itk-dev/devops_itksites --base develop --fill +``` + +PR body should link to: + +- This plan. +- The fork's successful Renovate run history (Actions → Renovate). +- The fork's first auto-release (`turegjorup/devops_itksites` Releases → `1.11.1`). + +Reviewers can verify the diff is small and that all the behavior they're approving has already been observed running on the fork. + +## Step 2.4 — Promote the files to upstream `main` + +After the PR merges to `upstream/develop`, the same promotion question from Step 1.9 applies upstream. Recommend the same quick path: a small, scoped PR cherry-picking the four pipeline files from `develop` to `main`. + +## Step 2.5 — First-run verification on upstream + +Same as Step 1.10, but on upstream. Watch the first cycle closely — security PR first (0-day soak) so feedback comes quickly. + +--- + +## Rollback + +Applies to both fork and upstream: + +- **Stop new PRs:** disable the `Renovate` workflow in the Actions tab. +- **Disable auto-release:** delete `.github/workflows/auto-release.yaml`. `github_build_release.yml` still works manually. +- **Disable auto-merge globally:** set top-level `automerge: false` in `renovate.json`. Renovate keeps opening PRs but waits for human merge. +- **Revert a bad back-merge:** the back-merge is `--no-ff`, so `git revert -m 1 ` cleanly undoes it on `develop` without touching the release on `main`. + +## Resolved decisions + +| Decision | Choice | Where it lives | +| --- | --- | --- | +| Upstream auth | **GitHub App** (`renovate-itkdev`-style). Personal PAT acceptable on the fork only. | Step 2.2 | +| Non-security soak window | **7 days.** Vuln-alert path overrides to 0 days for CVEs. | Step 1.2 patch package rule, Step 1.7 fork-side fallback | +| Version source for auto-release | **Git tags.** Highest `X.Y.Z` → bump patch. | Step 1.5 step 3 | +| Back-merge granularity | **Single grouped back-merge** per release, no per-package splitting. | Step 1.5 step 7 | + +## Pre-flight checklist (do before Phase 1 first-run) + +- [ ] Verify `CHANGELOG.md` on `main` does **not** already contain a `## [1.11.1]` header — if it does, either delete it or bump the auto-release seed to `1.11.2`. (Tag-based versioning will collide otherwise.) +- [ ] Confirm `composer.lock` is committed and matches `composer.json` (Renovate's `composer update` runs against the lock). +- [ ] Confirm `task lint` (or equivalent) passes locally on the four new files. diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..afa00a6 --- /dev/null +++ b/renovate.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":semanticCommitsDisabled", + ":maintainLockFilesWeekly", + "schedule:weekdays" + ], + "baseBranches": ["main", "develop"], + "timezone": "Europe/Copenhagen", + "labels": ["dependencies", "renovate"], + "prConcurrentLimit": 5, + "prHourlyLimit": 2, + "rangeStrategy": "bump", + "rebaseWhen": "behind-base-branch", + "platformAutomerge": true, + "ignoreUnstable": true, + "respectLatest": true, + + "vulnerabilityAlerts": { + "enabled": true, + "labels": ["security", "dependencies"], + "automerge": true, + "automergeType": "pr", + "minimumReleaseAge": "0 days", + "prPriority": 10, + "commitMessagePrefix": "security:", + "matchBaseBranches": ["main"] + }, + "osvVulnerabilityAlerts": true, + + "lockFileMaintenance": { + "enabled": true, + "schedule": ["before 6am on Monday"], + "automerge": true, + "automergeType": "pr", + "commitMessageAction": "Refresh", + "matchBaseBranches": ["main"] + }, + + "composer": { "enabled": true, "ignorePlatformReqs": [] }, + "npm": { "enabled": true }, + "github-actions": { "enabled": true, "pinDigests": true }, + "docker-compose": { "enabled": true, "pinDigests": true }, + "dockerfile": { "enabled": true, "pinDigests": true }, + + "packageRules": [ + { + "description": "Hotfix: auto-merge patches/digests/lockfile on main after 7-day soak", + "matchBaseBranches": ["main"], + "matchUpdateTypes": [ + "patch", + "pin", + "digest", + "lockFileMaintenance" + ], + "automerge": true, + "automergeType": "pr", + "automergeStrategy": "squash", + "minimumReleaseAge": "7 days" + }, + { + "description": "Normal flow: minor/major on develop, dashboard approval, never auto-merge", + "matchBaseBranches": ["develop"], + "matchUpdateTypes": ["minor", "major"], + "dependencyDashboardApproval": true, + "automerge": false + }, + { + "description": "Suppress minor/major against main (those belong on develop)", + "matchBaseBranches": ["main"], + "matchUpdateTypes": ["minor", "major"], + "enabled": false + }, + { + "description": "Suppress patches against develop (they reach develop via back-merge after release)", + "matchBaseBranches": ["develop"], + "matchUpdateTypes": [ + "patch", + "pin", + "digest", + "lockFileMaintenance" + ], + "enabled": false + }, + { + "description": "Group Symfony patches into one PR on main", + "matchBaseBranches": ["main"], + "matchPackagePatterns": ["^symfony/"], + "matchUpdateTypes": ["patch"], + "groupName": "symfony (patch)" + }, + { + "description": "Group Doctrine patches on main", + "matchBaseBranches": ["main"], + "matchPackagePatterns": ["^doctrine/"], + "matchUpdateTypes": ["patch"], + "groupName": "doctrine (patch)" + }, + { + "description": "Group api-platform patches on main", + "matchBaseBranches": ["main"], + "matchPackagePatterns": ["^api-platform/"], + "matchUpdateTypes": ["patch"], + "groupName": "api-platform (patch)" + }, + { + "description": "Never auto-bump the PHP platform requirement", + "matchBaseBranches": ["develop"], + "matchPackageNames": ["php"], + "rangeStrategy": "in-range-only", + "automerge": false, + "dependencyDashboardApproval": true + }, + { + "description": "GitHub Actions: pin digests, auto-merge digest/patch on main", + "matchBaseBranches": ["main"], + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["digest", "patch"], + "automerge": true + } + ], + + "postUpdateOptions": ["composerUpdateAllDependencies"], + + "postUpgradeTasks": { + "commands": [ + "bash .github/scripts/renovate-changelog.sh \"{{{branchName}}}\" \"{{{prTitle}}}\"" + ], + "fileFilters": ["CHANGELOG.md"], + "executionMode": "branch" + } +} From 1df789b7195c97de454dd3cb5af4406d725344fa Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 21 May 2026 11:53:35 +0200 Subject: [PATCH 51/94] chore: run Renovate at 12:00 Europe/Copenhagen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cron is in UTC so the run drifts ±1h between DST and standard time. Picked 10:00 UTC since DST (CEST, UTC+2) covers ~7 of 12 months in Denmark, so the workflow hits noon Aarhus during the larger part of the year and 11:00 in winter. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/renovate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yaml b/.github/workflows/renovate.yaml index 7bc78fe..fb220e2 100644 --- a/.github/workflows/renovate.yaml +++ b/.github/workflows/renovate.yaml @@ -12,7 +12,7 @@ name: Renovate on: schedule: - - cron: "0 5 * * 1-5" # weekdays 05:00 UTC ~= 06/07:00 Aarhus + - cron: "0 10 * * 1-5" # weekdays 10:00 UTC = 12:00 Copenhagen (CEST) / 11:00 (CET) workflow_dispatch: inputs: logLevel: From 36ae460ae85dcf09c3e2c65f7c61529a7d07ee3d Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 21 May 2026 12:52:06 +0200 Subject: [PATCH 52/94] chore: untrack RENOVATE_PLAN.md Working doc, kept locally via .gitignore. Removing from git also fixes the markdown-lint failures it was causing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 3 + RENOVATE_PLAN.md | 314 ----------------------------------------------- 2 files changed, 3 insertions(+), 314 deletions(-) delete mode 100644 RENOVATE_PLAN.md diff --git a/.gitignore b/.gitignore index 5fb8656..39d1d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ phpstan.neon .twig-cs-fixer.cache .playwright-mcp + +# Working docs not meant to ship +RENOVATE_PLAN.md diff --git a/RENOVATE_PLAN.md b/RENOVATE_PLAN.md deleted file mode 100644 index 89ade1e..0000000 --- a/RENOVATE_PLAN.md +++ /dev/null @@ -1,314 +0,0 @@ -# Plan: Renovate auto-patch + auto-release — fork test, then upstream - -## Objective - -Validate a Renovate + auto-release pipeline on the **fork** (`turegjorup/devops_itksites`), then re-implement the proven setup on **upstream** (`itk-dev/devops_itksites`). - -When complete, the pipeline will: - -1. Open grouped patch-update PRs (Composer/npm/GitHub Actions/Docker) **against `main` as hotfixes**, run the existing `pr.yaml` checks, and **auto-merge** when green. -2. Open security-advisory PRs (the `composer audit` set) against `main` with **0-day soak** so they ship as soon as CI passes. -3. After each merge to `main`, promote the `[Unreleased]` section of `CHANGELOG.md` to the next **patch** version, tag from `main`, let `github_build_release.yml` build the GitHub Release, then **back-merge `main` → `develop`** so the branches stay in sync. -4. Continue to route **minor/major** updates against `develop` with Dependency Dashboard approval (no auto-merge). - -## Why a fork test first - -- The pipeline writes commits, opens PRs, creates tags, publishes releases, and force-syncs `main` ↔ `develop`. Each of those is observable to the upstream team and to anyone subscribed to upstream releases. -- The repo already has a real git-flow with `hotfix/*`, `release/*` and `main`/`develop`. Renovate needs to slot into it cleanly — any misconfiguration produces tag noise, spurious PRs, or broken back-merges that are painful to clean up in a shared repo. -- The fork is a near-identical copy of upstream (same `pr.yaml`, same `changelog.yaml`, same `github_build_release.yml`, same composer.lock), so behavior should transfer 1:1 with only secret/owner substitutions. - -## Branch state (verified) - -| Branch | Origin (fork) | Upstream | HEAD | -| --- | --- | --- | --- | -| `develop` | ✅ | ✅ | `614c76e` (fork is ahead with renovate lock-file maintenance PRs) | -| `main` | ✅ (just pushed) | ✅ | `4402754` (Merge `release/1.11.0`) | - -Highest semver tag on upstream: **`1.11.0`**. First auto-release on the fork should produce `1.11.1`. - -## Context (why this is worth doing now) - -Running `composer audit` against `composer.lock` flags the May 20, 2026 coordinated Symfony + Twig disclosure: - -| Package | Locked | Fixed in | -| --- | --- | --- | -| `symfony/http-kernel`, `security-http`, `runtime`, etc. (v8.0.x) | v8.0.8–8.0.11 | **8.0.12** | -| `twig/twig` | v3.25.0 | **3.26.0** | - -Relevant CVEs: `CVE-2026-45075`, `CVE-2026-45065`, `CVE-2026-46626`, `CVE-2026-46633`, `CVE-2026-24425`, plus the HtmlSanitizer / Mailer / Notifier batch. All patch-level — exactly the class this pipeline auto-merges. - -## Architecture decisions (apply to both fork and upstream) - -- **Self-hosted Renovate** via `renovatebot/github-action` on a schedule. Self-hosted is required because `postUpgradeTasks` (the bit that writes `CHANGELOG.md` in the same commit as the dep bump) is disabled on Mend's hosted app. -- **Two base branches.** `baseBranches: ["main", "develop"]`. Patches/security/digest/lockFileMaintenance route to `main`; minor/major route to `develop`. `matchBaseBranches` in `packageRules` enforces the split. -- **Patch-only auto-merge.** Minor and major bumps open PRs against `develop` but require Dependency Dashboard approval. `php` package never auto-bumps. -- **CHANGELOG gate.** The repo's `changelog.yaml` workflow fails any PR that doesn't touch `CHANGELOG.md`. Rather than fork that workflow, Renovate appends an `[Unreleased]` bullet via `postUpgradeTasks` so every Renovate PR satisfies the gate. -- **Two PATs** so pushes re-trigger Actions (the default `GITHUB_TOKEN` doesn't): `RENOVATE_TOKEN` (Renovate's pushes to `renovate/*` branches) and `RELEASE_TOKEN` (the tag push that fires `github_build_release.yml`, plus the back-merge to `develop`). -- **Soak time:** 7 days for normal patches, 0 days for security advisories. The conservative window catches yanked releases and follow-up advisories; CVE-flagged patches still ship immediately via the vuln-alert path. - -## Deliverables (same files in both phases) - -``` -renovate.json -.github/workflows/renovate.yaml -.github/workflows/auto-release.yaml -.github/scripts/renovate-changelog.sh -``` - ---- - -# Phase 1 — Validate on the fork (`turegjorup/devops_itksites`) - -Goal: run the full pipeline against the fork's own `main` and `develop` until a clean Renovate PR → auto-merge → tag → GitHub Release → back-merge cycle completes end-to-end, twice (once for a regular patch, once for a security advisory). - -## Step 1.1 — Branch & baseline (already partly done) - -`main` has been fetched from upstream and pushed to origin (`4402754`). Now: - -```bash -git checkout develop && git pull origin develop -git checkout -b feat/renovate-auto-patch -``` - -Confirm no existing `renovate.json`, `.renovaterc*`, or `dependabot.yml` on develop. If `dependabot.yml` exists, delete it in this PR — Renovate replaces it. - -## Step 1.2 — Create `renovate.json` at repo root - -Key sections: - -- `extends`: `config:recommended`, `:dependencyDashboard`, `:semanticCommitsDisabled`, `:maintainLockFilesWeekly`, `schedule:weekdays`. -- `baseBranches: ["main", "develop"]`, `timezone: "Europe/Copenhagen"`. -- `vulnerabilityAlerts`: `automerge: true`, `minimumReleaseAge: "0 days"`, `prPriority: 10`, `commitMessagePrefix: "security:"`, `matchBaseBranches: ["main"]`. Also enable `osvVulnerabilityAlerts: true`. Security advisories always land on `main` so they ship as a hotfix release. -- `lockFileMaintenance`: weekly Monday morning, auto-merge, `matchBaseBranches: ["main"]`. -- `packageRules`: - - **Patch/pin/digest/lockFileMaintenance on `main`** → `matchBaseBranches: ["main"]`, `matchUpdateTypes: ["patch", "pin", "digest", "lockFileMaintenance"]`, `automerge: true`, `minimumReleaseAge: "7 days"`. The hotfix path. - - **Minor/major on `develop`** → `matchBaseBranches: ["develop"]`, `matchUpdateTypes: ["minor", "major"]`, `dependencyDashboardApproval: true`, `automerge: false`. The normal release path. - - **Suppression rules** so updates don't open against the wrong base: - - `matchBaseBranches: ["main"]` + `matchUpdateTypes: ["minor", "major"]` → `enabled: false`. - - `matchBaseBranches: ["develop"]` + `matchUpdateTypes: ["patch", "pin", "digest", "lockFileMaintenance"]` → `enabled: false` (patches reach develop via the back-merge). - - Group `^symfony/`, `^doctrine/`, `^api-platform/` patches each into one PR (apply on `main`). - - `php` package → `matchBaseBranches: ["develop"]`, `rangeStrategy: "in-range-only"`, no auto-merge. - - GitHub Actions → pin digests, auto-merge digest/patch on `main`. -- `postUpgradeTasks`: `bash .github/scripts/renovate-changelog.sh {{{branchName}}} {{{prTitle}}}`, `fileFilters: ["CHANGELOG.md"]`, `executionMode: "branch"`. - -## Step 1.3 — Create `.github/scripts/renovate-changelog.sh` - -Idempotent script that appends one bullet under `## [Unreleased]`: - -- `set -euo pipefail`. -- Exit 0 if `CHANGELOG.md` is already staged or modified in the working tree (prevents duplicate bullets when `postUpgradeTasks` fires multiple times for a grouped PR). -- Python heredoc splices `- Renovate: (\`\`)` into the `[Unreleased]` section. If no `[Unreleased]` section exists, create one after the intro paragraph. - -```bash -chmod +x .github/scripts/renovate-changelog.sh -``` - -Local validation: - -```bash -.github/scripts/renovate-changelog.sh renovate/symfony-patch "Update symfony (patch)" -git diff CHANGELOG.md # expect one new bullet under [Unreleased] -git checkout -- CHANGELOG.md -``` - -## Step 1.4 — Create `.github/workflows/renovate.yaml` - -- Triggers: `schedule` (weekdays 05:00 UTC) + `workflow_dispatch` with `logLevel` and `dryRun` inputs. -- `concurrency.group: renovate`, `cancel-in-progress: false`. -- Set up PHP 8.4 + Composer (needed so `composer update` works inside Renovate's container). -- `renovatebot/github-action@v43` with `token: ${{ secrets.RENOVATE_TOKEN }}`. -- Critical env vars for `postUpgradeTasks`: - - `RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^bash \\.github/scripts/renovate-changelog\\.sh"]'` - - `RENOVATE_ALLOW_POST_UPGRADE_COMMAND_TEMPLATING: "true"` -- `RENOVATE_REPOSITORIES: ${{ github.repository }}` — this means the same workflow file works unmodified on both fork and upstream; it always operates on its own repo. - -## Step 1.5 — Create `.github/workflows/auto-release.yaml` - -Trigger: `pull_request.closed` on `main`, gated by `github.event.pull_request.merged == true && github.event.pull_request.user.login == 'renovate[bot]'`. - -Steps: - -1. `actions/checkout@v6` with `ref: main`, `fetch-depth: 0`, `token: ${{ secrets.RELEASE_TOKEN }}`. -2. `lewagon/wait-on-check-action@v1.4.0` waiting on the merge commit's required checks (`PHP Unit tests`, `PHPStan`, `Validate Doctrine Schema`, `Load fixtures`, `Build assets`). -3. Compute next patch: `git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1`, bump the patch component. On the fork, the first run produces `1.11.1`. -4. Gate: if `[Unreleased]` is empty (`awk` extraction returns nothing), `skip=true` and stop. -5. Promote `[Unreleased]` → `[] - ` via Python heredoc. -6. `git commit -m "release: "`, `git tag -a `, push both to `main`. `github_build_release.yml` fires on the `*.*.*` tag. -7. **Back-merge `main` → `develop`:** - - `git fetch origin develop && git checkout develop` - - `git merge --no-ff main -m "chore: back-merge hotfix into develop"` - - On conflict: abort, then `gh pr create --base develop --head main --title "Back-merge hotfix "`. Do not force-push. - - Otherwise push develop with `RELEASE_TOKEN`. - -## Step 1.6 — Local validation before pushing - -```bash -python3 -c "import json; json.load(open('renovate.json'))" -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/renovate.yaml'))" -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/auto-release.yaml'))" -bash -n .github/scripts/renovate-changelog.sh -``` - -If Taskfile defines them, also: - -```bash -task lint -``` - -## Step 1.7 — Fork-side manual setup - -Done by **you (turegjorup)** as the fork owner before the pipeline runs: - -1. **Repository secrets on `turegjorup/devops_itksites`:** - - `RENOVATE_TOKEN` — a PAT on your account scoped to the fork with `contents:write`, `pull-requests:write`, `workflows`. For the test phase this can be a classic PAT or fine-grained PAT; a GitHub App is overkill. - - `RELEASE_TOKEN` — PAT with `contents:write`, `workflows` for the tag push + back-merge. -2. **Branch protection on `main` and `develop` (fork)** — match what you'll later configure on upstream: - - Required status checks: `PHP Unit tests`, `PHPStan`, `Validate Doctrine Schema`, `Load fixtures`, `Build assets`, `Changelog`, `composer-validate`, `composer-normalized`, `composer-audit`. - - Require branches up to date before merging. - - **Allow auto-merge** at the repo level. - - Allow `RELEASE_TOKEN` identity to direct-push (for back-merges); fallback to PR if blocked. -3. **(Optional) CODEOWNERS exemption** for `renovate[bot]` if you mirror upstream's CODEOWNERS. - -## Step 1.8 — Commit & open PR against the fork's `develop` - -```bash -git add renovate.json .github/workflows/renovate.yaml .github/workflows/auto-release.yaml .github/scripts/renovate-changelog.sh -# changelog.yaml will gate this PR — add a bullet to [Unreleased] -$EDITOR CHANGELOG.md -git add CHANGELOG.md -git commit -m "Add Renovate auto-patch + auto-release pipeline" -git push -u origin feat/renovate-auto-patch -gh pr create --repo turegjorup/devops_itksites --base develop --fill -``` - -Merge it once the existing `pr.yaml` checks pass. (No release fires from this — it lands on `develop`, not `main`.) - -## Step 1.9 — Promote the pipeline files to `main` on the fork - -The pipeline only takes effect for hotfix PRs once the workflow files and `renovate.json` exist on `main`. Two options: - -- **Quick:** cherry-pick the four pipeline files from develop to main with a manual PR (`gh pr create --base main --head feat/renovate-pipeline-main`). Safe because these are tooling files; no app behavior changes. -- **Slow:** wait for the next `release/*` → `main` cut to carry them across. - -Use the quick path for the fork test so the validation cycle isn't blocked on a release. - -## Step 1.10 — First-run verification on the fork - -1. **Dry-run.** Actions → Renovate → Run workflow with `dryRun: full`, `logLevel: debug`. Confirm logs show: - - Config parses cleanly. - - Patch candidates for `symfony/*` (8.0.8/9/10/11 → 8.0.12) and `twig/twig` (3.25.0 → 3.26.0) are scoped to `main`. - - Minor/major candidates (if any) scoped to `develop`. - - `postUpgradeTasks` listed as allowed. -2. **Live run.** Re-run with `dryRun: null`. Dependency Dashboard issue appears; first PRs open within minutes. -3. **First patch PR (against `main`):** - - `CHANGELOG.md` has exactly one new bullet. - - `pr.yaml`, `composer-audit`, `Changelog` all pass. - - PR auto-merges after `minimumReleaseAge` (security PRs immediately, regular patches after 7 days — use a security PR for the first test to skip the soak). -4. **Auto-release cycle:** - - `auto-release.yaml` runs after merge, computes `1.11.1`, promotes `[Unreleased]`, tags from `main`, pushes. - - `github_build_release.yml` builds the GitHub Release on the fork. -5. **Back-merge:** verify the release commit reached `develop` (push or PR). - -## Step 1.11 — Exit criteria for Phase 1 - -Move to Phase 2 only when **all of these** are observed on the fork: - -- One **security** PR (e.g. a Symfony 8.0.x patch) → auto-merged with 0-day soak → released → back-merged. -- One **regular patch** PR → auto-merged after 7-day soak → released → back-merged. (To avoid blocking the Phase 1 sign-off on a real 7-day wait, temporarily set `minimumReleaseAge: "0 days"` for this single test run, confirm the merge fires, then restore `"7 days"` before going to upstream.) -- One **grouped patch** PR (Symfony or Doctrine cluster) → auto-merged correctly without partial-update artifacts. -- One **minor or major** candidate appeared on the Dependency Dashboard against `develop` and did **not** auto-merge. -- A **back-merge conflict** scenario has been exercised at least once (synthesize one if it doesn't happen naturally) and the PR-fallback worked. -- The CHANGELOG ended each cycle correctly: `[Unreleased]` empty, `[1.11.x] - ` populated. - -Document any deviations or required tweaks in `RENOVATE_TEST_NOTES.md` (created during this phase, not promoted to upstream). - -## Step 1.12 — Cleanup before Phase 2 - -After the fork test is signed off: - -- Optionally delete the test releases (`1.11.1`, `1.11.2`, …) from the fork's Releases page to keep the fork's tag history tidy. The tags themselves can stay. -- Keep the `Renovate` Actions workflow enabled on the fork so it continues to track patches as a low-stakes early-warning system. - ---- - -# Phase 2 — Re-implement on upstream (`itk-dev/devops_itksites`) - -Goal: port the validated files to upstream with the minimum possible delta. The smaller the diff between fork and upstream, the cheaper the review and the lower the risk. - -## Step 2.1 — Sync fork's pipeline state to a fresh branch - -```bash -git fetch upstream develop -git checkout -b feat/renovate-auto-patch upstream/develop -git checkout feat/renovate-auto-patch -- \ - renovate.json \ - .github/workflows/renovate.yaml \ - .github/workflows/auto-release.yaml \ - .github/scripts/renovate-changelog.sh -# OR, if working from the merged develop on the fork: -# git checkout feat/renovate-auto-patch-fork -- -``` - -No content changes should be needed — `RENOVATE_REPOSITORIES: ${{ github.repository }}` makes the workflow portable. - -## Step 2.2 — Upstream-only manual setup (request from itk-dev admins) - -Mirror the fork's manual setup, but on upstream: - -1. **Repository secrets on `itk-dev/devops_itksites`:** - - `RENOVATE_TOKEN` — a **GitHub App** installation token. Create a dedicated app (e.g. `renovate-itkdev`) on the itk-dev org with `contents:write`, `pull-requests:write`, `workflows:write` permissions; install it on `devops_itksites`; store the App ID + private key as `RENOVATE_APP_ID` / `RENOVATE_APP_PRIVATE_KEY` secrets; the workflow exchanges them for an installation token at runtime via `actions/create-github-app-token`. The App identity (e.g. `renovate-itkdev[bot]`) is what shows up in commits and audit logs — no personnel coupling. - - `RELEASE_TOKEN` — same GitHub App token, or a separate App if you want auditing separation between Renovate pushes and release pushes. Needs `contents:write`, `workflows:write`. -2. **Branch protection on `main` and `develop`** — identical rule sets to the fork (status checks, up-to-date, auto-merge enabled, RELEASE_TOKEN allowed to push). -3. **CODEOWNERS** — if upstream has CODEOWNERS, either exempt `renovate[bot]` or accept that each PR needs a human approval before auto-merge. - -## Step 2.3 — PR + handoff - -```bash -$EDITOR CHANGELOG.md # add bullet under [Unreleased] -git add -A -git commit -m "Add Renovate auto-patch + auto-release pipeline" -git push -u upstream feat/renovate-auto-patch -gh pr create --repo itk-dev/devops_itksites --base develop --fill -``` - -PR body should link to: - -- This plan. -- The fork's successful Renovate run history (Actions → Renovate). -- The fork's first auto-release (`turegjorup/devops_itksites` Releases → `1.11.1`). - -Reviewers can verify the diff is small and that all the behavior they're approving has already been observed running on the fork. - -## Step 2.4 — Promote the files to upstream `main` - -After the PR merges to `upstream/develop`, the same promotion question from Step 1.9 applies upstream. Recommend the same quick path: a small, scoped PR cherry-picking the four pipeline files from `develop` to `main`. - -## Step 2.5 — First-run verification on upstream - -Same as Step 1.10, but on upstream. Watch the first cycle closely — security PR first (0-day soak) so feedback comes quickly. - ---- - -## Rollback - -Applies to both fork and upstream: - -- **Stop new PRs:** disable the `Renovate` workflow in the Actions tab. -- **Disable auto-release:** delete `.github/workflows/auto-release.yaml`. `github_build_release.yml` still works manually. -- **Disable auto-merge globally:** set top-level `automerge: false` in `renovate.json`. Renovate keeps opening PRs but waits for human merge. -- **Revert a bad back-merge:** the back-merge is `--no-ff`, so `git revert -m 1 ` cleanly undoes it on `develop` without touching the release on `main`. - -## Resolved decisions - -| Decision | Choice | Where it lives | -| --- | --- | --- | -| Upstream auth | **GitHub App** (`renovate-itkdev`-style). Personal PAT acceptable on the fork only. | Step 2.2 | -| Non-security soak window | **7 days.** Vuln-alert path overrides to 0 days for CVEs. | Step 1.2 patch package rule, Step 1.7 fork-side fallback | -| Version source for auto-release | **Git tags.** Highest `X.Y.Z` → bump patch. | Step 1.5 step 3 | -| Back-merge granularity | **Single grouped back-merge** per release, no per-package splitting. | Step 1.5 step 7 | - -## Pre-flight checklist (do before Phase 1 first-run) - -- [ ] Verify `CHANGELOG.md` on `main` does **not** already contain a `## [1.11.1]` header — if it does, either delete it or bump the auto-release seed to `1.11.2`. (Tag-based versioning will collide otherwise.) -- [ ] Confirm `composer.lock` is committed and matches `composer.json` (Renovate's `composer update` runs against the lock). -- [ ] Confirm `task lint` (or equivalent) passes locally on the four new files. From 36abadab649a73d47ca0330b65bf77c208b08faf Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 21 May 2026 12:53:18 +0200 Subject: [PATCH 53/94] chore: disable Codecov upload on the fork The fork has no CODECOV_TOKEN secret, so the upload step fails with fail_ci_if_error: true and takes the whole PHP Unit tests check down with it. Comment the step out here; restore it (or supply a token) when promoting to upstream. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pr.yaml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 5625c95..b7b2436 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -55,13 +55,15 @@ jobs: docker compose exec -e XDEBUG_MODE=coverage phpfpm bin/console --env=test doctrine:migrations:migrate --no-interaction --quiet docker compose exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-clover=coverage/unit.xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage/unit.xml - fail_ci_if_error: true - flags: unittests + # Codecov upload disabled on the fork — no CODECOV_TOKEN secret. + # Re-enable when promoting to upstream. + # - name: Upload coverage to Codecov + # uses: codecov/codecov-action@v5 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + # files: ./coverage/unit.xml + # fail_ci_if_error: true + # flags: unittests fixtures: runs-on: ubuntu-latest From 292ba8664bf668abe276613b4555312282a87848 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 21 May 2026 12:58:44 +0200 Subject: [PATCH 54/94] 13 --- .github/workflows/renovate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yaml b/.github/workflows/renovate.yaml index fb220e2..69a1f1f 100644 --- a/.github/workflows/renovate.yaml +++ b/.github/workflows/renovate.yaml @@ -12,7 +12,7 @@ name: Renovate on: schedule: - - cron: "0 10 * * 1-5" # weekdays 10:00 UTC = 12:00 Copenhagen (CEST) / 11:00 (CET) + - cron: "0 11 * * 1-5" # weekdays 11:00 UTC = 13:00 Copenhagen (CEST) / 12:00 (CET) workflow_dispatch: inputs: logLevel: From d77f4a099b57c90156a173623857aabb7375b500 Mon Sep 17 00:00:00 2001 From: martinyde Date: Wed, 27 May 2026 13:14:37 +0200 Subject: [PATCH 55/94] Added codeowner and project entities --- CHANGELOG.md | 14 +- migrations/Version20260527103011.php | 51 +++++ src/Command/SyncServiceAgreementsCommand.php | 12 +- .../Admin/SecurityContractCrudController.php | 29 +-- src/Entity/CodeOwner.php | 84 +++++++ src/Entity/Project.php | 184 +++++++++++++++ src/Entity/SecurityContract.php | 132 ++--------- src/Repository/CodeOwnerRepository.php | 25 +++ src/Repository/ProjectRepository.php | 25 +++ src/Service/ServiceAgreementSyncService.php | 211 +++++++++++++----- 10 files changed, 589 insertions(+), 178 deletions(-) create mode 100644 migrations/Version20260527103011.php create mode 100644 src/Entity/CodeOwner.php create mode 100644 src/Entity/Project.php create mode 100644 src/Repository/CodeOwnerRepository.php create mode 100644 src/Repository/ProjectRepository.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c21c572..0d9031d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- [#80](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements +- Adapt `/api/serviceagreements` consumer to new payload shape + - Add `Project` entity (top-level Economics project) with relations to + `CodeOwner` (ManyToMany), `GitRepo` (ManyToMany, matched by repo name), + and `SecurityContract` (OneToMany) + - Add `CodeOwner` entity (economicsId/name/email) + - Slim `SecurityContract` to the nested-agreement fields; drop + `projectName`, `clientName`, `leantimeUrl`, `projectTrackerKey`, + `gitRepos`, `quarterlyHours`, `cybersecurityPrice`, `cybersecurityNote`; + add ManyToOne to `Project` + - Rewrite `ServiceAgreementSyncService` to upsert projects, codeowners, + and contracts in one pass; warn when `githubRepos` names cannot be + linked to existing `GitRepo` rows +- [#80](https://github.com/itk-dev/devops_itksites/pull/80 ) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases - Add economics service and sync action/command for service agreement synchronization diff --git a/migrations/Version20260527103011.php b/migrations/Version20260527103011.php new file mode 100644 index 0000000..b1f8b2d --- /dev/null +++ b/migrations/Version20260527103011.php @@ -0,0 +1,51 @@ +addSql('CREATE TABLE code_owner (id BINARY(16) NOT NULL, created_at DATETIME NOT NULL, modified_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT \'\' NOT NULL, modified_by VARCHAR(255) DEFAULT \'\' NOT NULL, economics_id INT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2335FF304416F7E8 (economics_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + $this->addSql('CREATE TABLE project (id BINARY(16) NOT NULL, created_at DATETIME NOT NULL, modified_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT \'\' NOT NULL, modified_by VARCHAR(255) DEFAULT \'\' NOT NULL, economics_id INT NOT NULL, name VARCHAR(255) NOT NULL, leantime_id VARCHAR(255) DEFAULT NULL, leantime_url VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_2FB3D0EE4416F7E8 (economics_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + $this->addSql('CREATE TABLE project_code_owner (project_id BINARY(16) NOT NULL, code_owner_id BINARY(16) NOT NULL, INDEX IDX_3B938402166D1F9C (project_id), INDEX IDX_3B93840287BD19D2 (code_owner_id), PRIMARY KEY (project_id, code_owner_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + $this->addSql('CREATE TABLE project_git_repo (project_id BINARY(16) NOT NULL, git_repo_id BINARY(16) NOT NULL, INDEX IDX_CB848708166D1F9C (project_id), INDEX IDX_CB8487083E8A2A0D (git_repo_id), PRIMARY KEY (project_id, git_repo_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + $this->addSql('ALTER TABLE project_code_owner ADD CONSTRAINT FK_3B938402166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE project_code_owner ADD CONSTRAINT FK_3B93840287BD19D2 FOREIGN KEY (code_owner_id) REFERENCES code_owner (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE project_git_repo ADD CONSTRAINT FK_CB848708166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE project_git_repo ADD CONSTRAINT FK_CB8487083E8A2A0D FOREIGN KEY (git_repo_id) REFERENCES git_repo (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE security_contract ADD project_id BINARY(16) DEFAULT NULL, DROP project_name, DROP client_name, DROP leantime_url, DROP git_repos, DROP project_tracker_key, DROP quarterly_hours, DROP cybersecurity_price, DROP cybersecurity_note'); + $this->addSql('ALTER TABLE security_contract ADD CONSTRAINT FK_8AE4AF8B166D1F9C FOREIGN KEY (project_id) REFERENCES project (id)'); + $this->addSql('CREATE INDEX IDX_8AE4AF8B166D1F9C ON security_contract (project_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE project_code_owner DROP FOREIGN KEY FK_3B938402166D1F9C'); + $this->addSql('ALTER TABLE project_code_owner DROP FOREIGN KEY FK_3B93840287BD19D2'); + $this->addSql('ALTER TABLE project_git_repo DROP FOREIGN KEY FK_CB848708166D1F9C'); + $this->addSql('ALTER TABLE project_git_repo DROP FOREIGN KEY FK_CB8487083E8A2A0D'); + $this->addSql('DROP TABLE code_owner'); + $this->addSql('DROP TABLE project'); + $this->addSql('DROP TABLE project_code_owner'); + $this->addSql('DROP TABLE project_git_repo'); + $this->addSql('ALTER TABLE security_contract DROP FOREIGN KEY FK_8AE4AF8B166D1F9C'); + $this->addSql('DROP INDEX IDX_8AE4AF8B166D1F9C ON security_contract'); + $this->addSql('ALTER TABLE security_contract ADD project_name VARCHAR(255) NOT NULL, ADD client_name VARCHAR(255) DEFAULT NULL, ADD leantime_url VARCHAR(255) DEFAULT NULL, ADD git_repos LONGTEXT DEFAULT NULL, ADD project_tracker_key VARCHAR(255) DEFAULT NULL, ADD quarterly_hours DOUBLE PRECISION DEFAULT NULL, ADD cybersecurity_price DOUBLE PRECISION DEFAULT NULL, ADD cybersecurity_note LONGTEXT DEFAULT NULL, DROP project_id'); + } +} diff --git a/src/Command/SyncServiceAgreementsCommand.php b/src/Command/SyncServiceAgreementsCommand.php index 55fcb64..8c4ee44 100644 --- a/src/Command/SyncServiceAgreementsCommand.php +++ b/src/Command/SyncServiceAgreementsCommand.php @@ -26,9 +26,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); try { - $count = $this->syncService->syncAll(); + $result = $this->syncService->syncAll(); - $io->success(sprintf('Synced %d service agreements successfully.', $count)); + $io->success(sprintf('Synced %d projects successfully.', $result['projects'])); + + if (!empty($result['unmatchedRepoNames'])) { + $io->warning(sprintf( + 'Could not link %d GitHub repo name(s) to existing GitRepo entries: %s', + count($result['unmatchedRepoNames']), + implode(', ', $result['unmatchedRepoNames']), + )); + } } catch (\Throwable $e) { $io->error($e->getMessage()); diff --git a/src/Controller/Admin/SecurityContractCrudController.php b/src/Controller/Admin/SecurityContractCrudController.php index 758851d..1cac814 100644 --- a/src/Controller/Admin/SecurityContractCrudController.php +++ b/src/Controller/Admin/SecurityContractCrudController.php @@ -13,7 +13,6 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\DateField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField; use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField; -use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; @@ -37,8 +36,8 @@ public static function getEntityFqcn(): string public function configureCrud(Crud $crud): Crud { return $crud - ->setDefaultSort(['projectName' => 'ASC']) - ->setSearchFields(['projectName', 'clientName', 'hostingProvider']) + ->setDefaultSort(['project.name' => 'ASC']) + ->setSearchFields(['project.name', 'hostingProvider', 'serverSize']) ->showEntityActionsInlined() ->setPageTitle(Crud::PAGE_INDEX, 'Service Agreements') ->setHelp(Crud::PAGE_INDEX, 'Service agreements are synced from Economics. Click "Sync all" to update.'); @@ -59,12 +58,13 @@ public function configureFields(string $pageName): iterable yield FormField::addFieldset('Project'); yield BooleanField::new('active')->renderAsSwitch(false)->setColumns(2); yield BooleanField::new('eol')->setLabel('EOL')->renderAsSwitch(false)->setColumns(2); - yield TextField::new('projectName')->setColumns(8); - yield TextField::new('clientName')->hideOnIndex(); + yield TextField::new('project.name')->setLabel('Project')->setColumns(8); + yield TextField::new('project.leantimeId')->setLabel('Leantime ID')->hideOnIndex(); + yield TextField::new('projectGitRepos')->setLabel('GitHub repos')->hideOnIndex(); yield TextField::new('hostingProvider'); yield FormField::addFieldset('Links'); - yield UrlField::new('leantimeUrl')->setLabel('Leantime URL')->hideOnIndex(); + yield UrlField::new('project.leantimeUrl')->setLabel('Leantime URL')->hideOnIndex(); yield UrlField::new('documentUrl')->setLabel('Document URL')->hideOnIndex(); yield FormField::addFieldset('Contact'); @@ -73,15 +73,10 @@ public function configureFields(string $pageName): iterable yield FormField::addFieldset('Budget'); yield NumberField::new('monthlyPrice')->setTextAlign('right')->setColumns(6); - yield NumberField::new('quarterlyHours')->setTextAlign('right')->setColumns(6); - yield NumberField::new('cybersecurityPrice')->setTextAlign('right')->hideOnIndex()->setColumns(6); - yield TextareaField::new('cybersecurityNote')->hideOnIndex()->setColumns(12); yield FormField::addFieldset('Infrastructure'); yield BooleanField::new('dedicatedServer')->renderAsSwitch(false)->hideOnIndex(); yield TextField::new('serverSize')->hideOnIndex(); - yield TextareaField::new('gitRepos')->hideOnIndex(); - yield TextField::new('projectTrackerKey')->hideOnIndex(); yield FormField::addFieldset('Validity'); yield DateField::new('validFrom')->setColumns(6); @@ -92,9 +87,17 @@ public function configureFields(string $pageName): iterable public function syncAll(): RedirectResponse { try { - $count = $this->syncService->syncAll(); + $result = $this->syncService->syncAll(); - $this->addFlash('info', sprintf('Synced %d service agreements.', $count)); + $this->addFlash('info', sprintf('Synced %d projects.', $result['projects'])); + + if (!empty($result['unmatchedRepoNames'])) { + $this->addFlash('warning', sprintf( + 'Could not link %d GitHub repo name(s) to existing GitRepo entries: %s', + count($result['unmatchedRepoNames']), + implode(', ', $result['unmatchedRepoNames']), + )); + } } catch (\Throwable $e) { $this->addFlash('error', sprintf('An error occurred while syncing: %s', $e->getMessage())); } diff --git a/src/Entity/CodeOwner.php b/src/Entity/CodeOwner.php new file mode 100644 index 0000000..4d3e641 --- /dev/null +++ b/src/Entity/CodeOwner.php @@ -0,0 +1,84 @@ + + */ + #[ORM\ManyToMany(targetEntity: Project::class, mappedBy: 'codeOwners')] + private Collection $projects; + + public function __construct() + { + $this->projects = new ArrayCollection(); + } + + #[\Override] + public function __toString(): string + { + return $this->name; + } + + public function getEconomicsId(): ?int + { + return $this->economicsId; + } + + public function setEconomicsId(int $economicsId): static + { + $this->economicsId = $economicsId; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getEmail(): string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + /** + * @return Collection + */ + public function getProjects(): Collection + { + return $this->projects; + } +} diff --git a/src/Entity/Project.php b/src/Entity/Project.php new file mode 100644 index 0000000..acebcb9 --- /dev/null +++ b/src/Entity/Project.php @@ -0,0 +1,184 @@ + + */ + #[ORM\ManyToMany(targetEntity: CodeOwner::class, inversedBy: 'projects', cascade: ['persist'])] + #[ORM\JoinTable(name: 'project_code_owner')] + private Collection $codeOwners; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: GitRepo::class)] + #[ORM\JoinTable(name: 'project_git_repo')] + private Collection $gitRepos; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: SecurityContract::class, mappedBy: 'project', cascade: ['persist'])] + private Collection $serviceAgreements; + + public function __construct() + { + $this->codeOwners = new ArrayCollection(); + $this->gitRepos = new ArrayCollection(); + $this->serviceAgreements = new ArrayCollection(); + } + + #[\Override] + public function __toString(): string + { + return $this->name; + } + + public function getEconomicsId(): ?int + { + return $this->economicsId; + } + + public function setEconomicsId(int $economicsId): static + { + $this->economicsId = $economicsId; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getLeantimeId(): ?string + { + return $this->leantimeId; + } + + public function setLeantimeId(?string $leantimeId): static + { + $this->leantimeId = $leantimeId; + + return $this; + } + + public function getLeantimeUrl(): ?string + { + return $this->leantimeUrl; + } + + public function setLeantimeUrl(?string $leantimeUrl): static + { + $this->leantimeUrl = $leantimeUrl; + + return $this; + } + + /** + * @return Collection + */ + public function getCodeOwners(): Collection + { + return $this->codeOwners; + } + + public function addCodeOwner(CodeOwner $codeOwner): static + { + if (!$this->codeOwners->contains($codeOwner)) { + $this->codeOwners->add($codeOwner); + } + + return $this; + } + + public function removeCodeOwner(CodeOwner $codeOwner): static + { + $this->codeOwners->removeElement($codeOwner); + + return $this; + } + + /** + * @return Collection + */ + public function getGitRepos(): Collection + { + return $this->gitRepos; + } + + public function addGitRepo(GitRepo $gitRepo): static + { + if (!$this->gitRepos->contains($gitRepo)) { + $this->gitRepos->add($gitRepo); + } + + return $this; + } + + public function removeGitRepo(GitRepo $gitRepo): static + { + $this->gitRepos->removeElement($gitRepo); + + return $this; + } + + /** + * @return Collection + */ + public function getServiceAgreements(): Collection + { + return $this->serviceAgreements; + } + + public function addServiceAgreement(SecurityContract $serviceAgreement): static + { + if (!$this->serviceAgreements->contains($serviceAgreement)) { + $this->serviceAgreements->add($serviceAgreement); + $serviceAgreement->setProject($this); + } + + return $this; + } + + public function removeServiceAgreement(SecurityContract $serviceAgreement): static + { + if ($this->serviceAgreements->removeElement($serviceAgreement)) { + if ($serviceAgreement->getProject() === $this) { + $serviceAgreement->setProject(null); + } + } + + return $this; + } +} diff --git a/src/Entity/SecurityContract.php b/src/Entity/SecurityContract.php index 390cdd7..c03cdef 100644 --- a/src/Entity/SecurityContract.php +++ b/src/Entity/SecurityContract.php @@ -12,11 +12,9 @@ class SecurityContract extends AbstractBaseEntity implements \Stringable #[ORM\Column(unique: true)] private ?int $economicsId = null; - #[ORM\Column(length: 255)] - private string $projectName = ''; - - #[ORM\Column(length: 255, nullable: true)] - private ?string $clientName = null; + #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'serviceAgreements')] + #[ORM\JoinColumn(nullable: true)] + private ?Project $project = null; #[ORM\Column(length: 255, nullable: true)] private ?string $hostingProvider = null; @@ -39,9 +37,6 @@ class SecurityContract extends AbstractBaseEntity implements \Stringable #[ORM\Column] private bool $eol = false; - #[ORM\Column(length: 255, nullable: true)] - private ?string $leantimeUrl = null; - #[ORM\Column(length: 255, nullable: true)] private ?string $clientContactName = null; @@ -54,61 +49,48 @@ class SecurityContract extends AbstractBaseEntity implements \Stringable #[ORM\Column(length: 255, nullable: true)] private ?string $serverSize = null; - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $gitRepos = null; - #[ORM\Column(type: Types::JSON, nullable: true)] private ?array $systemOwnerNotices = null; - #[ORM\Column(length: 255, nullable: true)] - private ?string $projectTrackerKey = null; - - #[ORM\Column(nullable: true)] - private ?float $quarterlyHours = null; - - #[ORM\Column(nullable: true)] - private ?float $cybersecurityPrice = null; - - #[ORM\Column(type: Types::TEXT, nullable: true)] - private ?string $cybersecurityNote = null; - public function __toString(): string { - return $this->projectName; + return $this->project?->getName() ?? (string) $this->economicsId; } - public function getEconomicsId(): ?int + public function getProjectGitRepos(): ?string { - return $this->economicsId; - } + if (null === $this->project) { + return null; + } - public function setEconomicsId(int $economicsId): static - { - $this->economicsId = $economicsId; + $names = array_map( + static fn (GitRepo $repo): string => (string) $repo, + $this->project->getGitRepos()->toArray(), + ); - return $this; + return [] === $names ? null : implode(', ', $names); } - public function getProjectName(): string + public function getEconomicsId(): ?int { - return $this->projectName; + return $this->economicsId; } - public function setProjectName(string $projectName): static + public function setEconomicsId(int $economicsId): static { - $this->projectName = $projectName; + $this->economicsId = $economicsId; return $this; } - public function getClientName(): ?string + public function getProject(): ?Project { - return $this->clientName; + return $this->project; } - public function setClientName(?string $clientName): static + public function setProject(?Project $project): static { - $this->clientName = $clientName; + $this->project = $project; return $this; } @@ -197,18 +179,6 @@ public function setEol(bool $eol): static return $this; } - public function getLeantimeUrl(): ?string - { - return $this->leantimeUrl; - } - - public function setLeantimeUrl(?string $leantimeUrl): static - { - $this->leantimeUrl = $leantimeUrl; - - return $this; - } - public function getClientContactName(): ?string { return $this->clientContactName; @@ -257,18 +227,6 @@ public function setServerSize(?string $serverSize): static return $this; } - public function getGitRepos(): ?string - { - return $this->gitRepos; - } - - public function setGitRepos(?string $gitRepos): static - { - $this->gitRepos = $gitRepos; - - return $this; - } - public function getSystemOwnerNotices(): ?array { return $this->systemOwnerNotices; @@ -280,52 +238,4 @@ public function setSystemOwnerNotices(?array $systemOwnerNotices): static return $this; } - - public function getProjectTrackerKey(): ?string - { - return $this->projectTrackerKey; - } - - public function setProjectTrackerKey(?string $projectTrackerKey): static - { - $this->projectTrackerKey = $projectTrackerKey; - - return $this; - } - - public function getQuarterlyHours(): ?float - { - return $this->quarterlyHours; - } - - public function setQuarterlyHours(?float $quarterlyHours): static - { - $this->quarterlyHours = $quarterlyHours; - - return $this; - } - - public function getCybersecurityPrice(): ?float - { - return $this->cybersecurityPrice; - } - - public function setCybersecurityPrice(?float $cybersecurityPrice): static - { - $this->cybersecurityPrice = $cybersecurityPrice; - - return $this; - } - - public function getCybersecurityNote(): ?string - { - return $this->cybersecurityNote; - } - - public function setCybersecurityNote(?string $cybersecurityNote): static - { - $this->cybersecurityNote = $cybersecurityNote; - - return $this; - } } diff --git a/src/Repository/CodeOwnerRepository.php b/src/Repository/CodeOwnerRepository.php new file mode 100644 index 0000000..9e85b43 --- /dev/null +++ b/src/Repository/CodeOwnerRepository.php @@ -0,0 +1,25 @@ + + * + * @method CodeOwner|null find($id, $lockMode = null, $lockVersion = null) + * @method CodeOwner|null findOneBy(array $criteria, array $orderBy = null) + * @method CodeOwner[] findAll() + * @method CodeOwner[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class CodeOwnerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, CodeOwner::class); + } +} diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php new file mode 100644 index 0000000..d5c2e25 --- /dev/null +++ b/src/Repository/ProjectRepository.php @@ -0,0 +1,25 @@ + + * + * @method Project|null find($id, $lockMode = null, $lockVersion = null) + * @method Project|null findOneBy(array $criteria, array $orderBy = null) + * @method Project[] findAll() + * @method Project[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ProjectRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Project::class); + } +} diff --git a/src/Service/ServiceAgreementSyncService.php b/src/Service/ServiceAgreementSyncService.php index 4c7be95..99b8c0c 100644 --- a/src/Service/ServiceAgreementSyncService.php +++ b/src/Service/ServiceAgreementSyncService.php @@ -2,92 +2,213 @@ namespace App\Service; +use App\Entity\CodeOwner; +use App\Entity\GitRepo; +use App\Entity\Project; use App\Entity\SecurityContract; +use App\Repository\CodeOwnerRepository; +use App\Repository\GitRepoRepository; +use App\Repository\ProjectRepository; +use App\Repository\SecurityContractRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; /** - * Syncs service agreements from the Economics API (economics.itkdev.dk) - * into local SecurityContract entities. + * Syncs projects (with nested service agreements and codeowners) from the + * Economics API (economics.itkdev.dk) into local Project / CodeOwner / + * SecurityContract entities. */ readonly class ServiceAgreementSyncService { - /** - * @param EntityManagerInterface $entityManager Doctrine entity manager for persisting contracts - * @param HttpClientInterface $economicsClient scoped HTTP client for the Economics API - */ + private const string ENDPOINT = '/api/projects'; + private const string DEFAULT_TIMEZONE = 'UTC'; + public function __construct( private EntityManagerInterface $entityManager, private HttpClientInterface $economicsClient, + private ProjectRepository $projectRepository, + private CodeOwnerRepository $codeOwnerRepository, + private SecurityContractRepository $securityContractRepository, + private GitRepoRepository $gitRepoRepository, ) { } /** - * Fetch all service agreements from the Economics API and sync them locally. + * Fetch all projects from the Economics API and sync them locally. * - * Creates new, updates existing (matched by economics ID), and removes - * contracts that no longer exist in the API response. - * - * @return int the number of agreements synced + * @return array{projects:int, unmatchedRepoNames:list} * * @throws \RuntimeException|\Exception if the API request fails */ - public function syncAll(): int + public function syncAll(): array { try { - $response = $this->economicsClient->request('GET', '/api/serviceagreements'); - $agreements = $response->toArray(); + $response = $this->economicsClient->request('GET', self::ENDPOINT); + $projectsData = $response->toArray(); } catch (ExceptionInterface $e) { - throw new \RuntimeException('Failed to fetch service agreements from Economics API: '.$e->getMessage(), 0, $e); + throw new \RuntimeException('Failed to fetch projects from Economics API: '.$e->getMessage(), 0, $e); } - $existingContracts = $this->entityManager->getRepository(SecurityContract::class)->findAll(); - $existingByEconomicsId = []; - foreach ($existingContracts as $contract) { - $existingByEconomicsId[$contract->getEconomicsId()] = $contract; + $existingProjects = []; + foreach ($this->projectRepository->findAll() as $project) { + $existingProjects[$project->getEconomicsId()] = $project; } - $seenIds = []; + $existingCodeOwners = []; + foreach ($this->codeOwnerRepository->findAll() as $codeOwner) { + $existingCodeOwners[$codeOwner->getEconomicsId()] = $codeOwner; + } - foreach ($agreements as $data) { - $economicsId = $data['id']; - $seenIds[] = $economicsId; + $existingContracts = []; + foreach ($this->securityContractRepository->findAll() as $contract) { + $existingContracts[$contract->getEconomicsId()] = $contract; + } - $contract = $existingByEconomicsId[$economicsId] ?? new SecurityContract(); + $existingGitReposByRepo = []; + foreach ($this->gitRepoRepository->findAll() as $repo) { + $existingGitReposByRepo[$repo->getRepo()] = $repo; + } - $this->mapDataToContract($contract, $data); + $seenProjectIds = []; + $seenCodeOwnerIds = []; + $seenContractIds = []; + $unmatchedRepoNames = []; + + foreach ($projectsData as $data) { + $project = $existingProjects[$data['id']] ?? new Project(); + $project->setEconomicsId($data['id']); + $project->setName($data['name'] ?? ''); + $project->setLeantimeId(isset($data['projectTrackerId']) ? (string) $data['projectTrackerId'] : null); + $project->setLeantimeUrl($data['leantimeUrl'] ?? null); + + $this->syncCodeOwners($project, $data['codeowners'] ?? [], $existingCodeOwners, $seenCodeOwnerIds); + $this->syncGitRepos($project, $data['githubRepos'] ?? null, $existingGitReposByRepo, $unmatchedRepoNames); + + $this->entityManager->persist($project); + $existingProjects[$data['id']] = $project; + $seenProjectIds[] = $data['id']; + + $serviceAgreementData = $data['serviceAgreement'] ?? null; + if (is_array($serviceAgreementData) && isset($serviceAgreementData['id'])) { + $contract = $existingContracts[$serviceAgreementData['id']] ?? new SecurityContract(); + $this->mapServiceAgreementToContract($contract, $serviceAgreementData, $project); + $this->entityManager->persist($contract); + $existingContracts[$serviceAgreementData['id']] = $contract; + $seenContractIds[] = $serviceAgreementData['id']; + } + } - if (null === $contract->getEconomicsId()) { - $contract->setEconomicsId($economicsId); + foreach ($existingContracts as $economicsId => $contract) { + if (!in_array($economicsId, $seenContractIds, true)) { + $this->entityManager->remove($contract); } + } - $this->entityManager->persist($contract); + foreach ($existingProjects as $economicsId => $project) { + if (!in_array($economicsId, $seenProjectIds, true)) { + $this->entityManager->remove($project); + } } - foreach ($existingContracts as $contract) { - if (!in_array($contract->getEconomicsId(), $seenIds, true)) { - $this->entityManager->remove($contract); + foreach ($existingCodeOwners as $economicsId => $codeOwner) { + if (!in_array($economicsId, $seenCodeOwnerIds, true)) { + $this->entityManager->remove($codeOwner); } } $this->entityManager->flush(); - return count($agreements); + return [ + 'projects' => count($projectsData), + 'unmatchedRepoNames' => array_keys($unmatchedRepoNames), + ]; } /** - * Map API response data onto a SecurityContract entity. + * @param array> $codeOwnersData + * @param array $existingCodeOwners passed by reference so newly-created owners are reused within one sync run + * @param list $seenCodeOwnerIds * - * @param array $data a single service agreement from the API + * @param-out array $existingCodeOwners + * @param-out list $seenCodeOwnerIds + */ + private function syncCodeOwners(Project $project, array $codeOwnersData, array &$existingCodeOwners, array &$seenCodeOwnerIds): void + { + $desired = []; + foreach ($codeOwnersData as $ownerData) { + if (!isset($ownerData['id'])) { + continue; + } + + $economicsId = (int) $ownerData['id']; + $owner = $existingCodeOwners[$economicsId] ?? new CodeOwner(); + $owner->setEconomicsId($economicsId); + $owner->setName((string) ($ownerData['name'] ?? '')); + $owner->setEmail((string) ($ownerData['email'] ?? '')); + + $existingCodeOwners[$economicsId] = $owner; + $seenCodeOwnerIds[] = $economicsId; + + $this->entityManager->persist($owner); + $desired[$economicsId] = $owner; + } + + foreach ($project->getCodeOwners() as $existing) { + if (!isset($desired[$existing->getEconomicsId()])) { + $project->removeCodeOwner($existing); + } + } + foreach ($desired as $owner) { + $project->addCodeOwner($owner); + } + } + + /** + * @param array $existingGitReposByRepo + * @param array $unmatchedRepoNames accumulator across all projects + */ + private function syncGitRepos(Project $project, ?string $githubReposString, array $existingGitReposByRepo, array &$unmatchedRepoNames): void + { + $names = []; + if (null !== $githubReposString && '' !== $githubReposString) { + foreach (preg_split('/\r\n|\r|\n/', $githubReposString) ?: [] as $line) { + $trimmed = trim($line); + if ('' !== $trimmed) { + $names[] = $trimmed; + } + } + } + + $desired = []; + foreach ($names as $repoName) { + if (isset($existingGitReposByRepo[$repoName])) { + $repo = $existingGitReposByRepo[$repoName]; + $desired[spl_object_id($repo)] = $repo; + } else { + $unmatchedRepoNames[$repoName] = true; + } + } + + foreach ($project->getGitRepos() as $existing) { + if (!isset($desired[spl_object_id($existing)])) { + $project->removeGitRepo($existing); + } + } + foreach ($desired as $repo) { + $project->addGitRepo($repo); + } + } + + /** + * @param array $data * * @throws \Exception */ - private function mapDataToContract(SecurityContract $contract, array $data): void + private function mapServiceAgreementToContract(SecurityContract $contract, array $data, Project $project): void { $contract->setEconomicsId($data['id']); - $contract->setProjectName($data['projectName'] ?? ''); - $contract->setClientName($data['clientName'] ?? null); + $contract->setProject($project); $contract->setHostingProvider($data['hostingProvider'] ?? null); $contract->setDocumentUrl($data['documentUrl'] ?? null); $contract->setMonthlyPrice(isset($data['price']) ? (float) $data['price'] : null); @@ -95,29 +216,17 @@ private function mapDataToContract(SecurityContract $contract, array $data): voi $contract->setValidTo($this->parseDate($data['validTo'] ?? null)); $contract->setActive($data['isActive'] ?? false); $contract->setEol($data['isEol'] ?? false); - $contract->setLeantimeUrl($data['leantimeUrl'] ?? null); $contract->setClientContactName($data['clientContactName'] ?? null); $contract->setClientContactEmail($data['clientContactEmail'] ?? null); $contract->setDedicatedServer($data['dedicatedServer'] ?? false); $contract->setServerSize($data['serverSize'] ?? null); - $contract->setGitRepos($data['gitRepos'] ?? null); $contract->setSystemOwnerNotices($data['systemOwnerNotices'] ?? null); - $contract->setProjectTrackerKey($data['projectTrackerKey'] ?? null); - - $cyber = $data['cybersecurityAgreement'] ?? null; - if (is_array($cyber)) { - $contract->setQuarterlyHours(isset($cyber['quarterlyHours']) ? (float) $cyber['quarterlyHours'] : null); - $contract->setCybersecurityPrice(isset($cyber['price']) ? (float) $cyber['price'] : null); - $contract->setCybersecurityNote($cyber['note'] ?? null); - } } /** * Parse the Economics API date format ({date, timezone_type, timezone}) into a DateTimeImmutable. * - * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData raw date object from the API - * - * @return \DateTimeImmutable|null the parsed date, or null if no valid date data was provided + * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData * * @throws \Exception if the date string cannot be parsed */ @@ -127,7 +236,7 @@ private function parseDate(?array $dateData): ?\DateTimeImmutable return null; } - $timezone = new \DateTimeZone($dateData['timezone'] ?? 'UTC'); + $timezone = new \DateTimeZone($dateData['timezone'] ?? self::DEFAULT_TIMEZONE); return new \DateTimeImmutable($dateData['date'], $timezone); } From a3846dd7600c1615b8dc059ca3da9472e7622ead Mon Sep 17 00:00:00 2001 From: martinyde Date: Wed, 27 May 2026 14:21:41 +0200 Subject: [PATCH 56/94] Add advisory list across each repo --- src/Controller/Admin/DashboardController.php | 1 + .../Admin/RepoAdvisoryController.php | 87 +++++++++++++++++++ src/Repository/GitRepoRepository.php | 32 +++++++ src/Repository/ProjectRepository.php | 23 +++++ templates/admin/repo_advisory/index.html.twig | 79 +++++++++++++++++ 5 files changed, 222 insertions(+) create mode 100644 src/Controller/Admin/RepoAdvisoryController.php create mode 100644 templates/admin/repo_advisory/index.html.twig diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 7044546..5ca9619 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -57,6 +57,7 @@ public function configureMenuItems(): iterable yield MenuItem::linkTo(PackageCrudController::class, 'Packages', 'fas fa-cube'); yield MenuItem::linkTo(PackageVersionCrudController::class, 'Package Versions', 'fas fa-cubes'); yield MenuItem::linkTo(AdvisoryCrudController::class, 'Advisories', 'fas fa-skull-crossbones')->setBadge($this->advisoryRepository->count([]), 'dark'); + yield MenuItem::linkToRoute('Repo advisories', 'fas fa-shield-virus', 'admin_repo_advisories'); yield MenuItem::linkTo(ModuleCrudController::class, 'Modules', 'fas fa-cube'); yield MenuItem::linkTo(ModuleVersionCrudController::class, 'Modules Versions', 'fas fa-cubes'); yield MenuItem::linkTo(DockerImageCrudController::class, 'Docker Images', 'fas fa-cube'); diff --git a/src/Controller/Admin/RepoAdvisoryController.php b/src/Controller/Admin/RepoAdvisoryController.php new file mode 100644 index 0000000..ea67dd2 --- /dev/null +++ b/src/Controller/Admin/RepoAdvisoryController.php @@ -0,0 +1,87 @@ +gitRepoRepository->findReposWithAdvisoryCount(); + + $rows = []; + foreach ($reposWithCount as $entry) { + $repo = $entry['repo']; + $projects = $this->projectRepository->findByGitRepo($repo); + + $codeOwners = []; + foreach ($projects as $project) { + foreach ($project->getCodeOwners() as $codeOwner) { + $codeOwners[$codeOwner->getId()?->toRfc4122() ?? ''] = $codeOwner; + } + } + + $rows[] = [ + 'repo' => $repo, + 'advisoryCount' => $entry['advisoryCount'], + 'projects' => $projects, + 'codeOwners' => array_values($codeOwners), + ]; + } + + return $this->render('admin/repo_advisory/index.html.twig', [ + 'rows' => $rows, + 'csrf_intent' => self::CSRF_INTENT, + ]); + } + + #[Route('/admin/repo-advisories/{repo}/print-codeowner', name: 'admin_repo_advisories_print_codeowner', methods: ['POST'])] + public function printCodeOwner(Request $request, GitRepo $repo): RedirectResponse + { + if (!$this->isCsrfTokenValid(self::CSRF_INTENT, (string) $request->request->get('_token'))) { + $this->addFlash('error', 'Invalid CSRF token; please retry.'); + + return $this->redirectToRoute('admin_repo_advisories'); + } + + $codeOwnerId = (string) $request->request->get('codeOwnerId', ''); + if ('' === $codeOwnerId) { + $this->addFlash('warning', 'No code owner selected.'); + + return $this->redirectToRoute('admin_repo_advisories'); + } + + $codeOwner = $this->codeOwnerRepository->find($codeOwnerId); + if (!$codeOwner instanceof CodeOwner) { + $this->addFlash('error', 'Code owner not found.'); + + return $this->redirectToRoute('admin_repo_advisories'); + } + + $this->addFlash('info', sprintf('Code owner: %s', $codeOwner->getName())); + + return $this->redirectToRoute('admin_repo_advisories'); + } +} diff --git a/src/Repository/GitRepoRepository.php b/src/Repository/GitRepoRepository.php index d960d78..81dba04 100644 --- a/src/Repository/GitRepoRepository.php +++ b/src/Repository/GitRepoRepository.php @@ -22,4 +22,36 @@ public function __construct(ManagerRegistry $registry) { parent::__construct($registry, GitRepo::class); } + + /** + * Repos reachable from any package-version advisory, with their advisory count. + * + * Path: GitRepo → GitTag → Installation → PackageVersion → Advisory. + * + * @return list + */ + public function findReposWithAdvisoryCount(): array + { + /** @var list $rows */ + $rows = $this->createQueryBuilder('r') + ->select('r AS repo', 'COUNT(DISTINCT a.id) AS advisoryCount') + ->innerJoin('r.gitTags', 'gt') + ->innerJoin('gt.installations', 'i') + ->innerJoin('i.packageVersions', 'pv') + ->innerJoin('pv.advisories', 'a') + ->groupBy('r.id') + ->having('COUNT(DISTINCT a.id) > 0') + ->orderBy('r.organization', 'ASC') + ->addOrderBy('r.repo', 'ASC') + ->getQuery() + ->getResult(); + + return array_map( + static fn (array $row): array => [ + 'repo' => $row['repo'], + 'advisoryCount' => (int) $row['advisoryCount'], + ], + $rows, + ); + } } diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php index d5c2e25..8c62e7a 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/ProjectRepository.php @@ -4,6 +4,7 @@ namespace App\Repository; +use App\Entity\GitRepo; use App\Entity\Project; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -22,4 +23,26 @@ public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Project::class); } + + /** + * Projects that link to the given repo, with codeOwners and serviceAgreements eager-loaded. + * + * @return list + */ + public function findByGitRepo(GitRepo $repo): array + { + /** @var list $projects */ + $projects = $this->createQueryBuilder('p') + ->select('p', 'co', 'sc') + ->leftJoin('p.codeOwners', 'co') + ->leftJoin('p.serviceAgreements', 'sc') + ->innerJoin('p.gitRepos', 'gr') + ->where('gr = :repo') + ->setParameter('repo', $repo) + ->orderBy('p.name', 'ASC') + ->getQuery() + ->getResult(); + + return $projects; + } } diff --git a/templates/admin/repo_advisory/index.html.twig b/templates/admin/repo_advisory/index.html.twig new file mode 100644 index 0000000..64339ab --- /dev/null +++ b/templates/admin/repo_advisory/index.html.twig @@ -0,0 +1,79 @@ +{% extends '@!EasyAdmin/layout.html.twig' %} + +{% block title %}Repositories with advisories{% endblock %} + +{% block content_title %}Repositories with advisories{% endblock %} + +{% block main %} + {% if rows is empty %} +
No repositories have associated advisories.
+ {% else %} + + + + + + + + + + + + {% for row in rows %} + {% set repo = row.repo %} + {% set repo_url = ea_url() + .unsetAll() + .setController('App\\Controller\\Admin\\GitRepoCrudController') + .setAction('detail') + .setEntityId(repo.id) %} + + + + + + + + {% endfor %} + +
RepositoryAdvisoriesProjectService agreementsCode owner action
{{ repo }} + {{ row.advisoryCount }} + + {% for project in row.projects %} + {{ project.name }}{{ not loop.last ? ', ' }} + {% else %} + — + {% endfor %} + + {% set contracts = [] %} + {% for project in row.projects %} + {% for sc in project.serviceAgreements %} + {% set contracts = contracts|merge([sc]) %} + {% endfor %} + {% endfor %} + {% for sc in contracts %} +
+ {{ sc.hostingProvider ?? sc.economicsId }} + {% if sc.validTo %}(until {{ sc.validTo|date('Y-m-d') }}){% endif %} +
+ {% else %} + — + {% endfor %} +
+ {% if row.codeOwners is empty %} + No code owners + {% else %} +
+ + + +
+ {% endif %} +
+ {% endif %} +{% endblock %} From 73b2351b9a6757347efeb6037b4e37a2c1782a39 Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 10:01:32 +0200 Subject: [PATCH 57/94] Added leantime integration --- config/packages/framework.yaml | 6 + config/services.yaml | 1 + .../Admin/AdvisoryCrudController.php | 4 +- .../Admin/RepoAdvisoryController.php | 134 +++++++++- src/Repository/GitRepoRepository.php | 35 +++ src/Repository/ProjectRepository.php | 5 +- src/Service/LeantimeService.php | 235 ++++++++++++++++++ templates/admin/repo_advisory/index.html.twig | 44 +++- 8 files changed, 450 insertions(+), 14 deletions(-) create mode 100644 src/Service/LeantimeService.php diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 1aa06cb..7f37920 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -20,6 +20,12 @@ framework: scope: '%env(APP_ECONOMICS_URI)%' headers: x-api-key: '%env(APP_ECONOMICS_API_KEY)%' + leantime.client: + base_uri: '%env(default:default_leantime_uri:APP_LEANTIME_URI)%' + scope: '%env(default:default_leantime_uri:APP_LEANTIME_URI)%' + headers: + Content-Type: 'application/json' + x-api-key: '%env(default::APP_LEANTIME_API_KEY)%' when@test: framework: diff --git a/config/services.yaml b/config/services.yaml index 71d40c9..106c945 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,6 +4,7 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + default_leantime_uri: 'http://leantime.invalid' services: # default configuration for services in *this* file diff --git a/src/Controller/Admin/AdvisoryCrudController.php b/src/Controller/Admin/AdvisoryCrudController.php index 50c18ec..362085e 100644 --- a/src/Controller/Admin/AdvisoryCrudController.php +++ b/src/Controller/Admin/AdvisoryCrudController.php @@ -18,6 +18,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField; +use EasyCorp\Bundle\EasyAdminBundle\Filter\EntityFilter; class AdvisoryCrudController extends AbstractCrudController { @@ -66,7 +67,8 @@ public function configureFields(string $pageName): iterable public function configureFilters(Filters $filters): Filters { return $filters - ->add('package') + ->add(EntityFilter::new('package')->canSelectMultiple()) + ->add(EntityFilter::new('packageVersions')->canSelectMultiple()) ->add('advisoryId') ->add('cve') ->add('reportedAt') diff --git a/src/Controller/Admin/RepoAdvisoryController.php b/src/Controller/Admin/RepoAdvisoryController.php index ea67dd2..59c9f0e 100644 --- a/src/Controller/Admin/RepoAdvisoryController.php +++ b/src/Controller/Admin/RepoAdvisoryController.php @@ -5,31 +5,60 @@ namespace App\Controller\Admin; use App\Entity\CodeOwner; -use App\Entity\GitRepo; use App\Repository\CodeOwnerRepository; use App\Repository\GitRepoRepository; use App\Repository\ProjectRepository; +use App\Service\LeantimeService; +use App\Service\ServiceAgreementSyncService; +use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute; +use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; +use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; +/** + * Repo-advisories admin page. + * + * Not an EasyAdmin CRUD controller. The page joins four entities + * (GitRepo + Project + SecurityContract + CodeOwner) with derived columns, + * augments each row with live Leantime ticket state via LeantimeService, and + * renders an inline ` + + + + {% if rows is empty %}
No repositories have associated advisories.
{% else %} @@ -12,6 +23,7 @@ Repository + Project type & version Advisories Project Service agreements @@ -28,8 +40,21 @@ .setEntityId(repo.id) %} {{ repo }} + + {% for tv in row.typesAndVersions %} + {{ tv }} + {% else %} + — + {% endfor %} + - {{ row.advisoryCount }} + {% if row.advisoriesUrl %} + + {{ row.advisoryCount }} + + {% else %} + {{ row.advisoryCount }} + {% endif %} {% for project in row.projects %} @@ -55,19 +80,30 @@ {% endfor %} - {% if row.codeOwners is empty %} + {% if row.openTicket %} +
+ + Issue assigned to {{ row.openTicket.assigneeName ?? 'Unassigned' }} + {% if row.openTicket.createdAt %} + (created {{ row.openTicket.createdAt|date('Y-m-d') }}) + {% endif %} +
+ {% elseif row.leantimeProjectId is null %} + No Leantime project linked + {% elseif row.codeOwners is empty %} No code owners {% else %}
+ - +
{% endif %} From c61a4141416cc65a4c2f812c83ce887f092c26fe Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 11:29:23 +0200 Subject: [PATCH 58/94] Moved controller content to service --- .../Admin/RepoAdvisoryController.php | 120 ++--------- src/Service/LeantimeService.php | 80 +++++++- src/Service/RepoAdvisoryService.php | 190 ++++++++++++++++++ src/Service/ServiceAgreementSyncService.php | 73 +++++-- 4 files changed, 342 insertions(+), 121 deletions(-) create mode 100644 src/Service/RepoAdvisoryService.php diff --git a/src/Controller/Admin/RepoAdvisoryController.php b/src/Controller/Admin/RepoAdvisoryController.php index 59c9f0e..1ab836b 100644 --- a/src/Controller/Admin/RepoAdvisoryController.php +++ b/src/Controller/Admin/RepoAdvisoryController.php @@ -4,15 +4,9 @@ namespace App\Controller\Admin; -use App\Entity\CodeOwner; -use App\Repository\CodeOwnerRepository; -use App\Repository\GitRepoRepository; -use App\Repository\ProjectRepository; -use App\Service\LeantimeService; +use App\Service\RepoAdvisoryService; use App\Service\ServiceAgreementSyncService; use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute; -use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; -use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -23,11 +17,14 @@ * * Not an EasyAdmin CRUD controller. The page joins four entities * (GitRepo + Project + SecurityContract + CodeOwner) with derived columns, - * augments each row with live Leantime ticket state via LeantimeService, and - * renders an inline ` + submit` form per row for the create-ticket action. EA's Action + * API only emits link-style row actions, not in-row form widgets, so a CRUD + * controller would need a custom index.html.twig override that re-implements + * the same row loops anyway. + * + * Data assembly and Leantime orchestration live in RepoAdvisoryService — this + * controller only handles request parsing, CSRF, flashes, and rendering. * * The actions still integrate with the admin shell: routes use #[AdminRoute], * so EA auto-tags this class as an admin-route controller, populates @@ -38,92 +35,22 @@ class RepoAdvisoryController extends AbstractController private const string CSRF_INTENT = 'repo_advisory_action'; public function __construct( - private readonly AdminUrlGenerator $adminUrlGenerator, - private readonly GitRepoRepository $gitRepoRepository, - private readonly ProjectRepository $projectRepository, - private readonly CodeOwnerRepository $codeOwnerRepository, + private readonly RepoAdvisoryService $repoAdvisoryService, private readonly ServiceAgreementSyncService $serviceAgreementSyncService, - private readonly LeantimeService $leantimeService, ) { } #[AdminRoute(path: '/repo-advisories', name: 'repo_advisories', options: ['methods' => ['GET']])] public function index(): Response { - $reposWithCount = $this->gitRepoRepository->findReposWithAdvisoryCount(); - $packageVersionsPerRepo = $this->gitRepoRepository->findPackageVersionsPerRepoWithAdvisories(); - - $ticketsByLeantimeId = []; - try { - $ticketsByLeantimeId = $this->leantimeService->findOpenSecurityTickets(); - } catch (\Throwable $e) { - $this->addFlash('warning', sprintf('Could not fetch Leantime tickets: %s', $e->getMessage())); - } - - $rows = []; - foreach ($reposWithCount as $entry) { - $repo = $entry['repo']; - $projects = $this->projectRepository->findByGitRepo($repo); - - $codeOwners = []; - foreach ($projects as $project) { - foreach ($project->getCodeOwners() as $codeOwner) { - $codeOwners[$codeOwner->getId()?->toRfc4122() ?? ''] = $codeOwner; - } - } + $result = $this->repoAdvisoryService->buildIndexRows(); - $typesAndVersions = []; - foreach ($repo->getGitTags() as $gitTag) { - foreach ($gitTag->getInstallations() as $installation) { - $key = trim(($installation->getType() ?? '').' '.($installation->getFrameworkVersion() ?? '')); - if ('' !== $key) { - $typesAndVersions[$key] = true; - } - } - } - ksort($typesAndVersions); - - $repoKey = (string) $repo->getId(); - $packageVersionIds = $packageVersionsPerRepo[$repoKey] ?? []; - $advisoriesUrl = [] === $packageVersionIds ? null : $this->adminUrlGenerator - ->unsetAll() - ->setController(AdvisoryCrudController::class) - ->setAction(Crud::PAGE_INDEX) - ->set('filters', ['packageVersions' => ['comparison' => '=', 'value' => $packageVersionIds]]) - ->generateUrl(); - - $openTicket = null; - $leantimeProjectId = null; - foreach ($projects as $project) { - $rawLeantimeId = $project->getLeantimeId(); - if (null === $rawLeantimeId || '' === $rawLeantimeId) { - continue; - } - $candidateId = (int) $rawLeantimeId; - if (null === $leantimeProjectId) { - $leantimeProjectId = $candidateId; - } - if (isset($ticketsByLeantimeId[$candidateId])) { - $openTicket = $ticketsByLeantimeId[$candidateId]; - $leantimeProjectId = $candidateId; - break; - } - } - - $rows[] = [ - 'repo' => $repo, - 'advisoryCount' => $entry['advisoryCount'], - 'advisoriesUrl' => $advisoriesUrl, - 'typesAndVersions' => array_keys($typesAndVersions), - 'projects' => $projects, - 'codeOwners' => array_values($codeOwners), - 'openTicket' => $openTicket, - 'leantimeProjectId' => $leantimeProjectId, - ]; + if (null !== $result['leantimeError']) { + $this->addFlash('warning', sprintf('Could not fetch Leantime tickets: %s', $result['leantimeError'])); } return $this->render('admin/repo_advisory/index.html.twig', [ - 'rows' => $rows, + 'rows' => $result['rows'], 'csrf_intent' => self::CSRF_INTENT, ]); } @@ -181,25 +108,18 @@ public function createTicket(Request $request, string $repoId): RedirectResponse return $this->redirectToRoute('admin_repo_advisories'); } - $codeOwner = $this->codeOwnerRepository->find($codeOwnerId); - if (!$codeOwner instanceof CodeOwner) { - $this->addFlash('error', 'Code owner not found.'); - - return $this->redirectToRoute('admin_repo_advisories'); - } - try { - $userId = $this->leantimeService->findUserIdByEmail($codeOwner->getEmail()); - if (null === $userId) { + $result = $this->repoAdvisoryService->createSecurityTicketForCodeOwner($codeOwnerId, $leantimeProjectId); + + if ($result['unassigned']) { $this->addFlash('warning', sprintf( 'Code owner %s has no matching Leantime user (email: %s); creating ticket unassigned.', - $codeOwner->getName(), - $codeOwner->getEmail(), + $result['codeOwner']->getName(), + $result['codeOwner']->getEmail(), )); } - $ticketId = $this->leantimeService->createSecurityTicket($leantimeProjectId, $userId); - $this->addFlash('info', sprintf('Created Leantime ticket #%d.', $ticketId)); + $this->addFlash('info', sprintf('Created Leantime ticket #%d.', $result['ticketId'])); } catch (\Throwable $e) { $this->addFlash('error', sprintf('Failed to create Leantime ticket: %s', $e->getMessage())); } diff --git a/src/Service/LeantimeService.php b/src/Service/LeantimeService.php index 708d45b..7f28c9d 100644 --- a/src/Service/LeantimeService.php +++ b/src/Service/LeantimeService.php @@ -58,11 +58,15 @@ public function __construct( /** * Find currently-open security tickets across all Leantime projects. * - * Pre-filters via searchCriteria (term + type + status), then tightens - * the LIKE match to an exact headline check, then keeps the most recent - * match per projectId. + * Pre-filters via the Leantime `searchCriteria` (term + type + status), + * then tightens the server-side LIKE match into an exact headline check, + * and finally keeps only the most recent matching ticket per Leantime + * project id. Used by the repo-advisories page to show whether a project + * already has an open security ticket. * - * @return array keyed by Leantime project id + * @return array tickets keyed by Leantime project id + * + * @throws \RuntimeException if the Leantime API rejects the request or the transport fails */ public function findOpenSecurityTickets(): array { @@ -112,6 +116,20 @@ public function findOpenSecurityTickets(): array return $byProjectId; } + /** + * Resolve a Leantime user id for an email address. + * + * Loads (and caches) the Leantime user directory on first call and looks + * the email up case-insensitively. Returns null when the email is empty + * or not present in Leantime, so callers can decide whether to fall back + * to an unassigned ticket. + * + * @param string $email free-form email — leading/trailing whitespace and case are normalized + * + * @return int|null the Leantime user id, or null when no match exists + * + * @throws \RuntimeException if the user directory fetch fails + */ public function findUserIdByEmail(string $email): ?int { $email = mb_strtolower(trim($email)); @@ -126,9 +144,18 @@ public function findUserIdByEmail(string $email): ?int /** * Create a "Sikkerhedsopdatering" task in the given Leantime project. * - * @return int the new ticket's Leantime ID + * Submits a ticket with priority `critical`, status `new`, and a one-hour + * planned/remaining estimate. Dates default to "today" — Leantime requires + * editFrom/editTo/dateToFinish on creation, so we set all three. When + * `$userId` is null the ticket is created unassigned (Leantime accepts an + * empty editorId). + * + * @param int $projectId Leantime project id the ticket belongs to + * @param int|null $userId Leantime user id to assign the ticket to, or null for unassigned + * + * @return int the new ticket's Leantime id, or 0 if Leantime returned an unexpected response shape * - * @throws \RuntimeException if the API returns an error + * @throws \RuntimeException if the API rejects the request or the transport fails */ public function createSecurityTicket(int $projectId, ?int $userId = null): int { @@ -161,11 +188,20 @@ public function createSecurityTicket(int $projectId, ?int $userId = null): int } /** - * Send a JSON-RPC 2.0 request. + * Send a JSON-RPC 2.0 request to the Leantime API. + * + * Wraps the scoped HTTP client with the JSON-RPC envelope (jsonrpc/method/ + * params/id) and unwraps the response. The HTTP client supplies the base + * URI and x-api-key header; this method just shapes the body and decodes + * the response. Both transport-level failures and API-level error + * responses are normalized into a RuntimeException. + * + * @param string $method JSON-RPC method name (e.g. `leantime.rpc.tickets.getAll`) + * @param array $params method parameters to forward verbatim to Leantime * - * @param array $params + * @return mixed the decoded `result` field from the JSON-RPC response, or null when absent * - * @throws \RuntimeException on transport error or API error response + * @throws \RuntimeException on transport error or when the API responds with an `error` object */ private function request(string $method, array $params = []): mixed { @@ -190,6 +226,21 @@ private function request(string $method, array $params = []): mixed return $data['result'] ?? null; } + /** + * Look up a Leantime user's display name by id. + * + * Loads (and caches) the user directory on first call. Accepts mixed + * input because Leantime delivers ids as either strings or ints in + * different payloads; both are coerced to int for lookup. Returns null + * when the id is empty or unknown so the caller can fall back to a + * neutral label like "Unassigned". + * + * @param mixed $userId raw user id straight from the Leantime payload (int, numeric string, or null) + * + * @return string|null the user's display name, or null when no match exists + * + * @throws \RuntimeException if the user directory fetch fails + */ private function resolveUserName(mixed $userId): ?string { if (null === $userId || '' === $userId) { @@ -200,6 +251,17 @@ private function resolveUserName(mixed $userId): ?string return $this->userNamesById[(int) $userId] ?? null; } + /** + * Populate the user id/name/email caches from Leantime. + * + * Idempotent: a non-null `$userNamesById` short-circuits the call so the + * directory is fetched at most once per service instance. Builds both an + * id → display-name map (preferring firstname+lastname, falling back to + * username) and an email → id map keyed by lowercase email. Malformed + * entries (missing id) are skipped silently. + * + * @throws \RuntimeException if the Leantime API rejects the request or the transport fails + */ private function loadUsers(): void { if (null !== $this->userNamesById) { diff --git a/src/Service/RepoAdvisoryService.php b/src/Service/RepoAdvisoryService.php new file mode 100644 index 0000000..b4be6af --- /dev/null +++ b/src/Service/RepoAdvisoryService.php @@ -0,0 +1,190 @@ +>, leantimeError: ?string} rows keyed by repo plus an optional Leantime error message for the caller to flash + */ + public function buildIndexRows(): array + { + $reposWithCount = $this->gitRepoRepository->findReposWithAdvisoryCount(); + $packageVersionsPerRepo = $this->gitRepoRepository->findPackageVersionsPerRepoWithAdvisories(); + + $ticketsByLeantimeId = []; + $leantimeError = null; + try { + $ticketsByLeantimeId = $this->leantimeService->findOpenSecurityTickets(); + } catch (\Throwable $e) { + $leantimeError = $e->getMessage(); + } + + $rows = []; + foreach ($reposWithCount as $entry) { + $rows[] = $this->buildRow($entry, $packageVersionsPerRepo, $ticketsByLeantimeId); + } + + return [ + 'rows' => $rows, + 'leantimeError' => $leantimeError, + ]; + } + + /** + * Create a Leantime "Sikkerhedsopdatering" ticket on behalf of a code owner. + * + * Resolves the code owner via the repository, asks LeantimeService to + * translate its email into a Leantime user id, then creates the security + * ticket on the given project. When the email cannot be mapped to a + * Leantime user the ticket is still created — just unassigned — and the + * caller learns about it via the `unassigned` flag in the result. + * + * @param string $codeOwnerId RFC-4122 UUID of a CodeOwner entity + * @param int $leantimeProjectId numeric Leantime project id (external + * system's id, not an ORM id) + * + * @return array{ticketId: int, codeOwner: CodeOwner, unassigned: bool} the new ticket id, the resolved code owner, and whether the ticket is unassigned + * + * @throws \RuntimeException if the code owner cannot be found or the + * Leantime API rejects the request + */ + public function createSecurityTicketForCodeOwner(string $codeOwnerId, int $leantimeProjectId): array + { + $codeOwner = $this->codeOwnerRepository->find($codeOwnerId); + if (!$codeOwner instanceof CodeOwner) { + throw new \RuntimeException('Code owner not found.'); + } + + $userId = $this->leantimeService->findUserIdByEmail($codeOwner->getEmail()); + $ticketId = $this->leantimeService->createSecurityTicket($leantimeProjectId, $userId); + + return [ + 'ticketId' => $ticketId, + 'codeOwner' => $codeOwner, + 'unassigned' => null === $userId, + ]; + } + + /** + * Build a single repo-advisory row for the admin index. + * + * Encapsulates the per-repo joins: projects, code owners (deduplicated by + * id), installation type/version labels, the AdvisoryCrudController + * deep-link, and the matching Leantime ticket (preferring the project + * whose id has an open ticket; otherwise falling back to the first + * project's Leantime id). + * + * @param array{repo: \App\Entity\GitRepo, advisoryCount: int} $entry repo + precomputed advisory count + * @param array> $packageVersionsPerRepo map of repo-id → package version ids that have advisories + * @param array $ticketsByLeantimeId open security tickets keyed by Leantime project id + * + * @return array Row data ready for the Twig template + */ + private function buildRow(array $entry, array $packageVersionsPerRepo, array $ticketsByLeantimeId): array + { + $repo = $entry['repo']; + $projects = $this->projectRepository->findByGitRepo($repo); + + $codeOwners = []; + foreach ($projects as $project) { + foreach ($project->getCodeOwners() as $codeOwner) { + $codeOwners[$codeOwner->getId()?->toRfc4122() ?? ''] = $codeOwner; + } + } + + $typesAndVersions = []; + foreach ($repo->getGitTags() as $gitTag) { + foreach ($gitTag->getInstallations() as $installation) { + $key = trim(($installation->getType() ?? '').' '.($installation->getFrameworkVersion() ?? '')); + if ('' !== $key) { + $typesAndVersions[$key] = true; + } + } + } + ksort($typesAndVersions); + + $repoKey = (string) $repo->getId(); + $packageVersionIds = $packageVersionsPerRepo[$repoKey] ?? []; + $advisoriesUrl = [] === $packageVersionIds ? null : $this->adminUrlGenerator + ->unsetAll() + ->setController(AdvisoryCrudController::class) + ->setAction(Crud::PAGE_INDEX) + ->set('filters', ['packageVersions' => ['comparison' => '=', 'value' => $packageVersionIds]]) + ->generateUrl(); + + $openTicket = null; + $leantimeProjectId = null; + foreach ($projects as $project) { + $rawLeantimeId = $project->getLeantimeId(); + if (null === $rawLeantimeId || '' === $rawLeantimeId) { + continue; + } + $candidateId = (int) $rawLeantimeId; + if (null === $leantimeProjectId) { + $leantimeProjectId = $candidateId; + } + if (isset($ticketsByLeantimeId[$candidateId])) { + $openTicket = $ticketsByLeantimeId[$candidateId]; + $leantimeProjectId = $candidateId; + break; + } + } + + return [ + 'repo' => $repo, + 'advisoryCount' => $entry['advisoryCount'], + 'advisoriesUrl' => $advisoriesUrl, + 'typesAndVersions' => array_keys($typesAndVersions), + 'projects' => $projects, + 'codeOwners' => array_values($codeOwners), + 'openTicket' => $openTicket, + 'leantimeProjectId' => $leantimeProjectId, + ]; + } +} diff --git a/src/Service/ServiceAgreementSyncService.php b/src/Service/ServiceAgreementSyncService.php index 99b8c0c..fccbea6 100644 --- a/src/Service/ServiceAgreementSyncService.php +++ b/src/Service/ServiceAgreementSyncService.php @@ -37,9 +37,17 @@ public function __construct( /** * Fetch all projects from the Economics API and sync them locally. * - * @return array{projects:int, unmatchedRepoNames:list} + * The Economics API is treated as the source of truth: every Project, + * CodeOwner and SecurityContract whose `economicsId` is not present in + * the response is removed. Nested data (codeowners, github repos and the + * service-agreement payload) is reconciled per project. GitHub repo names + * that cannot be matched against an existing GitRepo entry are collected + * and returned to the caller so they can be surfaced to the admin. * - * @throws \RuntimeException|\Exception if the API request fails + * @return array{projects: int, unmatchedRepoNames: list} count of projects processed and the list of unresolvable GitHub repo names + * + * @throws \RuntimeException if the Economics API request fails + * @throws \Exception if a date string in the payload cannot be parsed */ public function syncAll(): array { @@ -126,9 +134,20 @@ public function syncAll(): array } /** - * @param array> $codeOwnersData - * @param array $existingCodeOwners passed by reference so newly-created owners are reused within one sync run - * @param list $seenCodeOwnerIds + * Reconcile a project's code owners against the Economics payload. + * + * Upserts every code owner present in `$codeOwnersData` (creating new + * CodeOwner entities when needed), persists them, and adjusts the + * project's association so it ends up linked to exactly the desired set. + * Both `$existingCodeOwners` and `$seenCodeOwnerIds` are passed by + * reference because the caller reuses them across all projects in a + * single sync run — newly created owners must be reachable on the next + * iteration and the seen-list drives the post-loop cleanup pass. + * + * @param Project $project project being synced + * @param array> $codeOwnersData raw `codeowners` array straight from Economics + * @param array $existingCodeOwners by-reference id-keyed lookup; mutated to include newly-created owners + * @param list $seenCodeOwnerIds by-reference accumulator of every economicsId touched this run * * @param-out array $existingCodeOwners * @param-out list $seenCodeOwnerIds @@ -165,8 +184,22 @@ private function syncCodeOwners(Project $project, array $codeOwnersData, array & } /** - * @param array $existingGitReposByRepo - * @param array $unmatchedRepoNames accumulator across all projects + * Reconcile a project's GitHub repo associations against the Economics payload. + * + * Splits the multi-line `githubRepos` string into individual repo names, + * matches each one against the existing GitRepo lookup, and adjusts the + * project's association to the desired set. Repo names that have no + * matching GitRepo entry are recorded in `$unmatchedRepoNames` so the + * caller can warn the operator; GitRepo entries are NOT created on the + * fly because they are normally populated by the harvester and creating + * a half-empty one here would mask the underlying onboarding gap. + * + * @param Project $project project being synced + * @param string|null $githubReposString raw multi-line list from Economics, or null when the field is unset + * @param array $existingGitReposByRepo lookup keyed by GitRepo::getRepo() + * @param array $unmatchedRepoNames by-reference accumulator across all projects (set-like) + * + * @param-out array $unmatchedRepoNames */ private function syncGitRepos(Project $project, ?string $githubReposString, array $existingGitReposByRepo, array &$unmatchedRepoNames): void { @@ -201,9 +234,19 @@ private function syncGitRepos(Project $project, ?string $githubReposString, arra } /** - * @param array $data + * Copy a single Economics `serviceAgreement` payload onto a SecurityContract. + * + * Pure field-by-field mapping plus a project association — no persistence, + * no fetches. The contract may be either an existing entity (re-synced) + * or a brand-new one; the caller decides and persists it afterwards. + * Dates run through `parseDate()` which handles the Economics-specific + * `{date, timezone_type, timezone}` shape. + * + * @param SecurityContract $contract entity to mutate in place + * @param array $data raw `serviceAgreement` payload from Economics + * @param Project $project project the contract belongs to * - * @throws \Exception + * @throws \Exception if a date string in the payload cannot be parsed */ private function mapServiceAgreementToContract(SecurityContract $contract, array $data, Project $project): void { @@ -224,11 +267,17 @@ private function mapServiceAgreementToContract(SecurityContract $contract, array } /** - * Parse the Economics API date format ({date, timezone_type, timezone}) into a DateTimeImmutable. + * Parse the Economics API's `{date, timezone_type, timezone}` shape into a DateTimeImmutable. + * + * Returns null when the payload is null or missing the `date` key so the + * caller can leave the contract field unset. The timezone string from the + * payload is used as-is when present; otherwise UTC is assumed. + * + * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData raw date payload from Economics, or null when absent * - * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData + * @return \DateTimeImmutable|null parsed date, or null when no date was supplied * - * @throws \Exception if the date string cannot be parsed + * @throws \Exception if the date string cannot be parsed by DateTimeImmutable */ private function parseDate(?array $dateData): ?\DateTimeImmutable { From 835450b6cd03a679d90973caf7ecf85e3d6cc87d Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 11:58:18 +0200 Subject: [PATCH 59/94] Updated documentation --- src/Service/LeantimeService.php | 61 ++++++++------------- src/Service/RepoAdvisoryService.php | 52 +++++++----------- src/Service/ServiceAgreementSyncService.php | 50 +++++++---------- 3 files changed, 64 insertions(+), 99 deletions(-) diff --git a/src/Service/LeantimeService.php b/src/Service/LeantimeService.php index 7f28c9d..ed1374a 100644 --- a/src/Service/LeantimeService.php +++ b/src/Service/LeantimeService.php @@ -10,14 +10,11 @@ /** * Minimal JSON-RPC 2.0 client for the Leantime API. * - * Only the calls this app needs: list open "Sikkerhedsopdatering" tickets, - * map codeowner emails to Leantime user IDs, and create new security tickets. - * The JSON-RPC contract (POST /api/jsonrpc/, x-api-key header) is copied - * verbatim from the reference implementation in leantime_ticket_generator. - * - * The scoped HTTP client `$leantimeClient` (configured in framework.yaml) - * provides the base URI and the x-api-key header, so this class only needs - * to assemble the JSON-RPC body and unwrap responses. + * Exposes the calls this app needs: listing open "Sikkerhedsopdatering" + * tickets, mapping codeowner emails to user IDs, and creating security + * tickets. The scoped HTTP client `$leantimeClient` supplies the base URI and + * the x-api-key header; this class only assembles the JSON-RPC body and + * unwraps responses. */ class LeantimeService { @@ -59,10 +56,8 @@ public function __construct( * Find currently-open security tickets across all Leantime projects. * * Pre-filters via the Leantime `searchCriteria` (term + type + status), - * then tightens the server-side LIKE match into an exact headline check, - * and finally keeps only the most recent matching ticket per Leantime - * project id. Used by the repo-advisories page to show whether a project - * already has an open security ticket. + * tightens the LIKE match into an exact headline check, and keeps the + * most recent matching ticket per Leantime project id. * * @return array tickets keyed by Leantime project id * @@ -119,10 +114,9 @@ public function findOpenSecurityTickets(): array /** * Resolve a Leantime user id for an email address. * - * Loads (and caches) the Leantime user directory on first call and looks - * the email up case-insensitively. Returns null when the email is empty - * or not present in Leantime, so callers can decide whether to fall back - * to an unassigned ticket. + * Lazy-loads the Leantime user directory on first call and looks the + * email up case-insensitively. Returns null when the email is empty or + * unknown so the caller can fall back to an unassigned ticket. * * @param string $email free-form email — leading/trailing whitespace and case are normalized * @@ -144,11 +138,9 @@ public function findUserIdByEmail(string $email): ?int /** * Create a "Sikkerhedsopdatering" task in the given Leantime project. * - * Submits a ticket with priority `critical`, status `new`, and a one-hour - * planned/remaining estimate. Dates default to "today" — Leantime requires - * editFrom/editTo/dateToFinish on creation, so we set all three. When - * `$userId` is null the ticket is created unassigned (Leantime accepts an - * empty editorId). + * Submits a ticket with priority `critical`, status `new`, a one-hour + * planned estimate, and editFrom/editTo/dateToFinish all set to today. + * A null `$userId` produces an unassigned ticket. * * @param int $projectId Leantime project id the ticket belongs to * @param int|null $userId Leantime user id to assign the ticket to, or null for unassigned @@ -190,11 +182,9 @@ public function createSecurityTicket(int $projectId, ?int $userId = null): int /** * Send a JSON-RPC 2.0 request to the Leantime API. * - * Wraps the scoped HTTP client with the JSON-RPC envelope (jsonrpc/method/ - * params/id) and unwraps the response. The HTTP client supplies the base - * URI and x-api-key header; this method just shapes the body and decodes - * the response. Both transport-level failures and API-level error - * responses are normalized into a RuntimeException. + * Wraps the call in the JSON-RPC envelope, decodes the response, and + * normalizes both transport failures and API-level error objects into + * RuntimeException. * * @param string $method JSON-RPC method name (e.g. `leantime.rpc.tickets.getAll`) * @param array $params method parameters to forward verbatim to Leantime @@ -229,13 +219,11 @@ private function request(string $method, array $params = []): mixed /** * Look up a Leantime user's display name by id. * - * Loads (and caches) the user directory on first call. Accepts mixed - * input because Leantime delivers ids as either strings or ints in - * different payloads; both are coerced to int for lookup. Returns null - * when the id is empty or unknown so the caller can fall back to a - * neutral label like "Unassigned". + * Lazy-loads the user directory on first call and coerces the id to int + * (Leantime delivers it as either string or int). Returns null when the + * id is empty or unknown. * - * @param mixed $userId raw user id straight from the Leantime payload (int, numeric string, or null) + * @param mixed $userId raw user id from the Leantime payload (int, numeric string, or null) * * @return string|null the user's display name, or null when no match exists * @@ -254,11 +242,10 @@ private function resolveUserName(mixed $userId): ?string /** * Populate the user id/name/email caches from Leantime. * - * Idempotent: a non-null `$userNamesById` short-circuits the call so the - * directory is fetched at most once per service instance. Builds both an - * id → display-name map (preferring firstname+lastname, falling back to - * username) and an email → id map keyed by lowercase email. Malformed - * entries (missing id) are skipped silently. + * Idempotent — fetches the directory at most once per service instance. + * Builds an id → display-name map (firstname+lastname, falling back to + * username) and an email → id map keyed by lowercase email; entries with + * no id are skipped. * * @throws \RuntimeException if the Leantime API rejects the request or the transport fails */ diff --git a/src/Service/RepoAdvisoryService.php b/src/Service/RepoAdvisoryService.php index b4be6af..82f094a 100644 --- a/src/Service/RepoAdvisoryService.php +++ b/src/Service/RepoAdvisoryService.php @@ -15,16 +15,10 @@ /** * Page-flow service backing the repo-advisories admin index. * - * Owns the data assembly that used to live in RepoAdvisoryController: joining - * GitRepo + Project + CodeOwner state, deriving installation type/version - * labels, building the deep-link URL into AdvisoryCrudController, and - * augmenting each row with live Leantime ticket state. Also orchestrates the - * inline "create security ticket" action by combining a CodeOwner lookup with - * LeantimeService calls. - * - * Kept separate from LeantimeService so the JSON-RPC client stays a thin - * Leantime API wrapper, and separate from ServiceAgreementSyncService since - * the shapes here exist only to feed the admin template. + * Joins GitRepo + Project + CodeOwner state into per-repo rows for the admin + * template, augments each row with the matching Leantime security ticket, and + * orchestrates the inline "create security ticket" action by combining a + * CodeOwner lookup with LeantimeService calls. */ class RepoAdvisoryService { @@ -40,17 +34,12 @@ public function __construct( /** * Build the rows + warnings rendered by the repo-advisories admin index. * - * Loads every GitRepo that has at least one advisory, attaches its - * projects/codeowners, derives installation-type/version labels, computes - * a filtered URL into AdvisoryCrudController for the repo's affected - * package versions, and pairs each row with the most recent matching - * Leantime "Sikkerhedsopdatering" ticket (if any). - * - * Failures while fetching Leantime tickets are caught and surfaced via the - * returned `leantimeError` so the page can still render the rows without - * ticket state — Leantime being down should not break the advisories view. + * Loads every GitRepo with at least one advisory and pairs each one with + * its matching open Leantime ticket. A Leantime fetch failure is captured + * in `leantimeError` so the page can still render the rows without ticket + * state. * - * @return array{rows: list>, leantimeError: ?string} rows keyed by repo plus an optional Leantime error message for the caller to flash + * @return array{rows: list>, leantimeError: ?string} rows plus an optional Leantime error for the caller to flash */ public function buildIndexRows(): array { @@ -79,15 +68,13 @@ public function buildIndexRows(): array /** * Create a Leantime "Sikkerhedsopdatering" ticket on behalf of a code owner. * - * Resolves the code owner via the repository, asks LeantimeService to - * translate its email into a Leantime user id, then creates the security - * ticket on the given project. When the email cannot be mapped to a - * Leantime user the ticket is still created — just unassigned — and the - * caller learns about it via the `unassigned` flag in the result. + * Resolves the code owner, maps its email to a Leantime user id, and + * creates the ticket on the given project. When no Leantime user matches + * the ticket is still created — just unassigned — signalled via the + * `unassigned` flag in the result. * * @param string $codeOwnerId RFC-4122 UUID of a CodeOwner entity - * @param int $leantimeProjectId numeric Leantime project id (external - * system's id, not an ORM id) + * @param int $leantimeProjectId numeric Leantime project id (external system's id, not an ORM id) * * @return array{ticketId: int, codeOwner: CodeOwner, unassigned: bool} the new ticket id, the resolved code owner, and whether the ticket is unassigned * @@ -114,17 +101,16 @@ public function createSecurityTicketForCodeOwner(string $codeOwnerId, int $leant /** * Build a single repo-advisory row for the admin index. * - * Encapsulates the per-repo joins: projects, code owners (deduplicated by - * id), installation type/version labels, the AdvisoryCrudController - * deep-link, and the matching Leantime ticket (preferring the project - * whose id has an open ticket; otherwise falling back to the first - * project's Leantime id). + * Resolves the repo's projects, deduplicates their code owners, derives + * installation type/version labels, builds the AdvisoryCrudController + * deep-link, and picks the Leantime project id — preferring one with an + * open ticket, otherwise the first non-empty project id. * * @param array{repo: \App\Entity\GitRepo, advisoryCount: int} $entry repo + precomputed advisory count * @param array> $packageVersionsPerRepo map of repo-id → package version ids that have advisories * @param array $ticketsByLeantimeId open security tickets keyed by Leantime project id * - * @return array Row data ready for the Twig template + * @return array row data ready for the Twig template */ private function buildRow(array $entry, array $packageVersionsPerRepo, array $ticketsByLeantimeId): array { diff --git a/src/Service/ServiceAgreementSyncService.php b/src/Service/ServiceAgreementSyncService.php index fccbea6..10007e3 100644 --- a/src/Service/ServiceAgreementSyncService.php +++ b/src/Service/ServiceAgreementSyncService.php @@ -37,12 +37,11 @@ public function __construct( /** * Fetch all projects from the Economics API and sync them locally. * - * The Economics API is treated as the source of truth: every Project, - * CodeOwner and SecurityContract whose `economicsId` is not present in - * the response is removed. Nested data (codeowners, github repos and the - * service-agreement payload) is reconciled per project. GitHub repo names - * that cannot be matched against an existing GitRepo entry are collected - * and returned to the caller so they can be surfaced to the admin. + * Treats the Economics API as the source of truth: any Project, + * CodeOwner or SecurityContract whose `economicsId` is missing from the + * response is removed. Nested codeowners, GitHub repos and service + * agreements are reconciled per project; GitHub repo names with no + * matching GitRepo entry are returned for the caller to surface. * * @return array{projects: int, unmatchedRepoNames: list} count of projects processed and the list of unresolvable GitHub repo names * @@ -136,16 +135,14 @@ public function syncAll(): array /** * Reconcile a project's code owners against the Economics payload. * - * Upserts every code owner present in `$codeOwnersData` (creating new - * CodeOwner entities when needed), persists them, and adjusts the - * project's association so it ends up linked to exactly the desired set. - * Both `$existingCodeOwners` and `$seenCodeOwnerIds` are passed by - * reference because the caller reuses them across all projects in a - * single sync run — newly created owners must be reachable on the next - * iteration and the seen-list drives the post-loop cleanup pass. + * Upserts each owner in `$codeOwnersData`, persists it, and links the + * project to exactly the desired set. The `$existingCodeOwners` and + * `$seenCodeOwnerIds` arrays are by-reference so newly created owners + * are reused across projects in the same sync run, and the seen-list + * drives the post-loop cleanup pass. * * @param Project $project project being synced - * @param array> $codeOwnersData raw `codeowners` array straight from Economics + * @param array> $codeOwnersData raw `codeowners` array from Economics * @param array $existingCodeOwners by-reference id-keyed lookup; mutated to include newly-created owners * @param list $seenCodeOwnerIds by-reference accumulator of every economicsId touched this run * @@ -186,13 +183,10 @@ private function syncCodeOwners(Project $project, array $codeOwnersData, array & /** * Reconcile a project's GitHub repo associations against the Economics payload. * - * Splits the multi-line `githubRepos` string into individual repo names, - * matches each one against the existing GitRepo lookup, and adjusts the - * project's association to the desired set. Repo names that have no - * matching GitRepo entry are recorded in `$unmatchedRepoNames` so the - * caller can warn the operator; GitRepo entries are NOT created on the - * fly because they are normally populated by the harvester and creating - * a half-empty one here would mask the underlying onboarding gap. + * Splits the multi-line `githubRepos` string and links the project to + * exactly the matching GitRepo entries. Unknown repo names are recorded + * in `$unmatchedRepoNames`; GitRepo entries are not created on the fly + * because that would hide the underlying harvester onboarding gap. * * @param Project $project project being synced * @param string|null $githubReposString raw multi-line list from Economics, or null when the field is unset @@ -236,11 +230,10 @@ private function syncGitRepos(Project $project, ?string $githubReposString, arra /** * Copy a single Economics `serviceAgreement` payload onto a SecurityContract. * - * Pure field-by-field mapping plus a project association — no persistence, - * no fetches. The contract may be either an existing entity (re-synced) - * or a brand-new one; the caller decides and persists it afterwards. - * Dates run through `parseDate()` which handles the Economics-specific - * `{date, timezone_type, timezone}` shape. + * Field-by-field mapping plus the project association. No persistence + * and no fetches — the caller persists the contract afterwards. Dates + * go through `parseDate()` to handle Economics' `{date, timezone_type, + * timezone}` shape. * * @param SecurityContract $contract entity to mutate in place * @param array $data raw `serviceAgreement` payload from Economics @@ -269,9 +262,8 @@ private function mapServiceAgreementToContract(SecurityContract $contract, array /** * Parse the Economics API's `{date, timezone_type, timezone}` shape into a DateTimeImmutable. * - * Returns null when the payload is null or missing the `date` key so the - * caller can leave the contract field unset. The timezone string from the - * payload is used as-is when present; otherwise UTC is assumed. + * Returns null when the payload is null or has no `date` key. The + * payload's timezone is honored when present; otherwise UTC is assumed. * * @param array{date?: string, timezone_type?: int, timezone?: string}|null $dateData raw date payload from Economics, or null when absent * From 62d3324e19b1cbc2dcaab3c4c343ef4c8145d8ad Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 12:25:16 +0200 Subject: [PATCH 60/94] Modified changelog --- CHANGELOG.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9031d..3414924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,18 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Adapt `/api/serviceagreements` consumer to new payload shape - - Add `Project` entity (top-level Economics project) with relations to - `CodeOwner` (ManyToMany), `GitRepo` (ManyToMany, matched by repo name), - and `SecurityContract` (OneToMany) - - Add `CodeOwner` entity (economicsId/name/email) - - Slim `SecurityContract` to the nested-agreement fields; drop - `projectName`, `clientName`, `leantimeUrl`, `projectTrackerKey`, - `gitRepos`, `quarterlyHours`, `cybersecurityPrice`, `cybersecurityNote`; - add ManyToOne to `Project` - - Rewrite `ServiceAgreementSyncService` to upsert projects, codeowners, - and contracts in one pass; warn when `githubRepos` names cannot be - linked to existing `GitRepo` rows +- [#80](https://github.com/itk-dev/devops_itksites/pull/80 ) 7523: Service agreements + - Add Project entity top-level Economics project + - Add CodeOwner entity + - Add Leantime integration - [#80](https://github.com/itk-dev/devops_itksites/pull/80 ) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases From de1aa9b19e173ff159a26dc4d23b705e6af12c51 Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 12:46:53 +0200 Subject: [PATCH 61/94] Modified changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3414924..9539aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - [#80](https://github.com/itk-dev/devops_itksites/pull/80 ) 7523: Service agreements - - Add Project entity top-level Economics project + - Add Project entity top-level Economics project. - Add CodeOwner entity - Add Leantime integration - [#80](https://github.com/itk-dev/devops_itksites/pull/80 ) 5566: Service agreements From c40c3489b56bd268609c7821009e0c1545577ca1 Mon Sep 17 00:00:00 2001 From: martinyde Date: Mon, 1 Jun 2026 14:46:48 +0200 Subject: [PATCH 62/94] Canged to use .env variables for leantime service --- .env | 5 +++++ config/packages/framework.yaml | 6 +++--- config/services.yaml | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.env b/.env index 0b6b01c..572d30d 100644 --- a/.env +++ b/.env @@ -68,3 +68,8 @@ APP_KEEP_RESULTS=5 APP_ECONOMICS_URI=https://economics.itkdev.dk APP_ECONOMICS_API_KEY=changeme ###< economics ### + +###> app/leantime ### +APP_LEANTIME_URI=http://leantime.invalid +APP_LEANTIME_API_KEY= +###< app/leantime ### diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7f37920..20230a0 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -21,11 +21,11 @@ framework: headers: x-api-key: '%env(APP_ECONOMICS_API_KEY)%' leantime.client: - base_uri: '%env(default:default_leantime_uri:APP_LEANTIME_URI)%' - scope: '%env(default:default_leantime_uri:APP_LEANTIME_URI)%' + base_uri: '%env(APP_LEANTIME_URI)%' + scope: '%env(APP_LEANTIME_URI)%' headers: Content-Type: 'application/json' - x-api-key: '%env(default::APP_LEANTIME_API_KEY)%' + x-api-key: '%env(APP_LEANTIME_API_KEY)%' when@test: framework: diff --git a/config/services.yaml b/config/services.yaml index 106c945..71d40c9 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,7 +4,6 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: - default_leantime_uri: 'http://leantime.invalid' services: # default configuration for services in *this* file From 175889f82eef11161a28e1e75b4b13f8de65cb43 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 2 Jun 2026 13:23:25 +0200 Subject: [PATCH 63/94] Update openid-connect-bundle to 5.0 and Symfony to 8.1 - Bump itk-dev/openid-connect-bundle to ^5.0 (pulls itk-dev/openid-connect 5.0) - Migrate AzureOIDCAuthenticator to the new OIDC exception marker interface OpenIdConnectExceptionInterface (5.0 BC: concrete exceptions no longer extend ItkOpenIdConnectException) - Update Symfony to 8.1 (Flex pin 8.1.*, asset-mapper ~8.1.0) - Regenerate config/reference.php for Symfony 8.1 Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 8 + composer.json | 6 +- composer.lock | 992 ++++++++++++++---------- config/reference.php | 39 +- src/Security/AzureOIDCAuthenticator.php | 5 +- 5 files changed, 604 insertions(+), 446 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e7423..629a870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#84](https://github.com/itk-dev/devops_itksites/pull/84) Update composer dependencies. Fix for Symfony and Twig CVE's +- [#85](https://github.com/itk-dev/devops_itksites/pull/85) + Update openid-connect-bundle to 5.0 and Symfony to 8.1 + - Bump `itk-dev/openid-connect-bundle` to `^5.0` (pulls `itk-dev/openid-connect` 5.0) + - Migrate OIDC authenticator to the new exception marker interface + `OpenIdConnectExceptionInterface` (5.0 BC: concrete exceptions no longer + extend `ItkOpenIdConnectException`) + - Update Symfony to 8.1 + ## [1.11.0] - 2026-05-19 - [#78](https://github.com/itk-dev/devops_itksites/pull/78) diff --git a/composer.json b/composer.json index efe1ca0..252f853 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,14 @@ "doctrine/doctrine-migrations-bundle": "^4.0", "doctrine/orm": "^3.0", "easycorp/easyadmin-bundle": "^5.0", - "itk-dev/openid-connect-bundle": "^4.0", + "itk-dev/openid-connect-bundle": "^5.0", "itk-dev/vault-bundle": "^1.0.0", "nelmio/cors-bundle": "^2.2", "ocramius/doctrine-batch-utils": "^2.8", "phpstan/phpdoc-parser": "^2.0", "symfony/amqp-messenger": "^8.0", "symfony/asset": "^8.0", - "symfony/asset-mapper": "~8.0.0", + "symfony/asset-mapper": "~8.1.0", "symfony/browser-kit": "^8.0", "symfony/console": "^8.0", "symfony/doctrine-messenger": "^8.0", @@ -100,7 +100,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "8.0.*" + "require": "8.1.*" } }, "scripts": { diff --git a/composer.lock b/composer.lock index b161786..cbd47e7 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": "2a1b30ad222f086b612ec724b183c9eb", + "content-hash": "ceb76d3549aace47bff7b8e9260728fe", "packages": [ { "name": "api-platform/core", @@ -1594,16 +1594,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.5", + "version": "7.10.6", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148" + "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7c8d84b39e680315f687e8662a9d6fb0865c5148", - "reference": "7c8d84b39e680315f687e8662a9d6fb0865c5148", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e7412b3180912c01650cc66647f18c1d1cbe9b94", + "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94", "shasum": "" }, "require": { @@ -1701,7 +1701,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.5" + "source": "https://github.com/guzzle/guzzle/tree/7.10.6" }, "funding": [ { @@ -1717,7 +1717,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T11:53:46+00:00" + "time": "2026-06-01T13:06:22+00:00" }, { "name": "guzzlehttp/promises", @@ -1921,16 +1921,16 @@ }, { "name": "itk-dev/openid-connect", - "version": "4.1.2", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect.git", - "reference": "f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5" + "reference": "f241f6794a2e74eab8c4808bc22f341a98e96f0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5", - "reference": "f19b9a39e7f1beae231d0b06e5b7a78a9efd8eb5", + "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/f241f6794a2e74eab8c4808bc22f341a98e96f0b", + "reference": "f241f6794a2e74eab8c4808bc22f341a98e96f0b", "shasum": "" }, "require": { @@ -1947,7 +1947,8 @@ "ergebnis/composer-normalize": "^2.50", "friendsofphp/php-cs-fixer": "^3.75", "mockery/mockery": "^1.6.12", - "phpstan/phpstan": "^2.1.40", + "phpstan/phpstan": "^2.1.41", + "phpstan/phpstan-mockery": "^2.0", "phpunit/php-code-coverage": "^12", "phpunit/phpunit": "^12" }, @@ -1978,29 +1979,29 @@ "description": "OpenID connect configuration package", "support": { "issues": "https://github.com/itk-dev/openid-connect/issues", - "source": "https://github.com/itk-dev/openid-connect/tree/4.1.2" + "source": "https://github.com/itk-dev/openid-connect/tree/5.0.0" }, - "time": "2026-05-11T12:03:06+00:00" + "time": "2026-06-02T09:01:56+00:00" }, { "name": "itk-dev/openid-connect-bundle", - "version": "4.2.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect-bundle.git", - "reference": "7964acbd630ba0a6b4a362f714bfed255ad88e05" + "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/7964acbd630ba0a6b4a362f714bfed255ad88e05", - "reference": "7964acbd630ba0a6b4a362f714bfed255ad88e05", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", + "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", "shasum": "" }, "require": { "doctrine/orm": "^2.8 || ^3.0", "ext-json": "*", "ext-openssl": "*", - "itk-dev/openid-connect": "^4.0", + "itk-dev/openid-connect": "^5.0", "php": "^8.3", "symfony/cache": "^6.4 || ^7.0 || ^8.0", "symfony/framework-bundle": "^6.4.13 || ^7.0 || ^8.0", @@ -2011,7 +2012,11 @@ "require-dev": { "ergebnis/composer-normalize": "^2.28", "friendsofphp/php-cs-fixer": "^3.11", - "phpstan/phpstan": "^2.1", + "phpstan/phpstan": "^2.1.41", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", "phpunit/phpunit": "^12.0", "rector/rector": "^2.0", "symfony/runtime": "^6.4.13 || ^7.0 || ^8.0" @@ -2039,9 +2044,9 @@ "description": "Symfony bundle for openid-connect", "support": { "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", - "source": "https://github.com/itk-dev/openid-connect-bundle/tree/4.2.0" + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/5.0.0" }, - "time": "2026-05-11T12:11:09+00:00" + "time": "2026-06-02T11:15:20+00:00" }, { "name": "itk-dev/vault", @@ -3177,22 +3182,22 @@ }, { "name": "symfony/amqp-messenger", - "version": "v8.0.11", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/amqp-messenger.git", - "reference": "26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e" + "reference": "6626ae1412965678f9cd046fac7f67d483e50dde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e", - "reference": "26e1eec5f9aa5cd8dd43d02cc80de468c0fa480e", + "url": "https://api.github.com/repos/symfony/amqp-messenger/zipball/6626ae1412965678f9cd046fac7f67d483e50dde", + "reference": "6626ae1412965678f9cd046fac7f67d483e50dde", "shasum": "" }, "require": { "ext-amqp": "*", - "php": ">=8.4", - "symfony/messenger": "^7.4|^8.0" + "php": ">=8.4.1", + "symfony/messenger": "^8.1" }, "require-dev": { "symfony/event-dispatcher": "^7.4|^8.0", @@ -3226,7 +3231,7 @@ "description": "Symfony AMQP extension Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/amqp-messenger/tree/v8.0.11" + "source": "https://github.com/symfony/amqp-messenger/tree/v8.1.0" }, "funding": [ { @@ -3246,24 +3251,24 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:07:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/asset", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "72eca261f3af1bef741c48bb2c91a4e619dca03a" + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/72eca261f3af1bef741c48bb2c91a4e619dca03a", - "reference": "72eca261f3af1bef741c48bb2c91a4e619dca03a", + "url": "https://api.github.com/repos/symfony/asset/zipball/4bd4d143b7e53f40d45877df52eb2b18282bdac4", + "reference": "4bd4d143b7e53f40d45877df52eb2b18282bdac4", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/http-client": "^7.4|^8.0", @@ -3296,7 +3301,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v8.0.8" + "source": "https://github.com/symfony/asset/tree/v8.1.0" }, "funding": [ { @@ -3316,25 +3321,25 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/asset-mapper", - "version": "v8.0.11", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/asset-mapper.git", - "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c" + "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", - "reference": "b2c33bf6934bfe5b37a6d70d0b0f7011d0ec4a0c", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/74b1b7b7019c728cb1f8672b502260e683b6374e", + "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e", "shasum": "" }, "require": { "composer/semver": "^3.0", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/filesystem": "^7.4|^8.0", "symfony/http-client": "^7.4|^8.0" }, @@ -3377,7 +3382,7 @@ "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset-mapper/tree/v8.0.11" + "source": "https://github.com/symfony/asset-mapper/tree/v8.1.0" }, "funding": [ { @@ -3397,24 +3402,24 @@ "type": "tidelift" } ], - "time": "2026-05-13T12:07:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/browser-kit", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "f5a28fca785416cf489dd579011e74c831100cc3" + "reference": "74e18e582cdda0eca35f7c74e1e48e62f0ede853" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f5a28fca785416cf489dd579011e74c831100cc3", - "reference": "f5a28fca785416cf489dd579011e74c831100cc3", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/74e18e582cdda0eca35f7c74e1e48e62f0ede853", + "reference": "74e18e582cdda0eca35f7c74e1e48e62f0ede853", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/dom-crawler": "^7.4|^8.0" }, "require-dev": { @@ -3449,7 +3454,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v8.0.8" + "source": "https://github.com/symfony/browser-kit/tree/v8.1.0" }, "funding": [ { @@ -3469,29 +3474,29 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/cache", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "75f92239836ce08bce4d19f2734737dae4276fe9" + "reference": "ba62e0ed9ea9bc26142844a891d4a3dfceb24aed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/75f92239836ce08bce4d19f2734737dae4276fe9", - "reference": "75f92239836ce08bce4d19f2734737dae4276fe9", + "url": "https://api.github.com/repos/symfony/cache/zipball/ba62e0ed9ea9bc26142844a891d4a3dfceb24aed", + "reference": "ba62e0ed9ea9bc26142844a891d4a3dfceb24aed", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^3.6", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^7.4|^8.0" + "symfony/var-exporter": "^8.1" }, "conflict": { "ext-redis": "<6.1", @@ -3548,7 +3553,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v8.0.13" + "source": "https://github.com/symfony/cache/tree/v8.1.0" }, "funding": [ { @@ -3568,7 +3573,7 @@ "type": "tidelift" } ], - "time": "2026-05-24T08:44:11+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/cache-contracts", @@ -3652,20 +3657,20 @@ }, { "name": "symfony/clock", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3" + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b55a638b189a6faa875e0ccdb00908fb87af95b3", - "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/clock": "^1.0" }, "provide": { @@ -3705,7 +3710,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v8.0.8" + "source": "https://github.com/symfony/clock/tree/v8.1.0" }, "funding": [ { @@ -3725,24 +3730,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/config", - "version": "v8.0.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "de665e669412ec2effe004d90298dbbdaf6e7e8b" + "reference": "429783a0c649696f2058ea5ab5315f082dba6de9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/de665e669412ec2effe004d90298dbbdaf6e7e8b", - "reference": "de665e669412ec2effe004d90298dbbdaf6e7e8b", + "url": "https://api.github.com/repos/symfony/config/zipball/429783a0c649696f2058ea5ab5315f082dba6de9", + "reference": "429783a0c649696f2058ea5ab5315f082dba6de9", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", "symfony/filesystem": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" @@ -3783,7 +3788,7 @@ "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/v8.0.10" + "source": "https://github.com/symfony/config/tree/v8.1.0" }, "funding": [ { @@ -3803,27 +3808,33 @@ "type": "tidelift" } ], - "time": "2026-05-04T13:41:39+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/console", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f200be111431cff4aeae8d086b91180bc4d7efd7" + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f200be111431cff4aeae8d086b91180bc4d7efd7", - "reference": "f200be111431cff4aeae8d086b91180bc4d7efd7", + "url": "https://api.github.com/repos/symfony/console/zipball/f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4|^8.0" + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -3831,14 +3842,18 @@ "require-dev": { "psr/log": "^1|^2|^3", "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", "symfony/lock": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", @@ -3873,7 +3888,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.13" + "source": "https://github.com/symfony/console/tree/v8.1.0" }, "funding": [ { @@ -3893,28 +3908,28 @@ "type": "tidelift" } ], - "time": "2026-05-24T08:59:15+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/dependency-injection", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "075a9a3f5b458f637a8bcf17400d8b021761f3c9" + "reference": "b6ba1f45127106885de4b77558c5ecca8feb1e1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/075a9a3f5b458f637a8bcf17400d8b021761f3c9", - "reference": "075a9a3f5b458f637a8bcf17400d8b021761f3c9", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b6ba1f45127106885de4b77558c5ecca8feb1e1b", + "reference": "b6ba1f45127106885de4b77558c5ecca8feb1e1b", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^3.6", - "symfony/var-exporter": "^7.4|^8.0" + "symfony/var-exporter": "^8.1" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -3954,7 +3969,7 @@ "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/v8.0.13" + "source": "https://github.com/symfony/dependency-injection/tree/v8.1.0" }, "funding": [ { @@ -3974,7 +3989,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4049,22 +4064,23 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "dfe3dddc9c22756b9b145785fb5fd4b0445cd06e" + "reference": "80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/dfe3dddc9c22756b9b145785fb5fd4b0445cd06e", - "reference": "dfe3dddc9c22756b9b145785fb5fd4b0445cd06e", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5", + "reference": "80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5", "shasum": "" }, "require": { "doctrine/event-manager": "^2", "doctrine/persistence": "^3.1|^4", - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3" @@ -4084,6 +4100,7 @@ "psr/log": "^1|^2|^3", "symfony/cache": "^7.4|^8.0", "symfony/config": "^7.4|^8.0", + "symfony/console": "^8.1", "symfony/dependency-injection": "^7.4|^8.0", "symfony/doctrine-messenger": "^7.4|^8.0", "symfony/expression-language": "^7.4|^8.0", @@ -4127,7 +4144,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v8.0.9" + "source": "https://github.com/symfony/doctrine-bridge/tree/v8.1.0" }, "funding": [ { @@ -4147,26 +4164,28 @@ "type": "tidelift" } ], - "time": "2026-04-29T15:02:55+00:00" + "time": "2026-05-29T05:18:49+00:00" }, { "name": "symfony/doctrine-messenger", - "version": "v8.0.6", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88" + "reference": "cafeac0dccfd4e971c9a5c646652d1037de469f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/88329a3faba5023cfb569b3fc5b8a771336c4a88", - "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/cafeac0dccfd4e971c9a5c646652d1037de469f8", + "reference": "cafeac0dccfd4e971c9a5c646652d1037de469f8", "shasum": "" }, "require": { "doctrine/dbal": "^4.3", - "php": ">=8.4", - "symfony/messenger": "^7.4|^8.0", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/messenger": "^8.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4203,7 +4222,7 @@ "description": "Symfony Doctrine Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-messenger/tree/v8.0.6" + "source": "https://github.com/symfony/doctrine-messenger/tree/v8.1.0" }, "funding": [ { @@ -4223,24 +4242,24 @@ "type": "tidelift" } ], - "time": "2026-02-20T07:51:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/dom-crawler", - "version": "v8.0.12", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "011b0ce60417f6d40052434d8ae6295b876ecbdd" + "reference": "77ca351474ea018daba5f2e473cbf1b9b8e72ac6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/011b0ce60417f6d40052434d8ae6295b876ecbdd", - "reference": "011b0ce60417f6d40052434d8ae6295b876ecbdd", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/77ca351474ea018daba5f2e473cbf1b9b8e72ac6", + "reference": "77ca351474ea018daba5f2e473cbf1b9b8e72ac6", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.0" }, @@ -4273,7 +4292,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v8.0.12" + "source": "https://github.com/symfony/dom-crawler/tree/v8.1.0" }, "funding": [ { @@ -4293,24 +4312,24 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:22:03+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/dotenv", - "version": "v8.0.11", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe" + "reference": "7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe", - "reference": "82e1d8f888896a215bb6673e6d1f6d5ca47a9dfe", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c", + "reference": "7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/console": "^7.4|^8.0", @@ -4347,7 +4366,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v8.0.11" + "source": "https://github.com/symfony/dotenv/tree/v8.1.0" }, "funding": [ { @@ -4367,24 +4386,24 @@ "type": "tidelift" } ], - "time": "2026-05-11T13:06:45+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/error-handler", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517" + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1119fe8dcfc3825ec74ec061b96ef0c8f281517", - "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", "symfony/polyfill-php85": "^1.32", "symfony/var-dumper": "^7.4|^8.0" @@ -4428,7 +4447,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v8.0.8" + "source": "https://github.com/symfony/error-handler/tree/v8.1.0" }, "funding": [ { @@ -4448,24 +4467,25 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f" + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0c3c1a17604c4dbbec4b93fe162c538482096e1f", - "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f249ae3f680958b6f1f9dd76e5747cf0695b4102", + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { @@ -4513,7 +4533,7 @@ "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/v8.0.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.0" }, "funding": [ { @@ -4533,7 +4553,7 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:51:42+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4617,20 +4637,20 @@ }, { "name": "symfony/expression-language", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b" + "reference": "67f31731d5b316d0183c565933017d5d3331d609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b", - "reference": "b2a5fd3b7331ae10cc0ed75a28d64b25b67d2c7b", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/67f31731d5b316d0183c565933017d5d3331d609", + "reference": "67f31731d5b316d0183c565933017d5d3331d609", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/cache": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, @@ -4660,7 +4680,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v8.0.8" + "source": "https://github.com/symfony/expression-language/tree/v8.1.0" }, "funding": [ { @@ -4680,24 +4700,25 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/filesystem", - "version": "v8.0.11", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7" + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/224db910898ce1317b892a9a1338f1f8f17eb7c7", - "reference": "224db910898ce1317b892a9a1338f1f8f17eb7c7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, @@ -4730,7 +4751,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.11" + "source": "https://github.com/symfony/filesystem/tree/v8.1.0" }, "funding": [ { @@ -4750,24 +4771,24 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:39:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/finder", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8da41214757b87d97f181e3d14a4179286151007" + "reference": "58d2e767a66052c1487356f953445634a8194c64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", - "reference": "8da41214757b87d97f181e3d14a4179286151007", + "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", + "reference": "58d2e767a66052c1487356f953445634a8194c64", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/filesystem": "^7.4|^8.0" @@ -4798,7 +4819,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.8" + "source": "https://github.com/symfony/finder/tree/v8.1.0" }, "funding": [ { @@ -4818,7 +4839,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/flex", @@ -4895,27 +4916,29 @@ }, { "name": "symfony/form", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "dd9f73dd3b92e657c97aeeca1f47e981c635ea91" + "reference": "82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/dd9f73dd3b92e657c97aeeca1f47e981c635ea91", - "reference": "dd9f73dd3b92e657c97aeeca1f47e981c635ea91", + "url": "https://api.github.com/repos/symfony/form/zipball/82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8", + "reference": "82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/options-resolver": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "^1.0", "symfony/property-access": "^7.4|^8.0", - "symfony/service-contracts": "^2.5|^3" + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^8.1" }, "conflict": { "symfony/intl": "<7.4", @@ -4966,7 +4989,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v8.0.9" + "source": "https://github.com/symfony/form/tree/v8.1.0" }, "funding": [ { @@ -4986,48 +5009,50 @@ "type": "tidelift" } ], - "time": "2026-04-29T15:02:55+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/framework-bundle", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "576595d274ebeb122fb4c88f2641153bcd5c6dc0" + "reference": "6a0953f4fd8b51db6136c2628af99b7193e63256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/576595d274ebeb122fb4c88f2641153bcd5c6dc0", - "reference": "576595d274ebeb122fb4c88f2641153bcd5c6dc0", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6a0953f4fd8b51db6136c2628af99b7193e63256", + "reference": "6a0953f4fd8b51db6136c2628af99b7193e63256", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/cache": "^7.4|^8.0", "symfony/config": "^7.4.4|^8.0.4", - "symfony/dependency-injection": "^7.4.4|^8.0.4", + "symfony/dependency-injection": "^8.1", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/event-dispatcher": "^8.1", "symfony/filesystem": "^7.4|^8.0", "symfony/finder": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", + "symfony/http-kernel": "^8.1", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php85": "^1.32", - "symfony/routing": "^7.4|^8.0" + "symfony/polyfill-php85": "^1.33", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^3.7", + "symfony/var-exporter": "^8.1" }, "conflict": { "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/console": "<7.4", + "symfony/console": "<8.1", "symfony/form": "<7.4", "symfony/json-streamer": "<7.4", - "symfony/messenger": "<7.4", + "symfony/messenger": "<7.4.10|>=8.0,<8.0.10", "symfony/mime": "<7.4.9|>=8.0,<8.0.9", "symfony/security-csrf": "<7.4", "symfony/serializer": "<7.4", @@ -5045,7 +5070,7 @@ "symfony/asset-mapper": "^7.4|^8.0", "symfony/browser-kit": "^7.4|^8.0", "symfony/clock": "^7.4|^8.0", - "symfony/console": "^7.4|^8.0", + "symfony/console": "^8.1", "symfony/css-selector": "^7.4|^8.0", "symfony/dom-crawler": "^7.4|^8.0", "symfony/dotenv": "^7.4|^8.0", @@ -5056,7 +5081,7 @@ "symfony/json-streamer": "^7.4|^8.0", "symfony/lock": "^7.4|^8.0", "symfony/mailer": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", + "symfony/messenger": "^7.4.10|^8.0.10", "symfony/mime": "^7.4.9|^8.0.9", "symfony/notifier": "^7.4|^8.0", "symfony/object-mapper": "^7.4.9|^8.0.9", @@ -5107,7 +5132,7 @@ "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/v8.0.13" + "source": "https://github.com/symfony/framework-bundle/tree/v8.1.0" }, "funding": [ { @@ -5127,26 +5152,27 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/http-client", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "c7f40f9103233630167c25c9a4570acf805fdade" + "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/c7f40f9103233630167c25c9a4570acf805fdade", - "reference": "c7f40f9103233630167c25c9a4570acf805fdade", + "url": "https://api.github.com/repos/symfony/http-client/zipball/68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", + "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/http-client-contracts": "^3.7", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -5162,7 +5188,7 @@ "require-dev": { "amphp/http-client": "^5.3.2", "amphp/http-tunnel": "^2.0", - "guzzlehttp/promises": "^1.4|^2.0", + "guzzlehttp/guzzle": "^7.10", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", @@ -5203,7 +5229,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v8.0.13" + "source": "https://github.com/symfony/http-client/tree/v8.1.0" }, "funding": [ { @@ -5223,7 +5249,7 @@ "type": "tidelift" } ], - "time": "2026-05-24T09:58:02+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/http-client-contracts", @@ -5309,20 +5335,21 @@ }, { "name": "symfony/http-foundation", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "30459bd82094c2572f3f78c04132e92f257a5f76" + "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/30459bd82094c2572f3f78c04132e92f257a5f76", - "reference": "30459bd82094c2572f3f78c04132e92f257a5f76", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/af11474600f06718086c2cda4fa6fa8d0a672e7e", + "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.1" }, "conflict": { @@ -5365,7 +5392,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v8.0.13" + "source": "https://github.com/symfony/http-foundation/tree/v8.1.0" }, "funding": [ { @@ -5385,34 +5412,38 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:21:57+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/http-kernel", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "0f2b114380b21d8736a496ab4672b012e3822ee7" + "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/0f2b114380b21d8736a496ab4672b012e3822ee7", - "reference": "0f2b114380b21d8736a496ab4672b012e3822ee7", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", + "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^7.4|^8.0", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { + "symfony/dependency-injection": "<8.1", "symfony/flex": "<2.10", "symfony/http-client-contracts": "<2.5", "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", "twig/twig": "<3.21" }, "provide": { @@ -5425,13 +5456,14 @@ "symfony/config": "^7.4|^8.0", "symfony/console": "^7.4|^8.0", "symfony/css-selector": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", "symfony/dom-crawler": "^7.4|^8.0", "symfony/expression-language": "^7.4|^8.0", "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3", "symfony/process": "^7.4|^8.0", "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", "symfony/routing": "^7.4|^8.0", "symfony/serializer": "^7.4|^8.0", "symfony/stopwatch": "^7.4|^8.0", @@ -5439,7 +5471,7 @@ "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", "symfony/var-exporter": "^7.4|^8.0", "twig/twig": "^3.21" }, @@ -5469,7 +5501,7 @@ "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/v8.0.13" + "source": "https://github.com/symfony/http-kernel/tree/v8.1.0" }, "funding": [ { @@ -5489,24 +5521,24 @@ "type": "tidelift" } ], - "time": "2026-05-27T08:48:59+00:00" + "time": "2026-05-29T08:46:08+00:00" }, { "name": "symfony/intl", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "604a1dbbd67471e885e93274379cadd80dc33535" + "reference": "7822dffb3e2128501a1ac781db5d8217fc9caac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/604a1dbbd67471e885e93274379cadd80dc33535", - "reference": "604a1dbbd67471e885e93274379cadd80dc33535", + "url": "https://api.github.com/repos/symfony/intl/zipball/7822dffb3e2128501a1ac781db5d8217fc9caac1", + "reference": "7822dffb3e2128501a1ac781db5d8217fc9caac1", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "conflict": { "symfony/string": "<7.4" @@ -5558,7 +5590,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v8.0.8" + "source": "https://github.com/symfony/intl/tree/v8.1.0" }, "funding": [ { @@ -5578,29 +5610,31 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/messenger", - "version": "v8.0.12", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "ec7ad0b1eb8ad19de1a99d4d051311fa99879d74" + "reference": "c095398338a8e6f5ae6579ebb2092b7a16b7df72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/ec7ad0b1eb8ad19de1a99d4d051311fa99879d74", - "reference": "ec7ad0b1eb8ad19de1a99d4d051311fa99879d74", + "url": "https://api.github.com/repos/symfony/messenger/zipball/c095398338a8e6f5ae6579ebb2092b7a16b7df72", + "reference": "c095398338a8e6f5ae6579ebb2092b7a16b7df72", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", - "symfony/clock": "^7.4|^8.0" + "symfony/clock": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "symfony/console": "<7.4", + "symfony/dependency-injection": "<8.1", "symfony/event-dispatcher-contracts": "<2.5", "symfony/lock": "<7.4", "symfony/serializer": "<7.4.4|>=8.0,<8.0.4" @@ -5608,7 +5642,7 @@ "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "symfony/console": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", "symfony/lock": "^7.4|^8.0", @@ -5648,7 +5682,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v8.0.12" + "source": "https://github.com/symfony/messenger/tree/v8.1.0" }, "funding": [ { @@ -5668,24 +5702,24 @@ "type": "tidelift" } ], - "time": "2026-05-19T19:56:11+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/mime", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7c078c464c5ce36e8bcaf54b3b3e3c51d30a9646" + "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7c078c464c5ce36e8bcaf54b3b3e3c51d30a9646", - "reference": "7c078c464c5ce36e8bcaf54b3b3e3c51d30a9646", + "url": "https://api.github.com/repos/symfony/mime/zipball/b164ae7e3f7915aacfe9ee155f2f358502440664", + "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -5734,7 +5768,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v8.0.13" + "source": "https://github.com/symfony/mime/tree/v8.1.0" }, "funding": [ { @@ -5754,25 +5788,25 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v8.0.12", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "fc53ae60b2d01d3086d28aea876cfef9bcf75f1e" + "reference": "38563fac41ede8521e5e3dc139a4f2b097471c8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/fc53ae60b2d01d3086d28aea876cfef9bcf75f1e", - "reference": "fc53ae60b2d01d3086d28aea876cfef9bcf75f1e", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/38563fac41ede8521e5e3dc139a4f2b097471c8c", + "reference": "38563fac41ede8521e5e3dc139a4f2b097471c8c", "shasum": "" }, "require": { "monolog/monolog": "^3", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/http-kernel": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, @@ -5811,7 +5845,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v8.0.12" + "source": "https://github.com/symfony/monolog-bridge/tree/v8.1.0" }, "funding": [ { @@ -5831,7 +5865,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:22:03+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/monolog-bundle", @@ -5914,20 +5948,20 @@ }, { "name": "symfony/options-resolver", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8" + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8", - "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -5961,7 +5995,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v8.0.8" + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" }, "funding": [ { @@ -5981,24 +6015,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/password-hasher", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "57ee968d3c38301ed3e5b838f850a10f2d06a7f6" + "reference": "6934d16beaa4677f2c4584229fff1b51099dd7af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/57ee968d3c38301ed3e5b838f850a10f2d06a7f6", - "reference": "57ee968d3c38301ed3e5b838f850a10f2d06a7f6", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/6934d16beaa4677f2c4584229fff1b51099dd7af", + "reference": "6934d16beaa4677f2c4584229fff1b51099dd7af", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/console": "^7.4|^8.0", @@ -6034,7 +6068,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v8.0.8" + "source": "https://github.com/symfony/password-hasher/tree/v8.1.0" }, "funding": [ { @@ -6054,7 +6088,94 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/polyfill-deepclone", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-deepclone.git", + "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", + "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "provide": { + "ext-deepclone": "*" + }, + "suggest": { + "ext-deepclone": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\DeepClone\\": "" + }, + "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 for the deepclone extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "deepclone", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-deepclone/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-26T13:03:27+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -6728,20 +6849,20 @@ }, { "name": "symfony/property-access", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "704c7808116fcdd67327db7b17de56b8ef6169e4" + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/704c7808116fcdd67327db7b17de56b8ef6169e4", - "reference": "704c7808116fcdd67327db7b17de56b8ef6169e4", + "url": "https://api.github.com/repos/symfony/property-access/zipball/9261ef060f26cc7b728f67f141ba19b98a6209a9", + "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/property-info": "^7.4.4|^8.0.4" }, "require-dev": { @@ -6785,7 +6906,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v8.0.8" + "source": "https://github.com/symfony/property-access/tree/v8.1.0" }, "funding": [ { @@ -6805,24 +6926,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/property-info", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "c21711980653360d6ef5c26d0f9ca6f58a1135c6" + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/c21711980653360d6ef5c26d0f9ca6f58a1135c6", - "reference": "c21711980653360d6ef5c26d0f9ca6f58a1135c6", + "url": "https://api.github.com/repos/symfony/property-info/zipball/4721e8c56d0cd2378e0ef9a9899f810008b859f7", + "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/string": "^7.4|^8.0", "symfony/type-info": "^7.4.7|^8.0.7" }, @@ -6871,7 +6992,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v8.0.8" + "source": "https://github.com/symfony/property-info/tree/v8.1.0" }, "funding": [ { @@ -6891,24 +7012,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/routing", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "18e6213e536842285dfdd29604e43ac8f784d17d" + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/18e6213e536842285dfdd29604e43ac8f784d17d", - "reference": "18e6213e536842285dfdd29604e43ac8f784d17d", + "url": "https://api.github.com/repos/symfony/routing/zipball/fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { @@ -6951,7 +7072,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v8.0.13" + "source": "https://github.com/symfony/routing/tree/v8.1.0" }, "funding": [ { @@ -6971,20 +7092,20 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:21:57+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/runtime", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "c9bf98f5f9b284a101d37e727eaaec00f8c18b83" + "reference": "b7ea1abe04561e814b3134db0f56c287cedb35cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/c9bf98f5f9b284a101d37e727eaaec00f8c18b83", - "reference": "c9bf98f5f9b284a101d37e727eaaec00f8c18b83", + "url": "https://api.github.com/repos/symfony/runtime/zipball/b7ea1abe04561e814b3134db0f56c287cedb35cc", + "reference": "b7ea1abe04561e814b3134db0f56c287cedb35cc", "shasum": "" }, "require": { @@ -7035,7 +7156,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v8.0.13" + "source": "https://github.com/symfony/runtime/tree/v8.1.0" }, "funding": [ { @@ -7055,26 +7176,26 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:18:22+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/security-bundle", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "641934c6813d583a8702dd750d11b330cb0cf068" + "reference": "0489a6247f729652db9b9ff408f69ac3bee3589e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/641934c6813d583a8702dd750d11b330cb0cf068", - "reference": "641934c6813d583a8702dd750d11b330cb0cf068", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/0489a6247f729652db9b9ff408f69ac3bee3589e", + "reference": "0489a6247f729652db9b9ff408f69ac3bee3589e", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/clock": "^7.4|^8.0", "symfony/config": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", @@ -7084,7 +7205,7 @@ "symfony/password-hasher": "^7.4|^8.0", "symfony/security-core": "^7.4|^8.0", "symfony/security-csrf": "^7.4|^8.0", - "symfony/security-http": "^7.4|^8.0", + "symfony/security-http": "^8.1", "symfony/service-contracts": "^2.5|^3" }, "require-dev": { @@ -7099,6 +7220,7 @@ "symfony/http-client": "^7.4|^8.0", "symfony/ldap": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", "symfony/rate-limiter": "^7.4|^8.0", "symfony/runtime": "^7.4|^8.0", "symfony/serializer": "^7.4|^8.0", @@ -7135,7 +7257,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v8.0.13" + "source": "https://github.com/symfony/security-bundle/tree/v8.1.0" }, "funding": [ { @@ -7155,24 +7277,24 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/security-core", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "61a8892f4d22ee7f4e8f4917e9fd11f574ac7d2d" + "reference": "a8239abe61dafdd0c01c0b4019138b2855717f97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/61a8892f4d22ee7f4e8f4917e9fd11f574ac7d2d", - "reference": "61a8892f4d22ee7f4e8f4917e9fd11f574ac7d2d", + "url": "https://api.github.com/repos/symfony/security-core/zipball/a8239abe61dafdd0c01c0b4019138b2855717f97", + "reference": "a8239abe61dafdd0c01c0b4019138b2855717f97", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/event-dispatcher-contracts": "^2.5|^3", "symfony/password-hasher": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" @@ -7187,6 +7309,7 @@ "symfony/expression-language": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/ldap": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", "symfony/string": "^7.4|^8.0", "symfony/translation": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0" @@ -7217,7 +7340,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v8.0.13" + "source": "https://github.com/symfony/security-core/tree/v8.1.0" }, "funding": [ { @@ -7237,24 +7360,25 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/security-csrf", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "83c8f60ef8d385c05ea863093c9efabe74800883" + "reference": "c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/83c8f60ef8d385c05ea863093c9efabe74800883", - "reference": "83c8f60ef8d385c05ea863093c9efabe74800883", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f", + "reference": "c865a8ee0d30b14545d7e5349b8e443f4fa9dc3f", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/security-core": "^7.4|^8.0" }, "require-dev": { @@ -7288,7 +7412,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v8.0.8" + "source": "https://github.com/symfony/security-csrf/tree/v8.1.0" }, "funding": [ { @@ -7308,26 +7432,27 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/security-http", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "ca895c1303cc1d290f190cd7301d48fcef9e73e5" + "reference": "e0e6c7b9e80eec37248b92359cbd6938c7086f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/ca895c1303cc1d290f190cd7301d48fcef9e73e5", - "reference": "ca895c1303cc1d290f190cd7301d48fcef9e73e5", + "url": "https://api.github.com/repos/symfony/security-http/zipball/e0e6c7b9e80eec37248b92359cbd6938c7086f4b", + "reference": "e0e6c7b9e80eec37248b92359cbd6938c7086f4b", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", + "symfony/http-kernel": "^8.1", "symfony/polyfill-mbstring": "^1.0", "symfony/property-access": "^7.4|^8.0", "symfony/security-core": "^7.4|^8.0", @@ -7375,7 +7500,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v8.0.13" + "source": "https://github.com/symfony/security-http/tree/v8.1.0" }, "funding": [ { @@ -7395,30 +7520,31 @@ "type": "tidelift" } ], - "time": "2026-05-25T06:08:44+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/serializer", - "version": "v8.0.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "72ed7e1475790714f07c3a59bd01fd32cd022fdf" + "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/72ed7e1475790714f07c3a59bd01fd32cd022fdf", - "reference": "72ed7e1475790714f07c3a59bd01fd32cd022fdf", + "url": "https://api.github.com/repos/symfony/serializer/zipball/d101886195c5f772cf7033641fe9c40c3e3969e1", + "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/property-access": "<7.4.2|>=8.0,<8.0.2", + "symfony/property-access": "<8.1", "symfony/property-info": "<7.4", "symfony/type-info": "<7.4" }, @@ -7437,7 +7563,7 @@ "symfony/http-kernel": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", "symfony/mime": "^7.4|^8.0", - "symfony/property-access": "^7.4.2|^8.0.2", + "symfony/property-access": "^8.1", "symfony/property-info": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3", "symfony/type-info": "^7.4|^8.0", @@ -7473,7 +7599,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v8.0.10" + "source": "https://github.com/symfony/serializer/tree/v8.1.0" }, "funding": [ { @@ -7493,7 +7619,7 @@ "type": "tidelift" } ], - "time": "2026-05-04T13:41:39+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/service-contracts", @@ -7584,20 +7710,20 @@ }, { "name": "symfony/stopwatch", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3" + "reference": "21c07b026905d596e8379caeb115d87aa479499d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3", - "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", + "reference": "21c07b026905d596e8379caeb115d87aa479499d", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -7626,7 +7752,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v8.0.8" + "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" }, "funding": [ { @@ -7646,24 +7772,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/string", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f2e3e4d33579350d1b12001ef2872f86b27ed3dc" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f2e3e4d33579350d1b12001ef2872f86b27ed3dc", - "reference": "f2e3e4d33579350d1b12001ef2872f86b27ed3dc", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-grapheme": "^1.33", "symfony/polyfill-intl-normalizer": "^1.0", @@ -7716,7 +7842,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.13" + "source": "https://github.com/symfony/string/tree/v8.1.0" }, "funding": [ { @@ -7736,24 +7862,24 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation", - "version": "v8.0.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67" + "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f63e9342e12646a57c91ef8a366a4f9d8e557b67", - "reference": "f63e9342e12646a57c91ef8a366a4f9d8e557b67", + "url": "https://api.github.com/repos/symfony/translation/zipball/b2bd012ca28c4acae830ee1206a5b6e35dd99693", + "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-mbstring": "^1.0", "symfony/translation-contracts": "^3.6.1" }, @@ -7809,7 +7935,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v8.0.10" + "source": "https://github.com/symfony/translation/tree/v8.1.0" }, "funding": [ { @@ -7829,7 +7955,7 @@ "type": "tidelift" } ], - "time": "2026-05-06T11:30:54+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation-contracts", @@ -7915,22 +8041,22 @@ }, { "name": "symfony/twig-bridge", - "version": "v8.0.12", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "f1397eb19ab4f738bd22570d65d40792c1ba3f79" + "reference": "25bb8c01edaab85e13142f6010df09b990388343" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/f1397eb19ab4f738bd22570d65d40792c1ba3f79", - "reference": "f1397eb19ab4f738bd22570d65d40792c1ba3f79", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/25bb8c01edaab85e13142f6010df09b990388343", + "reference": "25bb8c01edaab85e13142f6010df09b990388343", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.21" + "twig/twig": "^3.25" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", @@ -7999,7 +8125,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v8.0.12" + "source": "https://github.com/symfony/twig-bridge/tree/v8.1.0" }, "funding": [ { @@ -8019,25 +8145,25 @@ "type": "tidelift" } ], - "time": "2026-04-29T18:17:56+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/twig-bundle", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1" + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1", - "reference": "f83767b78e2580ca9fe9a2cf6fcff19cd5389bc1", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/b7f4a471a07b8b52174d153e4db12f46954192ed", + "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/config": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", @@ -8083,7 +8209,7 @@ "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/v8.0.8" + "source": "https://github.com/symfony/twig-bundle/tree/v8.1.0" }, "funding": [ { @@ -8103,24 +8229,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/type-info", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866" + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/08723aceb8c3271e8cb3db8b2565728b0c88e866", - "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866", + "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/container": "^1.1|^2.0" }, "conflict": { @@ -8165,7 +8291,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.0.9" + "source": "https://github.com/symfony/type-info/tree/v8.1.0" }, "funding": [ { @@ -8185,24 +8311,24 @@ "type": "tidelift" } ], - "time": "2026-04-29T15:02:55+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/uid", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "4d9d6510bbe88ebb4608b7200d18606cdf80825c" + "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/4d9d6510bbe88ebb4608b7200d18606cdf80825c", - "reference": "4d9d6510bbe88ebb4608b7200d18606cdf80825c", + "url": "https://api.github.com/repos/symfony/uid/zipball/7393f157a55f7e70a4de0334435c55a5a8fe749a", + "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { @@ -8243,7 +8369,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v8.0.9" + "source": "https://github.com/symfony/uid/tree/v8.1.0" }, "funding": [ { @@ -8263,7 +8389,7 @@ "type": "tidelift" } ], - "time": "2026-04-30T16:10:06+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/ux-twig-component", @@ -8355,20 +8481,21 @@ }, { "name": "symfony/validator", - "version": "v8.0.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "12bb4be483a8626bd1b2f46f5d44c9449cf4361f" + "reference": "b122b2e384fa84166213ce98b887f01a3eea8d94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/12bb4be483a8626bd1b2f46f5d44c9449cf4361f", - "reference": "12bb4be483a8626bd1b2f46f5d44c9449cf4361f", + "url": "https://api.github.com/repos/symfony/validator/zipball/b122b2e384fa84166213ce98b887f01a3eea8d94", + "reference": "b122b2e384fa84166213ce98b887f01a3eea8d94", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.0", "symfony/translation-contracts": "^2.5|^3" @@ -8381,6 +8508,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", "symfony/config": "^7.4|^8.0", "symfony/console": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", @@ -8426,7 +8554,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v8.0.10" + "source": "https://github.com/symfony/validator/tree/v8.1.0" }, "funding": [ { @@ -8446,24 +8574,24 @@ "type": "tidelift" } ], - "time": "2026-05-05T16:03:11+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/var-dumper", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1" + "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", - "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", + "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-mbstring": "^1.0" }, "conflict": { @@ -8513,7 +8641,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.0.8" + "source": "https://github.com/symfony/var-dumper/tree/v8.1.0" }, "funding": [ { @@ -8533,24 +8661,26 @@ "type": "tidelift" } ], - "time": "2026-03-31T07:15:36+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/var-exporter", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "24cf67be4dd0926e4413635418682f4fff831412" + "reference": "2dd18582c5f6c024db9fc0ff9c76d873af726f34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/24cf67be4dd0926e4413635418682f4fff831412", - "reference": "24cf67be4dd0926e4413635418682f4fff831412", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2dd18582c5f6c024db9fc0ff9c76d873af726f34", + "reference": "2dd18582c5f6c024db9fc0ff9c76d873af726f34", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-deepclone": "^1.37" }, "require-dev": { "symfony/property-access": "^7.4|^8.0", @@ -8580,11 +8710,12 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "description": "Provides tools to export, instantiate, hydrate, clone and lazy-load PHP objects", "homepage": "https://symfony.com", "keywords": [ "clone", "construct", + "deep-clone", "export", "hydrate", "instantiate", @@ -8593,7 +8724,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v8.0.9" + "source": "https://github.com/symfony/var-exporter/tree/v8.1.0" }, "funding": [ { @@ -8613,24 +8744,24 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:51:42+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/web-link", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "f76065fd8d59284332c8beaf2d7e1449f8c532c3" + "reference": "87ca04297acf837a1de25d3f6c31b9551235dcbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/f76065fd8d59284332c8beaf2d7e1449f8c532c3", - "reference": "f76065fd8d59284332c8beaf2d7e1449f8c532c3", + "url": "https://api.github.com/repos/symfony/web-link/zipball/87ca04297acf837a1de25d3f6c31b9551235dcbd", + "reference": "87ca04297acf837a1de25d3f6c31b9551235dcbd", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/link": "^1.1|^2.0" }, "provide": { @@ -8677,7 +8808,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v8.0.8" + "source": "https://github.com/symfony/web-link/tree/v8.1.0" }, "funding": [ { @@ -8697,7 +8828,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/webpack-encore-bundle", @@ -8777,27 +8908,28 @@ }, { "name": "symfony/yaml", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a1cdf99e359d5c001782a2db67a1fc71b2a5b34e" + "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a1cdf99e359d5c001782a2db67a1fc71b2a5b34e", - "reference": "a1cdf99e359d5c001782a2db67a1fc71b2a5b34e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/efb42bd2c6f4f3ccfd4683583449938b5fc146b0", + "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/console": "<7.4" }, "require-dev": { - "symfony/console": "^7.4|^8.0" + "symfony/console": "^7.4|^8.0", + "yaml/yaml-test-suite": "*" }, "bin": [ "Resources/bin/yaml-lint" @@ -8828,7 +8960,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.0.13" + "source": "https://github.com/symfony/yaml/tree/v8.1.0" }, "funding": [ { @@ -8848,7 +8980,7 @@ "type": "tidelift" } ], - "time": "2026-05-25T06:08:44+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "twig/extra-bundle", @@ -13316,20 +13448,20 @@ }, { "name": "symfony/css-selector", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "3665cfade90565430909b906394c73c8739e57d0" + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/3665cfade90565430909b906394c73c8739e57d0", - "reference": "3665cfade90565430909b906394c73c8739e57d0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -13361,7 +13493,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.0.9" + "source": "https://github.com/symfony/css-selector/tree/v8.1.0" }, "funding": [ { @@ -13381,26 +13513,26 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:51:42+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/debug-bundle", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "d9d127d61fb2aa7f7f324a8b1928767e2211d1bc" + "reference": "2da1f202b38f646dbee032529cfd8e727cd12cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/d9d127d61fb2aa7f7f324a8b1928767e2211d1bc", - "reference": "d9d127d61fb2aa7f7f324a8b1928767e2211d1bc", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/2da1f202b38f646dbee032529cfd8e727cd12cd1", + "reference": "2da1f202b38f646dbee032529cfd8e727cd12cd1", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/config": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", @@ -13436,7 +13568,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v8.0.8" + "source": "https://github.com/symfony/debug-bundle/tree/v8.1.0" }, "funding": [ { @@ -13456,7 +13588,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/maker-bundle", @@ -13723,20 +13855,20 @@ }, { "name": "symfony/process", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4" + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", - "reference": "4f23d9c7637ead9ed19f697fe93cb87fd9b379d4", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -13764,7 +13896,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.13" + "source": "https://github.com/symfony/process/tree/v8.1.0" }, "funding": [ { @@ -13784,28 +13916,28 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v8.0.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "5f4ac13a4c89c91087182bd464753bc2e58e53e0" + "reference": "f8ccea08797a511b85a698b0da40e1b9e6461086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/5f4ac13a4c89c91087182bd464753bc2e58e53e0", - "reference": "5f4ac13a4c89c91087182bd464753bc2e58e53e0", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f8ccea08797a511b85a698b0da40e1b9e6461086", + "reference": "f8ccea08797a511b85a698b0da40e1b9e6461086", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.4", + "php": ">=8.4.1", "symfony/config": "^7.4|^8.0", "symfony/framework-bundle": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", + "symfony/http-kernel": "^8.1", "symfony/routing": "^7.4|^8.0", "symfony/twig-bundle": "^7.4|^8.0" }, @@ -13849,7 +13981,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.0.13" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.1.0" }, "funding": [ { @@ -13869,7 +14001,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T18:05:53+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "theofidry/alice-data-fixtures", diff --git a/config/reference.php b/config/reference.php index 563c307..0367b0f 100644 --- a/config/reference.php +++ b/config/reference.php @@ -78,6 +78,7 @@ * tags?: TagsType, * resource_tags?: TagsType, * decorates?: string, + * decorates_tag?: string, * decoration_inner_name?: string, * decoration_priority?: int, * decoration_on_invalid?: 'exception'|'ignore'|null, @@ -118,6 +119,11 @@ * stack: list>, * public?: bool, * deprecated?: DeprecationType, + * decorates?: string, + * decorates_tag?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, * } * @psalm-type ServicesConfig = array{ * _defaults?: DefaultsType, @@ -168,7 +174,7 @@ * allow_revalidate?: bool|Param, * stale_while_revalidate?: int|Param, * stale_if_error?: int|Param, - * terminate_on_cache_hit?: bool|Param, + * terminate_on_cache_hit?: bool|Param, // Deprecated: Setting the "framework.http_cache.terminate_on_cache_hit.terminate_on_cache_hit" configuration option is deprecated. It will be removed in version 9.0. * }, * esi?: bool|array{ // ESI configuration * enabled?: bool|Param, // Default: false @@ -188,7 +194,7 @@ * only_exceptions?: bool|Param, // Default: false * only_main_requests?: bool|Param, // Default: false * dsn?: scalar|Param|null, // Default: "file:%kernel.cache_dir%/profiler" - * collect_serializer_data?: true|Param, // Default: true + * collect_serializer_data?: true|Param, // Deprecated: Setting the "framework.profiler.collect_serializer_data.collect_serializer_data" configuration option is deprecated. It will be removed in version 9.0. // Default: true * }, * workflows?: bool|array{ * enabled?: bool|Param, // Default: false @@ -340,6 +346,7 @@ * endpoint?: scalar|Param|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null * }, * disable_translation?: bool|Param, // Default: false + * property_metadata_existence_check?: bool|Param, // When enabled, validateProperty() and validatePropertyValue() throw an exception if no metadata is found for the given property. // Default: false * auto_mapping?: array, * }>, @@ -396,6 +403,7 @@ * provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter. * early_expiration_message_bus?: scalar|Param|null, * clearer?: scalar|Param|null, + * marshaller?: scalar|Param|null, // The marshaller service to use for this pool. * }>, * }, * php_errors?: array{ // PHP errors handling configuration @@ -420,9 +428,7 @@ * }, * messenger?: bool|array{ // Messenger configuration * enabled?: bool|Param, // Default: true - * routing?: array, - * }>, + * routing?: array>, * serializer?: array{ * default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" * symfony_serializer?: array{ @@ -498,7 +504,7 @@ * enabled?: bool|Param, // Default: false * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true - * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. // Default: 86400 * }, * retry_failed?: bool|array{ * enabled?: bool|Param, // Default: false @@ -514,7 +520,7 @@ * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 * }, * }, - * mock_response_factory?: scalar|Param|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * mock_response_factory?: scalar|Param|null, // `true` to always return empty 200 responses, or the id of the service to use to generate mock responses - which should be either an invokable or an iterable. * scoped_clients?: array, * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null * caching?: bool|array{ // Caching configuration. * enabled?: bool|Param, // Default: false * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true - * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. // Default: 86400 * }, * retry_failed?: bool|array{ * enabled?: bool|Param, // Default: false @@ -635,6 +642,7 @@ * interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 * }, + * anchor_at?: scalar|Param|null, // Aligns the "fixed_window" policy to a calendar (e.g. "2024-01-05 00:00:00 UTC" combined with `interval: 1 month` resets the counter on the 5th of each month). UTC if not specified. // Default: null * }>, * }, * uid?: bool|array{ // Uid configuration @@ -644,10 +652,12 @@ * name_based_uuid_namespace?: scalar|Param|null, * time_based_uuid_version?: 7|6|1|Param, // Default: 7 * time_based_uuid_node?: scalar|Param|null, + * uuid47_secret?: scalar|Param|null, // A high-entropy secret used by the "uuid47_transformer" service. Defaults to "kernel.secret". // Default: null * }, * html_sanitizer?: bool|array{ // HtmlSanitizer configuration * enabled?: bool|Param, // Default: false * sanitizers?: array, @@ -671,6 +681,10 @@ * webhook?: bool|array{ // Webhook configuration * enabled?: bool|Param, // Default: false * message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus" + * event_header_name?: scalar|Param|null, // Default: "Webhook-Event" + * id_header_name?: scalar|Param|null, // Default: "Webhook-Id" + * signature_header_name?: scalar|Param|null, // Default: "Webhook-Signature" + * signing_algorithm?: scalar|Param|null, // Default: "sha256" * routing?: array + * }, * }, * } * @psalm-type TwigConfig = array{ @@ -719,7 +737,7 @@ * access_denied_url?: scalar|Param|null, // Default: null * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" - * erase_credentials?: bool|Param, // Default: true + * erase_credentials?: bool|Param, // Deprecated: Setting the "security.erase_credentials.erase_credentials" configuration option is deprecated. It will be removed in Symfony 9.0, as the "eraseCredentials()" method was removed in Symfony 8.0. // Default: true * access_decision_manager?: array{ * strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param, * service?: scalar|Param|null, @@ -791,7 +809,7 @@ * path?: scalar|Param|null, // Default: "/logout" * target?: scalar|Param|null, // Default: "/" * invalidate_session?: bool|Param, // Default: true - * clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, + * clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"clientHints"|"executionContexts"|"prefetchCache"|"prerenderCache"|Param>, * delete_cookies?: string|arrayentityManager->flush(); return new SelfValidatingPassport(new UserBadge($user->getUserIdentifier())); - } catch (ItkOpenIdConnectException|InvalidProviderException $exception) { + } catch (OpenIdConnectExceptionInterface $exception) { throw new CustomUserMessageAuthenticationException($exception->getMessage()); } } From 86fab2f7a320afcd99196d24524a7671b7502d1d Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 2 Jun 2026 13:31:25 +0200 Subject: [PATCH 64/94] Fix CI: audit lock file, regenerate API spec, changelog 1.11.1 section - composer-audit: pass --locked so the audit runs against the committed lock file (CI container has no installed vendor/) - Regenerate public/api-spec-v1.yaml for Symfony YAML 8.1 dumper formatting - Move the 1.11.1 hotfix entry (#84) into a ## [1.11.1] section so the [1.11.1] link reference is used (fixes markdownlint MD053) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/composer.yaml | 2 +- CHANGELOG.md | 8 +++++--- public/api-spec-v1.yaml | 24 +++++++++++------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index da473c1..26a728a 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -76,4 +76,4 @@ jobs: docker network create frontend - run: | - docker compose run --rm phpfpm composer audit + docker compose run --rm phpfpm composer audit --locked diff --git a/CHANGELOG.md b/CHANGELOG.md index 629a870..2405e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#81](https://github.com/itk-dev/devops_itksites/pull/81) 5564: Asset Mapper migration - Add Symfony Asset Mapper bundle and importmap -- [#84](https://github.com/itk-dev/devops_itksites/pull/84) - Update composer dependencies. Fix for Symfony and Twig CVE's - - [#85](https://github.com/itk-dev/devops_itksites/pull/85) Update openid-connect-bundle to 5.0 and Symfony to 8.1 - Bump `itk-dev/openid-connect-bundle` to `^5.0` (pulls `itk-dev/openid-connect` 5.0) @@ -25,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 extend `ItkOpenIdConnectException`) - Update Symfony to 8.1 +## [1.11.1] - 2026-06-01 + +- [#84](https://github.com/itk-dev/devops_itksites/pull/84) + Update composer dependencies. Fix for Symfony and Twig CVE's + ## [1.11.0] - 2026-05-19 - [#78](https://github.com/itk-dev/devops_itksites/pull/78) diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index 72bb018..d2a0179 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -4,8 +4,7 @@ info: description: 'REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories.' version: 1.0.0 servers: - - - url: 'https://itksites.local.itkdev.dk' + - url: 'https://itksites.local.itkdev.dk' description: '' paths: /api/detection_results: @@ -16,7 +15,7 @@ paths: responses: '202': description: 'Detection result accepted for processing' - links: { } + links: {} '400': description: 'Invalid input — malformed request body' content: @@ -29,7 +28,7 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - links: { } + links: {} '401': description: 'Unauthorized — missing or invalid API key. The Authorization header must use the format: Apikey {key}' '403': @@ -46,7 +45,7 @@ paths: application/json: schema: $ref: '#/components/schemas/ConstraintViolation' - links: { } + links: {} summary: 'Submit a detection result for async processing' description: 'Accepts a detection result from the server harvester and queues it for asynchronous processing. The result is deduplicated by content hash — identical submissions update the last contact timestamp without triggering reprocessing. Returns 202 Accepted with an empty body.' parameters: [] @@ -251,11 +250,11 @@ components: type: - string - 'null' - responses: { } - parameters: { } - examples: { } - requestBodies: { } - headers: { } + responses: {} + parameters: {} + examples: {} + requestBodies: {} + headers: {} securitySchemes: apiKey: type: apiKey @@ -266,7 +265,6 @@ security: - apiKey: [] tags: - - - name: DetectionResult + - name: DetectionResult description: "Resource 'DetectionResult' operations." -webhooks: { } +webhooks: {} From e882b7e6541c8943854ea00fbf986d0123bacf2c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 8 Jun 2026 12:04:20 +0200 Subject: [PATCH 65/94] Remove fork-only Renovate pipeline merged upstream by mistake PR #82 (turegjorup/feat/renovate-auto-patch) was a Phase 1 fork-validation branch that should have stayed on the fork. It was merged into upstream develop, bringing the Renovate auto-patch/auto-release pipeline and a fork-specific change that commented out the Codecov upload step. Remove the fork-only files (renovate.json, renovate + auto-release workflows, renovate-changelog.sh) and restore the Codecov upload step in pr.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/scripts/renovate-changelog.sh | 43 --------- .github/workflows/auto-release.yaml | 117 ---------------------- .github/workflows/pr.yaml | 16 ++- .github/workflows/renovate.yaml | 62 ------------ renovate.json | 134 -------------------------- 5 files changed, 7 insertions(+), 365 deletions(-) delete mode 100755 .github/scripts/renovate-changelog.sh delete mode 100644 .github/workflows/auto-release.yaml delete mode 100644 .github/workflows/renovate.yaml delete mode 100644 renovate.json diff --git a/.github/scripts/renovate-changelog.sh b/.github/scripts/renovate-changelog.sh deleted file mode 100755 index 5f4ab6d..0000000 --- a/.github/scripts/renovate-changelog.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Called by Renovate postUpgradeTasks. Appends a single bullet under the -# `## [Unreleased]` section of CHANGELOG.md so the auto-release workflow has -# content to promote into a tagged release after the Renovate PR merges to main. -set -euo pipefail - -BRANCH="${1:-renovate/unknown}" -TITLE="${2:-Update dependencies}" - -# Skip if CHANGELOG.md was already touched this run (grouped PRs fire the hook -# more than once; we only want one bullet per PR). -if git diff --cached --name-only | grep -qx CHANGELOG.md; then - exit 0 -fi -if git diff --name-only | grep -qx CHANGELOG.md; then - exit 0 -fi - -ENTRY="- Renovate: ${TITLE} (\`${BRANCH}\`)" - -python3 - "$ENTRY" <<'PY' -import re, sys, pathlib -entry = sys.argv[1] -p = pathlib.Path("CHANGELOG.md") -text = p.read_text(encoding="utf-8") - -pattern = re.compile(r"(## \[Unreleased\]\n)(.*?)(\n## \[)", re.DOTALL) -m = pattern.search(text) -if not m: - # No [Unreleased] section yet — insert one after the intro paragraph - # (the first blank line after the H1 header). - head, _, rest = text.partition("\n\n") - text = f"{head}\n\n## [Unreleased]\n\n{entry}\n\n{rest}" -else: - body = m.group(2).rstrip("\n") - sep = "\n" if body else "" - text = pattern.sub( - lambda _m: f"{_m.group(1)}{body}{sep}{entry}\n{_m.group(3)}", - text, count=1, - ) - -p.write_text(text, encoding="utf-8") -PY diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml deleted file mode 100644 index 59c472b..0000000 --- a/.github/workflows/auto-release.yaml +++ /dev/null @@ -1,117 +0,0 @@ -# After a Renovate PR merges into `main`, promote the `[Unreleased]` section -# of CHANGELOG.md to the next patch version, commit, tag, push, and back-merge -# main into develop so the branches stay in sync. -# -# Required secrets: -# RELEASE_TOKEN PAT or GitHub App token with contents:write + workflows. -# Needed for the tag push to trigger github_build_release.yml -# (the default GITHUB_TOKEN cannot trigger other workflows) -# and for the back-merge push to `develop`. -# -# Gating: the existing pr.yaml is required by branch protection, so by the time -# a PR is merged into main, CI has already passed. We do not re-wait for checks -# on the merge commit (it has no associated check runs after a squash-merge). - -name: Auto Release on Renovate Merge - -on: - pull_request: - types: [closed] - branches: [main] - -permissions: - contents: read - -jobs: - release: - if: >- - github.event.pull_request.merged == true && - github.event.pull_request.user.login == 'renovate[bot]' - runs-on: ubuntu-latest - steps: - - name: Checkout main with full history - uses: actions/checkout@v6 - with: - ref: main - fetch-depth: 0 - token: ${{ secrets.RELEASE_TOKEN }} - - - name: Compute next patch version - id: version - run: | - latest=$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1) - : "${latest:=0.0.0}" - IFS=. read -r maj min pat <<<"$latest" - next="${maj}.${min}.$((pat + 1))" - { - echo "current=$latest" - echo "next=$next" - echo "date=$(date -u +%Y-%m-%d)" - } >>"$GITHUB_OUTPUT" - echo "Will release: $latest -> $next" - - - name: Bail if [Unreleased] is empty - id: gate - run: | - body=$(awk '/^## \[Unreleased\]/{f=1;next} /^## \[/{f=0} f' CHANGELOG.md \ - | grep -v '^[[:space:]]*$' || true) - if [ -z "$body" ]; then - echo "Nothing under [Unreleased]; skipping release." - echo "skip=true" >>"$GITHUB_OUTPUT" - else - echo "skip=false" >>"$GITHUB_OUTPUT" - fi - - - name: Promote [Unreleased] -> ${{ steps.version.outputs.next }} - if: steps.gate.outputs.skip != 'true' - env: - VER: ${{ steps.version.outputs.next }} - DATE: ${{ steps.version.outputs.date }} - run: | - python3 - <<'PY' - import os, re, pathlib - ver = os.environ["VER"] - date = os.environ["DATE"] - p = pathlib.Path("CHANGELOG.md") - text = p.read_text(encoding="utf-8") - text = re.sub( - r"## \[Unreleased\]\n", - f"## [Unreleased]\n\n## [{ver}] - {date}\n", - text, count=1, - ) - p.write_text(text, encoding="utf-8") - PY - - - name: Commit on main, tag, push - if: steps.gate.outputs.skip != 'true' - env: - VER: ${{ steps.version.outputs.next }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add CHANGELOG.md - git commit -m "release: ${VER}" - git tag -a "${VER}" -m "Release ${VER}" - git push origin main - git push origin "${VER}" - - - name: Back-merge main into develop - if: steps.gate.outputs.skip != 'true' - env: - VER: ${{ steps.version.outputs.next }} - GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: | - git fetch origin develop - git checkout -B develop origin/develop - if git merge --no-ff main -m "chore: back-merge hotfix ${VER} into develop"; then - git push origin develop - echo "Back-merged ${VER} to develop." - else - git merge --abort || true - gh pr create \ - --repo "${GITHUB_REPOSITORY}" \ - --base develop \ - --head main \ - --title "Back-merge hotfix ${VER} into develop" \ - --body "Automated back-merge from \`main\` conflicted with \`develop\`. Resolve and merge to keep branches in sync after release ${VER}." - fi diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index bd9a238..5b24bf2 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -37,15 +37,13 @@ jobs: docker compose exec -e XDEBUG_MODE=coverage phpfpm bin/console --env=test doctrine:migrations:migrate --no-interaction --quiet docker compose exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-clover=coverage/unit.xml - # Codecov upload disabled on the fork — no CODECOV_TOKEN secret. - # Re-enable when promoting to upstream. - # - name: Upload coverage to Codecov - # uses: codecov/codecov-action@v5 - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - # files: ./coverage/unit.xml - # fail_ci_if_error: true - # flags: unittests + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/unit.xml + fail_ci_if_error: true + flags: unittests build-assets: runs-on: ubuntu-latest diff --git a/.github/workflows/renovate.yaml b/.github/workflows/renovate.yaml deleted file mode 100644 index 69a1f1f..0000000 --- a/.github/workflows/renovate.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# Self-hosted Renovate runner. Runs on a schedule and on demand. -# Self-hosted is required because postUpgradeTasks (which writes CHANGELOG.md -# inline with the dep bump) is disabled on the Mend Renovate SaaS app. -# -# Required secrets: -# RENOVATE_TOKEN PAT or GitHub App token with repo write + workflows. -# Pushes by the default GITHUB_TOKEN don't re-trigger Actions, -# so a PAT/App token is needed to make CI re-run on -# Renovate's branch updates. - -name: Renovate - -on: - schedule: - - cron: "0 11 * * 1-5" # weekdays 11:00 UTC = 13:00 Copenhagen (CEST) / 12:00 (CET) - workflow_dispatch: - inputs: - logLevel: - description: Renovate log level - type: choice - default: info - options: [debug, info, warn, error] - dryRun: - description: Dry-run (no PRs) - type: choice - default: "null" - options: ["null", "full"] - -permissions: - contents: read - -concurrency: - group: renovate - cancel-in-progress: false - -jobs: - renovate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - # PHP/Composer are needed so Renovate can resolve composer.lock - # updates inside its container. - - uses: shivammathur/setup-php@v2 - with: - php-version: "8.3" - tools: composer:v2 - coverage: none - - - name: Run Renovate - uses: renovatebot/github-action@v43 - with: - configurationFile: renovate.json - token: ${{ secrets.RENOVATE_TOKEN }} - renovate-version: latest - env: - LOG_LEVEL: ${{ inputs.logLevel || 'info' }} - RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'null' }} - RENOVATE_REPOSITORIES: ${{ github.repository }} - # Allow the changelog script to run from postUpgradeTasks. - RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^bash \\.github/scripts/renovate-changelog\\.sh"]' - RENOVATE_ALLOW_POST_UPGRADE_COMMAND_TEMPLATING: "true" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index afa00a6..0000000 --- a/renovate.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":dependencyDashboard", - ":semanticCommitsDisabled", - ":maintainLockFilesWeekly", - "schedule:weekdays" - ], - "baseBranches": ["main", "develop"], - "timezone": "Europe/Copenhagen", - "labels": ["dependencies", "renovate"], - "prConcurrentLimit": 5, - "prHourlyLimit": 2, - "rangeStrategy": "bump", - "rebaseWhen": "behind-base-branch", - "platformAutomerge": true, - "ignoreUnstable": true, - "respectLatest": true, - - "vulnerabilityAlerts": { - "enabled": true, - "labels": ["security", "dependencies"], - "automerge": true, - "automergeType": "pr", - "minimumReleaseAge": "0 days", - "prPriority": 10, - "commitMessagePrefix": "security:", - "matchBaseBranches": ["main"] - }, - "osvVulnerabilityAlerts": true, - - "lockFileMaintenance": { - "enabled": true, - "schedule": ["before 6am on Monday"], - "automerge": true, - "automergeType": "pr", - "commitMessageAction": "Refresh", - "matchBaseBranches": ["main"] - }, - - "composer": { "enabled": true, "ignorePlatformReqs": [] }, - "npm": { "enabled": true }, - "github-actions": { "enabled": true, "pinDigests": true }, - "docker-compose": { "enabled": true, "pinDigests": true }, - "dockerfile": { "enabled": true, "pinDigests": true }, - - "packageRules": [ - { - "description": "Hotfix: auto-merge patches/digests/lockfile on main after 7-day soak", - "matchBaseBranches": ["main"], - "matchUpdateTypes": [ - "patch", - "pin", - "digest", - "lockFileMaintenance" - ], - "automerge": true, - "automergeType": "pr", - "automergeStrategy": "squash", - "minimumReleaseAge": "7 days" - }, - { - "description": "Normal flow: minor/major on develop, dashboard approval, never auto-merge", - "matchBaseBranches": ["develop"], - "matchUpdateTypes": ["minor", "major"], - "dependencyDashboardApproval": true, - "automerge": false - }, - { - "description": "Suppress minor/major against main (those belong on develop)", - "matchBaseBranches": ["main"], - "matchUpdateTypes": ["minor", "major"], - "enabled": false - }, - { - "description": "Suppress patches against develop (they reach develop via back-merge after release)", - "matchBaseBranches": ["develop"], - "matchUpdateTypes": [ - "patch", - "pin", - "digest", - "lockFileMaintenance" - ], - "enabled": false - }, - { - "description": "Group Symfony patches into one PR on main", - "matchBaseBranches": ["main"], - "matchPackagePatterns": ["^symfony/"], - "matchUpdateTypes": ["patch"], - "groupName": "symfony (patch)" - }, - { - "description": "Group Doctrine patches on main", - "matchBaseBranches": ["main"], - "matchPackagePatterns": ["^doctrine/"], - "matchUpdateTypes": ["patch"], - "groupName": "doctrine (patch)" - }, - { - "description": "Group api-platform patches on main", - "matchBaseBranches": ["main"], - "matchPackagePatterns": ["^api-platform/"], - "matchUpdateTypes": ["patch"], - "groupName": "api-platform (patch)" - }, - { - "description": "Never auto-bump the PHP platform requirement", - "matchBaseBranches": ["develop"], - "matchPackageNames": ["php"], - "rangeStrategy": "in-range-only", - "automerge": false, - "dependencyDashboardApproval": true - }, - { - "description": "GitHub Actions: pin digests, auto-merge digest/patch on main", - "matchBaseBranches": ["main"], - "matchManagers": ["github-actions"], - "matchUpdateTypes": ["digest", "patch"], - "automerge": true - } - ], - - "postUpdateOptions": ["composerUpdateAllDependencies"], - - "postUpgradeTasks": { - "commands": [ - "bash .github/scripts/renovate-changelog.sh \"{{{branchName}}}\" \"{{{prTitle}}}\"" - ], - "fileFilters": ["CHANGELOG.md"], - "executionMode": "branch" - } -} From fdcade528584bf459b5978d7995edef44cd3ff36 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 8 Jun 2026 12:26:06 +0200 Subject: [PATCH 66/94] CI: update codecov/codecov-action to v7 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/pr.yaml | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 5b24bf2..a726d00 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -38,7 +38,7 @@ jobs: docker compose exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-clover=coverage/unit.xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage/unit.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 095afd3..3dd793a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add economics service and sync action/command for service agreement synchronization - [#81](https://github.com/itk-dev/devops_itksites/pull/81) 5564: Asset Mapper migration - Add Symfony Asset Mapper bundle and importmap +- Update `codecov/codecov-action` to v7 ## [1.11.2] - 2026-06-02 From bae8493828c381566762131bdba687c53dd2379c Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 8 Jun 2026 12:27:36 +0200 Subject: [PATCH 67/94] CHANGELOG: add PR number for codecov v7 entry Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd793a..5f1daa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add economics service and sync action/command for service agreement synchronization - [#81](https://github.com/itk-dev/devops_itksites/pull/81) 5564: Asset Mapper migration - Add Symfony Asset Mapper bundle and importmap -- Update `codecov/codecov-action` to v7 +- [#87](https://github.com/itk-dev/devops_itksites/pull/87) Update `codecov/codecov-action` to v7 ## [1.11.2] - 2026-06-02 From 554131d6591b7dede8fe6174f44924c98f8c8f96 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 15:45:02 +0200 Subject: [PATCH 68/94] Added API key on users --- CHANGELOG.md | 2 ++ README.md | 18 ++++++++++ config/packages/security.yaml | 9 +++-- migrations/Version20260702123347.php | 33 ++++++++++++++++++ src/Command/UserSetApiKeyCommand.php | 52 ++++++++++++++++++++++++++++ src/Entity/Server.php | 26 +++----------- src/Entity/User.php | 4 +++ src/Security/ApiKeyAuthenticator.php | 18 +++++++++- src/Trait/ApiKeyEntityTrait.php | 35 +++++++++++++++++++ 9 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 migrations/Version20260702123347.php create mode 100644 src/Command/UserSetApiKeyCommand.php create mode 100644 src/Trait/ApiKeyEntityTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1daa2..924edfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#88](https://github.com/itk-dev/devops_itksites/pull/88) + - Let users use the API - [#80](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases diff --git a/README.md b/README.md index fc1305f..797838e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,24 @@ The system is build so that all analyzed data can be truncated safely and rebuil by "replaying" the DetectionResults. This means that care must be taken when manually maintained data and auto updated data must have cross references. +## API + +Authenticated users can access a simple read-only API – see the API documentation on `/api/docs` for details. + +### API keys + +Run the `app:user:set-api-key` console command to set the API for a user: + +``` shell +docker compose exec phpfpm php bin/console app:user:set-api-key +``` + +Use the API key to make an authenticated request, e.g. + +``` shell +curl --header 'accept: application/json' --header 'authorization: Apikey ' https://itksites.local.itkdev.dk/api/sites +``` + ## Development ```sh diff --git a/config/packages/security.yaml b/config/packages/security.yaml index f57e0e7..b7b8257 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -12,6 +12,11 @@ security: entity: class: App\Entity\User property: email + + app_api_users: + chain: + providers: [app_server_provider, app_user_provider] + firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ @@ -20,7 +25,7 @@ security: pattern: ^/api custom_authenticators: - App\Security\ApiKeyAuthenticator - provider: app_server_provider + provider: app_api_users main: custom_authenticators: @@ -41,7 +46,7 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/api/docs, roles: PUBLIC_ACCESS } - - { path: ^/api, roles: ROLE_SERVER } + - { path: ^/api, roles: [ROLE_SERVER, ROLE_USER] } - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } diff --git a/migrations/Version20260702123347.php b/migrations/Version20260702123347.php new file mode 100644 index 0000000..5552bbd --- /dev/null +++ b/migrations/Version20260702123347.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE user ADD api_key VARCHAR(255) NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649C912ED9D ON user (api_key)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_8D93D649C912ED9D ON user'); + $this->addSql('ALTER TABLE user DROP api_key'); + } +} diff --git a/src/Command/UserSetApiKeyCommand.php b/src/Command/UserSetApiKeyCommand.php new file mode 100644 index 0000000..8a0bd31 --- /dev/null +++ b/src/Command/UserSetApiKeyCommand.php @@ -0,0 +1,52 @@ +userRepository->findOneBy(['email' => $userId]) + ?? $this->userRepository->findOneBy(['name' => $userId]); + + if (null === $user) { + throw new InvalidArgumentException(sprintf('Cannot load user with id %s', $userId)); + } + + $question = sprintf('Really set API key on user %s', $user->getUserIdentifier()); + if (!$io->confirm($question)) { + return Command::SUCCESS; + } + + $user->setApiKey($user->generateApiKey()); + $this->entityManager->flush(); + + $io->success([ + sprintf('API key for user %s set to', $user->getUserIdentifier()), + $user->getApiKey(), + ]); + + return Command::SUCCESS; + } +} diff --git a/src/Entity/Server.php b/src/Entity/Server.php index cdf15b1..8786b19 100644 --- a/src/Entity/Server.php +++ b/src/Entity/Server.php @@ -5,6 +5,7 @@ namespace App\Entity; use App\Repository\ServerRepository; +use App\Trait\ApiKeyEntityTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -16,16 +17,9 @@ #[ORM\Entity(repositoryClass: ServerRepository::class)] class Server extends AbstractBaseEntity implements UserInterface, \Stringable { - private const array ROLES = ['ROLE_USER', 'ROLE_SERVER']; + use ApiKeyEntityTrait; - #[ORM\Column(type: 'string', length: 255, unique: true)] - #[Assert\Length( - min: 40, - max: 255, - minMessage: 'Api key must be at least {{ limit }} characters long', - maxMessage: 'Api key cannot be longer than {{ limit }} characters', - )] - private string $apiKey; + private const array ROLES = ['ROLE_USER', 'ROLE_SERVER']; #[ORM\Column(type: 'string', length: 255, unique: true)] #[Groups(['export'])] @@ -93,8 +87,8 @@ class Server extends AbstractBaseEntity implements UserInterface, \Stringable */ public function __construct() { + $this->setApiKey($this->generateApiKey()); $this->detectionResults = new ArrayCollection(); - $this->apiKey = sha1(\random_bytes(40)); $this->installations = new ArrayCollection(); } @@ -258,18 +252,6 @@ public function setUsedFor(?string $usedFor): self return $this; } - public function getApiKey(): string - { - return $this->apiKey; - } - - public function setApiKey(string $apiKey): self - { - $this->apiKey = $apiKey; - - return $this; - } - /** * @return Collection */ diff --git a/src/Entity/User.php b/src/Entity/User.php index 70ad536..15de1c2 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -5,12 +5,15 @@ namespace App\Entity; use App\Repository\UserRepository; +use App\Trait\ApiKeyEntityTrait; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] class User extends AbstractBaseEntity implements UserInterface { + use ApiKeyEntityTrait; + public function __construct( #[ORM\Column(length: 255)] private string $name, @@ -19,6 +22,7 @@ public function __construct( #[ORM\Column(type: 'json')] private array $roles = [], ) { + $this->setApiKey($this->generateApiKey()); } #[\Override] diff --git a/src/Security/ApiKeyAuthenticator.php b/src/Security/ApiKeyAuthenticator.php index 8232e27..f12e67e 100644 --- a/src/Security/ApiKeyAuthenticator.php +++ b/src/Security/ApiKeyAuthenticator.php @@ -4,11 +4,14 @@ namespace App\Security; +use App\Repository\ServerRepository; +use App\Repository\UserRepository; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; @@ -20,6 +23,12 @@ class ApiKeyAuthenticator extends AbstractAuthenticator public const string AUTH_HEADER = 'Authorization'; public const string AUTH_HEADER_PREFIX = 'Apikey '; + public function __construct( + private readonly ServerRepository $serverRepository, + private readonly UserRepository $userRepository, + ) { + } + /** * Called on every request to decide if this authenticator should be used for the request. * @@ -40,7 +49,14 @@ public function authenticate(Request $request): Passport throw new CustomUserMessageAuthenticationException('No API token provided'); } - return new SelfValidatingPassport(new UserBadge($apiKey)); + // Users and servers can authenticate to use the API. + $apiUser = $this->serverRepository->findOneBy(['apiKey' => $apiKey]) + ?? $this->userRepository->findOneBy(['apiKey' => $apiKey]); + if (null !== $apiUser) { + return new SelfValidatingPassport(new UserBadge($apiUser->getUserIdentifier())); + } + + throw new BadCredentialsException('Invalid credentials.'); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response diff --git a/src/Trait/ApiKeyEntityTrait.php b/src/Trait/ApiKeyEntityTrait.php new file mode 100644 index 0000000..7c65c81 --- /dev/null +++ b/src/Trait/ApiKeyEntityTrait.php @@ -0,0 +1,35 @@ +apiKey; + } + + public function setApiKey(string $apiKey): static + { + $this->apiKey = $apiKey; + + return $this; + } + + public function generateApiKey(): string + { + return sha1(\random_bytes(40)); + } +} From 5b72353f42eea2876e73de5f9f62ac9d12626c34 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 15:55:30 +0200 Subject: [PATCH 69/94] Added security to the detection result API --- CHANGELOG.md | 1 + src/Entity/DetectionResult.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 924edfb..c613424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#88](https://github.com/itk-dev/devops_itksites/pull/88) - Let users use the API + - Add security to detection results API endpoint - [#80](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases diff --git a/src/Entity/DetectionResult.php b/src/Entity/DetectionResult.php index d47821f..00769cd 100644 --- a/src/Entity/DetectionResult.php +++ b/src/Entity/DetectionResult.php @@ -17,6 +17,7 @@ #[ApiResource( operations: [ new Post( + security: "is_granted('ROLE_SERVER')", status: 202, output: false, messenger: true, From 895394048910911c8d2a2e58409186668b789d9e Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 15:56:01 +0200 Subject: [PATCH 70/94] Added API endpoints for server and site collections --- CHANGELOG.md | 1 + src/Entity/Server.php | 7 +++++++ src/Entity/Site.php | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c613424..ff10515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#88](https://github.com/itk-dev/devops_itksites/pull/88) - Let users use the API - Add security to detection results API endpoint + - Add server and site collections API endpoints - [#80](https://github.com/itk-dev/devops_itksites/pull/80) 5566: Service agreements - Add security contract entity with crud controller - Add Abstract full crud controller and extend on it in some cases diff --git a/src/Entity/Server.php b/src/Entity/Server.php index 8786b19..6b27d67 100644 --- a/src/Entity/Server.php +++ b/src/Entity/Server.php @@ -4,6 +4,8 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; use App\Repository\ServerRepository; use App\Trait\ApiKeyEntityTrait; use Doctrine\Common\Collections\ArrayCollection; @@ -14,6 +16,11 @@ use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Validator\Constraints as Assert; +#[ApiResource( + normalizationContext: ['groups' => ['export']], + security: "is_granted('ROLE_USER')", +)] +#[GetCollection()] #[ORM\Entity(repositoryClass: ServerRepository::class)] class Server extends AbstractBaseEntity implements UserInterface, \Stringable { diff --git a/src/Entity/Site.php b/src/Entity/Site.php index dbfd6c7..9c52cf9 100644 --- a/src/Entity/Site.php +++ b/src/Entity/Site.php @@ -4,6 +4,8 @@ namespace App\Entity; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; use App\Repository\SiteRepository; use App\Types\SiteType; use Doctrine\Common\Collections\ArrayCollection; @@ -13,6 +15,11 @@ use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Validator\Constraints as Assert; +#[ApiResource( + normalizationContext: ['groups' => ['export']], + security: "is_granted('ROLE_USER')", +)] +#[GetCollection()] #[ORM\Entity(repositoryClass: SiteRepository::class)] #[ORM\UniqueConstraint(name: 'server_rootDir_configFilePath_idx', fields: ['server', 'rootDir', 'configFilePath'])] class Site extends AbstractHandlerResult implements \Stringable From 94aa0ec73ac9df05894a1bb0e3168846df007905 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 15:56:16 +0200 Subject: [PATCH 71/94] Cleaned up user fixture. Added another user. --- fixtures/user.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fixtures/user.yaml b/fixtures/user.yaml index de04a82..1260509 100644 --- a/fixtures/user.yaml +++ b/fixtures/user.yaml @@ -1,3 +1,5 @@ App\Entity\User: user_admin: - __construct: ["admin@example.com", "Admin", [ROLE_ADMIN]] + __construct: ["Admin", "admin@example.com", [ROLE_ADMIN]] + user_user: + __construct: ["User", "user@example.com", [ROLE_USER]] From e2cf897e16b8ee99d4efcb5cc1f488ef135d89e8 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 16:06:17 +0200 Subject: [PATCH 72/94] Updated API spec --- public/api-spec-v1.json | 183 ++++++++++++++++++++++++++++++++++++++++ public/api-spec-v1.yaml | 122 +++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) diff --git a/public/api-spec-v1.json b/public/api-spec-v1.json index 47ce588..8b60c77 100644 --- a/public/api-spec-v1.json +++ b/public/api-spec-v1.json @@ -92,6 +92,144 @@ "required": true } } + }, + "/api/servers": { + "get": { + "operationId": "api_servers_get_collection", + "tags": [ + "Server" + ], + "responses": { + "200": { + "description": "Server collection", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Server-export" + } + } + }, + "application/ld+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Server-export" + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/Error.jsonld" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "links": {} + } + }, + "summary": "Retrieves the collection of Server resources.", + "description": "Retrieves the collection of Server resources.", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "The collection page number", + "required": false, + "deprecated": false, + "schema": { + "type": "integer", + "default": 1 + }, + "style": "form", + "explode": true + } + ] + } + }, + "/api/sites": { + "get": { + "operationId": "api_sites_get_collection", + "tags": [ + "Site" + ], + "responses": { + "200": { + "description": "Site collection", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Site-export" + } + } + }, + "application/ld+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Site-export" + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/ld+json": { + "schema": { + "$ref": "#/components/schemas/Error.jsonld" + } + }, + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "links": {} + } + }, + "summary": "Retrieves the collection of Site resources.", + "description": "Retrieves the collection of Site resources.", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "The collection page number", + "required": false, + "deprecated": false, + "schema": { + "type": "integer", + "default": 1 + }, + "style": "form", + "explode": true + } + ] + } } }, "components": { @@ -353,6 +491,43 @@ ] } } + }, + "Server-export": { + "type": "object", + "properties": { + "Name": { + "default": "", + "type": "string" + } + } + }, + "Site-export": { + "type": "object", + "required": [ + "PHP version" + ], + "properties": { + "PHP version": { + "minLength": 1, + "maxLength": 10, + "default": "", + "type": "string" + }, + "Primary domain": { + "readOnly": true, + "type": "string" + }, + "Type": { + "default": "", + "type": "string" + }, + "Server": { + "$ref": "#/components/schemas/Server-export" + }, + "rootDir": { + "type": "string" + } + } } }, "responses": {}, @@ -378,6 +553,14 @@ { "name": "DetectionResult", "description": "Resource 'DetectionResult' operations." + }, + { + "name": "Server", + "description": "Resource 'Server' operations." + }, + { + "name": "Site", + "description": "Resource 'Site' operations." } ], "webhooks": {} diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index d2a0179..e9be5b3 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -59,6 +59,98 @@ paths: schema: $ref: '#/components/schemas/DetectionResult-write' required: true + /api/servers: + get: + operationId: api_servers_get_collection + tags: + - Server + responses: + '200': + description: 'Server collection' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Server-export' + application/ld+json: + schema: + type: array + items: + $ref: '#/components/schemas/Server-export' + '403': + description: Forbidden + content: + application/ld+json: + schema: + $ref: '#/components/schemas/Error.jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + application/json: + schema: + $ref: '#/components/schemas/Error' + links: {} + summary: 'Retrieves the collection of Server resources.' + description: 'Retrieves the collection of Server resources.' + parameters: + - + name: page + in: query + description: 'The collection page number' + required: false + deprecated: false + schema: + type: integer + default: 1 + style: form + explode: true + /api/sites: + get: + operationId: api_sites_get_collection + tags: + - Site + responses: + '200': + description: 'Site collection' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Site-export' + application/ld+json: + schema: + type: array + items: + $ref: '#/components/schemas/Site-export' + '403': + description: Forbidden + content: + application/ld+json: + schema: + $ref: '#/components/schemas/Error.jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + application/json: + schema: + $ref: '#/components/schemas/Error' + links: {} + summary: 'Retrieves the collection of Site resources.' + description: 'Retrieves the collection of Site resources.' + parameters: + - + name: page + in: query + description: 'The collection page number' + required: false + deprecated: false + schema: + type: integer + default: 1 + style: form + explode: true components: schemas: ConstraintViolation: @@ -250,6 +342,32 @@ components: type: - string - 'null' + Server-export: + type: object + properties: + Name: + default: '' + type: string + Site-export: + type: object + required: + - 'PHP version' + properties: + 'PHP version': + minLength: 1 + maxLength: 10 + default: '' + type: string + 'Primary domain': + readOnly: true + type: string + Type: + default: '' + type: string + Server: + $ref: '#/components/schemas/Server-export' + rootDir: + type: string responses: {} parameters: {} examples: {} @@ -267,4 +385,8 @@ security: tags: - name: DetectionResult description: "Resource 'DetectionResult' operations." + - name: Server + description: "Resource 'Server' operations." + - name: Site + description: "Resource 'Site' operations." webhooks: {} From 813a912c648751a1bcccbe1cd9ae710652db6952 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 16:28:17 +0200 Subject: [PATCH 73/94] Added console command test --- src/Command/UserSetApiKeyCommand.php | 5 +- tests/Command/UserSetApiKeyCommandTest.php | 67 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/Command/UserSetApiKeyCommandTest.php diff --git a/src/Command/UserSetApiKeyCommand.php b/src/Command/UserSetApiKeyCommand.php index 8a0bd31..e792662 100644 --- a/src/Command/UserSetApiKeyCommand.php +++ b/src/Command/UserSetApiKeyCommand.php @@ -7,7 +7,6 @@ use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand( @@ -31,7 +30,9 @@ public function __invoke( ?? $this->userRepository->findOneBy(['name' => $userId]); if (null === $user) { - throw new InvalidArgumentException(sprintf('Cannot load user with id %s', $userId)); + $io->error(sprintf('Cannot load user with id %s', $userId)); + + return Command::INVALID; } $question = sprintf('Really set API key on user %s', $user->getUserIdentifier()); diff --git a/tests/Command/UserSetApiKeyCommandTest.php b/tests/Command/UserSetApiKeyCommandTest.php new file mode 100644 index 0000000..3f78289 --- /dev/null +++ b/tests/Command/UserSetApiKeyCommandTest.php @@ -0,0 +1,67 @@ +setAutoExit(false); + + $applicationTester = new ApplicationTester($application); + $applicationTester->run([ + 'command' => 'app:user:set-api-key', + '--help' => true, + ]); + $applicationTester->assertCommandIsSuccessful(); + + // Missing user ID argument. + $applicationTester->run([ + 'command' => 'app:user:set-api-key', + ]); + $applicationTester->assertCommandFailed(); + + // Invalid user ID argument. + $applicationTester->run([ + 'command' => 'app:user:set-api-key', + 'user-id' => 'this-user-does-no-exist', + ]); + $applicationTester->assertCommandIsInvalid(); + $output = $applicationTester->getDisplay(); + $this->assertStringContainsString('Cannot load user with id this-user-does-no-exist', $output); + + // Valid user ID (name). + $applicationTester->run([ + 'command' => 'app:user:set-api-key', + 'user-id' => 'admin', + '--no-interaction' => true, + ]); + $applicationTester->assertCommandIsSuccessful(); + + $output = $applicationTester->getDisplay(); + $this->assertStringContainsString('API key for user admin@example.com set to', $output); + + // Valid user ID (email). + $applicationTester->run([ + 'command' => 'app:user:set-api-key', + 'user-id' => 'admin@example.com', + '--no-interaction' => true, + ]); + $applicationTester->assertCommandIsSuccessful(); + + $output = $applicationTester->getDisplay(); + $this->assertStringContainsString('API key for user admin@example.com set to', $output); + } +} From b89bc6d9131b972cb53bd8594becd99ed24606b7 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 3 Jul 2026 08:54:38 +0200 Subject: [PATCH 74/94] Updated API name and description --- config/packages/api_platform.yaml | 7 +++++-- public/api-spec-v1.json | 4 ++-- public/api-spec-v1.yaml | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 6651fba..387fd87 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -1,6 +1,9 @@ api_platform: - title: 'ITKsites Detection API' - description: 'REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories.' + title: 'ITKsites API' + description: | + REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories. + + The API also provides read-only access to information on servers and sites created by processing detection results. version: '1.0.0' mapping: diff --git a/public/api-spec-v1.json b/public/api-spec-v1.json index 8b60c77..bc5923d 100644 --- a/public/api-spec-v1.json +++ b/public/api-spec-v1.json @@ -1,8 +1,8 @@ { "openapi": "3.1.0", "info": { - "title": "ITKsites Detection API", - "description": "REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories.", + "title": "ITKsites API", + "description": "REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories.\n\nThe API also provides read-only access to information on servers and sites created by processing detection results.", "version": "1.0.0" }, "servers": [ diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index e9be5b3..cdf7306 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -1,7 +1,10 @@ openapi: 3.1.0 info: - title: 'ITKsites Detection API' - description: 'REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories.' + title: 'ITKsites API' + description: |- + REST API for ingesting server detection results from the ITK sites server harvester. Detection results are processed asynchronously to track servers, sites, domains, packages, modules, Docker images, and git repositories. + + The API also provides read-only access to information on servers and sites created by processing detection results. version: 1.0.0 servers: - url: 'https://itksites.local.itkdev.dk' From 40b4c6052d76989fad8e26d5688928826dd51ffb Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 2 Jul 2026 16:53:46 +0200 Subject: [PATCH 75/94] Added `--rm` to `docker compose run` in prod deployment --- .woodpecker/prod.yml | 4 ++-- CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.woodpecker/prod.yml b/.woodpecker/prod.yml index e3c1cc2..e38d7e7 100644 --- a/.woodpecker/prod.yml +++ b/.woodpecker/prod.yml @@ -24,8 +24,8 @@ steps: keep: 4 playbook: "release" pre_up: - - itkdev-docker-compose-server run phpfpm bin/console doctrine:migrations:migrate --no-interaction - - itkdev-docker-compose-server run phpfpm bin/console messenger:setup-transports + - itkdev-docker-compose-server run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction + - itkdev-docker-compose-server run --rm phpfpm bin/console messenger:setup-transports - name: Run post deploy image: itkdev/ansible-plugin:1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ff10515..a90e2c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#89](https://github.com/itk-dev/devops_itksites/pull/89) + Added `--rm` to `docker compose run` in prod deployment - [#88](https://github.com/itk-dev/devops_itksites/pull/88) - Let users use the API - Add security to detection results API endpoint From ca17d7e2c193a2f8220b4d83453b73a07dee7517 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 29 Jul 2026 21:02:23 +0200 Subject: [PATCH 76/94] test: run migrations against a populated database in CI The existing Doctrine jobs migrate an empty database, so a migration that cannot cope with existing rows passes them unnoticed. Build the database as it looks before the pull request, fixtures included, then apply the pull request's migrations on top. Fixtures created a single user, and one row cannot collide with itself, so add a few more. --- .github/workflows/doctrine.yaml | 51 +++++++++++++++++++++++++++++++++ fixtures/user.yaml | 9 ++++++ 2 files changed, 60 insertions(+) diff --git a/.github/workflows/doctrine.yaml b/.github/workflows/doctrine.yaml index 38276a6..8eb28dd 100644 --- a/.github/workflows/doctrine.yaml +++ b/.github/workflows/doctrine.yaml @@ -62,3 +62,54 @@ jobs: - name: Load fixtures run: | docker compose run --rm phpfpm composer fixtures + + # The jobs above migrate an empty database. A deployment migrates a database + # that already holds rows, so a migration that cannot cope with existing + # data passes those jobs unnoticed. This job builds the database as it looks + # before the pull request, fixtures included, and then applies the pull + # request's migrations on top of it. + migrate-populated-database: + name: Run migrations on a populated database + runs-on: ubuntu-latest + # This workflow also runs on pushes to main and develop, where there is + # no pull request to read a base branch from: the checkout steps below + # would get an empty revision and fail. A push has no base to compare + # against, so skip the job rather than guess one. + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Create docker network + run: | + docker network create frontend + + - name: Check out the base branch + run: | + git checkout ${{ github.event.pull_request.base.sha }} + + - name: Run Composer Install + run: | + docker compose run --rm phpfpm composer install --no-interaction + + - name: Run Doctrine Migrations + run: | + docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction + + - name: Load fixtures + run: | + docker compose run --rm phpfpm composer fixtures + + - name: Check out the pull request + run: | + git checkout ${{ github.event.pull_request.head.sha }} + + - name: Run Composer Install + run: | + docker compose run --rm phpfpm composer install --no-interaction + + - name: Run Doctrine Migrations on the populated database + run: | + docker compose run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction diff --git a/fixtures/user.yaml b/fixtures/user.yaml index 1260509..76710ec 100644 --- a/fixtures/user.yaml +++ b/fixtures/user.yaml @@ -3,3 +3,12 @@ App\Entity\User: __construct: ["Admin", "admin@example.com", [ROLE_ADMIN]] user_user: __construct: ["User", "user@example.com", [ROLE_USER]] + # A few more users so migrations are exercised against a table holding more + # than one row. A single row cannot collide with itself, which hides + # migrations adding a unique column to an existing table. + user_1: + __construct: ["User 1", "user1@example.com", [ROLE_USER]] + user_2: + __construct: ["User 2", "user2@example.com", [ROLE_USER]] + user_3: + __construct: ["User 3", "user3@example.com", [ROLE_USER]] From 554ddd0539a886fd5f2817eb42df44a7a5a301b1 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 29 Jul 2026 21:02:23 +0200 Subject: [PATCH 77/94] fix: let user API key migration run on populated databases Version20260702123347 added user.api_key as NOT NULL and then put a unique index on it, so every existing user got the same empty string and the index collided on the second row. Any database holding more than one user failed to migrate. Generate a key for each existing user before creating the index, matching App\Entity\User::__construct, which gives every user created since #88 a key of its own. The keys come from PHP rather than SQL because they are credentials and SQL's RAND() and UUID() are not cryptographically secure. --- CHANGELOG.md | 5 +++++ migrations/Version20260702123347.php | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a90e2c0..0beda8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#90](https://github.com/itk-dev/devops_itksites/pull/90) + - Fixed user API key migration failing on databases with more than one user + - Generated an API key for existing users, as users created since already get + - Added users to the fixtures and a CI job running migrations on a populated + database - [#89](https://github.com/itk-dev/devops_itksites/pull/89) Added `--rm` to `docker compose run` in prod deployment - [#88](https://github.com/itk-dev/devops_itksites/pull/88) diff --git a/migrations/Version20260702123347.php b/migrations/Version20260702123347.php index 5552bbd..7139f4a 100644 --- a/migrations/Version20260702123347.php +++ b/migrations/Version20260702123347.php @@ -19,8 +19,18 @@ public function getDescription(): string public function up(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs + // Adding the column gives every existing user the same empty string, + // which collides on the unique index. Add random keys to allow unique index. + // + // The keys are generated in PHP because they are credentials and have + // to come from a cryptographically secure source. SQL's RAND() and + // UUID() are not one. $this->addSql('ALTER TABLE user ADD api_key VARCHAR(255) NOT NULL'); + + foreach ($this->connection->fetchFirstColumn('SELECT email FROM user') as $email) { + $this->addSql('UPDATE user SET api_key = ? WHERE email = ?', [sha1(\random_bytes(40)), $email]); + } + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649C912ED9D ON user (api_key)'); } From 613a7d640e7323672e23179e598cf970bb08c15e Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 3 Aug 2026 12:59:49 +0200 Subject: [PATCH 78/94] feat: add health endpoints for monitoring Add /health/live, /health/ready and /health/detail, with checks for the database, the RabbitMQ messenger transport and detection result freshness. Liveness touches no dependencies. Readiness reports the aggregated status only, never which dependency failed. The detailed endpoint discloses internals and is protected by the ITKBasicAuth Traefik middleware on its own router. ^/health is excluded from the Symfony firewalls on purpose: both user providers are Doctrine entity providers, so an application-level firewall could not authenticate while the database is unavailable. Results are cached in a dedicated filesystem-backed cache.health pool so that polling cannot amplify into load on the dependencies being checked. --- .env | 9 ++ CHANGELOG.md | 5 + README.md | 37 ++++++ config/packages/cache.yaml | 14 ++- config/packages/security.yaml | 8 ++ config/services.yaml | 17 +++ docker-compose.server.yml | 5 + docker-compose.yml | 4 + src/Controller/HealthController.php | 98 ++++++++++++++++ src/Health/Check/DatabaseHealthCheck.php | 48 ++++++++ .../Check/IngestFreshnessHealthCheck.php | 78 +++++++++++++ src/Health/Check/RabbitMqHealthCheck.php | 57 +++++++++ src/Health/HealthCheckInterface.php | 24 ++++ src/Health/HealthCheckResult.php | 51 ++++++++ src/Health/HealthChecker.php | 88 ++++++++++++++ src/Health/HealthStatus.php | 20 ++++ src/Repository/DetectionResultRepository.php | 18 +++ tests/Controller/HealthControllerTest.php | 110 ++++++++++++++++++ 18 files changed, 689 insertions(+), 2 deletions(-) create mode 100644 src/Controller/HealthController.php create mode 100644 src/Health/Check/DatabaseHealthCheck.php create mode 100644 src/Health/Check/IngestFreshnessHealthCheck.php create mode 100644 src/Health/Check/RabbitMqHealthCheck.php create mode 100644 src/Health/HealthCheckInterface.php create mode 100644 src/Health/HealthCheckResult.php create mode 100644 src/Health/HealthChecker.php create mode 100644 src/Health/HealthStatus.php create mode 100644 tests/Controller/HealthControllerTest.php diff --git a/.env b/.env index 0b6b01c..41d8887 100644 --- a/.env +++ b/.env @@ -68,3 +68,12 @@ APP_KEEP_RESULTS=5 APP_ECONOMICS_URI=https://economics.itkdev.dk APP_ECONOMICS_API_KEY=changeme ###< economics ### + +###> health ### +# Seconds to cache the health check results, so that monitoring polling +# /health/ready cannot amplify into load on the database and the broker. +HEALTH_CACHE_TTL=15 +# Seconds since the last detection result before ingest is reported degraded. +# The harvester currently reports several hundred times an hour. +HEALTH_INGEST_MAX_AGE=1800 +###< health ### diff --git a/CHANGELOG.md b/CHANGELOG.md index 0beda8d..71f41cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#91](https://github.com/itk-dev/devops_itksites/pull/91) Health endpoints + - Add `/health/live`, `/health/ready` and `/health/detail` endpoints + - Add health checks for database, RabbitMQ transport and detection result freshness + - Cache check results in a dedicated `cache.health` pool + - Exclude `^/health` from the firewalls and protect `/health/detail` with `ITKBasicAuth` - [#90](https://github.com/itk-dev/devops_itksites/pull/90) - Fixed user API key migration failing on databases with more than one user - Generated an API key for existing users, as users created since already get diff --git a/README.md b/README.md index 797838e..64b5416 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,43 @@ Use the API key to make an authenticated request, e.g. curl --header 'accept: application/json' --header 'authorization: Apikey ' https://itksites.local.itkdev.dk/api/sites ``` +## Health checks + +Three endpoints report on the application, in increasing order of detail: + +| Endpoint | Access | Checks | +| --- | --- | --- | +| `/health/live` | Public | Nothing – only that the app responds | +| `/health/ready` | Public | All checks, aggregated status only | +| `/health/detail` | `ITKBasicAuth` in Traefik | Per-check results and timings | + +`/health/ready` answers `200` when everything is well and `503` when it is not. +It deliberately does not say *what* is wrong – point monitoring at this one and +read `/health/detail` when it goes red: + +``` shell +curl --silent https://itksites.local.itkdev.dk/health/detail | jq +``` + +The checks cover the database, the RabbitMQ messenger transport and the +freshness of the most recent detection result. The last one catches an ingest +pipeline that has stopped while the application itself is still serving +requests. + +`HEALTH_INGEST_MAX_AGE` sets how old the most recent detection result may be +before ingest is reported as degraded. + +Results are cached for `HEALTH_CACHE_TTL` seconds so that polling does not turn +into load on the dependencies. The cache is the dedicated, filesystem-backed +`cache.health` pool in `config/packages/cache.yaml` – it has to keep working +while the database and the broker are down, and the adapter can be swapped +there without touching code. + +`^/health` is excluded from the Symfony firewalls: both user providers are +Doctrine entity providers, so an authenticated endpoint would fail to +authenticate during a database outage and answer `500` rather than reporting +that the database is down. + ## Development ```sh diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml index c3eb53d..c3cab50 100644 --- a/config/packages/cache.yaml +++ b/config/packages/cache.yaml @@ -15,5 +15,15 @@ framework: #app: cache.adapter.apcu # Namespaced pools use the above "app" backend by default - #pools: - #my.dedicated.cache: null + pools: + # Health check results. + # + # Filesystem-backed on purpose: this pool has to keep working while + # the database and the message broker are down, which is exactly + # when the health endpoints matter. + # + # It is a dedicated pool so the adapter can be swapped without + # touching code: cache.adapter.apcu is faster and shared between + # FPM workers, at the cost of being cleared on every FPM restart. + cache.health: + adapter: cache.adapter.filesystem diff --git a/config/packages/security.yaml b/config/packages/security.yaml index b7b8257..e89023b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -21,6 +21,14 @@ security: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false + # The health endpoints must answer while the database is down, so they + # cannot go through a firewall: both user providers above are Doctrine + # entity providers and authentication would itself fail. /health/detail + # is protected by the ITKBasicAuth middleware in Traefik instead. + health: + pattern: ^/health + security: false + api: pattern: ^/api custom_authenticators: diff --git a/config/services.yaml b/config/services.yaml index 71d40c9..100e7e4 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -27,6 +27,23 @@ services: App\Handler\DetectionResultHandlerInterface: tags: [app.handler.detection_result_handler] + App\Health\HealthCheckInterface: + tags: [app.health.check] + + App\Health\HealthChecker: + arguments: + $checks: !tagged_iterator app.health.check + $cache: '@cache.health' + $cacheTtl: '%env(int:HEALTH_CACHE_TTL)%' + + App\Health\Check\RabbitMqHealthCheck: + arguments: + $transport: '@messenger.transport.async' + + App\Health\Check\IngestFreshnessHealthCheck: + arguments: + $maxAgeSeconds: '%env(int:HEALTH_INGEST_MAX_AGE)%' + App\EventListener\RemovedRelationsListener: tags: - name: 'doctrine.event_listener' diff --git a/docker-compose.server.yml b/docker-compose.server.yml index fffc693..45f2720 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -50,3 +50,8 @@ services: # Cron-metrics protection. - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-metrics.rule=Host(`${COMPOSE_SERVER_DOMAIN:?}`) && PathPrefix(`/cron-metrics`) " - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-metrics.middlewares=ITKMetricsAuth@file" + # Detailed health check protection. /health/live and /health/ready stay + # public; only /health/detail discloses internals. + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-health.rule=Host(`${COMPOSE_SERVER_DOMAIN:?}`) && PathPrefix(`/health/detail`)" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-health.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-health.middlewares=ITKBasicAuth@file" diff --git a/docker-compose.yml b/docker-compose.yml index 4dff245..444ce64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -75,6 +75,10 @@ services: # Cron-metrics protection. - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-metrics.rule=Host(`${COMPOSE_DOMAIN:?}`) && PathPrefix(`/cron-metrics`) " - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-metrics.middlewares=ITKMetricsAuth@file" + # Detailed health check protection. /health/live and /health/ready stay + # public; only /health/detail discloses internals. + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-health.rule=Host(`${COMPOSE_DOMAIN:?}`) && PathPrefix(`/health/detail`)" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}-health.middlewares=ITKBasicAuth@file" mail: image: axllent/mailpit diff --git a/src/Controller/HealthController.php b/src/Controller/HealthController.php new file mode 100644 index 0000000..603df20 --- /dev/null +++ b/src/Controller/HealthController.php @@ -0,0 +1,98 @@ +respond(['status' => HealthStatus::Ok->value], true); + } + + #[Route('/health/ready', name: 'app_health_ready', methods: ['GET'])] + public function ready(): JsonResponse + { + $healthy = $this->healthChecker->isHealthy($this->healthChecker->run()); + + return $this->respond( + ['status' => $healthy ? HealthStatus::Ok->value : HealthStatus::Degraded->value], + $healthy + ); + } + + #[Route('/health/detail', name: 'app_health_detail', methods: ['GET'])] + public function detail(): JsonResponse + { + $results = $this->healthChecker->run(); + $healthy = $this->healthChecker->isHealthy($results); + + $checks = []; + foreach ($results as $result) { + $checks[$result->name] = array_filter([ + 'status' => $result->status->value, + 'message' => $result->message, + 'details' => $result->details, + ], static fn (mixed $value): bool => null !== $value && [] !== $value); + } + + return $this->respond([ + 'status' => $healthy ? HealthStatus::Ok->value : HealthStatus::Degraded->value, + 'checks' => $checks, + ], $healthy); + } + + /** + * @param array $payload + */ + private function respond(array $payload, bool $healthy): JsonResponse + { + $response = new JsonResponse( + $payload, + $healthy ? Response::HTTP_OK : Response::HTTP_SERVICE_UNAVAILABLE + ); + + // Health responses are cached inside HealthChecker, never by the client + // or an intermediary. + $response->headers->set('Cache-Control', 'no-store, private'); + + return $response; + } +} diff --git a/src/Health/Check/DatabaseHealthCheck.php b/src/Health/Check/DatabaseHealthCheck.php new file mode 100644 index 0000000..94bb8fb --- /dev/null +++ b/src/Health/Check/DatabaseHealthCheck.php @@ -0,0 +1,48 @@ +connection->executeQuery('SELECT 1'); + } catch (\Throwable $throwable) { + $this->logger->error('Health check "database" failed: {message}', [ + 'message' => $throwable->getMessage(), + 'exception' => $throwable, + ]); + + // The caller gets no detail; the reason stays in the logs. + return HealthCheckResult::degraded($this->getName(), 'Unable to query the database.'); + } + + return HealthCheckResult::ok($this->getName(), [ + 'response_time_ms' => round((microtime(true) - $start) * 1000, 1), + ]); + } +} diff --git a/src/Health/Check/IngestFreshnessHealthCheck.php b/src/Health/Check/IngestFreshnessHealthCheck.php new file mode 100644 index 0000000..ffe4f1e --- /dev/null +++ b/src/Health/Check/IngestFreshnessHealthCheck.php @@ -0,0 +1,78 @@ +repository->findLastContact(); + } catch (\Throwable $throwable) { + $this->logger->error('Health check "ingest_freshness" failed: {message}', [ + 'message' => $throwable->getMessage(), + 'exception' => $throwable, + ]); + + // Almost certainly the database being down, which the database + // check reports separately. + return HealthCheckResult::degraded($this->getName(), 'Unable to query the last detection result.'); + } + + if (!$lastContact instanceof \DateTimeImmutable) { + return HealthCheckResult::degraded( + $this->getName(), + 'No detection results have been received yet.', + ['max_age_seconds' => $this->maxAgeSeconds] + ); + } + + $ageSeconds = time() - $lastContact->getTimestamp(); + $details = [ + 'last_contact' => $lastContact->format(\DATE_ATOM), + 'age_seconds' => $ageSeconds, + 'max_age_seconds' => $this->maxAgeSeconds, + ]; + + if ($ageSeconds > $this->maxAgeSeconds) { + return HealthCheckResult::degraded( + $this->getName(), + \sprintf('No detection result received for %d seconds.', $ageSeconds), + $details + ); + } + + return HealthCheckResult::ok($this->getName(), $details); + } +} diff --git a/src/Health/Check/RabbitMqHealthCheck.php b/src/Health/Check/RabbitMqHealthCheck.php new file mode 100644 index 0000000..77a8ef4 --- /dev/null +++ b/src/Health/Check/RabbitMqHealthCheck.php @@ -0,0 +1,57 @@ +transport instanceof MessageCountAwareInterface) { + return HealthCheckResult::skipped( + $this->getName(), + 'The configured transport does not support message counting.' + ); + } + + try { + $count = $this->transport->getMessageCount(); + } catch (\Throwable $throwable) { + $this->logger->error('Health check "rabbitmq" failed: {message}', [ + 'message' => $throwable->getMessage(), + 'exception' => $throwable, + ]); + + return HealthCheckResult::degraded($this->getName(), 'Unable to reach the message queue.'); + } + + return HealthCheckResult::ok($this->getName(), ['queued_messages' => $count]); + } +} diff --git a/src/Health/HealthCheckInterface.php b/src/Health/HealthCheckInterface.php new file mode 100644 index 0000000..41bc448 --- /dev/null +++ b/src/Health/HealthCheckInterface.php @@ -0,0 +1,24 @@ + $details + */ + private function __construct( + public string $name, + public HealthStatus $status, + public ?string $message = null, + public array $details = [], + ) { + } + + /** + * @param array $details + */ + public static function ok(string $name, array $details = []): self + { + return new self($name, HealthStatus::Ok, null, $details); + } + + /** + * @param array $details + */ + public static function degraded(string $name, string $message, array $details = []): self + { + return new self($name, HealthStatus::Degraded, $message, $details); + } + + public static function skipped(string $name, string $message): self + { + return new self($name, HealthStatus::Skipped, $message); + } + + public function isDegraded(): bool + { + return HealthStatus::Degraded === $this->status; + } +} diff --git a/src/Health/HealthChecker.php b/src/Health/HealthChecker.php new file mode 100644 index 0000000..8f2a7cc --- /dev/null +++ b/src/Health/HealthChecker.php @@ -0,0 +1,88 @@ + $checks + */ + public function __construct( + private iterable $checks, + private CacheInterface $cache, + private LoggerInterface $logger, + private int $cacheTtl, + ) { + } + + /** + * @return array + */ + public function run(): array + { + return $this->cache->get(self::CACHE_KEY, function (ItemInterface $item): array { + $item->expiresAfter($this->cacheTtl); + + $results = []; + foreach ($this->checks as $check) { + $results[] = $this->runCheck($check); + } + + return $results; + }); + } + + /** + * @param array $results + */ + public function isHealthy(array $results): bool + { + foreach ($results as $result) { + if ($result->isDegraded()) { + return false; + } + } + + return true; + } + + /** + * Run a check, turning an unexpected failure into a degraded result. + * + * A check that throws must not take down the endpoint that reports on it. + */ + private function runCheck(HealthCheckInterface $check): HealthCheckResult + { + try { + return $check->check(); + } catch (\Throwable $throwable) { + $this->logger->error('Health check "{check}" threw an exception: {message}', [ + 'check' => $check->getName(), + 'message' => $throwable->getMessage(), + 'exception' => $throwable, + ]); + + return HealthCheckResult::degraded($check->getName(), 'The check failed unexpectedly.'); + } + } +} diff --git a/src/Health/HealthStatus.php b/src/Health/HealthStatus.php new file mode 100644 index 0000000..282466b --- /dev/null +++ b/src/Health/HealthStatus.php @@ -0,0 +1,20 @@ +createQueryBuilder('d') + ->select('MAX(d.lastContact)') + ->getQuery() + ->getSingleScalarResult(); + + return is_string($lastContact) ? new \DateTimeImmutable($lastContact) : null; + } + /** * Remove detection results base on last contact. * diff --git a/tests/Controller/HealthControllerTest.php b/tests/Controller/HealthControllerTest.php new file mode 100644 index 0000000..b8adf86 --- /dev/null +++ b/tests/Controller/HealthControllerTest.php @@ -0,0 +1,110 @@ +request('GET', '/health/live'); + + $this->assertResponseIsSuccessful(); + $this->assertSame(['status' => 'ok'], $this->decode($client->getResponse())); + } + + /** + * All three endpoints must bypass the firewalls. A redirect here means the + * OIDC entry point has caught them, which is what made a total ingest + * outage look healthy to monitoring. + */ + #[DataProvider('endpointProvider')] + public function testEndpointIsPubliclyReachable(string $path): void + { + $client = static::createClient(); + $client->request('GET', $path); + + $this->assertContains( + $client->getResponse()->getStatusCode(), + [Response::HTTP_OK, Response::HTTP_SERVICE_UNAVAILABLE], + \sprintf('%s should answer 200 or 503, never a redirect to login.', $path) + ); + } + + /** + * @return iterable + */ + public static function endpointProvider(): iterable + { + yield ['/health/live']; + yield ['/health/ready']; + yield ['/health/detail']; + } + + /** + * Readiness is public, so it must disclose the aggregated status and + * nothing else — no check names, no messages, no dependency detail. + */ + public function testReadyDisclosesNothingBeyondStatus(): void + { + $client = static::createClient(); + $client->request('GET', '/health/ready'); + + $payload = $this->decode($client->getResponse()); + + $this->assertSame(['status'], array_keys($payload)); + $this->assertContains($payload['status'], ['ok', 'degraded']); + } + + public function testDetailReportsEveryCheck(): void + { + $client = static::createClient(); + $client->request('GET', '/health/detail'); + + $payload = $this->decode($client->getResponse()); + + $this->assertArrayHasKey('checks', $payload); + $this->assertEqualsCanonicalizing( + ['database', 'rabbitmq', 'ingest_freshness'], + array_keys($payload['checks']) + ); + + foreach ($payload['checks'] as $check) { + $this->assertContains($check['status'], ['ok', 'degraded', 'skipped']); + } + } + + public function testResponsesAreNotCacheableByClients(): void + { + $client = static::createClient(); + $client->request('GET', '/health/ready'); + + $this->assertResponseHeaderSame('Cache-Control', 'no-store, private'); + } + + /** + * @return array + */ + private function decode(Response $response): array + { + $content = $response->getContent(); + $this->assertIsString($content); + + $payload = json_decode($content, true, 512, \JSON_THROW_ON_ERROR); + $this->assertIsArray($payload); + + return $payload; + } +} From 2d9fe6b3a2228fde1edc57c4795096a068d11be4 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 25 Aug 2026 21:29:20 +0200 Subject: [PATCH 79/94] chore: update documentation link --- templates/post_logout/index.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/post_logout/index.html.twig b/templates/post_logout/index.html.twig index 025585e..2adcb81 100644 --- a/templates/post_logout/index.html.twig +++ b/templates/post_logout/index.html.twig @@ -101,7 +101,7 @@ From 494535880bb4a71377dde6675cc97a8c8f1daef6 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 25 Aug 2026 21:32:11 +0200 Subject: [PATCH 80/94] chore: update openid-connect-bundle to 6.0 --- .env | 2 ++ composer.json | 2 +- composer.lock | 18 +++++++++++------- config/packages/itkdev_openid_connect.yaml | 3 +++ config/reference.php | 15 ++++++++++++++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.env b/.env index 41d8887..a5fceb9 100644 --- a/.env +++ b/.env @@ -50,6 +50,8 @@ MESSENGER_TRANSPORT_DSN_FAILED='doctrine://default?queue_name=failed' AZURE_AZ_OIDC_METADATA_URL=AZURE_AZ_APP_METADATA_URL AZURE_AZ_OIDC_CLIENT_ID=AZURE_AZ_APP_CLIENT_ID AZURE_AZ_OIDC_CLIENT_SECRET=AZURE_AZ_APP_CLIENT_SECRET +# Date the Azure client secret expires (any strtotime-parseable value) +AZURE_AZ_OIDC_CLIENT_SECRET_EXPIRES_AT=2027-01-31 AZURE_AZ_OIDC_REDIRECT_URI=AZURE_AZ_APP_REDIRECT_URI AZURE_AZ_OIDC_ALLOW_HTTP=false AZURE_AZ_OIDC_LEEWAY=10 diff --git a/composer.json b/composer.json index 252f853..c6b2dc4 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "doctrine/doctrine-migrations-bundle": "^4.0", "doctrine/orm": "^3.0", "easycorp/easyadmin-bundle": "^5.0", - "itk-dev/openid-connect-bundle": "^5.0", + "itk-dev/openid-connect-bundle": "^6.0", "itk-dev/vault-bundle": "^1.0.0", "nelmio/cors-bundle": "^2.2", "ocramius/doctrine-batch-utils": "^2.8", diff --git a/composer.lock b/composer.lock index cbd47e7..c108713 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": "ceb76d3549aace47bff7b8e9260728fe", + "content-hash": "18003b642aa58da510f8036137cbdfee", "packages": [ { "name": "api-platform/core", @@ -1985,16 +1985,16 @@ }, { "name": "itk-dev/openid-connect-bundle", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect-bundle.git", - "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4" + "reference": "79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", - "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d", + "reference": "79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d", "shasum": "" }, "require": { @@ -2003,7 +2003,10 @@ "ext-openssl": "*", "itk-dev/openid-connect": "^5.0", "php": "^8.3", + "psr/log": "^3.0", "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/clock": "^6.4 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^6.4 || ^7.0 || ^8.0", "symfony/framework-bundle": "^6.4.13 || ^7.0 || ^8.0", "symfony/security-bundle": "^6.4.13 || ^7.0 || ^8.0", "symfony/uid": "^6.4 || ^7.0 || ^8.0", @@ -2012,6 +2015,7 @@ "require-dev": { "ergebnis/composer-normalize": "^2.28", "friendsofphp/php-cs-fixer": "^3.11", + "infection/infection": "*", "phpstan/phpstan": "^2.1.41", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", @@ -2044,9 +2048,9 @@ "description": "Symfony bundle for openid-connect", "support": { "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", - "source": "https://github.com/itk-dev/openid-connect-bundle/tree/5.0.0" + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/6.0.0" }, - "time": "2026-06-02T11:15:20+00:00" + "time": "2026-08-25T19:09:29+00:00" }, { "name": "itk-dev/vault", diff --git a/config/packages/itkdev_openid_connect.yaml b/config/packages/itkdev_openid_connect.yaml index 854a6d3..6194344 100644 --- a/config/packages/itkdev_openid_connect.yaml +++ b/config/packages/itkdev_openid_connect.yaml @@ -15,6 +15,9 @@ itkdev_openid_connect: metadata_url: '%env(string:AZURE_AZ_OIDC_METADATA_URL)%' client_id: '%env(AZURE_AZ_OIDC_CLIENT_ID)%' client_secret: '%env(AZURE_AZ_OIDC_CLIENT_SECRET)%' + # Date the client secret expires. Lets the bundle warn before it + # breaks every login, as it did on 2026-08-12. + client_secret_expires_at: '%env(string:AZURE_AZ_OIDC_CLIENT_SECRET_EXPIRES_AT)%' # Specify redirect URI redirect_uri: '%env(string:AZURE_AZ_OIDC_REDIRECT_URI)%' allow_http: '%env(bool:AZURE_AZ_OIDC_ALLOW_HTTP)%' diff --git a/config/reference.php b/config/reference.php index 0367b0f..7453848 100644 --- a/config/reference.php +++ b/config/reference.php @@ -1741,19 +1741,32 @@ * route?: scalar|Param|null, // Return route for CLI login * }, * user_provider?: scalar|Param|null, // The User Provider to inject // Default: null + * logging_options?: array{ + * logger?: scalar|Param|null, // Service id of the PSR-3 logger to receive this bundle's failure logs, e.g. "monolog.logger.openid_connect". Defaults to the application logger, which Symfony always provides. Set "itkdev_openid_connect.null_logger" to turn logging off. // Default: null + * }, + * audit_options?: array{ + * enabled?: bool|Param, // Write an authentication audit trail (logins, failures, CLI token issuance). Off by default: audit records identify people, so an existing installation must opt in rather than start logging personal data on upgrade. // Default: false + * logger?: scalar|Param|null, // Service id of the PSR-3 logger to receive audit records, e.g. "monolog.logger.openid_connect_audit". Defaults to the application logger. Keep this separate from logging_options.logger: an operational threshold of "error" would otherwise discard the whole trail. // Default: null + * identifier?: "raw"|"hashed"|Param, // Record user identifiers as-is ("raw") or pseudonymised ("hashed"). Hashing is keyed with the application secret, so records still correlate. Cannot come from an environment variable; use environment-specific configuration instead. // Default: "raw" + * }, + * secret_expiry_options?: array{ + * warning_days?: int|Param, // How many days before a client secret expires the bundle starts warning (default: 30) // Default: 30 + * }, * openid_providers?: list, + * callback_path?: scalar|Param|null, // Optional. The request path the callback arrives on, for a proxy that rewrites it without sending X-Forwarded-Prefix. Include any base path. Defaults to the path of redirect_uri, or of the generated redirect_route; a trusted X-Forwarded-Prefix or a subdirectory deployment is already accounted for without this. * allow_http?: bool|Param, // Whether to allow http or not (default: false) // Default: false * http_client_options?: array{ // Options forwarded to the underlying Guzzle HTTP client. league/oauth2-client only forwards: timeout, proxy, verify (verify is only consulted when proxy is set). - * timeout?: float|Param, // Total request timeout in seconds + * timeout?: float|Param, // Total request timeout in seconds. Defaults to 30; set to 0 to wait indefinitely (Guzzle's own default). // Default: 30.0 * proxy?: scalar|Param|null, // HTTP proxy URI * verify?: bool|Param, // Verify TLS certificates (only consulted by Guzzle when proxy is set) * }, From 0b764183dd45e4de49de2e07b772a9fa2deb6de5 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 25 Aug 2026 21:32:11 +0200 Subject: [PATCH 81/94] feat: render a failed OIDC login as a page --- .env.test | 5 ++ CHANGELOG.md | 12 ++++ .../OpenIdConnectFailureListener.php | 64 ++++++++++++++++++ .../error/openid_connect_failed.html.twig | 67 +++++++++++++++++++ .../OpenIdConnectFailureListenerTest.php | 48 +++++++++++++ 5 files changed, 196 insertions(+) create mode 100644 src/EventListener/OpenIdConnectFailureListener.php create mode 100644 templates/error/openid_connect_failed.html.twig create mode 100644 tests/EventListener/OpenIdConnectFailureListenerTest.php diff --git a/.env.test b/.env.test index 022bf68..af09179 100644 --- a/.env.test +++ b/.env.test @@ -4,3 +4,8 @@ APP_SECRET='$ecretf0rt3st' SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots + +# The path of this URI is the only path the OIDC authenticator treats as a +# callback (openid-connect-bundle 6.0), so it has to be the app's own callback +# route for a callback to reach the authenticator at all. +AZURE_AZ_OIDC_REDIRECT_URI=https://itksites.example.org/openid-connect/generic diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f41cf..e0409c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#92](https://github.com/itk-dev/devops_itksites/pull/92) Update openid-connect-bundle to 6.0 + - Bump `itk-dev/openid-connect-bundle` to `^6.0` + - A failed OIDC callback now raises an error instead of redirecting to the + identity provider again, so an expired client secret can no longer put the + site in a login loop + - Only the provider's callback path is treated as a callback; the configured + `redirect_uri` covers this, no `callback_path` needed + - Set `client_secret_expires_at` for the `azure_az` provider from the new + `AZURE_AZ_OIDC_CLIENT_SECRET_EXPIRES_AT` variable, so the bundle warns + before the secret expires + - Render a failed login as a page saying so, instead of an unhandled + exception, in `OpenIdConnectFailureListener` - [#91](https://github.com/itk-dev/devops_itksites/pull/91) Health endpoints - Add `/health/live`, `/health/ready` and `/health/detail` endpoints - Add health checks for database, RabbitMQ transport and detection result freshness diff --git a/src/EventListener/OpenIdConnectFailureListener.php b/src/EventListener/OpenIdConnectFailureListener.php new file mode 100644 index 0000000..6a9dd3c --- /dev/null +++ b/src/EventListener/OpenIdConnectFailureListener.php @@ -0,0 +1,64 @@ +isMainRequest() || !$event->getThrowable() instanceof AuthenticationFailedException) { + return; + } + + // Deliberately without the exception message: it carries the identity + // provider's own error text, which belongs in the log, not in a browser. + $content = $this->twig->render('error/openid_connect_failed.html.twig', [ + 'login_url' => $this->urlGenerator->generate('itkdev_openid_connect_login', ['providerKey' => 'azure_az']), + ]); + + // Still a 500: the most likely cause is on this side of the login, and a + // failed login should read as an error in the log and in monitoring. + $response = new Response($content, Response::HTTP_INTERNAL_SERVER_ERROR); + $response->headers->set('Cache-Control', 'no-store, private'); + + $event->setResponse($response); + } +} diff --git a/templates/error/openid_connect_failed.html.twig b/templates/error/openid_connect_failed.html.twig new file mode 100644 index 0000000..e95fb0c --- /dev/null +++ b/templates/error/openid_connect_failed.html.twig @@ -0,0 +1,67 @@ +{# + Shown by App\EventListener\OpenIdConnectFailureListener when an OIDC login + could not be completed. + + Self-contained on purpose: an error page should not depend on the asset + build, and "Try again" is a link the user clicks rather than a redirect — + the bundle stopped redirecting to the identity provider precisely to end + the loop a broken login used to cause. +#} + + + + + + + + Login failed - ITKsites + + + + + +
+

ITKsites

+ +

+ We could not complete your login. +

+ +

+ This happens if the login was left half-finished for too long — in that + case try again. +

+ +

+ If it keeps failing, the login setup itself needs attention: tell ITK Dev, + and mention the time you tried. The details are in the log. +

+
+ + diff --git a/tests/EventListener/OpenIdConnectFailureListenerTest.php b/tests/EventListener/OpenIdConnectFailureListenerTest.php new file mode 100644 index 0000000..a9d0207 --- /dev/null +++ b/tests/EventListener/OpenIdConnectFailureListenerTest.php @@ -0,0 +1,48 @@ +request('GET', '/openid-connect/generic?state=bogus&code=bogus'); + + $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); + + $content = (string) $client->getResponse()->getContent(); + $this->assertStringContainsString('could not complete your login', $content); + $this->assertStringContainsString('/openidconnect/login/azure_az', $content); + } + + /** + * The identity provider's own error text says why a login failed and + * belongs in the log. The page says none of it. + */ + public function testErrorPageDisclosesNothingAboutTheFailure(): void + { + $client = static::createClient(); + $client->request('GET', '/openid-connect/generic?state=bogus&code=bogus'); + + $content = (string) $client->getResponse()->getContent(); + $this->assertStringNotContainsString('Error occurred validating openid login', $content); + $this->assertStringContainsString( + 'no-store', + (string) $client->getResponse()->headers->get('Cache-Control'), + ); + } +} From ea180290ec7db5e1c5d17f236e32633462fa9291 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 10:08:30 +0200 Subject: [PATCH 82/94] feat: report OIDC client secret expiry in health checks --- CHANGELOG.md | 2 + README.md | 15 ++- .../Check/ClientSecretExpiryHealthCheck.php | 85 ++++++++++++++ tests/Controller/HealthControllerTest.php | 2 +- .../ClientSecretExpiryHealthCheckTest.php | 109 ++++++++++++++++++ 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/Health/Check/ClientSecretExpiryHealthCheck.php create mode 100644 tests/Health/Check/ClientSecretExpiryHealthCheckTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e0409c3..b55e604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 before the secret expires - Render a failed login as a page saying so, instead of an unhandled exception, in `OpenIdConnectFailureListener` + - Add an `oidc_client_secret` health check, so a client secret nearing its + expiry shows up in `/health/detail` instead of in a login loop - [#91](https://github.com/itk-dev/devops_itksites/pull/91) Health endpoints - Add `/health/live`, `/health/ready` and `/health/detail` endpoints - Add health checks for database, RabbitMQ transport and detection result freshness diff --git a/README.md b/README.md index 64b5416..e9c1874 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,21 @@ read `/health/detail` when it goes red: curl --silent https://itksites.local.itkdev.dk/health/detail | jq ``` -The checks cover the database, the RabbitMQ messenger transport and the -freshness of the most recent detection result. The last one catches an ingest -pipeline that has stopped while the application itself is still serving -requests. +The checks cover the database, the RabbitMQ messenger transport, the freshness +of the most recent detection result and the expiry of the OIDC client secret. +The freshness check catches an ingest pipeline that has stopped while the +application itself is still serving requests. The client secret check catches +the expiry that breaks every login at once. `HEALTH_INGEST_MAX_AGE` sets how old the most recent detection result may be before ingest is reported as degraded. +`AZURE_AZ_OIDC_CLIENT_SECRET_EXPIRES_AT` is where the client secret check reads +the date. It reports degraded only once that date has passed, so watch +`days_remaining` in the detail payload rather than waiting for it to go red. With +no date configured the check reports skipped, which means nothing is watching the +secret. + Results are cached for `HEALTH_CACHE_TTL` seconds so that polling does not turn into load on the dependencies. The cache is the dedicated, filesystem-backed `cache.health` pool in `config/packages/cache.yaml` – it has to keep working diff --git a/src/Health/Check/ClientSecretExpiryHealthCheck.php b/src/Health/Check/ClientSecretExpiryHealthCheck.php new file mode 100644 index 0000000..ca0f56c --- /dev/null +++ b/src/Health/Check/ClientSecretExpiryHealthCheck.php @@ -0,0 +1,85 @@ +expiryChecker->getAllStatuses(); + + if ([] === $statuses) { + return HealthCheckResult::skipped($this->getName(), 'No OIDC providers are configured.'); + } + + $details = []; + $expired = []; + $unknown = []; + + foreach ($statuses as $providerKey => $expiry) { + $details[$providerKey.'.status'] = $expiry->status->value; + $details[$providerKey.'.expires_at'] = $expiry->expiresAt?->format(\DATE_ATOM); + $details[$providerKey.'.days_remaining'] = $expiry->daysRemaining; + + if ($expiry->isExpired()) { + $expired[] = $providerKey; + } elseif (ClientSecretExpiryStatus::Unknown === $expiry->status) { + $unknown[] = $providerKey; + } + } + + if ([] !== $expired) { + return HealthCheckResult::degraded( + $this->getName(), + \sprintf('Client secret past its configured expiry: %s.', implode(', ', $expired)), + $details + ); + } + + // No date anywhere is the state an installation is in before it + // configures any: nothing is being monitored, which is not the same as + // nothing being wrong, so it is reported as skipped rather than ok. + if (\count($unknown) === \count($statuses)) { + return HealthCheckResult::skipped($this->getName(), 'No client secret expiry dates are configured.'); + } + + return HealthCheckResult::ok($this->getName(), $details); + } +} diff --git a/tests/Controller/HealthControllerTest.php b/tests/Controller/HealthControllerTest.php index b8adf86..2bb63a1 100644 --- a/tests/Controller/HealthControllerTest.php +++ b/tests/Controller/HealthControllerTest.php @@ -77,7 +77,7 @@ public function testDetailReportsEveryCheck(): void $this->assertArrayHasKey('checks', $payload); $this->assertEqualsCanonicalizing( - ['database', 'rabbitmq', 'ingest_freshness'], + ['database', 'rabbitmq', 'ingest_freshness', 'oidc_client_secret'], array_keys($payload['checks']) ); diff --git a/tests/Health/Check/ClientSecretExpiryHealthCheckTest.php b/tests/Health/Check/ClientSecretExpiryHealthCheckTest.php new file mode 100644 index 0000000..3ad226b --- /dev/null +++ b/tests/Health/Check/ClientSecretExpiryHealthCheckTest.php @@ -0,0 +1,109 @@ +check(['azure_az' => '2026-08-01']); + + self::assertSame(HealthStatus::Degraded, $result->status); + self::assertSame('Client secret past its configured expiry: azure_az.', $result->message); + self::assertSame('expired', $result->details['azure_az.status']); + self::assertLessThan(0, $result->details['azure_az.days_remaining']); + } + + /** + * Expiring soon stays ok. Thirty days of 503 would train everyone to ignore + * the endpoint before the day it matters; the remaining days are in the + * payload for whatever watches it. + */ + public function testExpiringSoonIsOkWithDaysRemaining(): void + { + $result = $this->check(['azure_az' => '2026-09-05']); + + self::assertSame(HealthStatus::Ok, $result->status); + self::assertSame('expiring_soon', $result->details['azure_az.status']); + // 2026-08-26 12:00 UTC to midnight on 2026-09-05 is 9.5 days, floored. + self::assertSame(9, $result->details['azure_az.days_remaining']); + } + + public function testHealthySecretIsOk(): void + { + $result = $this->check(['azure_az' => '2027-01-31']); + + self::assertSame(HealthStatus::Ok, $result->status); + self::assertSame('ok', $result->details['azure_az.status']); + self::assertSame('2027-01-31T00:00:00+00:00', $result->details['azure_az.expires_at']); + } + + /** + * An unconfigured date means nothing is being monitored, which must not read + * as healthy and must not fail readiness either. + */ + public function testUnconfiguredDateIsSkipped(): void + { + $result = $this->check(['azure_az' => null]); + + self::assertSame(HealthStatus::Skipped, $result->status); + self::assertSame('No client secret expiry dates are configured.', $result->message); + } + + public function testNoProvidersIsSkipped(): void + { + $result = $this->check([]); + + self::assertSame(HealthStatus::Skipped, $result->status); + self::assertSame('No OIDC providers are configured.', $result->message); + } + + /** + * One expired provider degrades the check even when another is fine, and + * both stay visible in the payload. + */ + public function testExpiredProviderDegradesAlongsideAHealthyOne(): void + { + $result = $this->check(['azure_az' => '2027-01-31', 'legacy' => '2026-08-01']); + + self::assertSame(HealthStatus::Degraded, $result->status); + self::assertSame('Client secret past its configured expiry: legacy.', $result->message); + self::assertSame('ok', $result->details['azure_az.status']); + self::assertSame('expired', $result->details['legacy.status']); + } + + /** + * @param array $expiryDates + */ + private function check(array $expiryDates): HealthCheckResult + { + $checker = new ClientSecretExpiryChecker( + new MockClock('2026-08-26 12:00:00', 'UTC'), + $expiryDates, + self::WARNING_DAYS, + new NullLogger(), + ); + + return (new ClientSecretExpiryHealthCheck($checker))->check(); + } +} From 9d14251bd64e3d6e6ce12e82a5e45bed4d40cfe7 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 10:16:58 +0200 Subject: [PATCH 83/94] fix(deps): update composer dependencies for security advisories --- CHANGELOG.md | 6 + composer.lock | 1613 ++++++++++++++++++++------------------- config/reference.php | 195 ++--- public/api-spec-v1.yaml | 12 +- 4 files changed, 966 insertions(+), 860 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0beda8d..db4f636 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#93](https://github.com/itk-dev/devops_itksites/pull/93) + Update composer dependencies, clearing 15 security advisories + - `api-platform/core` 4.3.7 → 4.3.17, `easycorp/easyadmin-bundle` 5.0.11 → 5.5.1, + `guzzlehttp/guzzle` 7.10.6 → 7.15.5, `guzzlehttp/psr7` 2.10.4 → 2.13.1 + - Regenerated the API spec: `symfony/yaml` now writes sequence items on their + own line. No API changes - [#90](https://github.com/itk-dev/devops_itksites/pull/90) - Fixed user API key migration failing on databases with more than one user - Generated an API key for existing users, as users created since already get diff --git a/composer.lock b/composer.lock index cbd47e7..f1ad76b 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "api-platform/core", - "version": "v4.3.7", + "version": "v4.3.17", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "25f80814e1f9c849bae2bc17ed2e07f2461c72d8" + "reference": "815f7f401e6e3acdf0530283cf9dedcddc537839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/25f80814e1f9c849bae2bc17ed2e07f2461c72d8", - "reference": "25f80814e1f9c849bae2bc17ed2e07f2461c72d8", + "url": "https://api.github.com/repos/api-platform/core/zipball/815f7f401e6e3acdf0530283cf9dedcddc537839", + "reference": "815f7f401e6e3acdf0530283cf9dedcddc537839", "shasum": "" }, "require": { @@ -75,16 +75,11 @@ "api-platform/validator": "self.version" }, "require-dev": { - "behat/behat": "^3.11", - "behat/mink": "^1.9", "doctrine/common": "^3.2.2", "doctrine/dbal": "^4.0", "doctrine/doctrine-bundle": "^2.11 || ^3.1", "doctrine/orm": "^2.17 || ^3.0", "elasticsearch/elasticsearch": "^7.17 || ^8.4 || ^9.0", - "friends-of-behat/mink-browserkit-driver": "^1.3.1", - "friends-of-behat/mink-extension": "^2.2", - "friends-of-behat/symfony-extension": "^2.1", "friendsofphp/php-cs-fixer": "^3.93", "guzzlehttp/guzzle": "^6.0 || ^7.0", "illuminate/config": "^11.0 || ^12.0 || ^13.0", @@ -97,7 +92,7 @@ "jangregor/phpstan-prophecy": "^2.1.11", "justinrainbow/json-schema": "^6.5.2", "laravel/framework": "^11.0 || ^12.0 || ^13.0", - "mcp/sdk": ">=0.4 <1.0", + "mcp/sdk": "^0.6", "orchestra/testbench": "^10.9 || ^11.0", "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", @@ -110,7 +105,6 @@ "psr/log": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", - "soyuka/contexts": "^3.3.10", "soyuka/pmu": "^0.2.0", "soyuka/stubs-mongodb": "^1.0", "symfony/asset": "^6.4 || ^7.0 || ^8.0", @@ -226,9 +220,9 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v4.3.7" + "source": "https://github.com/api-platform/core/tree/v4.3.17" }, - "time": "2026-05-29T07:17:00+00:00" + "time": "2026-07-12T06:11:13+00:00" }, { "name": "composer/semver", @@ -395,16 +389,16 @@ }, { "name": "doctrine/dbal", - "version": "4.4.3", + "version": "4.4.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "61e730f1658814821a85f2402c945f3883407dec" + "reference": "fb9e0ffe15e1590e24dc61c0c0a23f9a33ee42ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/61e730f1658814821a85f2402c945f3883407dec", - "reference": "61e730f1658814821a85f2402c945f3883407dec", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/fb9e0ffe15e1590e24dc61c0c0a23f9a33ee42ce", + "reference": "fb9e0ffe15e1590e24dc61c0c0a23f9a33ee42ce", "shasum": "" }, "require": { @@ -481,7 +475,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.4.3" + "source": "https://github.com/doctrine/dbal/tree/4.4.4" }, "funding": [ { @@ -497,7 +491,7 @@ "type": "tidelift" } ], - "time": "2026-03-20T08:52:12+00:00" + "time": "2026-07-21T14:34:40+00:00" }, { "name": "doctrine/deprecations", @@ -549,16 +543,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "3.2.2", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce" + "reference": "9b231f957020de52d6ac94fd72f0cbe8cf1eaad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/af84173db6978c3d2688ea3bcf3a91720b0704ce", - "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/9b231f957020de52d6ac94fd72f0cbe8cf1eaad5", + "reference": "9b231f957020de52d6ac94fd72f0cbe8cf1eaad5", "shasum": "" }, "require": { @@ -566,6 +560,7 @@ "doctrine/deprecations": "^1.0", "doctrine/persistence": "^4", "doctrine/sql-formatter": "^1.0.1", + "ext-mbstring": "*", "php": "^8.4", "symfony/cache": "^6.4 || ^7.0 || ^8.0", "symfony/config": "^6.4 || ^7.0 || ^8.0", @@ -582,16 +577,18 @@ "require-dev": { "doctrine/coding-standard": "^14", "doctrine/orm": "^3.4.4", - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "^2.1.13", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-symfony": "^2.0.9", "phpunit/phpunit": "^12.3.10", "psr/log": "^3.0", "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0", "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", "symfony/messenger": "^6.4 || ^7.0 || ^8.0", "symfony/property-info": "^6.4 || ^7.0 || ^8.0", + "symfony/runtime": "^6.4 || ^7.0 || ^8.0", "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", "symfony/string": "^6.4 || ^7.0 || ^8.0", @@ -644,7 +641,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/3.2.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/3.3.1" }, "funding": [ { @@ -660,20 +657,20 @@ "type": "tidelift" } ], - "time": "2025-12-24T12:24:29+00:00" + "time": "2026-07-23T10:37:01+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c" + "reference": "43e9212c7441178e3748580e53f88c08f4e70e5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/20505da78735744fb4a42a3bb9a416b345ad6f7c", - "reference": "20505da78735744fb4a42a3bb9a416b345ad6f7c", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/43e9212c7441178e3748580e53f88c08f4e70e5f", + "reference": "43e9212c7441178e3748580e53f88c08f4e70e5f", "shasum": "" }, "require": { @@ -736,7 +733,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/4.0.0" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/4.0.1" }, "funding": [ { @@ -752,7 +749,7 @@ "type": "tidelift" } ], - "time": "2025-12-05T08:14:38+00:00" + "time": "2026-08-26T05:54:54+00:00" }, { "name": "doctrine/event-manager", @@ -1186,16 +1183,16 @@ }, { "name": "doctrine/orm", - "version": "3.6.7", + "version": "3.6.8", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c" + "reference": "a4d13ed5b11e7f7b4d654b1adf95432031ae3ffc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/bc217c0e19c3a9eadfa67697143b87c9ba01272c", - "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c", + "url": "https://api.github.com/repos/doctrine/orm/zipball/a4d13ed5b11e7f7b4d654b1adf95432031ae3ffc", + "reference": "a4d13ed5b11e7f7b4d654b1adf95432031ae3ffc", "shasum": "" }, "require": { @@ -1225,6 +1222,7 @@ "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "suggest": { + "ext-deepclone": "Improves performance when not using native lazy objects (Symfony 8.1+)", "ext-dom": "Provides support for XSD validation for XML mapping files", "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, @@ -1268,9 +1266,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.6.7" + "source": "https://github.com/doctrine/orm/tree/3.6.8" }, - "time": "2026-05-25T16:45:47+00:00" + "time": "2026-08-05T19:05:32+00:00" }, { "name": "doctrine/persistence", @@ -1423,16 +1421,16 @@ }, { "name": "easycorp/easyadmin-bundle", - "version": "v5.0.11", + "version": "v5.5.1", "source": { "type": "git", "url": "https://github.com/EasyCorp/EasyAdminBundle.git", - "reference": "e22ef0fddc532ab7847c887de428fbd7f6f118d5" + "reference": "77b2a6b13c7050cd5412b3ce2b86e909682b97c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/e22ef0fddc532ab7847c887de428fbd7f6f118d5", - "reference": "e22ef0fddc532ab7847c887de428fbd7f6f118d5", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/77b2a6b13c7050cd5412b3ce2b86e909682b97c3", + "reference": "77b2a6b13c7050cd5412b3ce2b86e909682b97c3", "shasum": "" }, "require": { @@ -1497,7 +1495,10 @@ "autoload": { "psr-4": { "EasyCorp\\Bundle\\EasyAdminBundle\\": "src/" - } + }, + "exclude-from-classmap": [ + "/tests/Functional/Apps/*/config/reference.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1518,7 +1519,7 @@ ], "support": { "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", - "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v5.0.11" + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v5.5.1" }, "funding": [ { @@ -1526,20 +1527,20 @@ "type": "github" } ], - "time": "2026-05-28T19:03:47+00:00" + "time": "2026-08-11T06:28:56+00:00" }, { "name": "firebase/php-jwt", - "version": "v7.0.5", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/googleapis/php-jwt.git", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0", "shasum": "" }, "require": { @@ -1548,6 +1549,7 @@ "require-dev": { "guzzlehttp/guzzle": "^7.4", "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", @@ -1556,7 +1558,8 @@ }, "suggest": { "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, "type": "library", "autoload": { @@ -1581,38 +1584,39 @@ } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "jwt", "php" ], "support": { "issues": "https://github.com/googleapis/php-jwt/issues", - "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0" }, - "time": "2026-04-01T20:38:03+00:00" + "time": "2026-06-11T17:54:14+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.10.6", + "version": "7.15.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94" + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e7412b3180912c01650cc66647f18c1d1cbe9b94", - "reference": "e7412b3180912c01650cc66647f18c1d1cbe9b94", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1620,8 +1624,8 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.4", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1701,7 +1705,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.6" + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" }, "funding": [ { @@ -1717,24 +1721,25 @@ "type": "tidelift" } ], - "time": "2026-06-01T13:06:22+00:00" + "time": "2026-08-24T09:21:06+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.4.1", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2" + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/09e8a212562fb1fb6a512c4156ed71525969d6c2", - "reference": "09e8a212562fb1fb6a512c4156ed71525969d6c2", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -1784,7 +1789,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.4.1" + "source": "https://github.com/guzzle/promises/tree/2.5.3" }, "funding": [ { @@ -1800,27 +1805,29 @@ "type": "tidelift" } ], - "time": "2026-05-20T22:57:30+00:00" + "time": "2026-08-24T09:11:28+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.10.4", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "d2a1a094e396da8957e797489fddaf860c340cfc" + "reference": "95e7828100de18b4e269fb1703be530082d5166d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/d2a1a094e396da8957e797489fddaf860c340cfc", - "reference": "d2a1a094e396da8957e797489fddaf860c340cfc", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1901,7 +1908,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.4" + "source": "https://github.com/guzzle/psr7/tree/2.13.1" }, "funding": [ { @@ -1917,7 +1924,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T12:59:07+00:00" + "time": "2026-08-24T09:13:11+00:00" }, { "name": "itk-dev/openid-connect", @@ -1985,16 +1992,16 @@ }, { "name": "itk-dev/openid-connect-bundle", - "version": "5.0.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect-bundle.git", - "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4" + "reference": "5021974bb605d3c5130b06e05bd0a277587277bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", - "reference": "af57823b41629ad5bc1abc50eeb388c7b4b6dbb4", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/5021974bb605d3c5130b06e05bd0a277587277bc", + "reference": "5021974bb605d3c5130b06e05bd0a277587277bc", "shasum": "" }, "require": { @@ -2003,7 +2010,11 @@ "ext-openssl": "*", "itk-dev/openid-connect": "^5.0", "php": "^8.3", + "psr/log": "^3.0", "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/clock": "^6.4 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/event-dispatcher": "^6.4 || ^7.0 || ^8.0", "symfony/framework-bundle": "^6.4.13 || ^7.0 || ^8.0", "symfony/security-bundle": "^6.4.13 || ^7.0 || ^8.0", "symfony/uid": "^6.4 || ^7.0 || ^8.0", @@ -2012,6 +2023,7 @@ "require-dev": { "ergebnis/composer-normalize": "^2.28", "friendsofphp/php-cs-fixer": "^3.11", + "infection/infection": "*", "phpstan/phpstan": "^2.1.41", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", @@ -2044,9 +2056,9 @@ "description": "Symfony bundle for openid-connect", "support": { "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", - "source": "https://github.com/itk-dev/openid-connect-bundle/tree/5.0.0" + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/5.1.1" }, - "time": "2026-06-02T11:15:20+00:00" + "time": "2026-08-19T13:41:15+00:00" }, { "name": "itk-dev/vault", @@ -2532,16 +2544,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", "shasum": "" }, "require": { @@ -2573,9 +2585,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" }, - "time": "2026-01-25T14:56:51+00:00" + "time": "2026-07-08T07:01:06+00:00" }, { "name": "psr/cache", @@ -3325,16 +3337,16 @@ }, { "name": "symfony/asset-mapper", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/asset-mapper.git", - "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e" + "reference": "b13fd8210675bafac7e782569d0cafac860f2e8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/74b1b7b7019c728cb1f8672b502260e683b6374e", - "reference": "74b1b7b7019c728cb1f8672b502260e683b6374e", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/b13fd8210675bafac7e782569d0cafac860f2e8f", + "reference": "b13fd8210675bafac7e782569d0cafac860f2e8f", "shasum": "" }, "require": { @@ -3382,7 +3394,7 @@ "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset-mapper/tree/v8.1.0" + "source": "https://github.com/symfony/asset-mapper/tree/v8.1.5" }, "funding": [ { @@ -3402,20 +3414,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-14T13:31:31+00:00" }, { "name": "symfony/browser-kit", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "74e18e582cdda0eca35f7c74e1e48e62f0ede853" + "reference": "f6766c9fb232e72897d6d55cf8f6aec38db2dbbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/74e18e582cdda0eca35f7c74e1e48e62f0ede853", - "reference": "74e18e582cdda0eca35f7c74e1e48e62f0ede853", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f6766c9fb232e72897d6d55cf8f6aec38db2dbbf", + "reference": "f6766c9fb232e72897d6d55cf8f6aec38db2dbbf", "shasum": "" }, "require": { @@ -3454,7 +3466,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v8.1.0" + "source": "https://github.com/symfony/browser-kit/tree/v8.1.5" }, "funding": [ { @@ -3474,20 +3486,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/cache", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "ba62e0ed9ea9bc26142844a891d4a3dfceb24aed" + "reference": "c12be586c0e4798e649e7501aa9181ddc9d93310" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/ba62e0ed9ea9bc26142844a891d4a3dfceb24aed", - "reference": "ba62e0ed9ea9bc26142844a891d4a3dfceb24aed", + "url": "https://api.github.com/repos/symfony/cache/zipball/c12be586c0e4798e649e7501aa9181ddc9d93310", + "reference": "c12be586c0e4798e649e7501aa9181ddc9d93310", "shasum": "" }, "require": { @@ -3508,7 +3520,7 @@ "symfony/cache-implementation": "1.1|2.0|3.0" }, "require-dev": { - "cache/integration-tests": "dev-master", + "cache/integration-tests": "^1.0.3", "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", @@ -3553,7 +3565,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v8.1.0" + "source": "https://github.com/symfony/cache/tree/v8.1.5" }, "funding": [ { @@ -3573,20 +3585,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-19T08:32:28+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "225e8a254166bd3442e370c6f50145465db63831" + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", - "reference": "225e8a254166bd3442e370c6f50145465db63831", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/9789738bc19af1106dc54d6afba9a0b467516cf2", + "reference": "9789738bc19af1106dc54d6afba9a0b467516cf2", "shasum": "" }, "require": { @@ -3633,7 +3645,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.1" }, "funding": [ { @@ -3653,7 +3665,7 @@ "type": "tidelift" } ], - "time": "2026-05-05T15:33:14+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/clock", @@ -3734,16 +3746,16 @@ }, { "name": "symfony/config", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "429783a0c649696f2058ea5ab5315f082dba6de9" + "reference": "ec711a6c14ae287d9618fbd2c9de4e223a1a4b02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/429783a0c649696f2058ea5ab5315f082dba6de9", - "reference": "429783a0c649696f2058ea5ab5315f082dba6de9", + "url": "https://api.github.com/repos/symfony/config/zipball/ec711a6c14ae287d9618fbd2c9de4e223a1a4b02", + "reference": "ec711a6c14ae287d9618fbd2c9de4e223a1a4b02", "shasum": "" }, "require": { @@ -3788,7 +3800,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/config/tree/v8.1.5" }, "funding": [ { @@ -3808,20 +3820,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-20T09:59:12+00:00" }, { "name": "symfony/console", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a" + "reference": "d07c06839e33047e2c894a6793248f3fb66c8129" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", - "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", + "url": "https://api.github.com/repos/symfony/console/zipball/d07c06839e33047e2c894a6793248f3fb66c8129", + "reference": "d07c06839e33047e2c894a6793248f3fb66c8129", "shasum": "" }, "require": { @@ -3888,7 +3900,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.1.0" + "source": "https://github.com/symfony/console/tree/v8.1.5" }, "funding": [ { @@ -3908,20 +3920,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T14:29:57+00:00" }, { "name": "symfony/dependency-injection", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "b6ba1f45127106885de4b77558c5ecca8feb1e1b" + "reference": "e79d512848b75f92374e473f8e9ead4202c965c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b6ba1f45127106885de4b77558c5ecca8feb1e1b", - "reference": "b6ba1f45127106885de4b77558c5ecca8feb1e1b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e79d512848b75f92374e473f8e9ead4202c965c5", + "reference": "e79d512848b75f92374e473f8e9ead4202c965c5", "shasum": "" }, "require": { @@ -3969,7 +3981,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/dependency-injection/tree/v8.1.5" }, "funding": [ { @@ -3989,20 +4001,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -4040,7 +4052,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -4060,20 +4072,20 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5" + "reference": "1f2acb6df169495f4993851c01bf98a44fa2c59e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5", - "reference": "80daf848dd39d9ff5a0f39aa6f2bf5448aa662c5", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/1f2acb6df169495f4993851c01bf98a44fa2c59e", + "reference": "1f2acb6df169495f4993851c01bf98a44fa2c59e", "shasum": "" }, "require": { @@ -4090,7 +4102,8 @@ "doctrine/dbal": "<4.3", "doctrine/lexer": "<1.1", "doctrine/orm": "<3.4", - "symfony/property-info": "<8.0" + "symfony/property-info": "<8.0", + "symfony/validator": "<7.4.17|>=8.0,<8.1.5" }, "require-dev": { "doctrine/collections": "^1.8|^2.0", @@ -4144,7 +4157,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v8.1.0" + "source": "https://github.com/symfony/doctrine-bridge/tree/v8.1.5" }, "funding": [ { @@ -4164,20 +4177,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:18:49+00:00" + "time": "2026-08-21T19:39:55+00:00" }, { "name": "symfony/doctrine-messenger", - "version": "v8.1.0", + "version": "v8.1.4", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "cafeac0dccfd4e971c9a5c646652d1037de469f8" + "reference": "a2d57cd7caf9160ce3e19a9da501e54b234cff72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/cafeac0dccfd4e971c9a5c646652d1037de469f8", - "reference": "cafeac0dccfd4e971c9a5c646652d1037de469f8", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/a2d57cd7caf9160ce3e19a9da501e54b234cff72", + "reference": "a2d57cd7caf9160ce3e19a9da501e54b234cff72", "shasum": "" }, "require": { @@ -4222,7 +4235,7 @@ "description": "Symfony Doctrine Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-messenger/tree/v8.1.0" + "source": "https://github.com/symfony/doctrine-messenger/tree/v8.1.4" }, "funding": [ { @@ -4242,20 +4255,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-07T13:15:43+00:00" }, { "name": "symfony/dom-crawler", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "77ca351474ea018daba5f2e473cbf1b9b8e72ac6" + "reference": "94f70e1b2e5b7b8f8618871fd9eaad3e39e9f03d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/77ca351474ea018daba5f2e473cbf1b9b8e72ac6", - "reference": "77ca351474ea018daba5f2e473cbf1b9b8e72ac6", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/94f70e1b2e5b7b8f8618871fd9eaad3e39e9f03d", + "reference": "94f70e1b2e5b7b8f8618871fd9eaad3e39e9f03d", "shasum": "" }, "require": { @@ -4292,7 +4305,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v8.1.0" + "source": "https://github.com/symfony/dom-crawler/tree/v8.1.5" }, "funding": [ { @@ -4312,20 +4325,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/dotenv", - "version": "v8.1.0", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c" + "reference": "4ea87b35c75f7052309ec9735c0eb5c1fd7d2182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c", - "reference": "7ed4e3a11e3c98235c70ded047d7ddf9e6ae854c", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/4ea87b35c75f7052309ec9735c0eb5c1fd7d2182", + "reference": "4ea87b35c75f7052309ec9735c0eb5c1fd7d2182", "shasum": "" }, "require": { @@ -4366,7 +4379,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v8.1.0" + "source": "https://github.com/symfony/dotenv/tree/v8.1.2" }, "funding": [ { @@ -4386,20 +4399,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-07-26T10:31:34+00:00" }, { "name": "symfony/error-handler", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", - "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", "shasum": "" }, "require": { @@ -4447,7 +4460,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v8.1.0" + "source": "https://github.com/symfony/error-handler/tree/v8.1.5" }, "funding": [ { @@ -4467,20 +4480,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102" + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f249ae3f680958b6f1f9dd76e5747cf0695b4102", - "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", "shasum": "" }, "require": { @@ -4533,7 +4546,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" }, "funding": [ { @@ -4553,20 +4566,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -4613,7 +4626,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -4633,20 +4646,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/expression-language", - "version": "v8.1.0", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "67f31731d5b316d0183c565933017d5d3331d609" + "reference": "13b74638d9c7c6854fcbb7e89a42a90fdca51f57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/67f31731d5b316d0183c565933017d5d3331d609", - "reference": "67f31731d5b316d0183c565933017d5d3331d609", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/13b74638d9c7c6854fcbb7e89a42a90fdca51f57", + "reference": "13b74638d9c7c6854fcbb7e89a42a90fdca51f57", "shasum": "" }, "require": { @@ -4680,7 +4693,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v8.1.0" + "source": "https://github.com/symfony/expression-language/tree/v8.1.1" }, "funding": [ { @@ -4700,20 +4713,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-06-09T10:54:51+00:00" }, { "name": "symfony/filesystem", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" + "reference": "6b2f4a0eeb28b5d74f90862592923a654bc629b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", - "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6b2f4a0eeb28b5d74f90862592923a654bc629b3", + "reference": "6b2f4a0eeb28b5d74f90862592923a654bc629b3", "shasum": "" }, "require": { @@ -4751,7 +4764,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.1.0" + "source": "https://github.com/symfony/filesystem/tree/v8.1.5" }, "funding": [ { @@ -4771,20 +4784,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T12:16:08+00:00" }, { "name": "symfony/finder", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "58d2e767a66052c1487356f953445634a8194c64" + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", - "reference": "58d2e767a66052c1487356f953445634a8194c64", + "url": "https://api.github.com/repos/symfony/finder/zipball/8d7acede2b2ae07605783d1c43e49b5767036474", + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474", "shasum": "" }, "require": { @@ -4819,7 +4832,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.1.0" + "source": "https://github.com/symfony/finder/tree/v8.1.5" }, "funding": [ { @@ -4839,7 +4852,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T12:16:08+00:00" }, { "name": "symfony/flex", @@ -4916,16 +4929,16 @@ }, { "name": "symfony/form", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8" + "reference": "0e51e9e9fb49832ea84b1f6ebdfeedc44dcde582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8", - "reference": "82f3b7834a1fa05ea3ea5dc944a15cd350ce60a8", + "url": "https://api.github.com/repos/symfony/form/zipball/0e51e9e9fb49832ea84b1f6ebdfeedc44dcde582", + "reference": "0e51e9e9fb49832ea84b1f6ebdfeedc44dcde582", "shasum": "" }, "require": { @@ -4989,7 +5002,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v8.1.0" + "source": "https://github.com/symfony/form/tree/v8.1.5" }, "funding": [ { @@ -5009,20 +5022,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/framework-bundle", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "6a0953f4fd8b51db6136c2628af99b7193e63256" + "reference": "1c7b41364312c67376a560e35c7f7f0be0c1b4ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6a0953f4fd8b51db6136c2628af99b7193e63256", - "reference": "6a0953f4fd8b51db6136c2628af99b7193e63256", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/1c7b41364312c67376a560e35c7f7f0be0c1b4ea", + "reference": "1c7b41364312c67376a560e35c7f7f0be0c1b4ea", "shasum": "" }, "require": { @@ -5031,7 +5044,7 @@ "php": ">=8.4.1", "symfony/cache": "^7.4|^8.0", "symfony/config": "^7.4.4|^8.0.4", - "symfony/dependency-injection": "^8.1", + "symfony/dependency-injection": "^8.1.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^7.4|^8.0", "symfony/event-dispatcher": "^8.1", @@ -5042,16 +5055,17 @@ "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php85": "^1.33", "symfony/routing": "^7.4|^8.0", - "symfony/service-contracts": "^3.7", + "symfony/service-contracts": "^3.7.1", "symfony/var-exporter": "^8.1" }, "conflict": { "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/console": "<8.1", + "symfony/console": "<8.1.2", "symfony/form": "<7.4", "symfony/json-streamer": "<7.4", + "symfony/mailer": "<7.4.17|>=8.0,<8.1.5", "symfony/messenger": "<7.4.10|>=8.0,<8.0.10", "symfony/mime": "<7.4.9|>=8.0,<8.0.9", "symfony/security-csrf": "<7.4", @@ -5070,7 +5084,7 @@ "symfony/asset-mapper": "^7.4|^8.0", "symfony/browser-kit": "^7.4|^8.0", "symfony/clock": "^7.4|^8.0", - "symfony/console": "^8.1", + "symfony/console": "^8.1.2", "symfony/css-selector": "^7.4|^8.0", "symfony/dom-crawler": "^7.4|^8.0", "symfony/dotenv": "^7.4|^8.0", @@ -5132,7 +5146,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/framework-bundle/tree/v8.1.5" }, "funding": [ { @@ -5152,20 +5166,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T19:39:55+00:00" }, { "name": "symfony/http-client", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb" + "reference": "9f941ed000bb11f16dc7eafed98a7b646d3b3e5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", - "reference": "68a48e4c31f63fcd1bdff997a85a09e55efe8cdb", + "url": "https://api.github.com/repos/symfony/http-client/zipball/9f941ed000bb11f16dc7eafed98a7b646d3b3e5d", + "reference": "9f941ed000bb11f16dc7eafed98a7b646d3b3e5d", "shasum": "" }, "require": { @@ -5188,7 +5202,7 @@ "require-dev": { "amphp/http-client": "^5.3.2", "amphp/http-tunnel": "^2.0", - "guzzlehttp/guzzle": "^7.10", + "guzzlehttp/guzzle": "^7.10|^8.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", @@ -5229,7 +5243,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v8.1.0" + "source": "https://github.com/symfony/http-client/tree/v8.1.5" }, "funding": [ { @@ -5249,20 +5263,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41fc42d276aeff21192465331ebbab7d83a743c0", + "reference": "41fc42d276aeff21192465331ebbab7d83a743c0", "shasum": "" }, "require": { @@ -5311,7 +5325,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.1" }, "funding": [ { @@ -5331,20 +5345,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:17:50+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/http-foundation", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e" + "reference": "ee16f97e95cfa011a742714d7c8c8f70fe7423f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/af11474600f06718086c2cda4fa6fa8d0a672e7e", - "reference": "af11474600f06718086c2cda4fa6fa8d0a672e7e", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee16f97e95cfa011a742714d7c8c8f70fe7423f4", + "reference": "ee16f97e95cfa011a742714d7c8c8f70fe7423f4", "shasum": "" }, "require": { @@ -5392,7 +5406,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v8.1.0" + "source": "https://github.com/symfony/http-foundation/tree/v8.1.5" }, "funding": [ { @@ -5412,20 +5426,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-20T09:59:12+00:00" }, { "name": "symfony/http-kernel", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39" + "reference": "0306e1e65b90023fe40de6c6be95d06396bcb2e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", - "reference": "cefeb37c82eed3e0c42fa25ba64cd3a908d90f39", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/0306e1e65b90023fe40de6c6be95d06396bcb2e6", + "reference": "0306e1e65b90023fe40de6c6be95d06396bcb2e6", "shasum": "" }, "require": { @@ -5441,6 +5455,7 @@ "symfony/dependency-injection": "<8.1", "symfony/flex": "<2.10", "symfony/http-client-contracts": "<2.5", + "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", "symfony/translation-contracts": "<2.5", "symfony/var-dumper": "<8.1", "symfony/web-profiler-bundle": "<8.1", @@ -5473,7 +5488,7 @@ "symfony/validator": "^7.4|^8.0", "symfony/var-dumper": "^8.1", "symfony/var-exporter": "^7.4|^8.0", - "twig/twig": "^3.21" + "twig/twig": "^3.21|^4.0" }, "type": "library", "autoload": { @@ -5501,7 +5516,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/http-kernel/tree/v8.1.5" }, "funding": [ { @@ -5521,20 +5536,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T08:46:08+00:00" + "time": "2026-08-22T13:45:00+00:00" }, { "name": "symfony/intl", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "7822dffb3e2128501a1ac781db5d8217fc9caac1" + "reference": "9a8ba3a0af4db32bf228ca841c319d5dc97f7d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/7822dffb3e2128501a1ac781db5d8217fc9caac1", - "reference": "7822dffb3e2128501a1ac781db5d8217fc9caac1", + "url": "https://api.github.com/repos/symfony/intl/zipball/9a8ba3a0af4db32bf228ca841c319d5dc97f7d02", + "reference": "9a8ba3a0af4db32bf228ca841c319d5dc97f7d02", "shasum": "" }, "require": { @@ -5590,7 +5605,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v8.1.0" + "source": "https://github.com/symfony/intl/tree/v8.1.5" }, "funding": [ { @@ -5610,20 +5625,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/messenger", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "c095398338a8e6f5ae6579ebb2092b7a16b7df72" + "reference": "fa890d0632db0b5e8bc547560b412de824b3b9cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/c095398338a8e6f5ae6579ebb2092b7a16b7df72", - "reference": "c095398338a8e6f5ae6579ebb2092b7a16b7df72", + "url": "https://api.github.com/repos/symfony/messenger/zipball/fa890d0632db0b5e8bc547560b412de824b3b9cd", + "reference": "fa890d0632db0b5e8bc547560b412de824b3b9cd", "shasum": "" }, "require": { @@ -5682,7 +5697,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v8.1.0" + "source": "https://github.com/symfony/messenger/tree/v8.1.5" }, "funding": [ { @@ -5702,20 +5717,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T20:43:01+00:00" }, { "name": "symfony/mime", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664" + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b164ae7e3f7915aacfe9ee155f2f358502440664", - "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664", + "url": "https://api.github.com/repos/symfony/mime/zipball/1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", "shasum": "" }, "require": { @@ -5736,7 +5751,7 @@ "symfony/process": "^7.4|^8.0", "symfony/property-access": "^7.4|^8.0", "symfony/property-info": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0" + "symfony/serializer": "^7.4.17|^8.1.5" }, "type": "library", "autoload": { @@ -5768,7 +5783,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v8.1.0" + "source": "https://github.com/symfony/mime/tree/v8.1.5" }, "funding": [ { @@ -5788,20 +5803,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "38563fac41ede8521e5e3dc139a4f2b097471c8c" + "reference": "710b0442eb63382bc64cffbb5989fa5c466eeddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/38563fac41ede8521e5e3dc139a4f2b097471c8c", - "reference": "38563fac41ede8521e5e3dc139a4f2b097471c8c", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/710b0442eb63382bc64cffbb5989fa5c466eeddc", + "reference": "710b0442eb63382bc64cffbb5989fa5c466eeddc", "shasum": "" }, "require": { @@ -5816,6 +5831,7 @@ "symfony/mailer": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", "symfony/mime": "^7.4|^8.0", + "symfony/notifier": "^7.4|^8.0", "symfony/security-core": "^7.4|^8.0", "symfony/var-dumper": "^7.4|^8.0" }, @@ -5845,7 +5861,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v8.1.0" + "source": "https://github.com/symfony/monolog-bridge/tree/v8.1.5" }, "funding": [ { @@ -5865,7 +5881,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/monolog-bundle", @@ -6092,16 +6108,16 @@ }, { "name": "symfony/polyfill-deepclone", - "version": "v1.37.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-deepclone.git", - "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4" + "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", - "reference": "2ca9e9e75ead5174f2b44613a646bdc9338b8eb4", + "url": "https://api.github.com/repos/symfony/polyfill-deepclone/zipball/70ba0627efc68e97ea392843458a2dd9d6dbd156", + "reference": "70ba0627efc68e97ea392843458a2dd9d6dbd156", "shasum": "" }, "require": { @@ -6155,7 +6171,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-deepclone/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-deepclone/tree/v1.42.0" }, "funding": [ { @@ -6175,20 +6191,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:03:27+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -6237,7 +6253,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -6257,7 +6273,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-icu", @@ -6349,16 +6365,16 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.38.1", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "dc21118016c039a66235cf93d96b435ffb282412" + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", - "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", "shasum": "" }, "require": { @@ -6412,7 +6428,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" }, "funding": [ { @@ -6432,20 +6448,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T15:22:23+00:00" + "time": "2026-08-24T10:51:20+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { @@ -6497,7 +6513,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -6517,20 +6533,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:48:31+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -6582,7 +6598,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -6602,7 +6618,91 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-php84", @@ -6686,16 +6786,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -6742,7 +6842,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -6762,7 +6862,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T02:25:22+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-uuid", @@ -6849,16 +6949,16 @@ }, { "name": "symfony/property-access", - "version": "v8.1.0", + "version": "v8.1.4", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9" + "reference": "1a41232c678972b93ce499a504e19ea09dfcd0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/9261ef060f26cc7b728f67f141ba19b98a6209a9", - "reference": "9261ef060f26cc7b728f67f141ba19b98a6209a9", + "url": "https://api.github.com/repos/symfony/property-access/zipball/1a41232c678972b93ce499a504e19ea09dfcd0b2", + "reference": "1a41232c678972b93ce499a504e19ea09dfcd0b2", "shasum": "" }, "require": { @@ -6906,7 +7006,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v8.1.0" + "source": "https://github.com/symfony/property-access/tree/v8.1.4" }, "funding": [ { @@ -6926,20 +7026,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-07-30T12:40:56+00:00" }, { "name": "symfony/property-info", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7" + "reference": "b335f8e7fb1440ed3448fb33340efc6127678e53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/4721e8c56d0cd2378e0ef9a9899f810008b859f7", - "reference": "4721e8c56d0cd2378e0ef9a9899f810008b859f7", + "url": "https://api.github.com/repos/symfony/property-info/zipball/b335f8e7fb1440ed3448fb33340efc6127678e53", + "reference": "b335f8e7fb1440ed3448fb33340efc6127678e53", "shasum": "" }, "require": { @@ -6992,7 +7092,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v8.1.0" + "source": "https://github.com/symfony/property-info/tree/v8.1.5" }, "funding": [ { @@ -7012,20 +7112,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/routing", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3" + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", - "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", + "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", "shasum": "" }, "require": { @@ -7072,7 +7172,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v8.1.0" + "source": "https://github.com/symfony/routing/tree/v8.1.5" }, "funding": [ { @@ -7092,7 +7192,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-17T13:18:34+00:00" }, { "name": "symfony/runtime", @@ -7180,16 +7280,16 @@ }, { "name": "symfony/security-bundle", - "version": "v8.1.0", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "0489a6247f729652db9b9ff408f69ac3bee3589e" + "reference": "c331a8e70da9568b26f341da8d66392e1536c797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/0489a6247f729652db9b9ff408f69ac3bee3589e", - "reference": "0489a6247f729652db9b9ff408f69ac3bee3589e", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/c331a8e70da9568b26f341da8d66392e1536c797", + "reference": "c331a8e70da9568b26f341da8d66392e1536c797", "shasum": "" }, "require": { @@ -7257,7 +7357,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v8.1.0" + "source": "https://github.com/symfony/security-bundle/tree/v8.1.2" }, "funding": [ { @@ -7277,20 +7377,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-07-29T07:22:54+00:00" }, { "name": "symfony/security-core", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "a8239abe61dafdd0c01c0b4019138b2855717f97" + "reference": "5fdd9ad1449af76d537bcea471aaabaf8dbe92e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/a8239abe61dafdd0c01c0b4019138b2855717f97", - "reference": "a8239abe61dafdd0c01c0b4019138b2855717f97", + "url": "https://api.github.com/repos/symfony/security-core/zipball/5fdd9ad1449af76d537bcea471aaabaf8dbe92e7", + "reference": "5fdd9ad1449af76d537bcea471aaabaf8dbe92e7", "shasum": "" }, "require": { @@ -7340,7 +7440,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v8.1.0" + "source": "https://github.com/symfony/security-core/tree/v8.1.5" }, "funding": [ { @@ -7360,7 +7460,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/security-csrf", @@ -7436,16 +7536,16 @@ }, { "name": "symfony/security-http", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "e0e6c7b9e80eec37248b92359cbd6938c7086f4b" + "reference": "3c45aece2c527722e276cf68f5751af578e59153" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/e0e6c7b9e80eec37248b92359cbd6938c7086f4b", - "reference": "e0e6c7b9e80eec37248b92359cbd6938c7086f4b", + "url": "https://api.github.com/repos/symfony/security-http/zipball/3c45aece2c527722e276cf68f5751af578e59153", + "reference": "3c45aece2c527722e276cf68f5751af578e59153", "shasum": "" }, "require": { @@ -7500,7 +7600,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v8.1.0" + "source": "https://github.com/symfony/security-http/tree/v8.1.5" }, "funding": [ { @@ -7520,20 +7620,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/serializer", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1" + "reference": "9c064d505c661e3e17aa8999870f4fb0ed05e024" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/d101886195c5f772cf7033641fe9c40c3e3969e1", - "reference": "d101886195c5f772cf7033641fe9c40c3e3969e1", + "url": "https://api.github.com/repos/symfony/serializer/zipball/9c064d505c661e3e17aa8999870f4fb0ed05e024", + "reference": "9c064d505c661e3e17aa8999870f4fb0ed05e024", "shasum": "" }, "require": { @@ -7545,7 +7645,7 @@ "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/property-access": "<8.1", - "symfony/property-info": "<7.4", + "symfony/property-info": "<7.4.15", "symfony/type-info": "<7.4" }, "require-dev": { @@ -7564,7 +7664,7 @@ "symfony/messenger": "^7.4|^8.0", "symfony/mime": "^7.4|^8.0", "symfony/property-access": "^8.1", - "symfony/property-info": "^7.4|^8.0", + "symfony/property-info": "^7.4.15|~8.0.15|^8.1.2", "symfony/translation-contracts": "^2.5|^3", "symfony/type-info": "^7.4|^8.0", "symfony/uid": "^7.4|^8.0", @@ -7599,7 +7699,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v8.1.0" + "source": "https://github.com/symfony/serializer/tree/v8.1.5" }, "funding": [ { @@ -7619,20 +7719,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -7686,7 +7786,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -7706,7 +7806,7 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/stopwatch", @@ -7776,16 +7876,16 @@ }, { "name": "symfony/string", - "version": "v8.1.0", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "url": "https://api.github.com/repos/symfony/string/zipball/286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", "shasum": "" }, "require": { @@ -7842,7 +7942,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.1.0" + "source": "https://github.com/symfony/string/tree/v8.1.2" }, "funding": [ { @@ -7862,20 +7962,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-07-28T07:35:25+00:00" }, { "name": "symfony/translation", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693" + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/b2bd012ca28c4acae830ee1206a5b6e35dd99693", - "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693", + "url": "https://api.github.com/repos/symfony/translation/zipball/d9e1caba0d6b6f9a26710af8a2f88d37f001215a", + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a", "shasum": "" }, "require": { @@ -7935,7 +8035,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v8.1.0" + "source": "https://github.com/symfony/translation/tree/v8.1.5" }, "funding": [ { @@ -7955,20 +8055,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -8017,7 +8117,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -8037,26 +8137,26 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/twig-bridge", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "25bb8c01edaab85e13142f6010df09b990388343" + "reference": "c721b1c98de3125ca195e35d52aaec5c97aa9c5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/25bb8c01edaab85e13142f6010df09b990388343", - "reference": "25bb8c01edaab85e13142f6010df09b990388343", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c721b1c98de3125ca195e35d52aaec5c97aa9c5f", + "reference": "c721b1c98de3125ca195e35d52aaec5c97aa9c5f", "shasum": "" }, "require": { "php": ">=8.4.1", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.25" + "twig/twig": "^3.25|^4.0" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2|>=7", @@ -8088,7 +8188,7 @@ "symfony/security-core": "^7.4|^8.0", "symfony/security-csrf": "^7.4|^8.0", "symfony/security-http": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0", + "symfony/serializer": "^7.4.17|^8.1.5", "symfony/stopwatch": "^7.4|^8.0", "symfony/translation": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0", @@ -8125,7 +8225,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v8.1.0" + "source": "https://github.com/symfony/twig-bridge/tree/v8.1.5" }, "funding": [ { @@ -8145,20 +8245,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/twig-bundle", - "version": "v8.1.0", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed" + "reference": "a70b67c2cd990d05e544ea5fab361aa0e69fab3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/b7f4a471a07b8b52174d153e4db12f46954192ed", - "reference": "b7f4a471a07b8b52174d153e4db12f46954192ed", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/a70b67c2cd990d05e544ea5fab361aa0e69fab3a", + "reference": "a70b67c2cd990d05e544ea5fab361aa0e69fab3a", "shasum": "" }, "require": { @@ -8209,7 +8309,7 @@ "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/v8.1.0" + "source": "https://github.com/symfony/twig-bundle/tree/v8.1.2" }, "funding": [ { @@ -8229,20 +8329,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/type-info", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", - "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "url": "https://api.github.com/repos/symfony/type-info/zipball/ceb48db5b38d6a48640c414be0c69d53980ae5c5", + "reference": "ceb48db5b38d6a48640c414be0c69d53980ae5c5", "shasum": "" }, "require": { @@ -8291,7 +8391,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.1.0" + "source": "https://github.com/symfony/type-info/tree/v8.1.5" }, "funding": [ { @@ -8311,20 +8411,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/uid", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a" + "reference": "a08aef47989093f32fe50fd11859be1b427df389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7393f157a55f7e70a4de0334435c55a5a8fe749a", - "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a", + "url": "https://api.github.com/repos/symfony/uid/zipball/a08aef47989093f32fe50fd11859be1b427df389", + "reference": "a08aef47989093f32fe50fd11859be1b427df389", "shasum": "" }, "require": { @@ -8369,7 +8469,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v8.1.0" + "source": "https://github.com/symfony/uid/tree/v8.1.5" }, "funding": [ { @@ -8389,25 +8489,25 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-11T13:39:01+00:00" }, { "name": "symfony/ux-twig-component", - "version": "v3.1.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "69763f39367d7185ebff3a8aec3e9d6ec7e10d55" + "reference": "520e1da46bbb5974a2cf5839369aec3c8fc1a6f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/69763f39367d7185ebff3a8aec3e9d6ec7e10d55", - "reference": "69763f39367d7185ebff3a8aec3e9d6ec7e10d55", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/520e1da46bbb5974a2cf5839369aec3c8fc1a6f0", + "reference": "520e1da46bbb5974a2cf5839369aec3c8fc1a6f0", "shasum": "" }, "require": { "php": ">=8.4", - "symfony/dependency-injection": "^7.4|^8.0", + "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.2|^3.0", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/property-access": "^7.4|^8.0", @@ -8420,6 +8520,7 @@ "phpunit/phpunit": "^11.1|^12.0", "symfony/console": "^7.4|^8.0", "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", "symfony/dom-crawler": "^7.4|^8.0", "symfony/framework-bundle": "^7.4|^8.0", "symfony/stimulus-bundle": "^2.9.1|^3.0", @@ -8457,7 +8558,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v3.1.0" + "source": "https://github.com/symfony/ux-twig-component/tree/v3.4.0" }, "funding": [ { @@ -8477,20 +8578,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T07:08:56+00:00" + "time": "2026-07-28T06:36:17+00:00" }, { "name": "symfony/validator", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "b122b2e384fa84166213ce98b887f01a3eea8d94" + "reference": "88b48d885df545a0fb1ffc25c8ebe215982168d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/b122b2e384fa84166213ce98b887f01a3eea8d94", - "reference": "b122b2e384fa84166213ce98b887f01a3eea8d94", + "url": "https://api.github.com/repos/symfony/validator/zipball/88b48d885df545a0fb1ffc25c8ebe215982168d8", + "reference": "88b48d885df545a0fb1ffc25c8ebe215982168d8", "shasum": "" }, "require": { @@ -8554,7 +8655,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v8.1.0" + "source": "https://github.com/symfony/validator/tree/v8.1.5" }, "funding": [ { @@ -8574,20 +8675,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/var-dumper", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e" + "reference": "61743d9bc7ab23b194527ca1be2fafd7dc93b74a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", - "reference": "c2c4df1d21477cc21c9f6dc1b14d07c3abc4963e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/61743d9bc7ab23b194527ca1be2fafd7dc93b74a", + "reference": "61743d9bc7ab23b194527ca1be2fafd7dc93b74a", "shasum": "" }, "require": { @@ -8603,7 +8704,7 @@ "symfony/http-kernel": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", "symfony/uid": "^7.4|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "bin": [ "Resources/bin/var-dump-server" @@ -8641,7 +8742,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.1.0" + "source": "https://github.com/symfony/var-dumper/tree/v8.1.5" }, "funding": [ { @@ -8661,26 +8762,26 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T12:16:08+00:00" }, { "name": "symfony/var-exporter", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "2dd18582c5f6c024db9fc0ff9c76d873af726f34" + "reference": "b8f7dd85493e8372c7c81a1547caaaf86b9c16d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2dd18582c5f6c024db9fc0ff9c76d873af726f34", - "reference": "2dd18582c5f6c024db9fc0ff9c76d873af726f34", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b8f7dd85493e8372c7c81a1547caaaf86b9c16d1", + "reference": "b8f7dd85493e8372c7c81a1547caaaf86b9c16d1", "shasum": "" }, "require": { "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-deepclone": "^1.37" + "symfony/polyfill-deepclone": "^1.40" }, "require-dev": { "symfony/property-access": "^7.4|^8.0", @@ -8724,7 +8825,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v8.1.0" + "source": "https://github.com/symfony/var-exporter/tree/v8.1.5" }, "funding": [ { @@ -8744,7 +8845,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-13T16:11:28+00:00" }, { "name": "symfony/web-link", @@ -8832,16 +8933,16 @@ }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.4.0", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e" + "reference": "cac8d6c722999c8add9272f9de6e8079628df4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", - "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/cac8d6c722999c8add9272f9de6e8079628df4f5", + "reference": "cac8d6c722999c8add9272f9de6e8079628df4f5", "shasum": "" }, "require": { @@ -8884,7 +8985,7 @@ "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.1" }, "funding": [ { @@ -8904,20 +9005,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:41:46+00:00" + "time": "2026-06-24T07:21:58+00:00" }, { "name": "symfony/yaml", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0" + "reference": "b3fc9e8888eeb9daddc33bfbd15d282a61f543cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/efb42bd2c6f4f3ccfd4683583449938b5fc146b0", - "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b3fc9e8888eeb9daddc33bfbd15d282a61f543cd", + "reference": "b3fc9e8888eeb9daddc33bfbd15d282a61f543cd", "shasum": "" }, "require": { @@ -8960,7 +9061,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.1.0" + "source": "https://github.com/symfony/yaml/tree/v8.1.5" }, "funding": [ { @@ -8980,7 +9081,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T12:16:08+00:00" }, { "name": "twig/extra-bundle", @@ -9058,16 +9159,16 @@ }, { "name": "twig/html-extra", - "version": "v3.24.0", + "version": "v3.28.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "313900fb98b371b006a55b1a29241a192634be13" + "reference": "760893ed7bdd0a381e4e00004c6f6e26ad3881d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/313900fb98b371b006a55b1a29241a192634be13", - "reference": "313900fb98b371b006a55b1a29241a192634be13", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/760893ed7bdd0a381e4e00004c6f6e26ad3881d7", + "reference": "760893ed7bdd0a381e4e00004c6f6e26ad3881d7", "shasum": "" }, "require": { @@ -9110,7 +9211,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.24.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.28.0" }, "funding": [ { @@ -9122,20 +9223,20 @@ "type": "tidelift" } ], - "time": "2026-03-17T07:24:08+00:00" + "time": "2026-06-25T06:50:01+00:00" }, { "name": "twig/twig", - "version": "v3.27.1", + "version": "v3.28.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74" + "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae2071bffb38f04847fc0864d730c94b9cb8ab74", - "reference": "ae2071bffb38f04847fc0864d730c94b9cb8ab74", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", + "reference": "597c12ed286fb9d1701a36684ce6e0cbe28ebc8b", "shasum": "" }, "require": { @@ -9190,7 +9291,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.27.1" + "source": "https://github.com/twigphp/Twig/tree/v3.28.0" }, "funding": [ { @@ -9202,7 +9303,7 @@ "type": "tidelift" } ], - "time": "2026-05-30T17:09:26+00:00" + "time": "2026-07-03T20:44:34+00:00" }, { "name": "willdurand/negotiation", @@ -9328,28 +9429,29 @@ }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.10" + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { @@ -9387,7 +9489,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -9397,13 +9499,9 @@ { "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" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/xdebug-handler", @@ -10264,16 +10362,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.3", + "version": "v3.95.22", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "3d681493acc0e93283481b1c63c263737df78687" + "reference": "482722e0783c16325a07a998a0d9eda560f9be99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/3d681493acc0e93283481b1c63c263737df78687", - "reference": "3d681493acc0e93283481b1c63c263737df78687", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/482722e0783c16325a07a998a0d9eda560f9be99", + "reference": "482722e0783c16325a07a998a0d9eda560f9be99", "shasum": "" }, "require": { @@ -10291,7 +10389,7 @@ "react/event-loop": "^1.5", "react/socket": "^1.16", "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", @@ -10307,16 +10405,15 @@ "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.11.0", "infection/infection": "^0.32.7", - "justinrainbow/json-schema": "^6.8.0", + "justinrainbow/json-schema": "^6.10.0", "keradus/cli-executor": "^2.3", - "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", - "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55", - "symfony/polyfill-php85": "^1.37", - "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.8", - "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.11" + "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31 || ^13.0.6", + "symfony/polyfill-php85": "^1.38", + "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.1", + "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -10357,7 +10454,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.3" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.22" }, "funding": [ { @@ -10365,7 +10462,7 @@ "type": "github" } ], - "time": "2026-05-29T20:35:26+00:00" + "time": "2026-08-24T10:37:43+00:00" }, { "name": "hautelook/alice-bundle", @@ -10445,19 +10542,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.8.2", + "version": "6.11.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + "reference": "7e420a943a6fbc95e60e3cf67acfbee85b3b4da7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", - "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/7e420a943a6fbc95e60e3cf67acfbee85b3b4da7", + "reference": "7e420a943a6fbc95e60e3cf67acfbee85b3b4da7", "shasum": "" }, "require": { + "ext-filter": "*", "ext-json": "*", "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" @@ -10490,20 +10588,9 @@ ], "authors": [ { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" + "name": "Danny van der Sluijs", + "email": "danny.vandersluijs@icloud.com", + "role": "Maintainer" } ], "description": "A library to validate a json schema.", @@ -10514,9 +10601,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.11.0" }, - "time": "2026-05-05T05:39:01+00:00" + "time": "2026-08-21T10:30:42+00:00" }, { "name": "localheinz/diff", @@ -10648,20 +10735,20 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -10696,15 +10783,15 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nelmio/alice", @@ -10803,20 +10890,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -10855,9 +10941,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "phar-io/manifest", @@ -11027,11 +11113,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.2.1", + "version": "2.2.9", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dea9c8f2d25cc849391042b71e429c1a4bf82660", - "reference": "dea9c8f2d25cc849391042b71e429c1a4bf82660", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/13d6b4f347bad222da436580c8304fa6f83e6bd0", + "reference": "13d6b4f347bad222da436580c8304fa6f83e6bd0", "shasum": "" }, "require": { @@ -11087,25 +11173,25 @@ "type": "github" } ], - "time": "2026-05-28T14:44:12+00:00" + "time": "2026-08-22T07:38:16+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.23", + "version": "2.0.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "4821d678585be36f585273de38c06b7e4a98bb91" + "reference": "b4623954d5ffee6311e4ddbaef65c074ae0f781a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4821d678585be36f585273de38c06b7e4a98bb91", - "reference": "4821d678585be36f585273de38c06b7e4a98bb91", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/b4623954d5ffee6311e4ddbaef65c074ae0f781a", + "reference": "b4623954d5ffee6311e4ddbaef65c074ae0f781a", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.34" + "phpstan/phpstan": "^2.2.2" }, "conflict": { "doctrine/collections": "<1.0", @@ -11162,27 +11248,28 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.23" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.28" }, - "time": "2026-05-25T15:58:25+00:00" + "time": "2026-07-13T08:43:43+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.16", + "version": "2.0.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", - "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f5dc20ff8082d02339b60cab68ec3eb0d859fb30", + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30", "shasum": "" }, "require": { + "phar-io/version": "^3.2", "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.32" + "phpstan/phpstan": "^2.2.3" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -11192,7 +11279,8 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6", + "shipmonk/name-collision-detector": "^2.1" }, "type": "phpstan-extension", "extra": { @@ -11218,22 +11306,22 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.18" }, - "time": "2026-02-14T09:05:21+00:00" + "time": "2026-07-04T12:16:09+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.19", + "version": "2.0.20", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "546071ed7f80a89ec30909346eb7cc741800740a" + "reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/546071ed7f80a89ec30909346eb7cc741800740a", - "reference": "546071ed7f80a89ec30909346eb7cc741800740a", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/53f1a6462dbe71fad36ce054caf5e1b725b740fd", + "reference": "53f1a6462dbe71fad36ce054caf5e1b725b740fd", "shasum": "" }, "require": { @@ -11292,22 +11380,22 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.19" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.20" }, - "time": "2026-05-29T12:52:44+00:00" + "time": "2026-06-16T09:17:35+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "14.1.9", + "version": "14.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "655533a65696bbc4231cd8027af150dadc40ec88" + "reference": "6ce313bb110384148d1dc7695a99175f59529069" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/655533a65696bbc4231cd8027af150dadc40ec88", - "reference": "655533a65696bbc4231cd8027af150dadc40ec88", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6ce313bb110384148d1dc7695a99175f59529069", + "reference": "6ce313bb110384148d1dc7695a99175f59529069", "shasum": "" }, "require": { @@ -11315,18 +11403,18 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", + "nikic/php-parser": "^5.8.0", "php": ">=8.4", "phpunit/php-text-template": "^6.0", "sebastian/complexity": "^6.0", - "sebastian/environment": "^9.2", + "sebastian/environment": "^9.3.2", "sebastian/git-state": "^1.0", - "sebastian/lines-of-code": "^5.0", + "sebastian/lines-of-code": "^5.0.2", "sebastian/version": "^7.0", "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.1" + "phpunit/phpunit": "^13.3.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -11335,7 +11423,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "14.1.x-dev" + "dev-main": "14.3.x-dev" } }, "autoload": { @@ -11364,7 +11452,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.1.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.3.1" }, "funding": [ { @@ -11384,27 +11472,27 @@ "type": "tidelift" } ], - "time": "2026-05-16T05:16:14+00:00" + "time": "2026-08-16T05:23:47+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "7.0.0", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" + "reference": "9bb4e6c58b62c1e043be995c66abec7c97307aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/9bb4e6c58b62c1e043be995c66abec7c97307aae", + "reference": "9bb4e6c58b62c1e043be995c66abec7c97307aae", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.3.1" }, "type": "library", "extra": { @@ -11437,7 +11525,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.2" }, "funding": [ { @@ -11457,7 +11545,7 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:33:26+00:00" + "time": "2026-08-25T14:47:43+00:00" }, { "name": "phpunit/php-invoker", @@ -11681,44 +11769,45 @@ }, { "name": "phpunit/phpunit", - "version": "13.1.13", + "version": "13.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ddf7f25d9ee9652b464475d7f3bacde2613e355e" + "reference": "fc024931d6ad047404e9d86536735923fe63a06b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ddf7f25d9ee9652b464475d7f3bacde2613e355e", - "reference": "ddf7f25d9ee9652b464475d7f3bacde2613e355e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc024931d6ad047404e9d86536735923fe63a06b", + "reference": "fc024931d6ad047404e9d86536735923fe63a06b", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", + "myclabs/deep-copy": "^1.14.0", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.4.1", - "phpunit/php-code-coverage": "^14.1.9", - "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-code-coverage": "^14.3", + "phpunit/php-file-iterator": "^7.0.1", "phpunit/php-invoker": "^7.0.0", "phpunit/php-text-template": "^6.0.0", "phpunit/php-timer": "^9.0.0", - "sebastian/cli-parser": "^5.0.0", - "sebastian/comparator": "^8.2.1", - "sebastian/diff": "^8.3.0", + "sebastian/cli-parser": "^5.0.1", + "sebastian/comparator": "^8.4", + "sebastian/diff": "^9.0", "sebastian/environment": "^9.3.2", - "sebastian/exporter": "^8.1.0", + "sebastian/exporter": "^8.2.1", + "sebastian/file-filter": "^1.0", "sebastian/git-state": "^1.0", - "sebastian/global-state": "^9.0.0", - "sebastian/object-enumerator": "^8.0.0", - "sebastian/recursion-context": "^8.0.0", - "sebastian/type": "^7.0.1", + "sebastian/global-state": "^9.0.1", + "sebastian/object-enumerator": "^8.1.0", + "sebastian/recursion-context": "^8.0.1", + "sebastian/type": "^7.0.2", "sebastian/version": "^7.0.0", "staabm/side-effects-detector": "^1.0.5" }, @@ -11728,7 +11817,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "13.1-dev" + "dev-main": "13.3-dev" } }, "autoload": { @@ -11760,7 +11849,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/13.1.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.3.1" }, "funding": [ { @@ -11768,7 +11857,7 @@ "type": "other" } ], - "time": "2026-05-27T14:03:08+00:00" + "time": "2026-08-13T13:14:23+00:00" }, { "name": "react/cache", @@ -12298,21 +12387,21 @@ }, { "name": "rector/rector", - "version": "2.4.5", + "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c" + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c", - "reference": "cbd86024be5014d3c14d9f0b3f7aae8ecbffd62c", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.56" + "phpstan/phpstan": "^2.2.6" }, "conflict": { "rector/rector-doctrine": "*", @@ -12346,7 +12435,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.4.5" + "source": "https://github.com/rectorphp/rector/tree/2.6.3" }, "funding": [ { @@ -12354,27 +12443,27 @@ "type": "github" } ], - "time": "2026-05-26T21:03:22+00:00" + "time": "2026-08-18T22:01:18+00:00" }, { "name": "sebastian/cli-parser", - "version": "5.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" + "reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/eeb759ad3146b7096fb59c3195d39e071cd409e3", + "reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.2.6" }, "type": "library", "extra": { @@ -12403,7 +12492,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.1" }, "funding": [ { @@ -12423,31 +12512,31 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:39:44+00:00" + "time": "2026-08-01T04:27:14+00:00" }, { "name": "sebastian/comparator", - "version": "8.2.1", + "version": "8.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "ce999bf08b2c387a5423fe56961c32eed3f88089" + "reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ce999bf08b2c387a5423fe56961c32eed3f88089", - "reference": "ce999bf08b2c387a5423fe56961c32eed3f88089", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/3b070e608146cba00fd6fd1f0ffba89e5a8897fb", + "reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", "php": ">=8.4", - "sebastian/diff": "^8.3", - "sebastian/exporter": "^8.0.3" + "sebastian/diff": "^9.0", + "sebastian/exporter": "^8.2" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^13.3" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -12455,7 +12544,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.2-dev" + "dev-main": "8.4-dev" } }, "autoload": { @@ -12495,7 +12584,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/8.2.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/8.4.0" }, "funding": [ { @@ -12515,7 +12604,7 @@ "type": "tidelift" } ], - "time": "2026-05-21T04:46:40+00:00" + "time": "2026-08-07T07:23:13+00:00" }, { "name": "sebastian/complexity", @@ -12589,29 +12678,29 @@ }, { "name": "sebastian/diff", - "version": "8.3.0", + "version": "9.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47" + "reference": "a2df6626c1baf31d5a88674882a3072f151b5a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b36d33b6e796513de7cb7df053afb3f55eefcd47", - "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2df6626c1baf31d5a88674882a3072f151b5a26", + "reference": "a2df6626c1baf31d5a88674882a3072f151b5a26", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.0", - "symfony/process": "^7.2" + "phpunit/phpunit": "^13.3.1", + "symfony/process": "^7.4.17" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.3-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -12644,7 +12733,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/8.3.0" + "source": "https://github.com/sebastianbergmann/diff/tree/9.0.1" }, "funding": [ { @@ -12664,7 +12753,7 @@ "type": "tidelift" } ], - "time": "2026-05-15T04:58:09+00:00" + "time": "2026-08-25T15:38:55+00:00" }, { "name": "sebastian/environment", @@ -12744,30 +12833,30 @@ }, { "name": "sebastian/exporter", - "version": "8.1.0", + "version": "8.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6" + "reference": "24a3b69bba4a12ab615fca9d34680c5598d9ab7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c0d29a945f8cf82f300a05e69874508e307ca4c6", - "reference": "c0d29a945f8cf82f300a05e69874508e307ca4c6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/24a3b69bba4a12ab615fca9d34680c5598d9ab7a", + "reference": "24a3b69bba4a12ab615fca9d34680c5598d9ab7a", "shasum": "" }, "require": { "ext-mbstring": "*", "php": ">=8.4", - "sebastian/recursion-context": "^8.0" + "sebastian/recursion-context": "^8.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^13.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.1-dev" + "dev-main": "8.2-dev" } }, "autoload": { @@ -12810,7 +12899,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/8.1.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/8.2.1" }, "funding": [ { @@ -12830,7 +12919,76 @@ "type": "tidelift" } ], - "time": "2026-05-21T11:50:56+00:00" + "time": "2026-08-07T07:22:06+00:00" + }, + { + "name": "sebastian/file-filter", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/file-filter.git", + "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/file-filter/zipball/33a26f394330f6faa7684bb9cc73afb7727aae93", + "reference": "33a26f394330f6faa7684bb9cc73afb7727aae93", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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 filtering files", + "homepage": "https://github.com/sebastianbergmann/file-filter", + "support": { + "issues": "https://github.com/sebastianbergmann/file-filter/issues", + "security": "https://github.com/sebastianbergmann/file-filter/security/policy", + "source": "https://github.com/sebastianbergmann/file-filter/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/file-filter", + "type": "tidelift" + } + ], + "time": "2026-04-22T07:20:04+00:00" }, { "name": "sebastian/git-state", @@ -12903,16 +13061,16 @@ }, { "name": "sebastian/global-state", - "version": "9.0.0", + "version": "9.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" + "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", + "reference": "ba68ba79da690cf7eddefd3ce5b78b20b9ba9945", "shasum": "" }, "require": { @@ -12922,7 +13080,7 @@ }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.1.13" }, "type": "library", "extra": { @@ -12953,7 +13111,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.1" }, "funding": [ { @@ -12973,28 +13131,28 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:45:13+00:00" + "time": "2026-06-01T15:11:33+00:00" }, { "name": "sebastian/lines-of-code", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7" + "reference": "d1b6f8fce682505dbd048977f1abedf1b8ad3ff8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d2cff273a90c79b0eb590baa682d4b5c318bdbb7", - "reference": "d2cff273a90c79b0eb590baa682d4b5c318bdbb7", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d1b6f8fce682505dbd048977f1abedf1b8ad3ff8", + "reference": "d1b6f8fce682505dbd048977f1abedf1b8ad3ff8", "shasum": "" }, "require": { - "nikic/php-parser": "^5.7.0", + "nikic/php-parser": "^5.8.0", "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^13.2.4" }, "type": "library", "extra": { @@ -13023,7 +13181,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.2" }, "funding": [ { @@ -13043,34 +13201,33 @@ "type": "tidelift" } ], - "time": "2026-05-19T16:23:37+00:00" + "time": "2026-07-09T08:42:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "8.0.0", + "version": "8.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" + "reference": "511064ecde82bd747e2ba2fab3dda8d977b59576" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/511064ecde82bd747e2ba2fab3dda8d977b59576", + "reference": "511064ecde82bd747e2ba2fab3dda8d977b59576", "shasum": "" }, "require": { "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" + "sebastian/recursion-context": "^8.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -13093,7 +13250,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.1.0" }, "funding": [ { @@ -13113,27 +13270,27 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:46:36+00:00" + "time": "2026-08-13T07:05:05+00:00" }, { "name": "sebastian/object-reflector", - "version": "6.0.0", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" + "reference": "f71bbcdc4f95456b4622810bec64eb06372e25b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f71bbcdc4f95456b4622810bec64eb06372e25b2", + "reference": "f71bbcdc4f95456b4622810bec64eb06372e25b2", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.3.0" }, "type": "library", "extra": { @@ -13161,7 +13318,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.1.0" }, "funding": [ { @@ -13181,27 +13338,27 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:47:13+00:00" + "time": "2026-08-13T06:34:36+00:00" }, { "name": "sebastian/recursion-context", - "version": "8.0.0", + "version": "8.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" + "reference": "32dba72f2b4642d6a93db22d6c0a9280ff2e3ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/32dba72f2b4642d6a93db22d6c0a9280ff2e3ca0", + "reference": "32dba72f2b4642d6a93db22d6c0a9280ff2e3ca0", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.2.6" }, "type": "library", "extra": { @@ -13237,7 +13394,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.1" }, "funding": [ { @@ -13257,27 +13414,27 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:51:28+00:00" + "time": "2026-08-03T05:58:12+00:00" }, { "name": "sebastian/type", - "version": "7.0.1", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fee0309275847fefd7636167085e379c1dbf6990" + "reference": "bd1df467864cb95140414059a535b2d906173fcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fee0309275847fefd7636167085e379c1dbf6990", - "reference": "fee0309275847fefd7636167085e379c1dbf6990", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/bd1df467864cb95140414059a535b2d906173fcf", + "reference": "bd1df467864cb95140414059a535b2d906173fcf", "shasum": "" }, "require": { "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^13.1.10" + "phpunit/phpunit": "^13.3.0" }, "type": "library", "extra": { @@ -13306,7 +13463,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/type/tree/7.0.2" }, "funding": [ { @@ -13326,7 +13483,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T06:49:11+00:00" + "time": "2026-08-10T08:00:57+00:00" }, { "name": "sebastian/version", @@ -13448,16 +13605,16 @@ }, { "name": "symfony/css-selector", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd" + "reference": "a291fb5adb65f52a4bb315db2d803698315dc64d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/dc0e2be45c9b5588c82414f02ac574b4b986abcd", - "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/a291fb5adb65f52a4bb315db2d803698315dc64d", + "reference": "a291fb5adb65f52a4bb315db2d803698315dc64d", "shasum": "" }, "require": { @@ -13493,7 +13650,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.1.0" + "source": "https://github.com/symfony/css-selector/tree/v8.1.5" }, "funding": [ { @@ -13513,7 +13670,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/debug-bundle", @@ -13689,90 +13846,6 @@ ], "time": "2026-03-18T13:39:06+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "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.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, { "name": "symfony/polyfill-php81", "version": "v1.38.1", @@ -13855,16 +13928,16 @@ }, { "name": "symfony/process", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "url": "https://api.github.com/repos/symfony/process/zipball/d863f5e70d7c87abb906ac11b61f83036093000b", + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b", "shasum": "" }, "require": { @@ -13896,7 +13969,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.1.0" + "source": "https://github.com/symfony/process/tree/v8.1.5" }, "funding": [ { @@ -13916,20 +13989,20 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v8.1.0", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "f8ccea08797a511b85a698b0da40e1b9e6461086" + "reference": "4ae952afc65cb63fc74067633466bd8295cdcebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f8ccea08797a511b85a698b0da40e1b9e6461086", - "reference": "f8ccea08797a511b85a698b0da40e1b9e6461086", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4ae952afc65cb63fc74067633466bd8295cdcebc", + "reference": "4ae952afc65cb63fc74067633466bd8295cdcebc", "shasum": "" }, "require": { @@ -13981,7 +14054,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.1.0" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.1.5" }, "funding": [ { @@ -14001,7 +14074,7 @@ "type": "tidelift" } ], - "time": "2026-05-29T05:06:50+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "theofidry/alice-data-fixtures", @@ -14231,16 +14304,16 @@ }, { "name": "webmozart/assert", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", - "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { @@ -14291,9 +14364,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.4.0" + "source": "https://github.com/webmozarts/assert/tree/2.4.1" }, - "time": "2026-05-20T13:07:01+00:00" + "time": "2026-06-15T15:31:57+00:00" } ], "aliases": [], diff --git a/config/reference.php b/config/reference.php index 0367b0f..df7802b 100644 --- a/config/reference.php +++ b/config/reference.php @@ -31,7 +31,7 @@ * @psalm-type ImportsConfig = list * @psalm-type ParametersConfig = array|Param|null>|Param|null> * @psalm-type ArgumentsType = list|array @@ -127,7 +127,7 @@ * } * @psalm-type ServicesConfig = array{ * _defaults?: DefaultsType, - * _instanceof?: InstanceofType, + * _instanceof?: array, * ... * } * @psalm-type ExtensionType = array @@ -142,9 +142,9 @@ * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false * enabled_locales?: list, - * trusted_hosts?: string|list, + * trusted_hosts?: Param|string|list, * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] - * trusted_headers?: string|list, + * trusted_headers?: Param|string|list, * error_controller?: scalar|Param|null, // Default: "error_controller" * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true * csrf_protection?: bool|array{ @@ -208,23 +208,23 @@ * property?: scalar|Param|null, * service?: scalar|Param|null, * }, - * supports?: string|list, + * supports?: Param|string|list, * definition_validators?: list, * support_strategy?: scalar|Param|null, - * initial_marking?: \BackedEnum|string|list, + * initial_marking?: \BackedEnum|Param|string|list, * events_to_dispatch?: null|list, - * places?: string|list, * }>, * transitions?: list, - * to?: \BackedEnum|string|list, @@ -264,7 +264,7 @@ * }, * request?: bool|array{ // Request configuration * enabled?: bool|Param, // Default: false - * formats?: array>, + * formats?: array>, * }, * assets?: bool|array{ // Assets configuration * enabled?: bool|Param, // Default: true @@ -274,7 +274,7 @@ * version_format?: scalar|Param|null, // Default: "%%s?%%s" * json_manifest_path?: scalar|Param|null, // Default: null * base_path?: scalar|Param|null, // Default: "" - * base_urls?: string|list, + * base_urls?: Param|string|list, * packages?: array, + * base_urls?: Param|string|list, * }>, * }, * asset_mapper?: bool|array{ // Asset Mapper configuration * enabled?: bool|Param, // Default: true - * paths?: string|array, + * paths?: Param|string|array, * excluded_patterns?: list, * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true @@ -306,7 +306,7 @@ * }, * translator?: bool|array{ // Translator configuration * enabled?: bool|Param, // Default: true - * fallbacks?: string|list, + * fallbacks?: Param|string|list, * logging?: bool|Param, // Default: false * formatter?: scalar|Param|null, // Default: "translator.formatter.default" * cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations" @@ -325,7 +325,7 @@ * domains?: list, * locales?: list, * }>, - * globals?: array, @@ -335,7 +335,7 @@ * validation?: bool|array{ // Validation configuration * enabled?: bool|Param, // Default: true * enable_attributes?: bool|Param, // Default: true - * static_method?: string|list, + * static_method?: Param|string|list, * translation_domain?: scalar|Param|null, // Default: "validators" * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|Param, // Default: "html5" * mapping?: array{ @@ -396,7 +396,7 @@ * default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection" * default_pdo_provider?: scalar|Param|null, // Default: null * pools?: array, + * adapters?: Param|string|list, * tags?: scalar|Param|null, // Default: null * public?: bool|Param, // Default: false * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. @@ -418,17 +418,17 @@ * web_link?: bool|array{ // Web links configuration * enabled?: bool|Param, // Default: true * }, - * lock?: bool|string|array{ // Lock configuration + * lock?: Param|bool|string|array{ // Lock configuration * enabled?: bool|Param, // Default: false - * resources?: string|array>, + * resources?: Param|string|array>, * }, - * semaphore?: bool|string|array{ // Semaphore configuration + * semaphore?: Param|bool|string|array{ // Semaphore configuration * enabled?: bool|Param, // Default: false - * resources?: string|array, + * resources?: Param|string|array, * }, * messenger?: bool|array{ // Messenger configuration * enabled?: bool|Param, // Default: true - * routing?: array>, + * routing?: array>, * serializer?: array{ * default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" * symfony_serializer?: array{ @@ -436,12 +436,12 @@ * context?: array, * }, * }, - * transports?: array, * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * retry_strategy?: string|array{ + * retry_strategy?: Param|string|array{ * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -452,15 +452,15 @@ * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null * }>, * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null - * stop_worker_on_signals?: int|string|list, + * stop_worker_on_signals?: Param|int|string|list, * default_bus?: scalar|Param|null, // Default: null * buses?: array, * }>, @@ -509,9 +509,9 @@ * retry_failed?: bool|array{ * enabled?: bool|Param, // Default: false * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null - * http_codes?: int|string|array, + * methods?: Param|string|list, * }>, * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -521,7 +521,7 @@ * }, * }, * mock_response_factory?: scalar|Param|null, // `true` to always return empty 200 responses, or the id of the service to use to generate mock responses - which should be either an invokable or an iterable. - * scoped_clients?: array, + * methods?: Param|string|list, * }>, * max_retries?: int|Param, // Default: 3 * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 @@ -582,10 +582,10 @@ * transports?: array, * envelope?: array{ // Mailer Envelope configuration * sender?: scalar|Param|null, - * recipients?: string|list, - * allowed_recipients?: string|list, + * recipients?: Param|string|list, + * allowed_recipients?: Param|string|list, * }, - * headers?: array, * dkim_signer?: bool|array{ // DKIM signer configuration @@ -622,7 +622,7 @@ * chatter_transports?: array, * texter_transports?: array, * notification_on_failed_messages?: bool|Param, // Default: false - * channel_policy?: array>, + * channel_policy?: array>, * admin_recipients?: list, + * limiters?: Param|string|list, * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". @@ -661,20 +661,20 @@ * allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false * allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false * allow_elements?: array, - * block_elements?: string|list, - * drop_elements?: string|list, + * block_elements?: Param|string|list, + * drop_elements?: Param|string|list, * allow_attributes?: array, * drop_attributes?: array, * force_attributes?: array>, * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false - * allowed_link_schemes?: string|list, - * allowed_link_hosts?: null|string|list, + * allowed_link_schemes?: Param|string|list, + * allowed_link_hosts?: Param|null|string|list, * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false - * allowed_media_schemes?: string|list, - * allowed_media_hosts?: null|string|list, + * allowed_media_schemes?: Param|string|list, + * allowed_media_hosts?: Param|null|string|list, * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false - * with_attribute_sanitizers?: string|list, - * without_attribute_sanitizers?: string|list, + * with_attribute_sanitizers?: Param|string|list, + * without_attribute_sanitizers?: Param|string|list, * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 * }>, * }, @@ -707,6 +707,7 @@ * id?: scalar|Param|null, * type?: scalar|Param|null, * value?: mixed, + * ... * }>, * autoescape_service?: scalar|Param|null, // Default: null * autoescape_service_method?: scalar|Param|null, // Default: null @@ -717,7 +718,7 @@ * auto_reload?: scalar|Param|null, * optimizations?: int|Param, * default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" - * file_name_pattern?: string|list, + * file_name_pattern?: Param|string|list, * paths?: array, * date?: array{ // The default format options used by the date filter. * format?: scalar|Param|null, // Default: "F j, Y H:i" @@ -745,9 +746,9 @@ * allow_if_all_abstain?: bool|Param, // Default: false * allow_if_equal_granted_denied?: bool|Param, // Default: true * }, - * password_hashers?: array, + * migrate_from?: Param|string|list, * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" * key_length?: scalar|Param|null, // Default: 40 * ignore_case?: bool|Param, // Default: false @@ -761,12 +762,12 @@ * providers?: array, + * providers?: Param|string|list, * }, * memory?: array{ * users?: array, + * roles?: Param|string|list, * }>, * }, * ldap?: array{ @@ -775,7 +776,7 @@ * search_dn?: scalar|Param|null, // Default: null * search_password?: scalar|Param|null, // Default: null * extra_fields?: list, - * default_roles?: string|list, + * default_roles?: Param|string|list, * role_fetcher?: scalar|Param|null, // Default: null * uid_key?: scalar|Param|null, // Default: "sAMAccountName" * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" @@ -790,7 +791,7 @@ * firewalls?: array, + * methods?: Param|string|list, * security?: bool|Param, // Default: true * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" * request_matcher?: scalar|Param|null, @@ -809,8 +810,8 @@ * path?: scalar|Param|null, // Default: "/logout" * target?: scalar|Param|null, // Default: "/" * invalidate_session?: bool|Param, // Default: true - * clear_site_data?: string|list<"*"|"cache"|"cookies"|"storage"|"clientHints"|"executionContexts"|"prefetchCache"|"prerenderCache"|Param>, - * delete_cookies?: string|array, + * delete_cookies?: Param|string|array, - * token_handler?: string|array{ + * token_extractors?: Param|string|list, + * token_handler?: Param|string|array{ * id?: scalar|Param|null, - * oidc_user_info?: string|array{ + * oidc_user_info?: Param|string|array{ * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). * discovery?: array{ // Enable the OIDC discovery. * cache?: array{ @@ -963,7 +964,7 @@ * }, * oidc?: array{ * discovery?: array{ // Enable the OIDC discovery. - * base_uri?: string|list, + * base_uri?: Param|string|list, * cache?: array{ * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. * }, @@ -1005,10 +1006,10 @@ * remember_me?: array{ * secret?: scalar|Param|null, // Default: "%kernel.secret%" * service?: scalar|Param|null, - * user_providers?: string|list, + * user_providers?: Param|string|list, * catch_exceptions?: bool|Param, // Default: true * signature_properties?: list, - * token_provider?: string|array{ + * token_provider?: Param|string|array{ * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. * doctrine?: bool|array{ * enabled?: bool|Param, // Default: false @@ -1033,19 +1034,19 @@ * path?: scalar|Param|null, // Use the urldecoded format. // Default: null * host?: scalar|Param|null, // Default: null * port?: int|Param, // Default: null - * ips?: string|list, + * ips?: Param|string|list, * attributes?: array, * route?: scalar|Param|null, // Default: null - * methods?: string|list, + * methods?: Param|string|list, * allow_if?: scalar|Param|null, // Default: null - * roles?: string|list, + * roles?: Param|string|list, * }>, - * role_hierarchy?: array>, + * role_hierarchy?: array>, * } * @psalm-type DoctrineConfig = array{ * dbal?: array{ * default_connection?: scalar|Param|null, - * types?: array, * driver_schemes?: array, @@ -1068,7 +1069,7 @@ * servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. * sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver * server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere. - * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion. + * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection. * sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. * sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. * sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL. @@ -1114,7 +1115,7 @@ * servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter. * sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver * server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere. - * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion. + * default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connection. * sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL. * sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities. * sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL. @@ -1124,8 +1125,11 @@ * MultipleActiveResultSets?: bool|Param, // Configuring MultipleActiveResultSets for the pdo_sqlsrv driver * instancename?: scalar|Param|null, // Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance. * connectstring?: scalar|Param|null, // Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected. + * ... * }>, + * ... * }>, + * ... * }, * orm?: array{ * default_entity_manager?: scalar|Param|null, @@ -1136,17 +1140,17 @@ * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false * }, * entity_managers?: array, * }>, * }>, + * ... * }, * connection?: scalar|Param|null, * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" @@ -1174,7 +1179,7 @@ * schema_ignore_classes?: list, * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false * second_level_cache?: array{ - * region_cache_driver?: string|array{ + * region_cache_driver?: Param|string|array{ * type?: scalar|Param|null, // Default: null * id?: scalar|Param|null, * pool?: scalar|Param|null, @@ -1185,7 +1190,7 @@ * enabled?: bool|Param, // Default: true * factory?: scalar|Param|null, * regions?: array, @@ -1203,7 +1208,7 @@ * }>, * }, * hydrators?: array, - * mappings?: array, * datetime_functions?: array, * }, - * filters?: array, + * ... * }>, * identity_generation_preferences?: array, * }>, * resolve_target_entities?: array, + * ... * }, * } * @psalm-type NelmioCorsConfig = array{ @@ -1270,6 +1277,7 @@ * }, * jsonapi?: array{ * use_iri_as_id?: bool|Param, // Set to false to use entity identifiers instead of IRIs as the "id" field in JSON:API responses. // Default: true + * allow_client_generated_id?: bool|Param, // Allow client-generated IDs on JSON:API POST per https://jsonapi.org/format/#crud-creating-client-ids. Off by default to prevent id spoofing on public endpoints. // Default: false * }, * eager_loading?: bool|array{ * enabled?: bool|Param, // Default: true @@ -1549,6 +1557,7 @@ * item_uri_template?: mixed, * ... * }, + * ... * } * @psalm-type MakerConfig = array{ * root_namespace?: scalar|Param|null, // Default: "App" @@ -1580,6 +1589,7 @@ * enabled?: bool|Param|null, // Default: null * date_format?: scalar|Param|null, * remove_used_context_fields?: bool|Param, + * ... * }, * path?: scalar|Param|null, // Default: "%kernel.logs_dir%/%kernel.environment%.log" * file_permission?: scalar|Param|null, // Default: null @@ -1640,18 +1650,18 @@ * delay_between_messages?: bool|Param, // Default: false * topic?: int|Param, // Default: null * factor?: int|Param, // Default: 1 - * tags?: string|list, + * tags?: Param|string|list, * console_formatter_options?: mixed, // Default: [] * formatter?: scalar|Param|null, * nested?: bool|Param, // Default: false - * publisher?: string|array{ + * publisher?: Param|string|array{ * id?: scalar|Param|null, * hostname?: scalar|Param|null, * port?: scalar|Param|null, // Default: 12201 * chunk_size?: scalar|Param|null, // Default: 1420 * encoder?: "json"|"compressed_json"|Param, * }, - * mongodb?: string|array{ + * mongodb?: Param|string|array{ * id?: scalar|Param|null, // ID of a MongoDB\Client service * uri?: scalar|Param|null, * username?: scalar|Param|null, @@ -1659,7 +1669,7 @@ * database?: scalar|Param|null, // Default: "monolog" * collection?: scalar|Param|null, // Default: "logs" * }, - * elasticsearch?: string|array{ + * elasticsearch?: Param|string|array{ * id?: scalar|Param|null, * hosts?: list, * host?: scalar|Param|null, @@ -1671,7 +1681,7 @@ * index?: scalar|Param|null, // Default: "monolog" * document_type?: scalar|Param|null, // Default: "logs" * ignore_error?: scalar|Param|null, // Default: false - * redis?: string|array{ + * redis?: Param|string|array{ * id?: scalar|Param|null, * host?: scalar|Param|null, * password?: scalar|Param|null, // Default: null @@ -1679,17 +1689,17 @@ * database?: scalar|Param|null, // Default: 0 * key_name?: scalar|Param|null, // Default: "monolog_redis" * }, - * predis?: string|array{ + * predis?: Param|string|array{ * id?: scalar|Param|null, * host?: scalar|Param|null, * }, * from_email?: scalar|Param|null, - * to_email?: string|list, + * to_email?: Param|string|list, * subject?: scalar|Param|null, * content_type?: scalar|Param|null, // Default: null * headers?: list, * mailer?: scalar|Param|null, // Default: null - * email_prototype?: string|array{ + * email_prototype?: Param|string|array{ * id?: scalar|Param|null, * method?: scalar|Param|null, // Default: null * }, @@ -1700,9 +1710,10 @@ * VERBOSITY_VERY_VERBOSE?: scalar|Param|null, // Default: "INFO" * VERBOSITY_DEBUG?: scalar|Param|null, // Default: "DEBUG" * }, - * channels?: string|array{ + * channels?: Param|string|array{ * type?: scalar|Param|null, * elements?: list, + * ... * }, * }>, * } @@ -1741,11 +1752,23 @@ * route?: scalar|Param|null, // Return route for CLI login * }, * user_provider?: scalar|Param|null, // The User Provider to inject // Default: null + * logging_options?: array{ + * logger?: scalar|Param|null, // Service id of the PSR-3 logger to receive this bundle's failure logs, e.g. "monolog.logger.openid_connect". Defaults to the application logger, which Symfony always provides. Set "itkdev_openid_connect.null_logger" to turn logging off. // Default: null + * }, + * audit_options?: array{ + * enabled?: bool|Param, // Write an authentication audit trail (logins, failures, CLI token issuance). Off by default: audit records identify people, so an existing installation must opt in rather than start logging personal data on upgrade. // Default: false + * logger?: scalar|Param|null, // Service id of the PSR-3 logger to receive audit records, e.g. "monolog.logger.openid_connect_audit". Defaults to the application logger. Keep this separate from logging_options.logger: an operational threshold of "error" would otherwise discard the whole trail. // Default: null + * identifier?: "raw"|"hashed"|Param, // Record user identifiers as-is ("raw") or pseudonymised ("hashed"). Hashing is keyed with the application secret, so records still correlate. Cannot come from an environment variable; use environment-specific configuration instead. // Default: "raw" + * }, + * secret_expiry_options?: array{ + * warning_days?: int|Param, // How many days before a client secret expires the bundle starts warning (default: 30) // Default: 30 + * }, * openid_providers?: list, * allow_http?: bool|Param, // Whether to allow http or not (default: false) // Default: false * http_client_options?: array{ // Options forwarded to the underlying Guzzle HTTP client. league/oauth2-client only forwards: timeout, proxy, verify (verify is only consulted when proxy is set). - * timeout?: float|Param, // Total request timeout in seconds + * timeout?: float|Param, // Total request timeout in seconds. Defaults to 30; set to 0 to wait indefinitely (Guzzle's own default). // Default: 30.0 * proxy?: scalar|Param|null, // HTTP proxy URI * verify?: bool|Param, // Verify TLS certificates (only consulted by Guzzle when proxy is set) * }, @@ -1823,7 +1846,7 @@ * }, * } * @psalm-type TwigComponentConfig = array{ - * defaults?: array, diff --git a/public/api-spec-v1.yaml b/public/api-spec-v1.yaml index cdf7306..2c8a7c8 100755 --- a/public/api-spec-v1.yaml +++ b/public/api-spec-v1.yaml @@ -7,7 +7,8 @@ info: The API also provides read-only access to information on servers and sites created by processing detection results. version: 1.0.0 servers: - - url: 'https://itksites.local.itkdev.dk' + - + url: 'https://itksites.local.itkdev.dk' description: '' paths: /api/detection_results: @@ -386,10 +387,13 @@ security: - apiKey: [] tags: - - name: DetectionResult + - + name: DetectionResult description: "Resource 'DetectionResult' operations." - - name: Server + - + name: Server description: "Resource 'Server' operations." - - name: Site + - + name: Site description: "Resource 'Site' operations." webhooks: {} From 0903c8282f424371998a52a96144200987d0570e Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 10:46:11 +0200 Subject: [PATCH 84/94] refactor: use EasyAdmin badge and icon components in admin templates --- templates/EasyAdminBundle/Fields/advisories.html.twig | 2 +- .../EasyAdminBundle/Fields/advisory_count.html.twig | 2 +- .../EasyAdminBundle/Fields/affected_sites.html.twig | 2 +- templates/EasyAdminBundle/Fields/changes.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/cloned_by.html.twig | 6 +++--- templates/EasyAdminBundle/Fields/code.html.twig | 2 +- templates/EasyAdminBundle/Fields/db_version.html.twig | 4 ++-- templates/EasyAdminBundle/Fields/domain.html.twig | 2 +- templates/EasyAdminBundle/Fields/eol.html.twig | 8 ++++---- .../EasyAdminBundle/Fields/hosting_provider.html.twig | 10 +++++----- .../EasyAdminBundle/Fields/latest-status.html.twig | 10 +++++----- templates/EasyAdminBundle/Fields/server_type.html.twig | 10 +++++----- templates/EasyAdminBundle/Fields/site.html.twig | 6 +++--- templates/EasyAdminBundle/Fields/ssh_link.html.twig | 4 +--- templates/EasyAdminBundle/Fields/text_mono.html.twig | 2 +- templates/EasyAdminBundle/Fields/version.html.twig | 6 +++--- templates/EasyAdminBundle/Fields/warning.html.twig | 4 ++-- .../EasyAdminBundle/crud/field/association.html.twig | 8 ++++---- 18 files changed, 45 insertions(+), 47 deletions(-) diff --git a/templates/EasyAdminBundle/Fields/advisories.html.twig b/templates/EasyAdminBundle/Fields/advisories.html.twig index cfe1bba..292cbfa 100644 --- a/templates/EasyAdminBundle/Fields/advisories.html.twig +++ b/templates/EasyAdminBundle/Fields/advisories.html.twig @@ -29,5 +29,5 @@ {% endif %} {% elseif field.formattedValue != 0 %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/advisory_count.html.twig b/templates/EasyAdminBundle/Fields/advisory_count.html.twig index 1b34775..e3b03da 100644 --- a/templates/EasyAdminBundle/Fields/advisory_count.html.twig +++ b/templates/EasyAdminBundle/Fields/advisory_count.html.twig @@ -2,5 +2,5 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue != 0 %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/affected_sites.html.twig b/templates/EasyAdminBundle/Fields/affected_sites.html.twig index a2eb5e3..3263118 100644 --- a/templates/EasyAdminBundle/Fields/affected_sites.html.twig +++ b/templates/EasyAdminBundle/Fields/affected_sites.html.twig @@ -29,5 +29,5 @@ None {% endif %} {% else %} - {{ sites|length }} + {{ sites|length }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/changes.html.twig b/templates/EasyAdminBundle/Fields/changes.html.twig index 06d19b9..a928d4f 100644 --- a/templates/EasyAdminBundle/Fields/changes.html.twig +++ b/templates/EasyAdminBundle/Fields/changes.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue == 0 %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/cloned_by.html.twig b/templates/EasyAdminBundle/Fields/cloned_by.html.twig index 157c2d8..f47deea 100644 --- a/templates/EasyAdminBundle/Fields/cloned_by.html.twig +++ b/templates/EasyAdminBundle/Fields/cloned_by.html.twig @@ -2,9 +2,9 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue == 'unknown' %} - ? + ? {% elseif field.formattedValue starts with 'ssh' %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/code.html.twig b/templates/EasyAdminBundle/Fields/code.html.twig index 28a08e7..b2b8143 100644 --- a/templates/EasyAdminBundle/Fields/code.html.twig +++ b/templates/EasyAdminBundle/Fields/code.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/db_version.html.twig b/templates/EasyAdminBundle/Fields/db_version.html.twig index c192aeb..0f757b0 100644 --- a/templates/EasyAdminBundle/Fields/db_version.html.twig +++ b/templates/EasyAdminBundle/Fields/db_version.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/domain.html.twig b/templates/EasyAdminBundle/Fields/domain.html.twig index accac03..233a7ac 100644 --- a/templates/EasyAdminBundle/Fields/domain.html.twig +++ b/templates/EasyAdminBundle/Fields/domain.html.twig @@ -4,7 +4,7 @@ {# NOTE: the rel="noopener" attr is needed to avoid performance and security issues (see https://web.dev/external-anchors-use-rel-noopener/) #} {% if field.formattedValue == 'unknown' %} - ? + ? {% elseif ea().crud.currentAction == 'detail' %} {{ field.value }} {% else %} diff --git a/templates/EasyAdminBundle/Fields/eol.html.twig b/templates/EasyAdminBundle/Fields/eol.html.twig index 91efa11..fcb9e5e 100644 --- a/templates/EasyAdminBundle/Fields/eol.html.twig +++ b/templates/EasyAdminBundle/Fields/eol.html.twig @@ -11,11 +11,11 @@ {% endif %} {% if field.formattedValue is empty %} - ? + ? {% elseif 'Expired' in field.formattedValue %} - {{ outputValue }} + {{ outputValue }} {% elseif date('01/' ~ slicedDate) < date('+180days') %} - {{ outputValue }} + {{ outputValue }} {% else %} - {{ outputValue }} + {{ outputValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/hosting_provider.html.twig b/templates/EasyAdminBundle/Fields/hosting_provider.html.twig index cc1d7ab..d4c63f7 100644 --- a/templates/EasyAdminBundle/Fields/hosting_provider.html.twig +++ b/templates/EasyAdminBundle/Fields/hosting_provider.html.twig @@ -2,13 +2,13 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is empty %} - ? + ? {% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::AZURE') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::IT_RELATION') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.formattedValue == constant('App\\Types\\HostingProviderType::DBC') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/latest-status.html.twig b/templates/EasyAdminBundle/Fields/latest-status.html.twig index 5ace6a7..c145cc9 100644 --- a/templates/EasyAdminBundle/Fields/latest-status.html.twig +++ b/templates/EasyAdminBundle/Fields/latest-status.html.twig @@ -2,13 +2,13 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue == 'unknown' %} - ? + ? {% elseif field.formattedValue == 'up-to-date' %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.formattedValue == 'semver-safe-update' %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.formattedValue == 'update-possible' %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/server_type.html.twig b/templates/EasyAdminBundle/Fields/server_type.html.twig index 52369c6..c9afaba 100644 --- a/templates/EasyAdminBundle/Fields/server_type.html.twig +++ b/templates/EasyAdminBundle/Fields/server_type.html.twig @@ -2,13 +2,13 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.value is empty %} - ? + ? {% elseif field.value == constant('App\\Types\\ServerTypeType::PROD') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.value == constant('App\\Types\\ServerTypeType::STG') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.value == constant('App\\Types\\ServerTypeType::DEVOPS') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/site.html.twig b/templates/EasyAdminBundle/Fields/site.html.twig index 6bc1a58..eae8d9a 100644 --- a/templates/EasyAdminBundle/Fields/site.html.twig +++ b/templates/EasyAdminBundle/Fields/site.html.twig @@ -2,9 +2,9 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue == constant('App\\Types\\SiteType::NGINX') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% elseif field.formattedValue == constant('App\\Types\\SiteType::DOCKER') %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/ssh_link.html.twig b/templates/EasyAdminBundle/Fields/ssh_link.html.twig index 11e692b..8dbc74d 100644 --- a/templates/EasyAdminBundle/Fields/ssh_link.html.twig +++ b/templates/EasyAdminBundle/Fields/ssh_link.html.twig @@ -5,9 +5,7 @@
{{ field.formattedValue }}
diff --git a/templates/EasyAdminBundle/Fields/text_mono.html.twig b/templates/EasyAdminBundle/Fields/text_mono.html.twig index dab826d..65cc065 100644 --- a/templates/EasyAdminBundle/Fields/text_mono.html.twig +++ b/templates/EasyAdminBundle/Fields/text_mono.html.twig @@ -2,7 +2,7 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue is null %} - {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} + {{ 'label.null'|trans(domain: 'EasyAdminBundle') }} {% else %} {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/version.html.twig b/templates/EasyAdminBundle/Fields/version.html.twig index 40c514a..185621b 100644 --- a/templates/EasyAdminBundle/Fields/version.html.twig +++ b/templates/EasyAdminBundle/Fields/version.html.twig @@ -2,9 +2,9 @@ {# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if field.formattedValue == 'unknown' %} - ? + ? {% elseif field.formattedValue starts with '${' %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% else %} - {{ field.formattedValue }} + {{ field.formattedValue }} {% endif %} diff --git a/templates/EasyAdminBundle/Fields/warning.html.twig b/templates/EasyAdminBundle/Fields/warning.html.twig index 27adefc..7b91650 100644 --- a/templates/EasyAdminBundle/Fields/warning.html.twig +++ b/templates/EasyAdminBundle/Fields/warning.html.twig @@ -5,10 +5,10 @@ {{ field.formattedValue|raw|nl2br }} {% else %} {% if field.formattedValue is null %} - + {% else %} - + {% endif %} {% endif %} diff --git a/templates/bundles/EasyAdminBundle/crud/field/association.html.twig b/templates/bundles/EasyAdminBundle/crud/field/association.html.twig index 7d5393a..6fd3fd9 100644 --- a/templates/bundles/EasyAdminBundle/crud/field/association.html.twig +++ b/templates/bundles/EasyAdminBundle/crud/field/association.html.twig @@ -3,9 +3,9 @@ {# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} {% if 'toMany' == field.customOptions.get('associationType') %} {% if field.value.count == 0 %} - None + None {% elseif not has_display(field.value[0]) and ea().crud.currentAction != 'detail' %} - {{ field.value|length }} + {{ field.value|length }} {% else %} {% for value in field.value %} {% if ea().crud.currentAction == 'detail' %} @@ -13,10 +13,10 @@ {% elseif has_display(value) %} {{ entity_display(value, ea().crud.currentAction) }} {% else %} - None + None {% endif %} {% else %} - None + None {% endfor %} {% endif %} {% else %} From 356cbe31b8e681a7a88a5231c49e173188407369 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 10:46:11 +0200 Subject: [PATCH 85/94] refactor: drop the AutoBadge menu item classes --- src/EasyAdmin/Config/AutoBadgeMenuItem.php | 31 ------------ .../Config/Menu/AutoBadgeCrudMenuItem.php | 50 ------------------- 2 files changed, 81 deletions(-) delete mode 100644 src/EasyAdmin/Config/AutoBadgeMenuItem.php delete mode 100644 src/EasyAdmin/Config/Menu/AutoBadgeCrudMenuItem.php diff --git a/src/EasyAdmin/Config/AutoBadgeMenuItem.php b/src/EasyAdmin/Config/AutoBadgeMenuItem.php deleted file mode 100644 index fec38b3..0000000 --- a/src/EasyAdmin/Config/AutoBadgeMenuItem.php +++ /dev/null @@ -1,31 +0,0 @@ -crudMenuItem = new CrudMenuItem($label, $icon, $entityFqcn); - } - - public function __call(string $name, array $arguments): mixed - { - return $this->crudMenuItem->$name(...$arguments); - } - - public static function __callStatic(string $name, array $arguments): never - { - throw new \BadMethodCallException(sprintf('Static method %s not implemented', $name)); - } - - public function setBadge(\Stringable|string|int|float|bool|null $content, string $style = 'secondary', array $htmlAttributes = []): self - { - if (!is_int($content)) { - throw new \InvalidArgumentException('The badge content must be an integer'); - } - - if ($content > 0) { - $this->crudMenuItem->setBadge($content, $style, $htmlAttributes); - } - - return $this; - } - - public function getAsDto(): MenuItemDto - { - return $this->crudMenuItem->getAsDto(); - } -} From 6bf01ae7c6d8d06ad98b4fcdc8d5a2414bc30c23 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 10:55:11 +0200 Subject: [PATCH 86/94] fix: restore ITK admin styling and hide the zero-count badge --- CHANGELOG.md | 10 ++++++ assets/styles/app.css | 12 +++----- src/Controller/Admin/DashboardController.php | 32 +++++++++++++++----- webpack.config.js | 4 +++ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db4f636..5cc35d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#94](https://github.com/itk-dev/devops_itksites/pull/94) + Use EasyAdmin's own components in the admin templates + - Replace hand-rolled badge and icon markup with `` and + ``, so the admin follows EasyAdmin's theming + - Drop the unused `AutoBadgeMenuItem`/`AutoBadgeCrudMenuItem` pair: EasyAdmin + hides a badge whose content is null + - Set the ITK blue with the theme API instead of overriding EasyAdmin's + colour variables one by one + - Load the admin stylesheet again: it was added as `css/admin.css`, a file + deleted in #81, so every admin page carried a 404 and no ITK styling - [#93](https://github.com/itk-dev/devops_itksites/pull/93) Update composer dependencies, clearing 15 security advisories - `api-platform/core` 4.3.7 → 4.3.17, `easycorp/easyadmin-bundle` 5.0.11 → 5.5.1, diff --git a/assets/styles/app.css b/assets/styles/app.css index 8218dc7..4181884 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -1,17 +1,15 @@ +/* EasyAdmin overrides. The ITK blue is set with the theme API in + DashboardController; what is left here is what that API cannot express. */ :root { --body-max-width: 100%; --sidebar-bg: #fff; - /* make the base font size smaller */ - --button-primary-bg: rgb(0, 123, 166); - --pagination-active-bg: rgb(0, 123, 166); - --link-color: rgb(0, 123, 166); - --sidebar-menu-active-item-color: rgb(0, 123, 166); - --badge-boolean-true-bg: rgb(0, 123, 166); + /* ITK red, for a false boolean badge and for danger states */ --badge-boolean-false-bg: rgb(228, 73, 48); --badge-boolean-false-color: var(--white); + --bs-danger-rgb: 228, 73, 48; + /* the theme API only takes named gray ramps, not an arbitrary gray */ --sidebar-menu-color: rgb(66, 66, 66); --text-color-dark: rgb(66, 66, 66); - --bs-danger-rgb: 228, 73, 48; } /* Grouped dropdown group styling for index pages */ diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 7044546..d64f2c9 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -10,6 +10,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; +use EasyCorp\Bundle\EasyAdminBundle\Config\Theme; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Component\HttpFoundation\Response; @@ -39,7 +40,12 @@ public function configureDashboard(): Dashboard return Dashboard::new() ->setTitle('ITK sites logo') ->setFaviconPath('img/favicon.ico') - ->renderContentMaximized(); + ->renderContentMaximized() + // ITK blue. Since EasyAdmin 5.4 one primary colour drives buttons, + // links, the active sidebar item and boolean badges, and the theme + // computes the text colour that sits on top of it — which the + // stylesheet used to approximate variable by variable. + ->setTheme(Theme::new()->primaryColor('#007ba6')); } #[\Override] @@ -56,7 +62,9 @@ public function configureMenuItems(): iterable yield MenuItem::section('Dependencies'); yield MenuItem::linkTo(PackageCrudController::class, 'Packages', 'fas fa-cube'); yield MenuItem::linkTo(PackageVersionCrudController::class, 'Package Versions', 'fas fa-cubes'); - yield MenuItem::linkTo(AdvisoryCrudController::class, 'Advisories', 'fas fa-skull-crossbones')->setBadge($this->advisoryRepository->count([]), 'dark'); + // `?: null` because EasyAdmin hides a badge whose content is null but + // renders a literal "0" for a zero count, which is noise on a menu item. + yield MenuItem::linkTo(AdvisoryCrudController::class, 'Advisories', 'fas fa-skull-crossbones')->setBadge($this->advisoryRepository->count([]) ?: null, 'dark'); yield MenuItem::linkTo(ModuleCrudController::class, 'Modules', 'fas fa-cube'); yield MenuItem::linkTo(ModuleVersionCrudController::class, 'Modules Versions', 'fas fa-cubes'); yield MenuItem::linkTo(DockerImageCrudController::class, 'Docker Images', 'fas fa-cube'); @@ -67,6 +75,20 @@ public function configureMenuItems(): iterable yield MenuItem::linkTo(DetectionResultCrudController::class, 'Detection Results', 'fas fa-upload'); } + /** + * The admin styles reach admin pages only from here. + * + * EasyAdmin renders its own layout rather than templates/base.html.twig, so + * neither `importmap()` nor that template's Encore tags apply to it. Until + * now this method added `css/admin.css`, a file deleted in #81, so every + * admin page carried a 404 and none of the ITK styling below it. + */ + #[\Override] + public function configureAssets(): Assets + { + return Assets::new()->addWebpackEncoreEntry('admin'); + } + #[\Override] public function configureCrud(): Crud { @@ -77,10 +99,4 @@ public function configureCrud(): Crud ->setPageTitle('detail', '%entity_label_singular%: %entity_as_string%') ; } - - #[\Override] - public function configureAssets(): Assets - { - return Assets::new()->addCssFile('css/admin.css'); - } } diff --git a/webpack.config.js b/webpack.config.js index ea5ec27..f48bb67 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,6 +22,10 @@ Encore */ .addEntry("easyadmin", "./assets/easyadmin.js") + // CSS-only entry, loaded by DashboardController for every admin page. The + // stylesheet is EasyAdmin-specific despite living at styles/app.css. + .addStyleEntry("admin", "./assets/styles/app.css") + // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() From db3082fa29b956e29075759c2947f72dda4ce981 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 11:38:11 +0200 Subject: [PATCH 87/94] chore(deps): update twig-cs-fixer to 4.0 --- CHANGELOG.md | 4 ++++ composer.json | 2 +- composer.lock | 16 ++++++++-------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc35d1..d2afa19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#95](https://github.com/itk-dev/devops_itksites/pull/95) + Update `vincentlanglet/twig-cs-fixer` to 4.0. Every other dependency is + already at its latest minor; the remaining majors are held back by their + dependents - [#94](https://github.com/itk-dev/devops_itksites/pull/94) Use EasyAdmin's own components in the admin templates - Replace hand-rolled badge and icon markup with `` and diff --git a/composer.json b/composer.json index 252f853..cf7804a 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ "symfony/stopwatch": "^8.0", "symfony/var-dumper": "^8.0", "symfony/web-profiler-bundle": "^8.0", - "vincentlanglet/twig-cs-fixer": "^3.14" + "vincentlanglet/twig-cs-fixer": "^4.0" }, "replace": { "symfony/polyfill-ctype": "*", diff --git a/composer.lock b/composer.lock index f1ad76b..436ebc9 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": "ceb76d3549aace47bff7b8e9260728fe", + "content-hash": "896639a35d4c350fd3b2b3fdfe6542fc", "packages": [ { "name": "api-platform/core", @@ -14227,16 +14227,16 @@ }, { "name": "vincentlanglet/twig-cs-fixer", - "version": "3.14.0", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/VincentLanglet/Twig-CS-Fixer.git", - "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3" + "reference": "1cb75618f7dd0f9bf51924aa6d3aa8c588f51d5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/599f110f192c31af5deb5736d6c1a970afdf51f3", - "reference": "599f110f192c31af5deb5736d6c1a970afdf51f3", + "url": "https://api.github.com/repos/VincentLanglet/Twig-CS-Fixer/zipball/1cb75618f7dd0f9bf51924aa6d3aa8c588f51d5a", + "reference": "1cb75618f7dd0f9bf51924aa6d3aa8c588f51d5a", "shasum": "" }, "require": { @@ -14247,7 +14247,7 @@ "symfony/filesystem": "^5.4 || ^6.4 || ^7.0 || ^8.0", "symfony/finder": "^5.4 || ^6.4 || ^7.0 || ^8.0", "symfony/string": "^5.4.42 || ^6.4.10 || ~7.0.10 || ^7.1.3 || ^8.0", - "twig/twig": "^3.4", + "twig/twig": "^3.15", "webmozart/assert": "^1.10 || ^2.0" }, "require-dev": { @@ -14292,7 +14292,7 @@ "homepage": "https://github.com/VincentLanglet/Twig-CS-Fixer", "support": { "issues": "https://github.com/VincentLanglet/Twig-CS-Fixer/issues", - "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/3.14.0" + "source": "https://github.com/VincentLanglet/Twig-CS-Fixer/tree/4.0.2" }, "funding": [ { @@ -14300,7 +14300,7 @@ "type": "github" } ], - "time": "2026-02-23T13:21:35+00:00" + "time": "2026-06-29T15:22:14+00:00" }, { "name": "webmozart/assert", From f1fa6eb676cb899ca3f207b2fc01bfb0035ee825 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 12:16:05 +0200 Subject: [PATCH 88/94] fix: make the Service Agreements page render and state its currency --- CHANGELOG.md | 6 ++ .../Admin/SecurityContractCrudController.php | 33 ++++++++++- .../Admin/SecurityContractCurrencyTest.php | 58 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/Controller/Admin/SecurityContractCurrencyTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d2afa19..900ed7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [#96](https://github.com/itk-dev/devops_itksites/pull/96) + Service Agreements: fix the page and name the currency + - Add `#[AdminRoute]` to `syncAll()`. EasyAdmin 5 requires it on a custom CRUD + action, and without it rendering the "Sync all" button threw, taking the + whole index page with it + - Show the amounts as Danish kroner, `12.500,50 kr.`, on index and detail - [#95](https://github.com/itk-dev/devops_itksites/pull/95) Update `vincentlanglet/twig-cs-fixer` to 4.0. Every other dependency is already at its latest minor; the remaining majors are held back by their diff --git a/src/Controller/Admin/SecurityContractCrudController.php b/src/Controller/Admin/SecurityContractCrudController.php index 758851d..bb54788 100644 --- a/src/Controller/Admin/SecurityContractCrudController.php +++ b/src/Controller/Admin/SecurityContractCrudController.php @@ -72,9 +72,14 @@ public function configureFields(string $pageName): iterable yield TextField::new('clientContactEmail')->hideOnIndex(); yield FormField::addFieldset('Budget'); - yield NumberField::new('monthlyPrice')->setTextAlign('right')->setColumns(6); + // The amounts are Danish kroner, which until now the admin never said + // anywhere. 5.5's prepend()/append() addons would be the way to show a + // unit inside an input, but they render on form pages only and this CRUD + // disables NEW and EDIT (see configureActions), so the only pages that + // exist are index and detail. Hence formatting instead of an addon. + yield NumberField::new('monthlyPrice')->setTextAlign('right')->setColumns(6)->formatValue(self::formatKroner(...)); yield NumberField::new('quarterlyHours')->setTextAlign('right')->setColumns(6); - yield NumberField::new('cybersecurityPrice')->setTextAlign('right')->hideOnIndex()->setColumns(6); + yield NumberField::new('cybersecurityPrice')->setTextAlign('right')->hideOnIndex()->setColumns(6)->formatValue(self::formatKroner(...)); yield TextareaField::new('cybersecurityNote')->hideOnIndex()->setColumns(12); yield FormField::addFieldset('Infrastructure'); @@ -88,6 +93,30 @@ public function configureFields(string $pageName): iterable yield DateField::new('validTo')->setColumns(6); } + #[AdminRoute] + /** + * An amount as Danish kroner: 12.500,50 kr. + * + * Through Intl rather than by pasting a suffix on, so the grouping and the + * decimal separator are Danish too. The application locale is `en`, which + * would otherwise render 12,500.5 with no currency at all. + */ + public static function formatKroner(?float $value): ?string + { + if (null === $value) { + return null; + } + + return (new \NumberFormatter('da_DK', \NumberFormatter::CURRENCY))->formatCurrency($value, 'DKK') ?: null; + } + + /** + * The attribute is what makes this method reachable as a CRUD action. + * + * Without it EasyAdmin throws while rendering the "Sync all" button, which + * took the whole index page with it — see the "Custom CRUD Actions" section + * of the bundle's UPGRADE.md. + */ #[AdminRoute] public function syncAll(): RedirectResponse { diff --git a/tests/Controller/Admin/SecurityContractCurrencyTest.php b/tests/Controller/Admin/SecurityContractCurrencyTest.php new file mode 100644 index 0000000..de931b4 --- /dev/null +++ b/tests/Controller/Admin/SecurityContractCurrencyTest.php @@ -0,0 +1,58 @@ +get(EntityManagerInterface::class); + $client->loginUser($entityManager->getRepository(User::class)->findOneBy([])); + + $contract = new SecurityContract(); + $contract->setEconomicsId(4711); + $contract->setProjectName('Kroner probe'); + $contract->setMonthlyPrice(12500.5); + $entityManager->persist($contract); + $entityManager->flush(); + + $url = static::getContainer()->get(AdminUrlGenerator::class) + ->setController(SecurityContractCrudController::class) + ->setAction(Crud::PAGE_INDEX) + ->generateUrl(); + + $client->request('GET', $url); + + $this->assertResponseIsSuccessful(); + + $content = (string) $client->getResponse()->getContent(); + + // Danish grouping and separator, not the application locale's 12,500.5. + // Amount and unit are asserted apart because Intl joins them with a + // non-breaking space. + $this->assertStringContainsString('12.500,50', $content); + $this->assertStringContainsString('kr.', $content); + } +} From 14385a740bd5955e5fc818515ad118fc8a3b7876 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 16:02:52 +0200 Subject: [PATCH 89/94] feat: run local development against a mock identity provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dev environment disabled the main firewall outright, because AAK's OIDC setup has no redirect URI for itksites.local.itkdev.dk. That meant no OpenID Connect code path was exercised until it reached a server — a broken callback path, a renamed claim or a login loop all stayed invisible locally — and the setting is easy to forget, so an authentication problem could be debugged for a long time against a firewall that was never running. A mock provider gives the whole authorization code flow locally instead. The container name is the external hostname on purpose, so the browser and the application reach it by the same name and the issuer matches. .env.dev carries the settings, so an ordinary setup needs nothing in .env.local. AZURE_AZ_OIDC_ALLOW_HTTP is true there and must not be anywhere else: from itk-dev/openid-connect 5.1 it governs every endpoint the discovery document announces, not only the metadata URL. --- .env.dev | 19 +++++++++++++++++++ README.md | 29 ++++++++++++++++++++++++----- config/packages/security.yaml | 7 ------- docker-compose.override.yml | 31 +++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 .env.dev diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..115e8ef --- /dev/null +++ b/.env.dev @@ -0,0 +1,19 @@ +# Committed defaults for the dev environment. +# +# Local development runs against the mock identity provider defined in +# docker-compose.override.yml, because the real one has no redirect URI +# registered for a developer machine. See README.md, "OpenID Connect". +# +# Override any of these in .env.local to point at a different provider. + +###> itk-dev/openid-connect-bundle ### +AZURE_AZ_OIDC_METADATA_URL=http://idp.itksites.local.itkdev.dk/.well-known/openid-configuration +# The mock accepts any client id and secret. +AZURE_AZ_OIDC_CLIENT_ID=client-id +AZURE_AZ_OIDC_CLIENT_SECRET=client-secret +AZURE_AZ_OIDC_REDIRECT_URI=https://itksites.local.itkdev.dk/openid-connect/generic +# The application reaches the mock over http inside the docker network. Never +# true anywhere else: since itk-dev/openid-connect 5.1 this governs every +# endpoint the discovery document announces, not only the metadata URL. +AZURE_AZ_OIDC_ALLOW_HTTP=true +###< itk-dev/openid-connect-bundle ### diff --git a/README.md b/README.md index 64b5416..5a50f68 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,22 @@ Then create a `.env.local` file to set secrets for your local setup. ### OpenID Connect -All users access is controlled by OpenID Connect. For local development you must -add the following to your `.env.local` file: +All user access is controlled by OpenID Connect. Locally the login runs against a +mock identity provider, defined as the `idp` service in `docker-compose.override.yml` +— the real provider has no redirect URI registered for a developer machine. + +Start it with the rest of the stack: + +```shell +docker compose up --detach +``` + +Then log in as `admin` or `editor`: the mock shows a form where you type the subject, +and hands back the claims for it. Both identities are defined in the compose file, and +their claims must include `name` and `upn`, which `AzureOIDCAuthenticator` reads. + +`.env.dev` carries the settings, so there is nothing to add to `.env.local` for an +ordinary setup. To develop against a real provider instead, override them there: ```dotenv ###> itk-dev/openid-connect-bundle ### @@ -122,13 +136,18 @@ AZURE_AZ_OIDC_METADATA_URL= AZURE_AZ_OIDC_CLIENT_ID= AZURE_AZ_OIDC_CLIENT_SECRET= AZURE_AZ_OIDC_REDIRECT_URI=https://itksites.local.itkdev.dk/openid-connect/generic +AZURE_AZ_OIDC_ALLOW_HTTP=false ###< itk-dev/openid-connect-bundle ### ``` > [!NOTE] -> In the `dev` environment the main firewall security is disabled -> (`security.yaml` → `when@dev`), so authentication is not required. -> This is because the current AAK OIDC setup doesn't support `itksites.local.itkdev.dk`. +> `AZURE_AZ_OIDC_ALLOW_HTTP=true` in `.env.dev` is what lets the application talk to +> the mock over http inside the docker network. It must never be true anywhere else: +> since `itk-dev/openid-connect` 5.1 it governs every endpoint the discovery document +> announces, not only the metadata URL. + +The mock accepts the PKCE challenge the bundle sends but does not verify it, so a +successful login here does not prove PKCE works against Azure. ### Fixtures diff --git a/config/packages/security.yaml b/config/packages/security.yaml index e89023b..c730e4c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -58,13 +58,6 @@ security: - { path: ^/admin, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } -# Current AAK OIDC setup doesn't support `itksites.local.itkdev.dk` -when@dev: - security: - firewalls: - main: - security: false - when@test: security: password_hashers: diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 73f8b43..be2dcc5 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,4 +1,35 @@ services: + # A mock identity provider, so the OpenID Connect login can be exercised locally. + # The real one has no redirect URI registered for a developer machine. + # + # https://github.com/geigerzaehler/oidc-provider-mock + # + # The container name is the external hostname on purpose: the browser and the + # application then reach the provider by the same name, so the issuer in the + # discovery document matches the one in the ID token. + # + # Claims must cover what AzureOIDCAuthenticator reads — `name` and `upn`. + idp: + image: ghcr.io/geigerzaehler/oidc-provider-mock:latest + container_name: idp.${COMPOSE_DOMAIN:?} + networks: + - app + - frontend + expose: + - "80" + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME:?}_idp.rule=Host(`idp.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME:?}_idp.loadbalancer.server.port=80" + command: + - "--port" + - "80" + - "--user-claims" + - '{"sub": "admin", "name": "Admin Jensen", "upn": "admin@example.org", "email": "admin@example.org"}' + - "--user-claims" + - '{"sub": "editor", "name": "Ed Editor", "upn": "editor@example.org", "email": "editor@example.org"}' + rabbit: image: rabbitmq:4-management-alpine networks: From 984543a5c778d8a9e8c95be4411518b7b9264fcd Mon Sep 17 00:00:00 2001 From: turegjorup Date: Wed, 26 Aug 2026 16:33:24 +0200 Subject: [PATCH 90/94] feat: update openid-connect-bundle to 6.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bundle now distinguishes a provider that refuses the authorization request — a cancelled login screen, an expired session at Azure, a tenant policy — from a callback that fails validation, and states the status the answer should have. Two changes here to make use of that: AzureOIDCAuthenticator chains the cause when it re-raises as Symfony's AuthenticationException. Without `previous` the bundle cannot tell the two apart and every failure arrives as a 500. OpenIdConnectFailureListener answers with the status the exception asks for — 403 where the user or a policy declined, 503 where Azure reports its own trouble, 500 otherwise — and the page says the login was declined rather than that something went wrong. It still repeats nothing the provider sent. PKCE comes with the bundle and needs no configuration. itk-dev/openid-connect moves to 5.1, which drops robrichards/xmlseclibs. --- composer.json | 2 +- composer.lock | 77 +++++-------------- config/reference.php | 2 + .../OpenIdConnectFailureListener.php | 38 ++++++--- src/Security/AzureOIDCAuthenticator.php | 5 +- .../error/openid_connect_failed.html.twig | 35 ++++++--- .../OpenIdConnectFailureListenerTest.php | 75 ++++++++++++++++++ 7 files changed, 151 insertions(+), 83 deletions(-) diff --git a/composer.json b/composer.json index 999ddaf..fdfdcc7 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "doctrine/doctrine-migrations-bundle": "^4.0", "doctrine/orm": "^3.0", "easycorp/easyadmin-bundle": "^5.0", - "itk-dev/openid-connect-bundle": "^6.0", + "itk-dev/openid-connect-bundle": "^6.1", "itk-dev/vault-bundle": "^1.0.0", "nelmio/cors-bundle": "^2.2", "ocramius/doctrine-batch-utils": "^2.8", diff --git a/composer.lock b/composer.lock index 4a8701e..4c17a85 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": "f78e1c30f5af0b778bf9f5779631a156", + "content-hash": "64cb1b11b5e4c3da1bdc1f83e64b414f", "packages": [ { "name": "api-platform/core", @@ -1928,31 +1928,31 @@ }, { "name": "itk-dev/openid-connect", - "version": "5.0.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect.git", - "reference": "f241f6794a2e74eab8c4808bc22f341a98e96f0b" + "reference": "950758b97397ab529f1df787c377ca54b321af79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/f241f6794a2e74eab8c4808bc22f341a98e96f0b", - "reference": "f241f6794a2e74eab8c4808bc22f341a98e96f0b", + "url": "https://api.github.com/repos/itk-dev/openid-connect/zipball/950758b97397ab529f1df787c377ca54b321af79", + "reference": "950758b97397ab529f1df787c377ca54b321af79", "shasum": "" }, "require": { "ext-json": "*", "ext-openssl": "*", "firebase/php-jwt": "^7.0", - "league/oauth2-client": "^2.6", + "league/oauth2-client": "^2.8.1", "php": "^8.3", "psr/cache": "^2.0 || ^3.0", - "psr/http-client": "^1.0", - "robrichards/xmlseclibs": "^3.1.5" + "psr/http-client": "^1.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.50", "friendsofphp/php-cs-fixer": "^3.75", + "infection/infection": "^0.35.2", "mockery/mockery": "^1.6.12", "phpstan/phpstan": "^2.1.41", "phpstan/phpstan-mockery": "^2.0", @@ -1986,29 +1986,29 @@ "description": "OpenID connect configuration package", "support": { "issues": "https://github.com/itk-dev/openid-connect/issues", - "source": "https://github.com/itk-dev/openid-connect/tree/5.0.0" + "source": "https://github.com/itk-dev/openid-connect/tree/5.1.0" }, - "time": "2026-06-02T09:01:56+00:00" + "time": "2026-08-26T12:01:21+00:00" }, { "name": "itk-dev/openid-connect-bundle", - "version": "6.0.0", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/itk-dev/openid-connect-bundle.git", - "reference": "79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d" + "reference": "c990bd64fca599de02766671b99eab4cc260915b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d", - "reference": "79be9eb80767e6c0f7945de8cee9bbfe2a4b5f3d", + "url": "https://api.github.com/repos/itk-dev/openid-connect-bundle/zipball/c990bd64fca599de02766671b99eab4cc260915b", + "reference": "c990bd64fca599de02766671b99eab4cc260915b", "shasum": "" }, "require": { "doctrine/orm": "^2.8 || ^3.0", "ext-json": "*", "ext-openssl": "*", - "itk-dev/openid-connect": "^5.0", + "itk-dev/openid-connect": "^5.1", "php": "^8.3", "psr/log": "^3.0", "symfony/cache": "^6.4 || ^7.0 || ^8.0", @@ -2022,6 +2022,7 @@ "require-dev": { "ergebnis/composer-normalize": "^2.28", "friendsofphp/php-cs-fixer": "^3.11", + "igor-php/igor-php": "^0.9", "infection/infection": "*", "phpstan/phpstan": "^2.1.41", "phpstan/phpstan-deprecation-rules": "^2.0", @@ -2055,9 +2056,9 @@ "description": "Symfony bundle for openid-connect", "support": { "issues": "https://github.com/itk-dev/openid-connect-bundle/issues", - "source": "https://github.com/itk-dev/openid-connect-bundle/tree/6.0.0" + "source": "https://github.com/itk-dev/openid-connect-bundle/tree/6.1.0" }, - "time": "2026-08-25T19:09:29+00:00" + "time": "2026-08-26T14:22:26+00:00" }, { "name": "itk-dev/vault", @@ -3149,48 +3150,6 @@ }, "time": "2019-03-08T08:55:37+00:00" }, - { - "name": "robrichards/xmlseclibs", - "version": "3.1.5", - "source": { - "type": "git", - "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "03062be78178cbb5e8f605cd255dc32a14981f92" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92", - "reference": "03062be78178cbb5e8f605cd255dc32a14981f92", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "php": ">= 5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "RobRichards\\XMLSecLibs\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "A PHP library for XML Security", - "homepage": "https://github.com/robrichards/xmlseclibs", - "keywords": [ - "security", - "signature", - "xml", - "xmldsig" - ], - "support": { - "issues": "https://github.com/robrichards/xmlseclibs/issues", - "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5" - }, - "time": "2026-03-13T10:31:56+00:00" - }, { "name": "symfony/amqp-messenger", "version": "v8.1.0", diff --git a/config/reference.php b/config/reference.php index fa1bf93..db69eea 100644 --- a/config/reference.php +++ b/config/reference.php @@ -1771,6 +1771,8 @@ * client_secret_expires_at?: scalar|Param|null, // Optional. Date the client secret expires, e.g. "2027-01-31". Anything strtotime() understands, and usually an environment variable. Set it and the bundle warns before the secret expires; leave it unset and the provider reports "unknown" and is not monitored. Set it where the real secret lives — a date carried in a committed default is a date nobody maintains. * leeway?: int|Param, // Leeway in seconds to account for clock skew between server and provider // Default: 10 * cache_duration?: int|Param, // Cache duration in seconds for the OIDC discovery document and JWKS (default: 86400 — 24 hours) // Default: 86400 + * scopes?: Param|string|list, + * pkce?: bool|Param, // Send a PKCE challenge (RFC 7636, S256) with the authorization request // Default: true * redirect_uri?: scalar|Param|null, // Redirect URI registered at identity provider * redirect_route?: scalar|Param|null, // Redirect route registered at identity provider (must not be set if redirect_uri is set) * redirect_route_parameters?: array, diff --git a/src/EventListener/OpenIdConnectFailureListener.php b/src/EventListener/OpenIdConnectFailureListener.php index 6a9dd3c..f233885 100644 --- a/src/EventListener/OpenIdConnectFailureListener.php +++ b/src/EventListener/OpenIdConnectFailureListener.php @@ -5,9 +5,11 @@ namespace App\EventListener; use ItkDev\OpenIdConnectBundle\Exception\AuthenticationFailedException; +use ItkDev\OpenIdConnectBundle\Exception\ProviderErrorException; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Twig\Environment; @@ -15,12 +17,16 @@ /** * Renders a failed OIDC login as a page rather than as an unhandled exception. * - * Since openid-connect-bundle 6.0 a failed callback throws - * `AuthenticationFailedException`, which is deliberately not an - * `AuthenticationException`: the firewall no longer catches it and sends the - * browser back to Azure, which is what turned the expired client secret into a - * redirect loop. What escapes the firewall instead is an unhandled exception, - * so without this listener a failed login shows the default error page. + * A failed callback throws `AuthenticationFailedException`, which is deliberately + * not an `AuthenticationException`: the firewall does not catch it and does not + * send the browser back to Azure, which is what turned the expired client secret + * into a redirect loop. What escapes the firewall instead is an unhandled + * exception, so without this listener a failed login shows the default error page. + * + * `ProviderErrorException` is the subclass Azure sends when it refuses the request + * — a cancelled consent screen, an expired session there, a tenant policy. It + * carries the status the answer should have, and the page says the login was + * declined rather than that something went wrong. * * Only `AuthenticationFailedException` is handled. The bundle's other failures * are configuration bugs — an unknown provider key, an unreachable @@ -44,19 +50,31 @@ public function __construct( public function onKernelException(ExceptionEvent $event): void { - if (!$event->isMainRequest() || !$event->getThrowable() instanceof AuthenticationFailedException) { + $exception = $event->getThrowable(); + + if (!$event->isMainRequest() || !$exception instanceof AuthenticationFailedException) { return; } // Deliberately without the exception message: it carries the identity // provider's own error text, which belongs in the log, not in a browser. + // `declined` is the one thing worth telling the user, and it is the error + // code rather than that text. $content = $this->twig->render('error/openid_connect_failed.html.twig', [ 'login_url' => $this->urlGenerator->generate('itkdev_openid_connect_login', ['providerKey' => 'azure_az']), + 'declined' => $exception instanceof ProviderErrorException + && ProviderErrorException::ACCESS_DENIED === $exception->getError(), ]); - // Still a 500: the most likely cause is on this side of the login, and a - // failed login should read as an error in the log and in monitoring. - $response = new Response($content, Response::HTTP_INTERNAL_SERVER_ERROR); + // A refusal states its own status — 403 where the user or a policy said + // no, 503 where Azure reports its own trouble. Everything else is a 500: + // the likely cause is on this side of the login, and it should read as an + // error in the log and in monitoring. + $status = $exception instanceof HttpExceptionInterface + ? $exception->getStatusCode() + : Response::HTTP_INTERNAL_SERVER_ERROR; + + $response = new Response($content, $status); $response->headers->set('Cache-Control', 'no-store, private'); $event->setResponse($response); diff --git a/src/Security/AzureOIDCAuthenticator.php b/src/Security/AzureOIDCAuthenticator.php index d2ab5d1..3d6198b 100644 --- a/src/Security/AzureOIDCAuthenticator.php +++ b/src/Security/AzureOIDCAuthenticator.php @@ -63,7 +63,10 @@ public function authenticate(Request $request): Passport return new SelfValidatingPassport(new UserBadge($user->getUserIdentifier())); } catch (OpenIdConnectExceptionInterface $exception) { - throw new CustomUserMessageAuthenticationException($exception->getMessage()); + // Chained: the bundle reads the cause back in onAuthenticationFailure() + // to decide what the user is shown. Dropping it turns a refusal the + // user caused into an unexplained 500. + throw new CustomUserMessageAuthenticationException($exception->getMessage(), previous: $exception); } } diff --git a/templates/error/openid_connect_failed.html.twig b/templates/error/openid_connect_failed.html.twig index e95fb0c..1141b94 100644 --- a/templates/error/openid_connect_failed.html.twig +++ b/templates/error/openid_connect_failed.html.twig @@ -14,7 +14,7 @@ - Login failed - ITKsites + {% if declined %}Login declined{% else %}Login failed{% endif %} - ITKsites