From 6cacb0dea6f6f6abf7f8b000bf7b10053ec9ca5e Mon Sep 17 00:00:00 2001 From: MusikAnimal Date: Tue, 19 Aug 2025 04:21:00 -0400 Subject: [PATCH] Upgrade to Symfony 6.4 and employ new PHP 8.2 features In addition to bringing us up-to-date with the latest LTS release, this fixes the annoying issue with local envs where the cache directory had to be manually cleared. Symfony 6 brings a refreshed debug toolbar among other benefits as well. Employed PHP features now that we can use 8.2: - We now use native PHP method attributes instead of annotations for routing. Other annotations can likely be moved to attributes at some point, too. - Use unioned types in method signatures where possible, and if the resulting doc block adds no information, remove it - Use constructor property promotion - Use str_starts_with() where possible - Use nullsafe operations in conjunction with null coalesce - Remove unused variables from signature of catch statements Bug: T400929 --- .env | 1 - bin/console | 39 +- composer.json | 45 +- composer.lock | 2625 ++++++++--------- config/bootstrap.php | 23 - config/packages/framework.yaml | 22 +- config/packages/test/framework.yaml | 4 - config/preload.php | 4 - config/routes/dev/framework.yaml | 3 - config/routes/framework.yaml | 4 + config/services.yaml | 2 +- i18n/en.json | 1 - i18n/qqq.json | 1 - public/index.php | 26 +- src/Controller/AdminScoreController.php | 15 +- src/Controller/AdminStatsController.php | 102 +- src/Controller/AuthorshipController.php | 48 +- src/Controller/AutomatedEditsController.php | 187 +- src/Controller/BlameController.php | 29 +- src/Controller/CategoryEditsController.php | 85 +- src/Controller/DefaultController.php | 50 +- src/Controller/EditCounterController.php | 251 +- src/Controller/EditSummaryController.php | 57 +- src/Controller/GlobalContribsController.php | 114 +- src/Controller/LargestPagesController.php | 35 +- src/Controller/MetaController.php | 30 +- src/Controller/PageInfoController.php | 243 +- src/Controller/PagesController.php | 162 +- src/Controller/QuoteController.php | 37 +- .../SimpleEditCounterController.php | 78 +- src/Controller/TopEditsController.php | 115 +- src/Controller/XtoolsController.php | 80 +- .../DisabledToolSubscriber.php | 6 +- src/EventSubscriber/ExceptionListener.php | 28 +- src/EventSubscriber/RateLimitSubscriber.php | 45 +- src/Exception/BadGatewayException.php | 5 +- src/Exception/XtoolsHttpException.php | 21 +- src/Helper/AutomatedEditsHelper.php | 12 +- src/Helper/I18nHelper.php | 14 +- src/Kernel.php | 53 - src/Model/AdminScore.php | 15 +- src/Model/AdminStats.php | 35 +- src/Model/Authorship.php | 22 +- src/Model/AutoEdits.php | 48 +- src/Model/Blame.php | 26 +- src/Model/CategoryEdits.php | 23 +- src/Model/Edit.php | 19 +- src/Model/EditCounter.php | 32 +- src/Model/EditSummary.php | 30 +- src/Model/GlobalContribs.php | 35 +- src/Model/LargestPages.php | 23 +- src/Model/Model.php | 10 +- src/Model/Page.php | 45 +- src/Model/PageAssessments.php | 13 +- src/Model/PageInfo.php | 2 +- src/Model/PageInfoApi.php | 32 +- src/Model/Pages.php | 25 +- src/Model/Project.php | 16 +- src/Model/SimpleEditCounter.php | 22 +- src/Model/TopEdits.php | 76 +- src/Model/User.php | 30 +- src/Model/UserRights.php | 43 +- src/Monolog/WebProcessorMonolog.php | 7 +- src/Repository/AdminStatsRepository.php | 7 +- src/Repository/AutoEditsRepository.php | 72 +- src/Repository/BlameRepository.php | 23 +- src/Repository/CategoryEditsRepository.php | 57 +- src/Repository/EditCounterRepository.php | 63 +- src/Repository/EditRepository.php | 23 +- src/Repository/EditSummaryRepository.php | 18 +- src/Repository/GlobalContribsRepository.php | 35 +- src/Repository/LargestPagesRepository.php | 27 +- src/Repository/PageInfoRepository.php | 59 +- src/Repository/PageRepository.php | 55 +- src/Repository/PagesRepository.php | 34 +- src/Repository/ProjectRepository.php | 55 +- src/Repository/Repository.php | 89 +- .../SimpleEditCounterRepository.php | 21 +- src/Repository/TopEditsRepository.php | 98 +- src/Repository/UserRepository.php | 57 +- src/Twig/AppExtension.php | 59 +- symfony.lock | 19 +- templates/pages/result.html.twig | 1 + .../topedits/result_namespace.wikitext.twig | 6 +- .../Controller/EditCounterControllerTest.php | 2 - .../OverridableXtoolsController.php | 1 - tests/Model/ModelTest.php | 2 +- tests/Model/PageTest.php | 10 +- 88 files changed, 2627 insertions(+), 3597 deletions(-) delete mode 100644 config/bootstrap.php delete mode 100644 config/packages/test/framework.yaml delete mode 100644 config/routes/dev/framework.yaml create mode 100644 config/routes/framework.yaml diff --git a/.env b/.env index 056f42eb4..02f59be17 100644 --- a/.env +++ b/.env @@ -7,7 +7,6 @@ APP_ENV=dev APP_SECRET=ThisTokenIsNotSoSecretChangeIt # Wikimedia Cloud Services TRUSTED_PROXIES=172.16.0.0/21,172.16.8.0/21,172.16.16.0/21,2a02:ec80:a000:1::/64 -#TRUSTED_HOSTS= ###< symfony/framework-bundle ### APP_VERSION=3.22.4 diff --git a/bin/console b/bin/console index 5de0e1c5b..c933dc535 100755 --- a/bin/console +++ b/bin/console @@ -3,40 +3,15 @@ use App\Kernel; use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\ErrorHandler\Debug; -if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { - echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL; +if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) { + throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".'); } -set_time_limit(0); +require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; -require dirname(__DIR__).'/vendor/autoload.php'; +return function (array $context) { + $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); -if (!class_exists(Application::class)) { - throw new LogicException('You need to add "symfony/framework-bundle" as a Composer dependency.'); -} - -$input = new ArgvInput(); -if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) { - putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); -} - -if ($input->hasParameterOption('--no-debug', true)) { - putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); -} - -require dirname(__DIR__).'/config/bootstrap.php'; - -if ($_SERVER['APP_DEBUG']) { - umask(0000); - - if (class_exists(Debug::class)) { - Debug::enable(); - } -} - -$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); -$application = new Application($kernel); -$application->run($input); + return new Application($kernel); +}; diff --git a/composer.json b/composer.json index 8ad48f869..d345a55d8 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ }, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, - "symfony/flex": true + "symfony/flex": true, + "symfony/runtime": true }, "sort-packages": true }, @@ -30,10 +31,9 @@ "ext-intl": "*", "ext-json": "*", "doctrine/common": "~3.1", - "doctrine/doctrine-bundle": "~2.2", - "doctrine/doctrine-migrations-bundle": "~2.0", + "doctrine/doctrine-migrations-bundle": "^3.0", "eightpoints/guzzle-bundle": "^8.0", - "jms/serializer-bundle": "^3.4", + "jms/serializer-bundle": "^5.0", "krinkle/intuition": "^2.3", "mediawiki/oauthclient": "2.0.*", "nelmio/api-doc-bundle": "~4.11", @@ -41,34 +41,35 @@ "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.16", "slevomat/coding-standard": "^8.0", - "symfony/asset": "~5.4", - "symfony/cache": "~5.4", - "symfony/config": "~5.4", - "symfony/css-selector": "~5.4", - "symfony/dom-crawler": "~5.4", - "symfony/dotenv": "~5.4", + "symfony/asset": "~6.4", + "symfony/cache": "~6.4", + "symfony/config": "~6.4", + "symfony/css-selector": "~6.4", + "symfony/dom-crawler": "~6.4", + "symfony/dotenv": "~6.4", "symfony/flex": "^1.19", - "symfony/http-kernel": "~5.4", - "symfony/mailer": "~5.4", + "symfony/http-kernel": "~6.4", + "symfony/mailer": "~6.4", "symfony/monolog-bundle": "^3.3", - "symfony/property-access": "~5.4", - "symfony/property-info": "~5.4", - "symfony/routing": "~5.4", - "symfony/security-csrf": "~5.4", - "symfony/serializer": "~5.4", - "symfony/twig-bridge": "~5.4", - "symfony/web-profiler-bundle": "~5.4", + "symfony/property-access": "~6.4", + "symfony/property-info": "~6.4", + "symfony/routing": "~6.4", + "symfony/runtime": "^7.3", + "symfony/security-csrf": "~6.4", + "symfony/serializer": "~6.4", + "symfony/twig-bridge": "~6.4", + "symfony/web-profiler-bundle": "~6.4", "symfony/webpack-encore-bundle": "^1.16", - "symfony/yaml": "~5.4", + "symfony/yaml": "~6.4", "twig/twig": "^3.0", "wikimedia/ip-utils": "^5.0" }, "require-dev": { - "symfony/phpunit-bridge": "~5.4", + "symfony/phpunit-bridge": "~6.4", "squizlabs/php_codesniffer": "^3.3.0", "mediawiki/minus-x": "^1.0.0", "dms/phpunit-arraysubset-asserts": "^0.4.0", - "symfony/browser-kit": "~5.4" + "symfony/browser-kit": "~6.4" }, "scripts": { "test": [ diff --git a/composer.lock b/composer.lock index d2d6b8230..dd5943c34 100644 --- a/composer.lock +++ b/composer.lock @@ -4,81 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f2ec431a83186af43899673da601b33e", + "content-hash": "f85505b82b61c9d6b4c5d99e0456c621", "packages": [ - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.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": "2022-01-17T14:14:24+00:00" - }, { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v1.1.2", @@ -175,175 +102,6 @@ ], "time": "2025-07-17T20:45:56+00:00" }, - { - "name": "doctrine/annotations", - "version": "1.14.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/253dca476f70808a5aeed3a47cc2cc88c5cab915", - "reference": "253dca476f70808a5aeed3a47cc2cc88c5cab915", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1 || ^2", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "~1.4.10 || ^1.10.28", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7", - "vimeo/psalm": "^4.30 || ^5.14" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "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": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.4" - }, - "time": "2024-09-05T10:15:52+00:00" - }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "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 Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.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%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, { "name": "doctrine/common", "version": "3.5.0", @@ -437,46 +195,44 @@ }, { "name": "doctrine/dbal", - "version": "2.13.9", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8" + "reference": "7669f131d43b880de168b2d2df9687d152d6c762" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c480849ca3ad6706a39c970cdfe6888fa8a058b8", - "reference": "c480849ca3ad6706a39c970cdfe6888fa8a058b8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7669f131d43b880de168b2d2df9687d152d6c762", + "reference": "7669f131d43b880de168b2d2df9687d152d6c762", "shasum": "" }, "require": { - "doctrine/cache": "^1.0|^2.0", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.1 || ^8" + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.22.0" + "doctrine/coding-standard": "13.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-phpunit": "2.0.6", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "11.5.23", + "slevomat/coding-standard": "8.16.2", + "squizlabs/php_codesniffer": "3.13.1", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + "Doctrine\\DBAL\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -519,14 +275,13 @@ "queryobject", "sasql", "sql", - "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.9" + "source": "https://github.com/doctrine/dbal/tree/4.3.2" }, "funding": [ { @@ -542,7 +297,7 @@ "type": "tidelift" } ], - "time": "2022-05-02T20:28:55+00:00" + "time": "2025-08-05T13:30:38+00:00" }, { "name": "doctrine/deprecations", @@ -594,56 +349,64 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.7.2", + "version": "2.15.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "22d53b2c5ad03929628fb4a928b01135585b7179" + "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/22d53b2c5ad03929628fb4a928b01135585b7179", - "reference": "22d53b2c5ad03929628fb4a928b01135585b7179", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/5a305c5e776f9d3eb87f5b94d40d50aff439211d", + "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d", "shasum": "" }, "require": { - "doctrine/annotations": "^1", - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/dbal": "^2.13.1 || ^3.3.2", - "doctrine/persistence": "^2.2 || ^3", + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^3.1 || ^4", "doctrine/sql-formatter": "^1.0.1", - "php": "^7.1 || ^8.0", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/config": "^4.4.3 || ^5.4 || ^6.0", - "symfony/console": "^4.4 || ^5.4 || ^6.0", - "symfony/dependency-injection": "^4.4.18 || ^5.4 || ^6.0", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "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": "^4.4.22 || ^5.4 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0", - "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5 || ^3" }, "conflict": { - "doctrine/orm": "<2.11 || >=3.0", - "twig/twig": "<1.34 || >=2.0,<2.4" + "doctrine/annotations": ">=3.0", + "doctrine/cache": "< 1.11", + "doctrine/orm": "<2.17 || >=4.0", + "symfony/var-exporter": "< 6.4.1 || 7.0.0", + "twig/twig": "<2.13 || >=3.0 <3.0.4" }, "require-dev": { - "doctrine/coding-standard": "^9.0", - "doctrine/orm": "^2.11 || ^3.0", + "doctrine/annotations": "^1 || ^2", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^13", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.1", "friendsofphp/proxy-manager-lts": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3 || ^10.0", - "psalm/plugin-phpunit": "^0.16.1", - "psalm/plugin-symfony": "^3", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9.6.22", "psr/log": "^1.1.4 || ^2.0 || ^3.0", - "symfony/phpunit-bridge": "^6.1", - "symfony/property-info": "^4.4 || ^5.4 || ^6.0", - "symfony/proxy-manager-bridge": "^4.4 || ^5.4 || ^6.0", - "symfony/security-bundle": "^4.4 || ^5.4 || ^6.0", - "symfony/twig-bridge": "^4.4 || ^5.4 || ^6.0", - "symfony/validator": "^4.4 || ^5.4 || ^6.0", - "symfony/web-profiler-bundle": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^4.4 || ^5.4 || ^6.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0", - "vimeo/psalm": "^4.7" + "symfony/doctrine-messenger": "^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", + "symfony/string": "^6.4 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "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" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -653,7 +416,7 @@ "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\DoctrineBundle\\": "" + "Doctrine\\Bundle\\DoctrineBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -688,7 +451,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.7.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.15.1" }, "funding": [ { @@ -704,43 +467,47 @@ "type": "tidelift" } ], - "time": "2022-12-07T12:07:11+00:00" + "time": "2025-07-30T15:48:28+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "2.2.3", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "0a081b55a88259a887af7be654743a8c5f703e99" + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/0a081b55a88259a887af7be654743a8c5f703e99", - "reference": "0a081b55a88259a887af7be654743a8c5f703e99", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "~1.0|~2.0", - "doctrine/migrations": "^2.2", - "php": "^7.1|^8.0", - "symfony/framework-bundle": "~3.4|~4.0|~5.0" + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2", + "php": "^7.2 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", - "mikey179/vfsstream": "^1.6", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "composer/semver": "^3.0", + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.6 || ^3", + "phpstan/phpstan": "^1.4 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-symfony": "^1.3 || ^2", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\MigrationsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -753,11 +520,11 @@ }, { "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org" + "homepage": "https://www.doctrine-project.org" }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony DoctrineMigrationsBundle", @@ -769,7 +536,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/2.2.3" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" }, "funding": [ { @@ -785,34 +552,33 @@ "type": "tidelift" } ], - "time": "2021-03-18T20:55:50+00:00" + "time": "2025-03-11T17:36:26+00:00" }, { "name": "doctrine/event-manager", - "version": "1.2.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.24" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", "autoload": { @@ -861,7 +627,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" }, "funding": [ { @@ -877,7 +643,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:51:15+00:00" + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/instantiator", @@ -951,28 +717,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.21" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -1009,7 +774,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1025,47 +790,55 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:35:39+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "doctrine/migrations", - "version": "2.3.5", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "28d92a34348fee5daeb80879e56461b2e862fc05" + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/28d92a34348fee5daeb80879e56461b2e862fc05", - "reference": "28d92a34348fee5daeb80879e56461b2e862fc05", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.8", - "doctrine/dbal": "^2.9", - "friendsofphp/proxy-manager-lts": "^1.0", - "php": "^7.1 || ^8.0", - "symfony/console": "^3.4||^4.4.16||^5.0", - "symfony/stopwatch": "^3.4||^4.0||^5.0" + "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", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^8.2", - "doctrine/orm": "^2.6", + "doctrine/coding-standard": "^13", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", + "doctrine/sql-formatter": "^1.0", "ext-pdo_sqlite": "*", - "jdorn/sql-formatter": "^1.1", - "mikey179/vfsstream": "^1.6", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "symfony/cache": "^4.4. || ^5.3", - "symfony/process": "^3.4||^4.0||^5.0", - "symfony/yaml": "^3.4||^4.0||^5.0" + "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", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { - "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", "symfony/yaml": "Allows the use of yaml for migration configuration files." }, "bin": [ @@ -1074,7 +847,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + "Doctrine\\Migrations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1100,12 +873,11 @@ "keywords": [ "database", "dbal", - "migrations", - "php" + "migrations" ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/2.3.5" + "source": "https://github.com/doctrine/migrations/tree/3.9.4" }, "funding": [ { @@ -1121,25 +893,25 @@ "type": "tidelift" } ], - "time": "2021-10-19T19:55:20+00:00" + "time": "2025-08-19T06:41:07+00:00" }, { "name": "doctrine/persistence", - "version": "3.4.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff" + "reference": "45004aca79189474f113cbe3a53847c2115a55fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/0ea965320cec355dba75031c1b23d4c78362e3ff", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/45004aca79189474f113cbe3a53847c2115a55fa", + "reference": "45004aca79189474f113cbe3a53847c2115a55fa", "shasum": "" }, "require": { "doctrine/event-manager": "^1 || ^2", - "php": "^7.2 || ^8.0", + "php": "^8.1", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, "conflict": { @@ -1147,11 +919,10 @@ }, "require-dev": { "doctrine/coding-standard": "^12", - "doctrine/common": "^3.0", "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5.38 || ^9.5", + "phpunit/phpunit": "^9.6", "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", @@ -1201,7 +972,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.4.0" + "source": "https://github.com/doctrine/persistence/tree/4.0.0" }, "funding": [ { @@ -1217,7 +988,7 @@ "type": "tidelift" } ], - "time": "2024-10-30T19:48:12+00:00" + "time": "2024-11-01T21:49:07+00:00" }, { "name": "doctrine/sql-formatter", @@ -1413,88 +1184,6 @@ }, "time": "2025-01-03T09:42:03+00:00" }, - { - "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.18", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", - "reference": "2c8a6cffc3220e99352ad958fe7cf06bf6f7690f", - "shasum": "" - }, - "require": { - "laminas/laminas-code": "~3.4.1|^4.0", - "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0" - }, - "conflict": { - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" - }, - "replace": { - "ocramius/proxy-manager": "^2.1" - }, - "require-dev": { - "ext-phar": "*", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/Ocramius/ProxyManager", - "name": "ocramius/proxy-manager" - } - }, - "autoload": { - "psr-4": { - "ProxyManager\\": "src/ProxyManager" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - } - ], - "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", - "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", - "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "support": { - "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.18" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", - "type": "tidelift" - } - ], - "time": "2024-03-20T12:50:41+00:00" - }, { "name": "guzzlehttp/guzzle", "version": "7.9.3", @@ -1988,46 +1677,49 @@ }, { "name": "jms/serializer-bundle", - "version": "3.10.0", + "version": "5.5.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/JMSSerializerBundle.git", - "reference": "1a030ecaf913b7a895eeaab04191cd7ff1810b46" + "reference": "0538a2bae32a448fdeded53d729308816b5ad2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/JMSSerializerBundle/zipball/1a030ecaf913b7a895eeaab04191cd7ff1810b46", - "reference": "1a030ecaf913b7a895eeaab04191cd7ff1810b46", + "url": "https://api.github.com/repos/schmittjoh/JMSSerializerBundle/zipball/0538a2bae32a448fdeded53d729308816b5ad2e8", + "reference": "0538a2bae32a448fdeded53d729308816b5ad2e8", "shasum": "" }, "require": { - "jms/metadata": "^2.5", - "jms/serializer": "^3.0", - "php": "^7.2 || ^8.0", - "symfony/dependency-injection": "^3.3 || ^4.0 || ^5.0", - "symfony/framework-bundle": "^3.0 || ^4.0 || ^5.0" + "jms/metadata": "^2.6", + "jms/serializer": "^3.31", + "php": "^7.4 || ^8.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "doctrine/coding-standard": "^8.1", - "doctrine/orm": "^2.4", + "doctrine/annotations": "^1.14 || ^2.0", + "doctrine/coding-standard": "^12.0", + "doctrine/orm": "^2.14", "phpunit/phpunit": "^8.0 || ^9.0", - "symfony/expression-language": "^3.0 || ^4.0 || ^5.0", - "symfony/finder": "^3.0 || ^4.0 || ^5.0", - "symfony/form": "^3.0 || ^4.0 || ^5.0", - "symfony/stopwatch": "^3.0 || ^4.0 || ^5.0", - "symfony/twig-bundle": "*", - "symfony/validator": "^3.0 || ^4.0 || ^5.0", - "symfony/yaml": "^3.0 || ^4.0 || ^5.0" + "symfony/expression-language": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/templating": "^5.4 || ^6.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0", + "symfony/uid": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" }, "suggest": { - "jms/di-extra-bundle": "Required to get lazy loading (de)serialization visitors, ^1.3", - "symfony/expression-language": "Required for opcache preloading, ^3.0 || ^4.0 || ^5.0", - "symfony/finder": "Required for cache warmup, supported versions ^3.0|^4.0" + "symfony/expression-language": "Required for opcache preloading ^5.4 || ^6.0 || ^7.0", + "symfony/finder": "Required for cache warmup, supported versions ^5.4 || ^6.0 || ^7.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "3.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -2062,7 +1754,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/JMSSerializerBundle/issues", - "source": "https://github.com/schmittjoh/JMSSerializerBundle/tree/3.10.0" + "source": "https://github.com/schmittjoh/JMSSerializerBundle/tree/5.5.1" }, "funding": [ { @@ -2070,7 +1762,7 @@ "type": "github" } ], - "time": "2021-08-06T12:12:38+00:00" + "time": "2024-11-06T12:45:22+00:00" }, { "name": "krinkle/intuition", @@ -2118,67 +1810,71 @@ "time": "2024-03-28T22:32:35+00:00" }, { - "name": "laminas/laminas-code", - "version": "4.16.0", + "name": "masterminds/html5", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "1793e78dad4108b594084d05d1fb818b85b110af" + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1793e78dad4108b594084d05d1fb818b85b110af", - "reference": "1793e78dad4108b594084d05d1fb818b85b110af", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + "ext-dom": "*", + "php": ">=5.3.0" }, "require-dev": { - "doctrine/annotations": "^2.0.1", - "ext-phar": "*", - "laminas/laminas-coding-standard": "^3.0.0", - "laminas/laminas-stdlib": "^3.18.0", - "phpunit/phpunit": "^10.5.37", - "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.15.0" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, "autoload": { "psr-4": { - "Laminas\\Code\\": "src/" + "Masterminds\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", "keywords": [ - "code", - "laminas", - "laminasframework" + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2024-11-20T13:15:13+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "mediawiki/oauthclient", @@ -2808,16 +2504,16 @@ }, { "name": "psr/cache", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/213f9dbc5b9bfbc4f8db86d2838dc968752ce13b", - "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { @@ -2851,28 +2547,33 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/2.0.0" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2021-02-03T23:23:37+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", - "version": "1.1.2", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "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/" @@ -2899,9 +2600,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -3358,33 +3059,28 @@ }, { "name": "symfony/asset", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "b7a18eaff1d717c321b4f13403413f8815bf9cb0" + "reference": "cfee7c0d64be113383db74a2fdd65d426b7f3aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/b7a18eaff1d717c321b4f13403413f8815bf9cb0", - "reference": "b7a18eaff1d717c321b4f13403413f8815bf9cb0", + "url": "https://api.github.com/repos/symfony/asset/zipball/cfee7c0d64be113383db74a2fdd65d426b7f3aab", + "reference": "cfee7c0d64be113383db74a2fdd65d426b7f3aab", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "conflict": { - "symfony/http-foundation": "<5.3" + "symfony/http-foundation": "<5.4" }, "require-dev": { - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/http-foundation": "" + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3412,7 +3108,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/v5.4.45" + "source": "https://github.com/symfony/asset/tree/v6.4.24" }, "funding": [ { @@ -3423,67 +3119,70 @@ "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-22T13:05:35+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/cache", - "version": "v5.4.46", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b" + "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", - "reference": "0fe08ee32cec2748fbfea10c52d3ee02049e0f6b", + "url": "https://api.github.com/repos/symfony/cache/zipball/d038cd3054aeaf1c674022a77048b2ef6376a175", + "reference": "d038cd3054aeaf1c674022a77048b2ef6376a175", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.3.6|^7.0" }, "conflict": { "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" + "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/cache": "^1.6|^2.0", "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -3509,7 +3208,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.46" + "source": "https://github.com/symfony/cache/tree/v6.4.24" }, "funding": [ { @@ -3520,33 +3219,34 @@ "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-04T11:43:55+00:00" + "time": "2025-07-30T09:32:03+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.5.4", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/517c3a3619dadfa6952c4651767fcadffb4df65e", - "reference": "517c3a3619dadfa6952c4651767fcadffb4df65e", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.1", + "psr/cache": "^3.0" }, "type": "library", "extra": { @@ -3555,7 +3255,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3588,7 +3288,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -3604,42 +3304,38 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/config", - "version": "v5.4.46", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "977c88a02d7d3f16904a81907531b19666a08e78" + "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/977c88a02d7d3f16904a81907531b19666a08e78", - "reference": "977c88a02d7d3f16904a81907531b19666a08e78", + "url": "https://api.github.com/repos/symfony/config/zipball/80e2cf005cf17138c97193be0434cdcfd1b2212e", + "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3667,7 +3363,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/v5.4.46" + "source": "https://github.com/symfony/config/tree/v6.4.24" }, "funding": [ { @@ -3678,61 +3374,60 @@ "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-30T07:58:02+00:00" + "time": "2025-07-26T13:50:30+00:00" }, { "name": "symfony/console", - "version": "v5.4.47", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" + "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "url": "https://api.github.com/repos/symfony/console/zipball/59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", + "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3766,7 +3461,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.47" + "source": "https://github.com/symfony/console/tree/v6.4.24" }, "funding": [ { @@ -3777,30 +3472,33 @@ "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-06T11:30:55+00:00" + "time": "2025-07-30T10:38:54+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4f7f3c35fba88146b56d0025d20ace3f3901f097" + "reference": "9b784413143701aa3c94ac1869a159a9e53e8761" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4f7f3c35fba88146b56d0025d20ace3f3901f097", - "reference": "4f7f3c35fba88146b56d0025d20ace3f3901f097", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/9b784413143701aa3c94ac1869a159a9e53e8761", + "reference": "9b784413143701aa3c94ac1869a159a9e53e8761", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -3832,7 +3530,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.45" + "source": "https://github.com/symfony/css-selector/tree/v6.4.24" }, "funding": [ { @@ -3843,57 +3541,53 @@ "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-25T14:11:13+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e5ca16dee39ef7d63e552ff0bf0a2526a1142c92" + "reference": "929ab73b93247a15166ee79e807ccee4f930322d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e5ca16dee39ef7d63e552ff0bf0a2526a1142c92", - "reference": "e5ca16dee39ef7d63e552ff0bf0a2526a1142c92", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/929ab73b93247a15166ee79e807ccee4f930322d", + "reference": "929ab73b93247a15166ee79e807ccee4f930322d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<5.3", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4.26" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4.26|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3921,7 +3615,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/v5.4.48" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.24" }, "funding": [ { @@ -3932,12 +3626,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": "2024-11-20T10:51:57+00:00" + "time": "2025-07-30T17:30:48+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4008,74 +3706,68 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v6.1.11", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "f50159673ec3f4a68b81db4712f4a5bbe47a9442" + "reference": "a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/f50159673ec3f4a68b81db4712f4a5bbe47a9442", - "reference": "f50159673ec3f4a68b81db4712f4a5bbe47a9442", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca", + "reference": "a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca", "shasum": "" }, "require": { - "doctrine/event-manager": "^1|^2", - "doctrine/persistence": "^2|^3", - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "doctrine/dbal": "<2.13.1", + "doctrine/collections": "<1.8", + "doctrine/dbal": "<3.6", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.7.4", - "phpunit/phpunit": "<5.4.3", - "symfony/cache": "<5.4", - "symfony/dependency-injection": "<5.4", - "symfony/form": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/messenger": "<5.4", - "symfony/property-info": "<5.4", - "symfony/security-bundle": "<5.4", - "symfony/security-core": "<6.0", - "symfony/validator": "<5.4" + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "doctrine/collections": "^1.0|^2.0", - "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/doctrine-messenger": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4.9|^6.0.9", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/proxy-manager-bridge": "^5.4|^6.0", - "symfony/security-core": "^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "doctrine/data-fixtures": "", - "doctrine/dbal": "", - "doctrine/orm": "", - "symfony/form": "", - "symfony/property-info": "", - "symfony/validator": "" + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "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" }, "type": "symfony-bridge", "autoload": { @@ -4103,7 +3795,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.1.11" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.2" }, "funding": [ { @@ -4114,43 +3806,39 @@ "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": "2023-01-10T18:53:01+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/dom-crawler", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b57df76f4757a9a8dfbb57ba48d7780cc20776c6" + "reference": "202a37e973b7e789604b96fba6473f74c43da045" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b57df76f4757a9a8dfbb57ba48d7780cc20776c6", - "reference": "b57df76f4757a9a8dfbb57ba48d7780cc20776c6", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/202a37e973b7e789604b96fba6473f74c43da045", + "reference": "202a37e973b7e789604b96fba6473f74c43da045", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "masterminds/html5": "^2.6", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/css-selector": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4178,7 +3866,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.48" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.24" }, "funding": [ { @@ -4189,34 +3877,41 @@ "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-13T14:36:38+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/dotenv", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "08013403089c8a126c968179179b817a552841ab" + "reference": "234b6c602f12b00693f4b0d1054386fb30dfc8ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/08013403089c8a126c968179179b817a552841ab", - "reference": "08013403089c8a126c968179179b817a552841ab", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/234b6c602f12b00693f4b0d1054386fb30dfc8ff", + "reference": "234b6c602f12b00693f4b0d1054386fb30dfc8ff", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4249,7 +3944,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.4.48" + "source": "https://github.com/symfony/dotenv/tree/v6.4.24" }, "funding": [ { @@ -4260,39 +3955,46 @@ "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-27T09:33:00+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/error-handler", - "version": "v6.3.12", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "93a8400a7eaaaf385b2d6f71e30e064baa639629" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/93a8400a7eaaaf385b2d6f71e30e064baa639629", - "reference": "93a8400a7eaaaf385b2d6f71e30e064baa639629", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/var-dumper": "^6.4|^7.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5" + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -4323,7 +4025,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/v6.3.12" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -4334,33 +4036,37 @@ "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-01-23T14:35:58+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.24", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "307a09d8d7228d14a05e5e05b95fffdacab032b2" + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/307a09d8d7228d14a05e5e05b95fffdacab032b2", - "reference": "307a09d8d7228d14a05e5e05b95fffdacab032b2", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -4369,13 +4075,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -4403,7 +4109,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/v6.4.24" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" }, "funding": [ { @@ -4414,16 +4120,12 @@ "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-07-10T08:14:14+00:00" + "time": "2025-04-22T09:11:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4503,21 +4205,21 @@ }, { "name": "symfony/expression-language", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "1ea0adaa53539ea7e70821ae9de49ebe03ae7091" + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/1ea0adaa53539ea7e70821ae9de49ebe03ae7091", - "reference": "1ea0adaa53539ea7e70821ae9de49ebe03ae7091", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/32d2d19c62e58767e6552166c32fb259975d2b23", + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/cache": "^5.4|^6.0|^7.0", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3" }, @@ -4547,7 +4249,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v6.4.24" + "source": "https://github.com/symfony/expression-language/tree/v7.3.2" }, "funding": [ { @@ -4567,29 +4269,29 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:14:14+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", - "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -4617,7 +4319,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.24" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -4637,27 +4339,27 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:14:14+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "73089124388c8510efb8d2d1689285d285937b08" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/73089124388c8510efb8d2d1689285d285937b08", - "reference": "73089124388c8510efb8d2d1689285d285937b08", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.0|^7.0" + "symfony/filesystem": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -4685,7 +4387,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.24" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -4705,7 +4407,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T12:02:45+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/flex", @@ -4777,111 +4479,109 @@ }, { "name": "symfony/framework-bundle", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "3d70f14176422d4d8ee400b6acae4e21f7c25ca2" + "reference": "869b94902dd38f2f33718908f2b5d4868e3b9241" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3d70f14176422d4d8ee400b6acae4e21f7c25ca2", - "reference": "3d70f14176422d4d8ee400b6acae4e21f7c25ca2", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/869b94902dd38f2f33718908f2b5d4868e3b9241", + "reference": "869b94902dd38f2f33718908f2b5d4868e3b9241", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=7.2.5", - "symfony/cache": "^5.2|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.4.44|^6.0.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.4.24|^6.2.11", - "symfony/http-kernel": "^5.4|^6.0", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.4.12|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/routing": "^5.3|^6.0" + "symfony/routing": "^6.4|^7.0" }, "conflict": { "doctrine/annotations": "<1.13.1", - "doctrine/cache": "<1.11", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/asset": "<5.3", - "symfony/console": "<5.2.5|>=7.0", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/form": "<5.2", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<5.4", - "symfony/mime": "<4.4", - "symfony/property-access": "<5.3", - "symfony/property-info": "<4.4", + "symfony/asset": "<5.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4|>=7.0", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", "symfony/runtime": "<5.4.45|>=6.0,<6.4.13|>=7.0,<7.1.6", - "symfony/security-csrf": "<5.3", - "symfony/serializer": "<5.2", - "symfony/service-contracts": ">=3.0", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.3", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.3.11", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.4", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.4", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/workflow": "<6.4" }, "require-dev": { "doctrine/annotations": "^1.13.1|^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.3|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/form": "^5.2|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/mailer": "^5.2|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/notifier": "^5.4|^6.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/console": "^5.4.9|^6.0.9|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/mailer": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.3|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/property-info": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/string": "^5.0|^6.0", - "symfony/translation": "^5.3|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.3.11|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/semaphore": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/string": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/twig": "^2.10|^3.0.4" }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" - }, "type": "symfony-bundle", "autoload": { "psr-4": { @@ -4908,7 +4608,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/v5.4.45" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.24" }, "funding": [ { @@ -4919,44 +4619,51 @@ "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-22T13:05:35+00:00" + "time": "2025-07-30T07:06:12+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.48", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3f38b8af283b830e1363acd79e5bc3412d055341" + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3f38b8af283b830e1363acd79e5bc3412d055341", - "reference": "3f38b8af283b830e1363acd79e5bc3412d055341", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php83": "^1.27" }, - "require-dev": { - "predis/predis": "^1.0|^2.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -4984,7 +4691,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.48" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" }, "funding": [ { @@ -4995,82 +4702,87 @@ "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-13T18:58:02+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "c2dbfc92b851404567160d1ecf3fb7d9b7bde9b0" + "reference": "b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c2dbfc92b851404567160d1ecf3fb7d9b7bde9b0", - "reference": "c2dbfc92b851404567160d1ecf3fb7d9b7bde9b0", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726", + "reference": "b81dcdbe34b8e8f7b3fc7b2a47fa065d5bf30726", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^5.0|^6.0", - "symfony/http-foundation": "^5.4.21|^6.2.7", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", - "symfony/var-dumper": "^4.4.31|^5.4", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, "type": "library", "autoload": { "psr-4": { @@ -5097,7 +4809,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/v5.4.48" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.24" }, "funding": [ { @@ -5108,44 +4820,52 @@ "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-27T12:43:17+00:00" + "time": "2025-07-31T09:23:30+00:00" }, { "name": "symfony/mailer", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f732e1fafdf0f4a2d865e91f1018aaca174aeed9" + "reference": "b4d7fa2c69641109979ed06e98a588d245362062" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f732e1fafdf0f4a2d865e91f1018aaca174aeed9", - "reference": "f732e1fafdf0f4a2d865e91f1018aaca174aeed9", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b4d7fa2c69641109979ed06e98a588d245362062", + "reference": "b4d7fa2c69641109979ed06e98a588d245362062", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2.6|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/http-kernel": "<4.4" + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" }, "require-dev": { - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.2|^7.0", + "symfony/twig-bridge": "^6.2|^7.0" }, "type": "library", "autoload": { @@ -5173,7 +4893,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v5.4.45" + "source": "https://github.com/symfony/mailer/tree/v6.4.24" }, "funding": [ { @@ -5184,29 +4904,33 @@ "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-25T14:11:13+00:00" + "time": "2025-07-24T08:25:04+00:00" }, { "name": "symfony/mime", - "version": "v6.1.11", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2bff58573e81a1df51bf99ad01725428beda1cbc" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2bff58573e81a1df51bf99ad01725428beda1cbc", - "reference": "2bff58573e81a1df51bf99ad01725428beda1cbc", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -5214,15 +4938,18 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4" + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^5.2|^6.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -5254,7 +4981,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.1.11" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -5265,12 +4992,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": "2023-01-10T18:53:01+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/monolog-bridge", @@ -5581,7 +5312,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5640,7 +5371,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -5651,6 +5382,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" @@ -5660,16 +5395,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": { @@ -5718,7 +5453,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": [ { @@ -5729,16 +5464,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-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+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", @@ -5801,7 +5540,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": [ { @@ -5812,6 +5551,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" @@ -5821,7 +5564,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", @@ -5882,7 +5625,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": [ { @@ -5893,6 +5636,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" @@ -5902,7 +5649,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -5963,7 +5710,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": [ { @@ -5974,6 +5721,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" @@ -5982,17 +5733,17 @@ "time": "2024-12-23T08:48:59+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.32.0", + "name": "symfony/polyfill-php80", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -6010,7 +5761,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" @@ -6021,6 +5772,10 @@ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -6030,7 +5785,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6039,7 +5794,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -6050,25 +5805,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-01-02T08:10:11+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "name": "symfony/polyfill-php83", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -6086,7 +5845,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -6097,10 +5856,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -6110,7 +5865,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6119,7 +5874,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -6130,46 +5885,46 @@ "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-01-02T08:10:11+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "name": "symfony/property-access", + "version": "v6.4.24", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/property-access.git", + "reference": "a33acdae7c76f837c1db5465cc3445adf3ace94a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/property-access/zipball/a33acdae7c76f837c1db5465cc3445adf3ace94a", + "reference": "a33acdae7c76f837c1db5465cc3445adf3ace94a", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/property-info": "^5.4|^6.0|^7.0" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } + "require-dev": { + "symfony/cache": "^5.4|^6.0|^7.0" }, + "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Component\\PropertyAccess\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6178,24 +5933,29 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/property-access/tree/v6.4.24" }, "funding": [ { @@ -6206,43 +5966,55 @@ "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-15T12:03:16+00:00" }, { - "name": "symfony/property-access", - "version": "v5.4.45", + "name": "symfony/property-info", + "version": "v6.4.24", "source": { "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "111e7ed617509f1a9139686055d234aad6e388e0" + "url": "https://github.com/symfony/property-info.git", + "reference": "1056ae3621eeddd78d7c5ec074f1c1784324eec6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/111e7ed617509f1a9139686055d234aad6e388e0", - "reference": "111e7ed617509f1a9139686055d234aad6e388e0", + "url": "https://api.github.com/repos/symfony/property-info/zipball/1056ae3621eeddd78d7c5ec074f1c1784324eec6", + "reference": "1056ae3621eeddd78d7c5ec074f1c1784324eec6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/property-info": "^5.2|^6.0" + "php": ">=8.1", + "symfony/string": "^5.4|^6.0|^7.0" }, - "require-dev": { - "symfony/cache": "^4.4|^5.0|^6.0" + "conflict": { + "doctrine/annotations": "<1.12", + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<5.4", + "symfony/dependency-injection": "<5.4|>=6.0,<6.4", + "symfony/serializer": "<5.4" }, - "suggest": { - "psr/cache-implementation": "To cache access methods." + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/serializer": "^5.4|^6.4|^7.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" + "Symfony\\Component\\PropertyInfo\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6254,29 +6026,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Kévin Dunglas", + "email": "dunglas@gmail.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", + "description": "Extracts information about PHP class' properties using metadata of popular sources", "homepage": "https://symfony.com", "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", + "doctrine", + "phpdoc", "property", - "property-path", - "reflection" + "symfony", + "type", + "validator" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v5.4.45" + "source": "https://github.com/symfony/property-info/tree/v6.4.24" }, "funding": [ { @@ -6287,56 +6056,54 @@ "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-25T14:11:13+00:00" + "time": "2025-07-14T16:38:25+00:00" }, { - "name": "symfony/property-info", - "version": "v5.4.48", + "name": "symfony/routing", + "version": "v6.4.24", "source": { "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "a0396295ad585f95fccd690bc6a281e5bd303902" + "url": "https://github.com/symfony/routing.git", + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/a0396295ad585f95fccd690bc6a281e5bd303902", - "reference": "a0396295ad585f95fccd690bc6a281e5bd303902", + "url": "https://api.github.com/repos/symfony/routing/zipball/e4f94e625c8e6f910aa004a0042f7b2d398278f5", + "reference": "e4f94e625c8e6f910aa004a0042f7b2d398278f5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/string": "^5.1|^6.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4" + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" + "doctrine/annotations": "^1.12|^2", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" + "Symfony\\Component\\Routing\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6348,26 +6115,24 @@ ], "authors": [ { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" + "router", + "routing", + "uri", + "url" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v5.4.48" + "source": "https://github.com/symfony/routing/tree/v6.4.24" }, "funding": [ { @@ -6378,57 +6143,53 @@ "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-25T16:14:41+00:00" + "time": "2025-07-15T08:46:37+00:00" }, { - "name": "symfony/routing", - "version": "v5.4.48", + "name": "symfony/runtime", + "version": "v7.3.1", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1" + "url": "https://github.com/symfony/runtime.git", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/dd08c19879a9b37ff14fd30dcbdf99a4cf045db1", - "reference": "dd08c19879a9b37ff14fd30dcbdf99a4cf045db1", + "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" }, "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/dotenv": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", - "psr/log": "^1|^2|^3", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" }, - "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" }, "exclude-from-classmap": [ "/Tests/" @@ -6440,24 +6201,21 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "description": "Enables decoupling PHP applications from global state", "homepage": "https://symfony.com", "keywords": [ - "router", - "routing", - "uri", - "url" + "runtime" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.48" + "source": "https://github.com/symfony/runtime/tree/v7.3.1" }, "funding": [ { @@ -6473,48 +6231,49 @@ "type": "tidelift" } ], - "time": "2024-11-12T18:20:21+00:00" + "time": "2025-06-13T07:48:40+00:00" }, { "name": "symfony/security-core", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "8ff659ffd3b823f0b3969b6c7a602b80b6ec2e53" + "reference": "d8e1bb0de26266e2e4525beda0aed7f774e9c80d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/8ff659ffd3b823f0b3969b6c7a602b80b6ec2e53", - "reference": "8ff659ffd3b823f0b3969b6c7a602b80b6ec2e53", + "url": "https://api.github.com/repos/symfony/security-core/zipball/d8e1bb0de26266e2e4525beda0aed7f774e9c80d", + "reference": "d8e1bb0de26266e2e4525beda0aed7f774e9c80d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^5.4|^6.0|^7.0", + "symfony/password-hasher": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/event-dispatcher": "<5.4", - "symfony/http-foundation": "<5.4", - "symfony/ldap": "<5.4", - "symfony/security-guard": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", - "symfony/validator": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/ldap": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", "symfony/validator": "^6.4|^7.0" }, "type": "library", @@ -6543,7 +6302,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.4.24" + "source": "https://github.com/symfony/security-core/tree/v7.3.2" }, "funding": [ { @@ -6563,36 +6322,31 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:14:14+00:00" + "time": "2025-07-23T09:11:24+00:00" }, { "name": "symfony/security-csrf", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "28dcafc3220f12264bb2aabe2389a2163458c1f4" + "reference": "9a1efc8c10b86bcedc9233affd10c716b54ca1b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/28dcafc3220f12264bb2aabe2389a2163458c1f4", - "reference": "28dcafc3220f12264bb2aabe2389a2163458c1f4", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/9a1efc8c10b86bcedc9233affd10c716b54ca1b7", + "reference": "9a1efc8c10b86bcedc9233affd10c716b54ca1b7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/security-core": "^4.4|^5.0|^6.0" + "php": ">=8.1", + "symfony/security-core": "^5.4|^6.0|^7.0" }, "conflict": { - "symfony/http-foundation": "<5.3" + "symfony/http-foundation": "<5.4" }, "require-dev": { - "symfony/http-foundation": "^5.3|^6.0" - }, - "suggest": { - "symfony/http-foundation": "For using the class SessionTokenStorage." + "symfony/http-foundation": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -6620,7 +6374,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v5.4.45" + "source": "https://github.com/symfony/security-csrf/tree/v6.4.24" }, "funding": [ { @@ -6631,71 +6385,70 @@ "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-25T14:11:13+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/serializer", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "460c5df9fb6c39d10d5b7f386e4feae4b6370221" + "reference": "c01c719c8a837173dc100f2bd141a6271ea68a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/460c5df9fb6c39d10d5b7f386e4feae4b6370221", - "reference": "460c5df9fb6c39d10d5b7f386e4feae4b6370221", + "url": "https://api.github.com/repos/symfony/serializer/zipball/c01c719c8a837173dc100f2bd141a6271ea68a1d", + "reference": "c01c719c8a837173dc100f2bd141a6271ea68a1d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4", + "symfony/dependency-injection": "<5.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4.24|>=6,<6.2.11", - "symfony/uid": "<5.3", - "symfony/yaml": "<4.4" + "symfony/uid": "<5.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/property-access": "^5.4.26|^6.3", - "symfony/property-info": "^5.4.24|^6.2.11", - "symfony/uid": "^5.3|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0", - "symfony/var-exporter": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/cache-implementation": "For using the metadata cache.", - "symfony/config": "For using the XML mapping loader.", - "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/property-info": "To deserialize relations.", - "symfony/var-exporter": "For using the metadata compiler.", - "symfony/yaml": "For using the default YAML mapping loader." + "seld/jsonlint": "^1.10", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.26|^6.3|^7.0", + "symfony/property-info": "^5.4.24|^6.2.11|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -6723,7 +6476,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/v5.4.45" + "source": "https://github.com/symfony/serializer/tree/v6.4.24" }, "funding": [ { @@ -6734,38 +6487,39 @@ "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-25T14:11:13+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.4", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -6773,13 +6527,16 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6806,7 +6563,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -6822,25 +6579,25 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.45", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -6868,7 +6625,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" }, "funding": [ { @@ -6884,24 +6641,24 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "symfony/string", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f0ce0bd36a3accb4a225435be077b4b4875587f4" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f0ce0bd36a3accb4a225435be077b4b4875587f4", - "reference": "f0ce0bd36a3accb4a225435be077b4b4875587f4", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -6911,11 +6668,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "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", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6954,7 +6712,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.24" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -6974,7 +6732,7 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:14:14+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/translation-contracts", @@ -7056,81 +6814,69 @@ }, { "name": "symfony/twig-bridge", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "853a0c9aa40123a9feeb335c865b659d94e49e5d" + "reference": "af9ef04e348f93410c83d04d2806103689a3d924" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/853a0c9aa40123a9feeb335c865b659d94e49e5d", - "reference": "853a0c9aa40123a9feeb335c865b659d94e49e5d", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/af9ef04e348f93410c83d04d2806103689a3d924", + "reference": "af9ef04e348f93410c83d04d2806103689a3d924", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.3", - "symfony/form": "<5.4.21|>=6,<6.2.7", - "symfony/http-foundation": "<5.3", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.2", + "symfony/serializer": "<6.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/console": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.4.20|^7.2.5", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-http": "^4.4|^5.0|^6.0", - "symfony/serializer": "^5.4.35|~6.3.12|^6.4.3", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" }, - "suggest": { - "symfony/asset": "For using the AssetExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/finder": "", - "symfony/form": "For using the FormExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/web-link": "For using the WebLinkExtension", - "symfony/yaml": "For using the YamlExtension" - }, "type": "symfony-bridge", "autoload": { "psr-4": { @@ -7157,7 +6903,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.48" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.24" }, "funding": [ { @@ -7168,36 +6914,39 @@ "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-22T08:19:51+00:00" + "time": "2025-07-26T12:47:35+00:00" }, { "name": "symfony/twig-bundle", - "version": "v6.1.11", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "ecfae619319e56475e671ac04049234c34de3e18" + "reference": "3b48b6e8225495c6d2438828982b4d219ca565ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ecfae619319e56475e671ac04049234c34de3e18", - "reference": "ecfae619319e56475e671ac04049234c34de3e18", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/3b48b6e8225495c6d2438828982b4d219ca565ba", + "reference": "3b48b6e8225495c6d2438828982b4d219ca565ba", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "php": ">=8.1", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^5.4|^6.0", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", "twig/twig": "^2.13|^3.0.4" }, "conflict": { @@ -7205,17 +6954,16 @@ "symfony/translation": "<5.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "symfony/asset": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/web-link": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -7243,7 +6991,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/v6.1.11" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.24" }, "funding": [ { @@ -7254,42 +7002,45 @@ "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": "2023-01-01T08:36:55+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "aa29484ce0544bd69fa9f0df902e5ed7b7fe5034" + "reference": "53205bea27450dc5c65377518b3275e126d45e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/aa29484ce0544bd69fa9f0df902e5ed7b7fe5034", - "reference": "aa29484ce0544bd69fa9f0df902e5ed7b7fe5034", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", + "reference": "53205bea27450dc5c65377518b3275e126d45e75", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -7327,7 +7078,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.24" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" }, "funding": [ { @@ -7347,30 +7098,30 @@ "type": "tidelift" } ], - "time": "2025-07-29T18:40:01+00:00" + "time": "2025-07-29T20:02:46+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "1e742d559fe5b19d0cdc281b1bf0b1fcc243bd35" + "reference": "05b3e90654c097817325d6abd284f7938b05f467" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1e742d559fe5b19d0cdc281b1bf0b1fcc243bd35", - "reference": "1e742d559fe5b19d0cdc281b1bf0b1fcc243bd35", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/05b3e90654c097817325d6abd284f7938b05f467", + "reference": "05b3e90654c097817325d6abd284f7938b05f467", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7408,7 +7159,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.24" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.2" }, "funding": [ { @@ -7428,43 +7179,42 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:14:14+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "4afb0399456b966be92410d2bbd6146cc3ce2174" + "reference": "ae16f886ab3e3ed0a8db07d2a7c4d9d60b1eafcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4afb0399456b966be92410d2bbd6146cc3ce2174", - "reference": "4afb0399456b966be92410d2bbd6146cc3ce2174", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ae16f886ab3e3ed0a8db07d2a7c4d9d60b1eafcd", + "reference": "ae16f886ab3e3ed0a8db07d2a7c4d9d60b1eafcd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.3|^6.0,<6.4", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "php": ">=8.1", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/form": "<4.4", + "symfony/form": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<4.4" + "symfony/messenger": "<5.4", + "symfony/twig-bundle": ">=7.0" }, "require-dev": { - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -7491,8 +7241,11 @@ ], "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/v5.4.48" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.24" }, "funding": [ { @@ -7503,12 +7256,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": "2024-11-19T09:26:40+00:00" + "time": "2025-07-20T15:15:57+00:00" }, { "name": "symfony/webpack-encore-bundle", @@ -7585,31 +7342,28 @@ }, { "name": "symfony/yaml", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a454d47278cc16a5db371fe73ae66a78a633371e" + "reference": "742a8efc94027624b36b10ba58e23d402f961f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a454d47278cc16a5db371fe73ae66a78a633371e", - "reference": "a454d47278cc16a5db371fe73ae66a78a633371e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/742a8efc94027624b36b10ba58e23d402f961f51", + "reference": "742a8efc94027624b36b10ba58e23d402f961f51", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^5.4|^6.0|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -7640,7 +7394,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.45" + "source": "https://github.com/symfony/yaml/tree/v6.4.24" }, "funding": [ { @@ -7651,12 +7405,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": "2024-09-25T14:11:13+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "twig/twig", @@ -9689,31 +9447,27 @@ }, { "name": "symfony/browser-kit", - "version": "v5.4.45", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "03cce39764429e07fbab9b989a1182a24578341d" + "reference": "3537d17782f8c20795b194acb6859071b60c6fac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/03cce39764429e07fbab9b989a1182a24578341d", - "reference": "03cce39764429e07fbab9b989a1182a24578341d", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/3537d17782f8c20795b194acb6859071b60c6fac", + "reference": "3537d17782f8c20795b194acb6859071b60c6fac", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0|^7.0" }, "require-dev": { - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/process": "" + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -9741,7 +9495,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/v5.4.45" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.24" }, "funding": [ { @@ -9752,39 +9506,41 @@ "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-22T13:05:35+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v5.4.48", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "c6839a192ac526a7905980552d5997ea7f738eb2" + "reference": "c7bd97db095cb2f560b675e3fa0ae5ca6a2e5f59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c6839a192ac526a7905980552d5997ea7f738eb2", - "reference": "c6839a192ac526a7905980552d5997ea7f738eb2", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c7bd97db095cb2f560b675e3fa0ae5ca6a2e5f59", + "reference": "c7bd97db095cb2f560b675e3fa0ae5ca6a2e5f59", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=7.1.3" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/polyfill-php81": "^1.27" }, "bin": [ "bin/simple-phpunit" @@ -9824,8 +9580,11 @@ ], "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", + "keywords": [ + "testing" + ], "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v5.4.48" + "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.24" }, "funding": [ { @@ -9836,12 +9595,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": "2024-11-10T21:17:27+00:00" + "time": "2025-07-24T11:44:59+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/bootstrap.php b/config/bootstrap.php deleted file mode 100644 index 55560fb83..000000000 --- a/config/bootstrap.php +++ /dev/null @@ -1,23 +0,0 @@ -=1.2) -if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) { - (new Dotenv(false))->populate($env); -} else { - // load all the .env files - (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); -} - -$_SERVER += $_ENV; -$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; -$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; -$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index b6e91c050..1b51223f0 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,18 +1,24 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: - #esi: ~ - #translator: { fallbacks: ["%locale%"] } - secret: "%env(APP_SECRET)%" - csrf_protection: true - #serializer: { enable_annotations: true } - trusted_hosts: ~ trusted_proxies: '%env(TRUSTED_PROXIES)%' + secret: '%env(APP_SECRET)%' + annotations: false + http_method_override: false + handle_all_throwables: true + + # Enables session support. Note that the session will ONLY be started if you read or write from it. + # Remove or comment this section to explicitly disable session support. session: # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler cookie_lifetime: 604800 # 1 week gc_divisor: 500 # Try to see if this reduces lock timeouts; see https://github.com/symfony/symfony/issues/20619 - fragments: ~ - http_method_override: true + fragments: ~ php_errors: log: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/config/packages/test/framework.yaml b/config/packages/test/framework.yaml deleted file mode 100644 index ee44ae54c..000000000 --- a/config/packages/test/framework.yaml +++ /dev/null @@ -1,4 +0,0 @@ -framework: - test: true - session: - storage_factory_id: session.storage.factory.mock_file diff --git a/config/preload.php b/config/preload.php index a6d909d42..234fbcc21 100644 --- a/config/preload.php +++ b/config/preload.php @@ -2,10 +2,6 @@ declare(strict_types = 1); -if (file_exists(dirname(__DIR__).'/var/cache/prod/srcApp_KernelProdContainer.preload.php')) { - require dirname(__DIR__).'/var/cache/prod/srcApp_KernelProdContainer.preload.php'; -} - if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) { require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php'; } diff --git a/config/routes/dev/framework.yaml b/config/routes/dev/framework.yaml deleted file mode 100644 index bcbbf13d0..000000000 --- a/config/routes/dev/framework.yaml +++ /dev/null @@ -1,3 +0,0 @@ -_errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' - prefix: /_error diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml new file mode 100644 index 000000000..0fc74bbac --- /dev/null +++ b/config/routes/framework.yaml @@ -0,0 +1,4 @@ +when@dev: + _errors: + resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + prefix: /_error diff --git a/config/services.yaml b/config/services.yaml index ab60f8449..c441fa293 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -12,7 +12,7 @@ imports: - { resource: user_group_icons.yaml } # Put parameters here that don't need to change on each machine where the app is deployed -# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration +# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: app.version: '%env(APP_VERSION)%' locale: 'en' diff --git a/i18n/en.json b/i18n/en.json index b9b0f4341..6e3642569 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -204,7 +204,6 @@ "last-year": "Edits in the past 365 days", "latest-action": "Latest logged action", "latest-edit": "Latest edit", - "latest-global-edits": "Latest global edits", "latest-revision": "Latest revision", "limit": "Limit", "links": "Links", diff --git a/i18n/qqq.json b/i18n/qqq.json index 79e3c0670..d7dc2f60b 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -223,7 +223,6 @@ "last-year": "Label for number of edits a user made in the past year. This message should be brief.", "latest-action": "The latest action a user made as shown in the public logs at Special:Log.", "latest-edit": "Latest edit of a user", - "latest-global-edits": "Title for the list of recent edits a user made across all projects.", "latest-revision": "Label for option to use the latest revision of an article in the Authorship tool.", "limit": "Option to specify a limit of results\n{{Identical|Limit}}", "links": "General term for one or more links.\n{{Identical|Links}}", diff --git a/public/index.php b/public/index.php index d0b6e0207..9982c218d 100644 --- a/public/index.php +++ b/public/index.php @@ -1,27 +1,9 @@ handle($request); -$response->send(); -$kernel->terminate($request, $response); +return function (array $context) { + return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); +}; diff --git a/src/Controller/AdminScoreController.php b/src/Controller/AdminScoreController.php index aa621b31a..6a6da1219 100644 --- a/src/Controller/AdminScoreController.php +++ b/src/Controller/AdminScoreController.php @@ -7,7 +7,7 @@ use App\Model\AdminScore; use App\Repository\AdminScoreRepository; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * The AdminScoreController serves the search form and results page of the AdminScore tool. @@ -25,12 +25,11 @@ public function getIndexRoute(): string /** * Display the AdminScore search form. - * @Route("/adminscore", name="AdminScore") - * @Route("/adminscore/index.php", name="AdminScoreIndexPhp") - * @Route("/scottywong tools/adminscore.php", name="AdminScoreLegacy") - * @Route("/adminscore/{project}", name="AdminScoreProject") - * @return Response */ + #[Route('/adminscore', name: 'AdminScore')] + #[Route('/adminscore/index.php', name: 'AdminScoreIndexPhp')] + #[Route('/scottywong tools/adminscore.php', name: 'AdminScoreLegacy')] + #[Route('/adminscore/{project}', name: 'AdminScoreProject')] public function indexAction(): Response { // Redirect if we have a project and user. @@ -48,11 +47,9 @@ public function indexAction(): Response /** * Display the AdminScore results. - * @Route("/adminscore/{project}/{username}", name="AdminScoreResult") - * @param AdminScoreRepository $adminScoreRepo - * @return Response * @codeCoverageIgnore */ + #[Route('/adminscore/{project}/{username}', name: 'AdminScoreResult')] public function resultAction(AdminScoreRepository $adminScoreRepo): Response { $adminScore = new AdminScore($adminScoreRepo, $this->project, $this->user); diff --git a/src/Controller/AdminStatsController.php b/src/Controller/AdminStatsController.php index 8a7ae05cc..371d252bf 100644 --- a/src/Controller/AdminStatsController.php +++ b/src/Controller/AdminStatsController.php @@ -12,7 +12,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * The AdminStatsController serves the search form and results page of the AdminStats tool. @@ -56,24 +56,25 @@ public function defaultDays(): ?int /** * Method for rendering the AdminStats Main Form. * This method redirects if valid parameters are found, making it a valid form endpoint as well. - * @Route( - * "/adminstats", name="AdminStats", - * requirements={"group"="admin|patroller|steward"}, - * defaults={"group"="admin"} - * ) - * @Route( - * "/patrollerstats", name="PatrollerStats", - * requirements={"group"="admin|patroller|steward"}, - * defaults={"group"="patroller"} - * ) - * @Route( - * "/stewardstats", name="StewardStats", - * requirements={"group"="admin|patroller|steward"}, - * defaults={"group"="steward"} - * ) - * @param AdminStatsRepository $adminStatsRepo - * @return Response */ + #[Route( + "/adminstats", + name: "AdminStats", + requirements: ["group" => "admin|patroller|steward"], + defaults: ["group" => "admin"] + )] + #[Route( + "/patrollerstats", + name: "PatrollerStats", + requirements: ["group" => "admin|patroller|steward"], + defaults: ["group" => "patroller"] + )] + #[Route( + "/stewardstats", + name: "StewardStats", + requirements: ["group" => "admin|patroller|steward"], + defaults: ["group" => "steward"] + )] public function indexAction(AdminStatsRepository $adminStatsRepo): Response { $this->getAndSetRequestedActions(); @@ -182,8 +183,6 @@ private function getActionNames(string $group): array /** * Every action in this controller (other than 'index') calls this first. - * @param AdminStatsRepository $adminStatsRepo - * @return AdminStats * @codeCoverageIgnore */ public function setUpAdminStats(AdminStatsRepository $adminStatsRepo): AdminStats @@ -205,17 +204,22 @@ public function setUpAdminStats(AdminStatsRepository $adminStatsRepo): AdminStat /** * Method for rendering the AdminStats results. - * @Route( - * "/{group}stats/{project}/{start}/{end}", name="AdminStatsResult", - * requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="admin|patroller|steward"}, - * defaults={"start"=false, "end"=false, "group"="admin"} - * ) - * @param AdminStatsRepository $adminStatsRepo - * @param UserRightsRepository $userRightsRepo - * @param I18nHelper $i18n - * @return Response * @codeCoverageIgnore */ + #[Route( + "/{group}stats/{project}/{start}/{end}", + name: "AdminStatsResult", + requirements: [ + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "group" => "admin|patroller|steward", + ], + defaults: [ + "start" => false, + "end" => false, + "group" => "admin", + ] + )] public function resultAction( AdminStatsRepository $adminStatsRepo, UserRightsRepository $userRightsRepo, @@ -241,13 +245,6 @@ public function resultAction( /** * Get users of the project that are capable of making admin, patroller, or steward actions. - * @Route( - * "/api/project/{group}_groups/{project}", - * name="ProjectApiAdminsGroups", - * requirements={"group"="admin|patroller|steward"}, - * defaults={"group"="admin"}, - * methods={"GET"} - * ) * @OA\Tag(name="Project API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/Group") @@ -268,10 +265,15 @@ public function resultAction( * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param AdminStatsRepository $adminStatsRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/project/{group}_groups/{project}", + name: "ProjectApiAdminsGroups", + requirements: ["group" => "admin|patroller|steward"], + defaults: ["group" => "admin"], + methods: ["GET"] + )] public function adminsGroupsApiAction(AdminStatsRepository $adminStatsRepo): JsonResponse { $this->recordApiUsage('project/admin_groups'); @@ -289,13 +291,6 @@ public function adminsGroupsApiAction(AdminStatsRepository $adminStatsRepo): Jso /** * Get counts of logged actions by admins, patrollers, or stewards. - * @Route( - * "/api/project/{group}_stats/{project}/{start}/{end}", - * name="ProjectApiAdminStats", - * requirements={"start"="|\d{4}-\d{2}-\d{2}", "end"="|\d{4}-\d{2}-\d{2}", "group"="admin|patroller|steward"}, - * defaults={"start"=false, "end"=false, "group"="admin"}, - * methods={"GET"} - * ) * @OA\Tag(name="Project API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/Group") @@ -328,10 +323,23 @@ public function adminsGroupsApiAction(AdminStatsRepository $adminStatsRepo): Jso * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param AdminStatsRepository $adminStatsRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/project/{group}_stats/{project}/{start}/{end}", + name: "ProjectApiAdminStats", + requirements: [ + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "group" => "admin|patroller|steward", + ], + defaults: [ + "start" => false, + "end" => false, + "group" => "admin", + ], + methods: ["GET"] + )] public function adminStatsApiAction(AdminStatsRepository $adminStatsRepo): JsonResponse { $this->recordApiUsage('project/adminstats'); diff --git a/src/Controller/AuthorshipController.php b/src/Controller/AuthorshipController.php index a4878a8c0..09a291cf2 100644 --- a/src/Controller/AuthorshipController.php +++ b/src/Controller/AuthorshipController.php @@ -8,7 +8,7 @@ use App\Repository\AuthorshipRepository; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the search form and results for the Authorship tool @@ -36,10 +36,9 @@ public function supportedProjects(): array /** * The search form. - * @Route("/authorship", name="Authorship") - * @Route("/authorship/{project}", name="AuthorshipProject") - * @return Response */ + #[Route('/authorship', name: 'Authorship')] + #[Route('/authorship/{project}', name: 'AuthorshipProject')] public function indexAction(): Response { $this->params['target'] = $this->request->query->get('target', ''); @@ -73,29 +72,26 @@ public function indexAction(): Response } /** - * @Route( - * "/articleinfo-authorship/{project}/{page}", - * name="AuthorshipResultLegacy", - * requirements={ - * "page"="(.+?)", - * "target"="|latest|\d+|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"target"="latest"} - * ) - * @Route( - * "/authorship/{project}/{page}/{target}", - * name="AuthorshipResult", - * requirements={ - * "page"="(.+?)", - * "target"="|latest|\d+|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"target"="latest"} - * ) - * @param string $target - * @param AuthorshipRepository $authorshipRepo - * @param RequestStack $requestStack - * @return Response + * The result page. */ + #[Route( + '/authorship/{project}/{page}/{target}', + name: 'AuthorshipResult', + requirements: [ + 'page' => '(.+?)', + 'target' => '|latest|\d+|\d{4}-\d{2}-\d{2}', + ], + defaults: ['target' => 'latest'] + )] + #[Route( + '/articleinfo-authorship/{project}/{page}', + name: 'AuthorshipResultLegacy', + requirements: [ + 'page' => '(.+?)', + 'target' => '|latest|\d+|\d{4}-\d{2}-\d{2}', + ], + defaults: ['target' => 'latest'] + )] public function resultAction( string $target, AuthorshipRepository $authorshipRepo, diff --git a/src/Controller/AutomatedEditsController.php b/src/Controller/AutomatedEditsController.php index 75b52ffba..a617945fe 100644 --- a/src/Controller/AutomatedEditsController.php +++ b/src/Controller/AutomatedEditsController.php @@ -10,9 +10,8 @@ use DateTime; use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the AutomatedEdits tool. @@ -46,13 +45,12 @@ public function tooHighEditCountRoute(): string /** * Display the search form. - * @Route("/autoedits", name="AutoEdits") - * @Route("/automatededits", name="AutoEditsLong") - * @Route("/autoedits/index.php", name="AutoEditsIndexPhp") - * @Route("/automatededits/index.php", name="AutoEditsLongIndexPhp") - * @Route("/autoedits/{project}", name="AutoEditsProject") - * @return Response */ + #[Route("/autoedits", name: "AutoEdits")] + #[Route("/automatededits", name: "AutoEditsLong")] + #[Route("/autoedits/index.php", name: "AutoEditsIndexPhp")] + #[Route("/automatededits/index.php", name: "AutoEditsLongIndexPhp")] + #[Route("/autoedits/{project}", name: "AutoEditsProject")] public function indexAction(): Response { // Redirect if at minimum project and username are provided. @@ -89,8 +87,6 @@ public function indexAction(): Response /** * Set defaults, and instantiate the AutoEdits model. This is called at the top of every view action. - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo * @codeCoverageIgnore */ private function setupAutoEdits(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): void @@ -151,22 +147,20 @@ private function setupAutoEdits(AutoEditsRepository $autoEditsRepo, EditReposito /** * Display the results. - * @Route( - * "/autoedits/{project}/{username}/{namespace}/{start}/{end}/{offset}", name="AutoEditsResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false} - * ) - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + "/autoedits/{project}/{username}/{namespace}/{start}/{end}/{offset}", + name: "AutoEditsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", + ], + defaults: ["namespace" => 0, "start" => false, "end" => false, "offset" => false] + )] public function resultAction(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): Response { // Will redirect back to index if the user has too high of an edit count. @@ -181,23 +175,20 @@ public function resultAction(AutoEditsRepository $autoEditsRepo, EditRepository /** * Get non-automated edits for the given user. - * @Route( - * "/nonautoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}", - * name="NonAutoEditsContributionsResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false} - * ) - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return Response|RedirectResponse * @codeCoverageIgnore */ + #[Route( + "/nonautoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}", + name: "NonAutoEditsContributionsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", + ], + defaults: ["namespace" => 0, "start" => false, "end" => false, "offset" => false] + )] public function nonAutomatedEditsAction(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): Response { $this->setupAutoEdits($autoEditsRepo, $editRepo); @@ -206,23 +197,20 @@ public function nonAutomatedEditsAction(AutoEditsRepository $autoEditsRepo, Edit /** * Get automated edits for the given user using the given tool. - * @Route( - * "/autoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}", - * name="AutoEditsContributionsResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false} - * ) - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + "/autoedits-contributions/{project}/{username}/{namespace}/{start}/{end}/{offset}", + name: "AutoEditsContributionsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", + ], + defaults: ["namespace" => 0, "start" => false, "end" => false, "offset" => false] + )] public function automatedEditsAction(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): Response { $this->setupAutoEdits($autoEditsRepo, $editRepo); @@ -234,7 +222,6 @@ public function automatedEditsAction(AutoEditsRepository $autoEditsRepo, EditRep /** * Get a list of the known automated tools for a project along with their regex/tags/etc. - * @Route("/api/project/automated_tools/{project}", name="ProjectApiAutoEditsTools", methods={"GET"}) * @OA\Tag(name="Project API") * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/XTools/API/Project#Automated_tools") * @OA\Parameter(ref="#/components/parameters/Project") @@ -256,10 +243,9 @@ public function automatedEditsAction(AutoEditsRepository $autoEditsRepo, EditRep * ) * ) * @OA\Response(response=404, ref="#/components/responses/404") - * @param AutoEditsRepository $autoEditsRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route("/api/project/automated_tools/{project}", name: "ProjectApiAutoEditsTools", methods: ["GET"])] public function automatedToolsApiAction(AutoEditsRepository $autoEditsRepo): JsonResponse { $this->recordApiUsage('user/automated_tools'); @@ -270,18 +256,6 @@ public function automatedToolsApiAction(AutoEditsRepository $autoEditsRepo): Jso /** * Get the number of automated edits a user has made. - * @Route( - * "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}", - * name="UserApiAutoEditsCount", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}" - * }, - * defaults={"namespace"="all", "start"=false, "end"=false, "tools"=false}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get the number of edits a user has made using [known semi-automated tools](https://w.wiki/6oKQ), and optionally how many times each tool was used.") @@ -312,11 +286,20 @@ public function automatedToolsApiAction(AutoEditsRepository $autoEditsRepo): Jso * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/automated_editcount/{project}/{username}/{namespace}/{start}/{end}/{tools}", + name: "UserApiAutoEditsCount", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + ], + defaults: ["namespace" => "all", "start" => false, "end" => false, "tools" => false], + methods: ["GET"] + )] public function automatedEditCountApiAction( AutoEditsRepository $autoEditsRepo, EditRepository $editRepo @@ -341,19 +324,6 @@ public function automatedEditCountApiAction( /** * Get non-automated contributions for a user. - * @Route( - * "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}", - * name="UserApiNonAutoEdits", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?" - * }, - * defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false, "limit"=50}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get a list of contributions a user has made without the use of any [known (semi-)automated tools](https://w.wiki/6oKQ). If more results are available than the `limit`, a @@ -386,11 +356,21 @@ public function automatedEditCountApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/nonautomated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}", + name: "UserApiNonAutoEdits", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", + ], + defaults: ["namespace" => 0, "start" => false, "end" => false, "offset" => false, "limit" => 50], + methods: ["GET"] + )] public function nonAutomatedEditsApiAction( AutoEditsRepository $autoEditsRepo, EditRepository $editRepo @@ -410,19 +390,6 @@ public function nonAutomatedEditsApiAction( /** * Get (semi-)automated contributions made by a user. - * @Route( - * "/api/user/automated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}", - * name="UserApiAutoEdits", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", - * }, - * defaults={"namespace"=0, "start"=false, "end"=false, "offset"=false, "limit"=50}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get a list of contributions a user has made using of any of the [known (semi-)automated tools](https://w.wiki/6oKQ). If more results are available than the `limit`, a @@ -458,12 +425,22 @@ public function nonAutomatedEditsApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param AutoEditsRepository $autoEditsRepo - * @param EditRepository $editRepo - * @return Response * @codeCoverageIgnore */ - public function automatedEditsApiAction(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): Response + #[Route( + "/api/user/automated_edits/{project}/{username}/{namespace}/{start}/{end}/{offset}", + name: "UserApiAutoEdits", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", + ], + defaults: ["namespace" => 0, "start" => false, "end" => false, "offset" => false, "limit" => 50], + methods: ["GET"] + )] + public function automatedEditsApiAction(AutoEditsRepository $autoEditsRepo, EditRepository $editRepo): JsonResponse { $this->recordApiUsage('user/automated_edits'); diff --git a/src/Controller/BlameController.php b/src/Controller/BlameController.php index 857569848..418ccc93f 100644 --- a/src/Controller/BlameController.php +++ b/src/Controller/BlameController.php @@ -8,7 +8,7 @@ use App\Model\Blame; use App\Repository\BlameRepository; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller provides the search form and results page for the Blame tool. @@ -36,10 +36,9 @@ public function supportedProjects(): array /** * The search form. - * @Route("/blame", name="Blame") - * @Route("/blame/{project}", name="BlameProject") - * @return Response */ + #[Route("/blame", name: "Blame")] + #[Route("/blame/{project}", name: "BlameProject")] public function indexAction(): Response { $this->params['target'] = $this->request->query->get('target', ''); @@ -72,19 +71,17 @@ public function indexAction(): Response } /** - * @Route( - * "/blame/{project}/{page}/{target}", - * name="BlameResult", - * requirements={ - * "page"="(.+?)", - * "target"="|latest|\d+|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"target"="latest"} - * ) - * @param string $target - * @param BlameRepository $blameRepo - * @return Response + * The results page. */ + #[Route( + "/blame/{project}/{page}/{target}", + name: "BlameResult", + requirements: [ + "page" => "(.+?)", + "target" => "|latest|\d+|\d{4}-\d{2}-\d{2}", + ], + defaults: ["target" => "latest"] + )] public function resultAction(string $target, BlameRepository $blameRepo): Response { if (!isset($this->params['q'])) { diff --git a/src/Controller/CategoryEditsController.php b/src/Controller/CategoryEditsController.php index 625bfbd38..f3aa003e4 100644 --- a/src/Controller/CategoryEditsController.php +++ b/src/Controller/CategoryEditsController.php @@ -10,7 +10,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the Category Edits tool. @@ -45,11 +45,10 @@ public function tooHighEditCountRoute(): string /** * Display the search form. - * @Route("/categoryedits", name="CategoryEdits") - * @Route("/categoryedits/{project}", name="CategoryEditsProject") - * @return Response * @codeCoverageIgnore */ + #[Route(path: '/categoryedits', name: 'CategoryEdits')] + #[Route(path: '/categoryedits/{project}', name: 'CategoryEditsProject')] public function indexAction(): Response { // Redirect if at minimum project, username and categories are provided. @@ -135,22 +134,20 @@ private function extractCategories(): void /** * Display the results. - * @Route( - * "/categoryedits/{project}/{username}/{categories}/{start}/{end}/{offset}", - * name="CategoryEditsResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "categories"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", - * }, - * defaults={"start"=false, "end"=false, "offset"=false} - * ) - * @param CategoryEditsRepository $categoryEditsRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + "/categoryedits/{project}/{username}/{categories}/{start}/{end}/{offset}", + name: "CategoryEditsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "categories" => "(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", + ], + defaults: ["start" => false, "end" => false, "offset" => false] + )] public function resultAction(CategoryEditsRepository $categoryEditsRepo): Response { $this->setupCategoryEdits($categoryEditsRepo); @@ -160,22 +157,20 @@ public function resultAction(CategoryEditsRepository $categoryEditsRepo): Respon /** * Get edits by a user to pages in given categories. - * @Route( - * "/categoryedits-contributions/{project}/{username}/{categories}/{start}/{end}/{offset}", - * name="CategoryContributionsResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "categories"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2}))?", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", - * }, - * defaults={"start"=false, "end"=false, "offset"=false} - * ) - * @param CategoryEditsRepository $categoryEditsRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + "/categoryedits-contributions/{project}/{username}/{categories}/{start}/{end}/{offset}", + name: "CategoryContributionsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "categories" => "(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2}))?", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-?\d{2}-?\d{2}T?\d{2}:?\d{2}:?\d{2}Z?", + ], + defaults: ["start" => false, "end" => false, "offset" => false] + )] public function categoryContributionsAction(CategoryEditsRepository $categoryEditsRepo): Response { $this->setupCategoryEdits($categoryEditsRepo); @@ -187,18 +182,6 @@ public function categoryContributionsAction(CategoryEditsRepository $categoryEdi /** * Count the number of edits a user has made in a category. - * @Route( - * "/api/user/category_editcount/{project}/{username}/{categories}/{start}/{end}", - * name="UserApiCategoryEditCount", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "categories" = "(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start" = "|\d{4}-\d{2}-\d{2}", - * "end" = "|\d{4}-\d{2}-\d{2}" - * }, - * defaults={"start" = false, "end" = false}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Count the number of edits a user has made to pages in any of the given [categories](https://w.wiki/6oKx).") @@ -231,10 +214,20 @@ public function categoryContributionsAction(CategoryEditsRepository $categoryEdi * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param CategoryEditsRepository $categoryEditsRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/category_editcount/{project}/{username}/{categories}/{start}/{end}", + name: "UserApiCategoryEditCount", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "categories" => "(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", + "start" => "|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + ], + defaults: ["start" => false, "end" => false], + methods: ["GET"] + )] public function categoryEditCountApiAction(CategoryEditsRepository $categoryEditsRepo): JsonResponse { $this->recordApiUsage('user/category_editcount'); diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index c0effda89..e5806e1d0 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -17,7 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** @@ -38,12 +38,8 @@ public function getIndexRoute(): string return 'homepage'; } - /** - * Display the homepage. - * @Route("/", name="homepage") - * @Route("/index.php", name="homepageIndexPhp") - * @return Response - */ + #[Route('/', name: 'homepage')] + #[Route('/index.php', name: 'homepageIndexPhp')] public function indexAction(): Response { return $this->render('default/index.html.twig', [ @@ -53,14 +49,8 @@ public function indexAction(): Response /** * Redirect to the default project (or Meta) for Oauth authentication. - * @Route("/login", name="login") - * @param Request $request - * @param RequestStack $requestStack - * @param ProjectRepository $projectRepo - * @param string $centralAuthProject - * @return RedirectResponse - * @codeCoverageIgnore */ + #[Route('/login', name: 'login')] public function loginAction( Request $request, RequestStack $requestStack, @@ -83,14 +73,9 @@ public function loginAction( /** * Receive authentication credentials back from the Oauth wiki. - * @Route("/oauth_callback", name="oauth_callback") - * @Route("/oauthredirector.php", name="old_oauth_callback") - * @param RequestStack $requestStack - * @param ProjectRepository $projectRepo - * @param UrlGeneratorInterface $urlGenerator - * @param string $centralAuthProject - * @return RedirectResponse */ + #[Route('/oauth_callback', name: 'oauth_callback')] + #[Route('/oauthredirector.php', name: 'old_oauth_callback')] public function oauthCallbackAction( RequestStack $requestStack, ProjectRepository $projectRepo, @@ -139,11 +124,6 @@ public function oauthCallbackAction( /** * Get an OAuth client, configured to the default project. * (This shouldn't really be in this class, but oh well.) - * @param Request $request - * @param ProjectRepository $projectRepo - * @param UrlGeneratorInterface $urlGenerator - * @param string $centralAuthProject - * @return Client * @codeCoverageIgnore */ protected function getOauthClient( @@ -182,10 +162,8 @@ protected function getOauthClient( /** * Log out the user and return to the homepage. - * @Route("/logout", name="logout") - * @param RequestStack $requestStack - * @return RedirectResponse */ + #[Route('/logout', name: 'logout')] public function logoutAction(RequestStack $requestStack): RedirectResponse { $requestStack->getSession()->invalidate(); @@ -196,7 +174,6 @@ public function logoutAction(RequestStack $requestStack): RedirectResponse /** * Get domain name, URL, API path and database name for the given project. - * @Route("/api/project/normalize/{project}", name="ProjectApiNormalize", methods={"GET"}) * @OA\Tag(name="Project API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Response( @@ -214,8 +191,8 @@ public function logoutAction(RequestStack $requestStack): RedirectResponse * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @return JsonResponse */ + #[Route('/api/project/normalize/{project}', name: 'ProjectApiNormalize', methods: ['GET'])] public function normalizeProjectApiAction(): JsonResponse { return $this->getFormattedApiResponse([ @@ -228,7 +205,6 @@ public function normalizeProjectApiAction(): JsonResponse /** * Get the localized names for each namespaces of the given project. - * @Route("/api/project/namespaces/{project}", name="ProjectApiNamespaces", methods={"GET"}) * @OA\Tag(name="Project API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Response( @@ -246,8 +222,8 @@ public function normalizeProjectApiAction(): JsonResponse * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @return JsonResponse */ + #[Route('/api/project/namespaces/{project}', name: 'ProjectApiNamespaces', methods: ['GET'])] public function namespacesApiAction(): JsonResponse { return $this->getFormattedApiResponse([ @@ -261,7 +237,6 @@ public function namespacesApiAction(): JsonResponse /** * Get page assessment metadata for a project. - * @Route("/api/project/assessments/{project}", name="ProjectApiAssessments", methods={"GET"}) * @OA\Tag(name="Project API") * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageAssessments") * @OA\Parameter(ref="#/components/parameters/Project") @@ -291,8 +266,8 @@ public function namespacesApiAction(): JsonResponse * ) * ) * @OA\Response(response=404, ref="#/components/responses/404") - * @return JsonResponse */ + #[Route('/api/project/assessments/{project}', name: 'ProjectApiAssessments', methods: ['GET'])] public function projectAssessmentsApiAction(): JsonResponse { return $this->getFormattedApiResponse([ @@ -303,7 +278,6 @@ public function projectAssessmentsApiAction(): JsonResponse /** * Get assessment metadata for all projects. - * @Route("/api/project/assessments", name="ApiAssessmentsConfig", methods={"GET"}) * @OA\Tag(name="Project API") * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageAssessments") * @OA\Response( @@ -336,8 +310,8 @@ public function projectAssessmentsApiAction(): JsonResponse * @OA\Property(property="elapsed_time", ref="#/components/schemas/elapsed_time") * ) * ) - * @return JsonResponse */ + #[Route('/api/project/assessments', name: 'ApiAssessmentsConfig', methods: ['GET'])] public function assessmentsConfigApiAction(): JsonResponse { // Here there is no Project, so we don't use XtoolsController::getFormattedApiResponse(). @@ -354,9 +328,9 @@ public function assessmentsConfigApiAction(): JsonResponse /** * Transform given wikitext to HTML using the XTools parser. Wikitext must be passed in as the query 'wikitext'. - * @Route("/api/project/parser/{project}") * @return JsonResponse Safe HTML. */ + #[Route('/api/project/parser/{project}')] public function wikifyApiAction(): JsonResponse { return new JsonResponse( diff --git a/src/Controller/EditCounterController.php b/src/Controller/EditCounterController.php index 40a3af2b0..1fa9044dd 100644 --- a/src/Controller/EditCounterController.php +++ b/src/Controller/EditCounterController.php @@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * Class EditCounterController @@ -128,12 +128,11 @@ protected function setUpEditCounter( /** * The initial GET request that displays the search form. - * @Route("/ec", name="EditCounter") - * @Route("/ec/index.php", name="EditCounterIndexPhp") - * @Route("/ec/{project}", name="EditCounterProject") - * @return RedirectResponse|Response */ - public function indexAction() + #[Route("/ec", name: "EditCounter")] + #[Route("/ec/index.php", name: "EditCounterIndexPhp")] + #[Route("/ec/{project}", name: "EditCounterProject")] + public function indexAction(): Response|RedirectResponse { if (isset($this->params['project']) && isset($this->params['username'])) { return $this->redirectFromSections(); @@ -235,26 +234,21 @@ private function redirectFromSections(): RedirectResponse /** * Display all results. - * @Route( - * "/ec/{project}/{username}", - * name="EditCounterResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @param AutomatedEditsHelper $autoEditsHelper - * @return Response|RedirectResponse * @codeCoverageIgnore */ + #[Route( + "/ec/{project}/{username}", + name: "EditCounterResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function resultAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, RequestStack $requestStack, AutomatedEditsHelper $autoEditsHelper - ) { + ): Response|RedirectResponse { $this->setUpEditCounter($editCounterRepo, $userRightsRepo, $requestStack, $autoEditsHelper); if (1 === count($this->sections)) { @@ -282,21 +276,15 @@ public function resultAction( /** * Display the general statistics section. - * @Route( - * "/ec-generalstats/{project}/{username}", - * name="EditCounterGeneralStats", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param GlobalContribsRepository $globalContribsRepo - * @param EditRepository $editRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-generalstats/{project}/{username}", + name: "EditCounterGeneralStats", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function generalStatsAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -331,15 +319,14 @@ public function generalStatsAction( /** * Search form for general stats. - * @Route( - * "/ec-generalstats", - * name="EditCounterGeneralStatsIndex", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @return Response */ + #[Route( + "/ec-generalstats", + name: "EditCounterGeneralStatsIndex", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function generalStatsIndexAction(): Response { $this->sections = ['general-stats']; @@ -348,19 +335,15 @@ public function generalStatsIndexAction(): Response /** * Display the namespace totals section. - * @Route( - * "/ec-namespacetotals/{project}/{username}", - * name="EditCounterNamespaceTotals", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-namespacetotals/{project}/{username}", + name: "EditCounterNamespaceTotals", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function namespaceTotalsAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -385,9 +368,8 @@ public function namespaceTotalsAction( /** * Search form for namespace totals. - * @Route("/ec-namespacetotals", name="EditCounterNamespaceTotalsIndex") - * @return Response */ + #[Route("/ec-namespacetotals", name: "EditCounterNamespaceTotalsIndex")] public function namespaceTotalsIndexAction(): Response { $this->sections = ['namespace-totals']; @@ -396,19 +378,15 @@ public function namespaceTotalsIndexAction(): Response /** * Display the timecard section. - * @Route( - * "/ec-timecard/{project}/{username}", - * name="EditCounterTimecard", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-timecard/{project}/{username}", + name: "EditCounterTimecard", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function timecardAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -434,9 +412,8 @@ public function timecardAction( /** * Search form for timecard. - * @Route("/ec-timecard", name="EditCounterTimecardIndex") - * @return Response */ + #[Route("/ec-timecard", name: "EditCounterTimecardIndex")] public function timecardIndexAction(): Response { $this->sections = ['timecard']; @@ -445,19 +422,15 @@ public function timecardIndexAction(): Response /** * Display the year counts section. - * @Route( - * "/ec-yearcounts/{project}/{username}", - * name="EditCounterYearCounts", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-yearcounts/{project}/{username}", + name: "EditCounterYearCounts", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function yearCountsAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -482,9 +455,9 @@ public function yearCountsAction( /** * Search form for year counts. - * @Route("/ec-yearcounts", name="EditCounterYearCountsIndex") * @return Response */ + #[Route("/ec-yearcounts", name: "EditCounterYearCountsIndex")] public function yearCountsIndexAction(): Response { $this->sections = ['year-counts']; @@ -493,19 +466,15 @@ public function yearCountsIndexAction(): Response /** * Display the month counts section. - * @Route( - * "/ec-monthcounts/{project}/{username}", - * name="EditCounterMonthCounts", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-monthcounts/{project}/{username}", + name: "EditCounterMonthCounts", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function monthCountsAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -531,9 +500,8 @@ public function monthCountsAction( /** * Search form for month counts. - * @Route("/ec-monthcounts", name="EditCounterMonthCountsIndex") - * @return Response */ + #[Route("/ec-monthcounts", name: "EditCounterMonthCountsIndex")] public function monthCountsIndexAction(): Response { $this->sections = ['month-counts']; @@ -542,19 +510,15 @@ public function monthCountsIndexAction(): Response /** * Display the user rights changes section. - * @Route( - * "/ec-rightschanges/{project}/{username}", - * name="EditCounterRightsChanges", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * } - * ) - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return Response * @codeCoverageIgnore */ + #[Route( + "/ec-rightschanges/{project}/{username}", + name: "EditCounterRightsChanges", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ] + )] public function rightsChangesAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -582,9 +546,8 @@ public function rightsChangesAction( /** * Search form for rights changes. - * @Route("/ec-rightschanges", name="EditCounterRightsChangesIndex") - * @return Response */ + #[Route("/ec-rightschanges", name: "EditCounterRightsChangesIndex")] public function rightsChangesIndexAction(): Response { $this->sections = ['rights-changes']; @@ -595,14 +558,6 @@ public function rightsChangesIndexAction(): Response /** * Get counts of various log actions made by the user. - * @Route( - * "/api/user/log_counts/{project}/{username}", - * name="UserApiLogCounts", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get counts of various logged actions made by a user. The keys of the returned `log_counts` property describe the log type and log action in the form of _type-action_. @@ -630,12 +585,16 @@ public function rightsChangesIndexAction(): Response * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/log_counts/{project}/{username}", + name: "UserApiLogCounts", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ], + methods: ["GET"] + )] public function logCountsApiAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -651,14 +610,6 @@ public function logCountsApiAction( /** * Get the number of edits made by the user to each namespace. - * @Route( - * "/api/user/namespace_totals/{project}/{username}", - * name="UserApiNamespaceTotals", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get edit counts of a user broken down by [namespace](https://w.wiki/6oKq).") * @OA\Parameter(ref="#/components/parameters/Project") @@ -677,12 +628,16 @@ public function logCountsApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/namespace_totals/{project}/{username}", + name: "UserApiNamespaceTotals", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ], + methods: ["GET"] + )] public function namespaceTotalsApiAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -698,14 +653,6 @@ public function namespaceTotalsApiAction( /** * Get the number of edits made by the user for each month, grouped by namespace. - * @Route( - * "/api/user/month_counts/{project}/{username}", - * name="UserApiMonthCounts", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get the number of edits a user has made grouped by namespace and month.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -734,12 +681,16 @@ public function namespaceTotalsApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/month_counts/{project}/{username}", + name: "UserApiMonthCounts", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ], + methods: ["GET"] + )] public function monthCountsApiAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, @@ -762,14 +713,6 @@ public function monthCountsApiAction( /** * Get the total number of edits made by a user during each hour of day and day of week. - * @Route( - * "/api/user/timecard/{project}/{username}", - * name="UserApiTimeCard", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get the raw number of edits made by a user during each hour of day and day of week. The `scale` is a value that indicates the number of edits made relative to other hours and days of the week.") @@ -795,12 +738,16 @@ public function monthCountsApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param EditCounterRepository $editCounterRepo - * @param UserRightsRepository $userRightsRepo - * @param RequestStack $requestStack - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/timecard/{project}/{username}", + name: "UserApiTimeCard", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + ], + methods: ["GET"] + )] public function timecardApiAction( EditCounterRepository $editCounterRepo, UserRightsRepository $userRightsRepo, diff --git a/src/Controller/EditSummaryController.php b/src/Controller/EditSummaryController.php index 63981c40a..580645048 100644 --- a/src/Controller/EditSummaryController.php +++ b/src/Controller/EditSummaryController.php @@ -9,7 +9,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller handles the Simple Edit Counter tool. @@ -27,11 +27,10 @@ public function getIndexRoute(): string /** * The Edit Summary search form. - * @Route("/editsummary", name="EditSummary") - * @Route("/editsummary/index.php", name="EditSummaryIndexPhp") - * @Route("/editsummary/{project}", name="EditSummaryProject") - * @return Response */ + #[Route('/editsummary', name: 'EditSummary')] + #[Route('/editsummary/index.php', name: 'EditSummaryIndexPhp')] + #[Route('/editsummary/{project}', name: 'EditSummaryProject')] public function indexAction(): Response { // If we've got a project, user, and namespace, redirect to results. @@ -55,20 +54,19 @@ public function indexAction(): Response /** * Display the Edit Summary results - * @Route( - * "/editsummary/{project}/{username}/{namespace}/{start}/{end}", name="EditSummaryResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace"="all", "start"=false, "end"=false} - * ) - * @param EditSummaryRepository $editSummaryRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + "/editsummary/{project}/{username}/{namespace}/{start}/{end}", + name: 'EditSummaryResult', + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}-\d{2}", + ], + defaults: ["namespace" => "all", "start" => false, "end" => false] + )] public function resultAction(EditSummaryRepository $editSummaryRepo): Response { // Instantiate an EditSummary, treating the past 150 edits as 'recent'. @@ -94,17 +92,6 @@ public function resultAction(EditSummaryRepository $editSummaryRepo): Response /** * Get statistics on how many times a user has used edit summaries. - * @Route( - * "/api/user/edit_summaries/{project}/{username}/{namespace}/{start}/{end}", name="UserApiEditSummaries", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace"="all", "start"=false, "end"=false}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get edit summage usage statistics for the user, with a month-by-month breakdown.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -140,10 +127,20 @@ public function resultAction(EditSummaryRepository $editSummaryRepo): Response * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param EditSummaryRepository $editSummaryRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/edit_summaries/{project}/{username}/{namespace}/{start}/{end}", + name: 'UserApiEditSummaries', + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d{4}-\d{2}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}-\d{2}", + ], + defaults: ["namespace" => "all", "start" => false, "end" => false], + methods: ["GET"] + )] public function editSummariesApiAction(EditSummaryRepository $editSummaryRepo): JsonResponse { $this->recordApiUsage('user/edit_summaries'); diff --git a/src/Controller/GlobalContribsController.php b/src/Controller/GlobalContribsController.php index d2808021a..c432fae1f 100644 --- a/src/Controller/GlobalContribsController.php +++ b/src/Controller/GlobalContribsController.php @@ -11,7 +11,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the search form and results for the Global Contributions tool. @@ -40,13 +40,8 @@ public function maxLimit(): int /** * The search form. - * @Route("/globalcontribs", name="GlobalContribs") - * @Route("/ec-latestglobal", name="EditCounterLatestGlobalIndex") - * @Route("/ec-latestglobal-contributions", name="EditCounterLatestGlobalContribsIndex") - * @Route("/ec-latestglobaledits", name="EditCounterLatestGlobalEditsIndex") - * @param string $centralAuthProject - * @return Response */ + #[Route('/globalcontribs', name: 'GlobalContribs')] public function indexAction(string $centralAuthProject): Response { // Redirect if username is given. @@ -74,9 +69,6 @@ public function indexAction(string $centralAuthProject): Response } /** - * @param GlobalContribsRepository $globalContribsRepo - * @param EditRepository $editRepo - * @return GlobalContribs * @codeCoverageIgnore */ public function getGlobalContribs( @@ -99,51 +91,25 @@ public function getGlobalContribs( /** * Display the latest global edits tool. First two routes are legacy. - * @Route( - * "/ec-latestglobal-contributions/{project}/{username}", - * name="EditCounterLatestGlobalContribs", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * defaults={ - * "project"="", - * "namespace"="all" - * } - * ) - * @Route( - * "/ec-latestglobal/{project}/{username}", - * name="EditCounterLatestGlobal", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * }, - * defaults={ - * "project"="", - * "namespace"="all" - * } - * ), - * @Route( - * "/globalcontribs/{username}/{namespace}/{start}/{end}/{offset}", - * name="GlobalContribsResult", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d*|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={ - * "namespace"="all", - * "start"=false, - * "end"=false, - * "offset"=false, - * } - * ), - * @param GlobalContribsRepository $globalContribsRepo - * @param EditRepository $editRepo - * @param string $centralAuthProject - * @return Response * @codeCoverageIgnore */ + #[Route( + "/globalcontribs/{username}/{namespace}/{start}/{end}/{offset}", + name: "GlobalContribsResult", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d*|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", + ], + defaults: [ + "namespace" => "all", + "start" => false, + "end" => false, + "offset" => false, + ] + )] public function resultsAction( GlobalContribsRepository $globalContribsRepo, EditRepository $editRepo, @@ -166,25 +132,6 @@ public function resultsAction( /** * Get global edits made by a user, IP or IP range. - * @Route( - * "/api/user/globalcontribs/{username}/{namespace}/{start}/{end}/{offset}", - * name="UserApiGlobalContribs", - * requirements={ - * "username"="(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d*|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={ - * "namespace"="all", - * "start"=false, - * "end"=false, - * "offset"=false, - * "limit"=50, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get contributions made by a user, IP or IP range across all Wikimedia projects.") * @OA\Parameter(ref="#/components/parameters/UsernameOrIp") @@ -212,12 +159,27 @@ public function resultsAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param GlobalContribsRepository $globalContribsRepo - * @param EditRepository $editRepo - * @param string $centralAuthProject - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + "/api/user/globalcontribs/{username}/{namespace}/{start}/{end}/{offset}", + name: "UserApiGlobalContribs", + requirements: [ + "username" => "(ipr-.+\/\d+[^\/])|([^\/]+)", + "namespace" => "|all|\d+", + "start" => "|\d*|\d{4}-\d{2}-\d{2}", + "end" => "|\d{4}-\d{2}-\d{2}", + "offset" => "|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", + ], + defaults: [ + "namespace" => "all", + "start" => false, + "end" => false, + "offset" => false, + "limit" => 50, + ], + methods: ["GET"] + )] public function resultsApiAction( GlobalContribsRepository $globalContribsRepo, EditRepository $editRepo, diff --git a/src/Controller/LargestPagesController.php b/src/Controller/LargestPagesController.php index 8381578db..0aa45b1a9 100644 --- a/src/Controller/LargestPagesController.php +++ b/src/Controller/LargestPagesController.php @@ -9,7 +9,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the search form and results for the Largest Pages tool. @@ -27,9 +27,8 @@ public function getIndexRoute(): string /** * The search form. - * @Route("/largestpages", name="LargestPages") - * @return Response */ + #[Route(path: '/largestpages', name: 'LargestPages')] public function indexAction(): Response { // Redirect if required params are given. @@ -73,17 +72,13 @@ protected function getLargestPages(LargestPagesRepository $largestPagesRepo): La /** * Display the largest pages on the requested project. - * @Route( - * "/largestpages/{project}/{namespace}", - * name="LargestPagesResult", - * defaults={ - * "namespace"="all" - * } - * ) - * @param LargestPagesRepository $largestPagesRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + path: '/largestpages/{project}/{namespace}', + name: 'LargestPagesResult', + defaults: ['namespace' => 'all'] + )] public function resultsAction(LargestPagesRepository $largestPagesRepo): Response { $ret = [ @@ -99,14 +94,6 @@ public function resultsAction(LargestPagesRepository $largestPagesRepo): Respons /** * Get the largest pages on a project. - * @Route( - * "/api/project/largest_pages/{project}/{namespace}", - * name="ProjectApiLargestPages", - * defaults={ - * "namespace"="all" - * }, - * methods={"GET"} - * ) * @OA\Tag(name="Project API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/Namespace") @@ -141,10 +128,14 @@ public function resultsAction(LargestPagesRepository $largestPagesRepo): Respons * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param LargestPagesRepository $largestPagesRepo - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + path: "/api/project/largest_pages/{project}/{namespace}", + name: "ProjectApiLargestPages", + defaults: ["namespace" => "all"], + methods: ["GET"] + )] public function resultsApiAction(LargestPagesRepository $largestPagesRepo): JsonResponse { $this->recordApiUsage('project/largest_pages'); diff --git a/src/Controller/MetaController.php b/src/Controller/MetaController.php index b0be51493..80beb6734 100644 --- a/src/Controller/MetaController.php +++ b/src/Controller/MetaController.php @@ -11,7 +11,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves everything for the Meta tool. @@ -29,11 +29,10 @@ public function getIndexRoute(): string /** * Display the form. - * @Route("/meta", name="meta") - * @Route("/meta", name="Meta") - * @Route("/meta/index.php", name="MetaIndexPhp") - * @return Response */ + #[Route("/meta", name: "meta")] + #[Route("/meta", name: "Meta")] + #[Route("/meta/index.php", name: "MetaIndexPhp")] public function indexAction(): Response { if (isset($this->params['start']) && isset($this->params['end'])) { @@ -49,19 +48,16 @@ public function indexAction(): Response /** * Display the results. - * @Route( - * "/meta/{start}/{end}/{legacy}", - * name="MetaResult", - * requirements={ - * "start"="\d{4}-\d{2}-\d{2}", - * "end"="\d{4}-\d{2}-\d{2}", - * }, - * ) - * @param ManagerRegistry $managerRegistry - * @param bool $legacy Non-blank value indicates to show stats for legacy XTools - * @return Response * @codeCoverageIgnore */ + #[Route( + "/meta/{start}/{end}/{legacy}", + name: "MetaResult", + requirements: [ + "start" => "\d{4}-\d{2}-\d{2}", + "end" => "\d{4}-\d{2}-\d{2}", + ] + )] public function resultAction(ManagerRegistry $managerRegistry, bool $legacy = false): Response { $db = $legacy ? 'toolsdb' : 'default'; @@ -194,7 +190,6 @@ private function getApiUsageStats(object $client): array /** * Record usage of a particular XTools tool. This is called automatically * in base.html.twig via JavaScript so that it is done asynchronously. - * @Route("/meta/usage/{tool}/{project}/{token}") * @param Request $request * @param ParameterBagInterface $parameterBag * @param ManagerRegistry $managerRegistry @@ -205,6 +200,7 @@ private function getApiUsageStats(object $client): array * @return JsonResponse * @codeCoverageIgnore */ + #[Route("/meta/usage/{tool}/{project}/{token}", name: "RecordMetaUsage")] public function recordUsageAction( Request $request, ParameterBagInterface $parameterBag, diff --git a/src/Controller/PageInfoController.php b/src/Controller/PageInfoController.php index a1825fc28..654b5f7d0 100644 --- a/src/Controller/PageInfoController.php +++ b/src/Controller/PageInfoController.php @@ -15,7 +15,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Twig\Markup; /** @@ -36,12 +36,11 @@ public function getIndexRoute(): string /** * The search form. - * @Route("/pageinfo", name="PageInfo") - * @Route("/pageinfo/{project}", name="PageInfoProject") - * @Route("/articleinfo", name="PageInfoLegacy") - * @Route("/articleinfo/index.php", name="PageInfoLegacyPhp") - * @return Response */ + #[Route('/pageinfo', name: 'PageInfo')] + #[Route('/pageinfo/{project}', name: 'PageInfoProject')] + #[Route('/articleinfo', name: 'PageInfoLegacy')] + #[Route('/articleinfo/index.php', name: 'PageInfoLegacyPhp')] public function indexAction(): Response { if (isset($this->params['project']) && isset($this->params['page'])) { @@ -88,12 +87,10 @@ private function setupPageInfo( * Generate PageInfo gadget script for use on-wiki. This automatically points the * script to this installation's API. * - * @Route("/pageinfo-gadget.js", name="PageInfoGadget") * @link https://www.mediawiki.org/wiki/XTools/PageInfo_gadget - * - * @return Response * @codeCoverageIgnore */ + #[Route('/pageinfo-gadget.js', name: 'PageInfoGadget')] public function gadgetAction(): Response { $rendered = $this->renderView('pageInfo/pageinfo.js.twig'); @@ -104,35 +101,34 @@ public function gadgetAction(): Response /** * Display the results in given date range. - * @Route( - * "/pageinfo/{project}/{page}/{start}/{end}", name="PageInfoResult", - * requirements={ - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "start"=false, - * "end"=false, - * } - * ) - * @Route( - * "/articleinfo/{project}/{page}/{start}/{end}", name="PageInfoResultLegacy", - * requirements={ - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "start"=false, - * "end"=false, - * } - * ) - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return Response * @codeCoverageIgnore */ + #[Route( + '/pageinfo/{project}/{page}/{start}/{end}', + name: 'PageInfoResult', + requirements: [ + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + ] + )] + #[Route( + '/articleinfo/{project}/{page}/{start}/{end}', + name: 'PageInfoResultLegacy', + requirements: [ + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + ] + )] public function resultAction( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper @@ -197,12 +193,8 @@ public function resultAction( /** * Check if there were any revisions of given page in given date range. - * @param Page $page - * @param false|int $start - * @param false|int $end - * @return bool */ - private function isDateRangeValid(Page $page, $start, $end): bool + private function isDateRangeValid(Page $page, false|int $start, false|int $end): bool { return $page->getNumRevisions(null, $start, $end) > 0; } @@ -211,18 +203,6 @@ private function isDateRangeValid(Page $page, $start, $end): bool /** * Get basic information about a page. - * @Route( - * "/api/page/pageinfo/{project}/{page}", - * name="PageApiPageInfo", - * requirements={"page"=".+"}, - * methods={"GET"} - * ) - * @Route( - * "/api/page/articleinfo/{project}/{page}", - * name="PageApiPageInfoLegacy", - * requirements={"page"=".+"}, - * methods={"GET"} - * ) * @OA\Get(description="Get basic information about the history of a page. See also the [pageviews](https://w.wiki/6o9k) and [edit data](https://w.wiki/6o9m) REST APIs.") * @OA\Tag(name="Page API") @@ -262,16 +242,25 @@ private function isDateRangeValid(Page $page, $start, $end): bool * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return Response|JsonResponse * See PageInfoControllerTest::testPageInfoApi() * @codeCoverageIgnore */ + #[Route( + '/api/page/pageinfo/{project}/{page}', + name: 'PageApiPageInfo', + requirements: ['page' => '.+'], + methods: ['GET'] + )] + #[Route( + '/api/page/articleinfo/{project}/{page}', + name: 'PageApiPageInfoLegacy', + requirements: ['page' => '.+'], + methods: ['GET'] + )] public function pageInfoApiAction( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper - ): Response { + ): Response|JsonResponse { $this->recordApiUsage('page/pageinfo'); $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); @@ -279,7 +268,7 @@ public function pageInfoApiAction( try { $data = $this->pageInfo->getPageInfoApiData($this->project, $this->page); - } catch (ServerException $e) { + } catch (ServerException) { // The Wikimedia action API can fail for any number of reasons. To our users // any ServerException means the data could not be fetched, so we capture it here // to avoid the flood of automated emails when the API goes down, etc. @@ -323,12 +312,6 @@ private function getApiHtmlResponse(Project $project, Page $page, array $data): /** * Get prose statistics for the given page. - * @Route( - * "/api/page/prose/{project}/{page}", - * name="PageApiProse", - * requirements={"page"=".+"}, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\ExternalDocumentation(url="https://www.mediawiki.org/wiki/XTools/Page_History#Prose") * @OA\Get(description="Get statistics about the [prose](https://en.wiktionary.org/wiki/prose) (characters, @@ -351,11 +334,14 @@ private function getApiHtmlResponse(Project $project, Page $page, array $data): * ) * ) * @OA\Response(response=404, ref="#/components/responses/404") - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/prose/{project}/{page}', + name: 'PageApiProse', + requirements: ['page' => '.+'], + methods: ['GET'] + )] public function proseStatsApiAction( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper @@ -363,8 +349,6 @@ public function proseStatsApiAction( $responseCode = Response::HTTP_OK; $this->recordApiUsage('page/prose'); $this->setupPageInfo($pageInfoRepo, $autoEditsHelper); - $this->addFlash('info', 'The algorithm used by this API has recently changed. ' . - 'See https://www.mediawiki.org/wiki/XTools/Page_History#Prose for details.'); $ret = $this->pageInfo->getProseStats(); if (null === $ret) { $this->addFlashMessage('error', 'api-error-wikimedia'); @@ -376,12 +360,6 @@ public function proseStatsApiAction( /** * Get the page assessments of one or more pages, along with various related metadata. - * @Route( - * "/api/page/assessments/{project}/{pages}", - * name="PageApiAssessments", - * requirements={"pages"=".+"}, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\Get(description="Get [assessment data](https://w.wiki/6oAM) of the given pages, including the overall quality classifications, along with a list of the WikiProjects and their classifications and importance levels.") @@ -409,10 +387,14 @@ public function proseStatsApiAction( * ) * ) * @OA\Response(response=404, ref="#/components/responses/404") - * @param string $pages May be multiple pages separated by pipes, e.g. Foo|Bar|Baz - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/assessments/{project}/{pages}', + name: 'PageApiAssessments', + requirements: ['pages' => '.+'], + methods: ['GET'] + )] public function assessmentsApiAction(string $pages): JsonResponse { $this->recordApiUsage('page/assessments'); @@ -442,12 +424,6 @@ public function assessmentsApiAction(string $pages): JsonResponse /** * Get number of in and outgoing links, external links, and redirects to the given page. - * @Route( - * "/api/page/links/{project}/{page}", - * name="PageApiLinks", - * requirements={"page"=".+"}, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/Page") @@ -467,9 +443,14 @@ public function assessmentsApiAction(string $pages): JsonResponse * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/links/{project}/{page}', + name: 'PageApiLinks', + requirements: ['page' => '.+'], + methods: ['GET'] + )] public function linksApiAction(): JsonResponse { $this->recordApiUsage('page/links'); @@ -478,21 +459,6 @@ public function linksApiAction(): JsonResponse /** * Get the top editors (by number of edits) of a page. - * @Route( - * "/api/page/top_editors/{project}/{page}/{start}/{end}/{limit}", name="PageApiTopEditors", - * requirements={ - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "limit"="\d+" - * }, - * defaults={ - * "start"=false, - * "end"=false, - * "limit"=20, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/Page") @@ -533,11 +499,24 @@ public function linksApiAction(): JsonResponse * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/top_editors/{project}/{page}/{start}/{end}/{limit}', + name: 'PageApiTopEditors', + requirements: [ + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?(?:\/(\d+))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + 'limit' => '\d+', + ], + defaults: [ + 'start' => false, + 'end' => false, + 'limit' => 20, + ], + methods: ['GET'] + )] public function topEditorsApiAction( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper @@ -557,19 +536,6 @@ public function topEditorsApiAction( /** * Get data about bots that have edited a page. - * @Route( - * "/api/page/bot_data/{project}/{page}/{start}/{end}", name="PageApiBotData", - * requirements={ - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "start"=false, - * "end"=false, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\Get(description="List bots that have edited a page, with edit counts and whether the account is still in the `bot` user group.") @@ -599,11 +565,22 @@ public function topEditorsApiAction( * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/bot_data/{project}/{page}/{start}/{end}', + name: 'PageApiBotData', + requirements: [ + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + ], + methods: ['GET'] + )] public function botDataApiAction( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper @@ -620,19 +597,6 @@ public function botDataApiAction( /** * Get counts of (semi-)automated tools that were used to edit the page. - * @Route( - * "/api/page/automated_edits/{project}/{page}/{start}/{end}", name="PageApiAutoEdits", - * requirements={ - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "start"=false, - * "end"=false, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="Page API") * @OA\Get(description="Get counts of the number of times known (semi-)automated tools were used to edit the page.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -654,11 +618,22 @@ public function botDataApiAction( * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PageInfoRepository $pageInfoRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/page/automated_edits/{project}/{page}/{start}/{end}', + name: 'PageApiAutoEdits', + requirements: [ + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + ], + methods: ['GET'] + )] public function getAutoEdits( PageInfoRepository $pageInfoRepo, AutomatedEditsHelper $autoEditsHelper diff --git a/src/Controller/PagesController.php b/src/Controller/PagesController.php index c5bb5bde6..c3b45fe28 100644 --- a/src/Controller/PagesController.php +++ b/src/Controller/PagesController.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller serves the Pages tool. @@ -51,11 +51,10 @@ public function tooHighEditCountActionAllowlist(): array /** * Display the form. - * @Route("/pages", name="Pages") - * @Route("/pages/index.php", name="PagesIndexPhp") - * @Route("/pages/{project}", name="PagesProject") - * @return Response */ + #[Route('/pages', name: 'Pages')] + #[Route('/pages/index.php', name: 'PagesIndexPhp')] + #[Route('/pages/{project}', name: 'PagesProject')] public function indexAction(): Response { // Redirect if at minimum project and username are given. @@ -109,36 +108,32 @@ protected function setUpPages(PagesRepository $pagesRepo, string $redirects, str /** * Display the results. - * @Route( - * "/pages/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}/{offset}", - * name="PagesResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "redirects"="|[^/]+", - * "deleted"="|all|live|deleted", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={ - * "namespace"=0, - * "start"=false, - * "end"=false, - * "offset"=false, - * } - * ) - * @param PagesRepository $pagesRepo - * @param string $redirects One of the Pages::REDIR_ constants. - * @param string $deleted One of the Pages::DEL_ constants. - * @return RedirectResponse|Response * @codeCoverageIgnore */ + #[Route( + '/pages/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}/{offset}', + name: 'PagesResult', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'redirects' => '|[^/]+', + 'deleted' => '|all|live|deleted', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + 'offset' => '|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?', + ], + defaults: [ + 'namespace' => 0, + 'start' => false, + 'end' => false, + 'offset' => false, + ] + )] public function resultAction( PagesRepository $pagesRepo, string $redirects = Pages::REDIR_NONE, string $deleted = Pages::DEL_ALL - ) { + ): RedirectResponse|Response { // Check for legacy values for 'redirects', and redirect // back with correct values if need be. This could be refactored // out to XtoolsController, but this is the only tool in the suite @@ -172,9 +167,6 @@ public function resultAction( /** * Create a PagePile for the given pages, and get a Redirect to that PagePile. - * @param Project $project - * @param Pages $pages - * @return RedirectResponse * @throws HttpException * @see https://pagepile.toolforge.org * @codeCoverageIgnore @@ -205,8 +197,6 @@ private function getPagepileResult(Project $project, Pages $pages): RedirectResp /** * Create a PagePile with the given titles. - * @param Project $project - * @param string[] $pageTitles * @return int The PagePile ID. * @throws HttpException * @see https://pagepile.toolforge.org/ @@ -222,7 +212,7 @@ private function createPagePile(Project $project, array $pageTitles): int 'wiki' => $project->getDatabaseName(), 'data' => implode("\n", $pageTitles), ]]); - } catch (ClientException $e) { + } catch (ClientException) { throw new HttpException( 414, 'error-pagepile-too-large' @@ -245,26 +235,6 @@ private function createPagePile(Project $project, array $pageTitles): int /** * Count the number of pages created by a user. - * @Route( - * "/api/user/pages_count/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}", - * name="UserApiPagesCount", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|\d+|all", - * "redirects"="|noredirects|onlyredirects|all", - * "deleted"="|all|live|deleted", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "namespace"=0, - * "redirects"="noredirects", - * "deleted"="all", - * "start"=false, - * "end"=false, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get the number of pages created by a user, keyed by namespace.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -304,12 +274,28 @@ private function createPagePile(Project $project, array $pageTitles): int * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PagesRepository $pagesRepo - * @param string $redirects One of 'noredirects', 'onlyredirects' or 'all' for both. - * @param string $deleted One of 'live', 'deleted' or 'all' for both. - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/user/pages_count/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}', + name: 'UserApiPagesCount', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'redirects' => '|noredirects|onlyredirects|all', + 'deleted' => '|all|live|deleted', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'namespace' => 0, + 'redirects' => Pages::REDIR_NONE, + 'deleted' => Pages::DEL_ALL, + 'start' => false, + 'end' => false, + ], + methods: ['GET'] + )] public function countPagesApiAction( PagesRepository $pagesRepo, string $redirects = Pages::REDIR_NONE, @@ -325,28 +311,6 @@ public function countPagesApiAction( /** * Get the pages created by by a user. - * @Route( - * "/api/user/pages/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}/{offset}", - * name="UserApiPagesCreated", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|\d+|all", - * "redirects"="|noredirects|onlyredirects|all", - * "deleted"="|all|live|deleted", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * "offset"="|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?", - * }, - * defaults={ - * "namespace"=0, - * "redirects"="noredirects", - * "deleted"="all", - * "start"=false, - * "end"=false, - * "offset"=false, - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get pages created by a user, keyed by namespace.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -381,12 +345,30 @@ public function countPagesApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param PagesRepository $pagesRepo - * @param string $redirects One of 'noredirects', 'onlyredirects' or 'all' for both. - * @param string $deleted One of 'live', 'deleted' or blank for both. - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/user/pages/{project}/{username}/{namespace}/{redirects}/{deleted}/{start}/{end}/{offset}', + name: 'UserApiPagesCreated', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'redirects' => '|noredirects|onlyredirects|all', + 'deleted' => '|all|live|deleted', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + 'offset' => '|\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?', + ], + defaults: [ + 'namespace' => 0, + 'redirects' => Pages::REDIR_NONE, + 'deleted' => Pages::DEL_ALL, + 'start' => false, + 'end' => false, + 'offset' => false, + ], + methods: ['GET'] + )] public function getPagesApiAction( PagesRepository $pagesRepo, string $redirects = Pages::REDIR_NONE, @@ -406,14 +388,14 @@ public function getPagesApiAction( /** * Get the deletion summary to be shown when hovering over the "Deleted" text in the UI. - * @Route( - * "/pages/deletion_summary/{project}/{username}/{namespace}/{pageTitle}/{timestamp}", - * name="PagesApiDeletionSummary" - * ) - * @return JsonResponse * @codeCoverageIgnore * @internal */ + #[Route( + '/api/pages/deletion_summary/{project}/{namespace}/{pageTitle}/{timestamp}', + name: 'PagesApiDeletionSummary', + methods: ['GET'] + )] public function getDeletionSummaryApiAction( PagesRepository $pagesRepo, int $namespace, diff --git a/src/Controller/QuoteController.php b/src/Controller/QuoteController.php index 981873261..27923358c 100644 --- a/src/Controller/QuoteController.php +++ b/src/Controller/QuoteController.php @@ -9,7 +9,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Exception\InvalidParameterException; /** @@ -34,11 +34,10 @@ public function getIndexRoute(): string /** * Method for rendering the Bash Main Form. This method redirects if valid parameters are found, * making it a valid form endpoint as well. - * @Route("/bash", name="Bash") - * @Route("/quote", name="Quote") - * @Route("/bash/base.php", name="BashBase") - * @return Response */ + #[Route("/bash", name: "Bash")] + #[Route("/quote", name: "Quote")] + #[Route("/bash/base.php", name: "BashBase")] public function indexAction(): Response { // Check to see if the quote is a param. If so, @@ -63,10 +62,9 @@ public function indexAction(): Response /** * Method for rendering a random quote. This should redirect to the /quote/{id} path below. - * @Route("/quote/random", name="QuoteRandom") - * @Route("/bash/random", name="BashRandom") - * @return RedirectResponse */ + #[Route("/quote/random", name: "QuoteRandom")] + #[Route("/bash/random", name: "BashRandom")] public function randomQuoteAction(): RedirectResponse { // Choose a random quote by ID. If we can't find the quotes, return back to @@ -83,10 +81,9 @@ public function randomQuoteAction(): RedirectResponse /** * Method to show all quotes. - * @Route("/quote/all", name="QuoteAll") - * @Route("/bash/all", name="BashAll") - * @return Response */ + #[Route("/quote/all", name: "QuoteAll")] + #[Route("/bash/all", name: "BashAll")] public function quoteAllAction(): Response { // Load up an array of all the quotes. @@ -111,11 +108,9 @@ public function quoteAllAction(): Response /** * Method to render a single quote. - * @param int $id ID of the quote - * @Route("/quote/{id}", name="QuoteID") - * @Route("/bash/{id}", name="BashID") - * @return Response */ + #[Route("/quote/{id}", name: "QuoteID", requirements: ["id" => "\d+"])] + #[Route("/bash/{id}", name: "BashID", requirements: ["id" => "\d+"])] public function quoteAction(int $id): Response { // Get the singular quote. @@ -153,7 +148,6 @@ public function quoteAction(int $id): Response /** * Get random quote. - * @Route("/api/quote/random", name="QuoteApiRandom", methods={"GET"}) * @OA\Tag(name="Quote API") * @OA\Get(description="Get a random quote. The quotes are sourced from [developer quips](https://w.wiki/6rpo) and [IRC quotes](https://meta.wikimedia.org/wiki/IRC/Quotes/archives).") @@ -164,9 +158,9 @@ public function quoteAction(int $id): Response * @OA\Property(property="", type="string") * ) * ) - * @return JsonResponse * @codeCoverageIgnore */ + #[Route("/api/quote/random", name: "QuoteApiRandom", methods: ["GET"])] public function randomQuoteApiAction(): JsonResponse { $this->validateIsEnabled(); @@ -183,7 +177,6 @@ public function randomQuoteApiAction(): JsonResponse /** * Get all quotes. - * @Route("/api/quote/all", name="QuoteApiAll", methods={"GET"}) * @OA\Tag(name="Quote API") * @OA\Get(description="Get a list of all quotes, sourced from [developer quips](https://w.wiki/6rpo) and [IRC quotes](https://meta.wikimedia.org/wiki/IRC/Quotes/archives).") @@ -194,10 +187,10 @@ public function randomQuoteApiAction(): JsonResponse * @OA\Property(property="", type="string") * ) * ) - * @return Response * @codeCoverageIgnore */ - public function allQuotesApiAction(): Response + #[Route("/api/quote/all", name: "QuoteApiAll", methods: ["GET"])] + public function allQuotesApiAction(): JsonResponse { $this->validateIsEnabled(); @@ -215,7 +208,6 @@ public function allQuotesApiAction(): Response /** * Get the quote with the given ID. - * @Route("/api/quote/{id}", name="QuoteApiQuote", requirements={"id"="\d+"}, methods={"GET"}) * @OA\Tag(name="Quote API") * @OA\Get(description="Get a quote with the given ID.") * @OA\Parameter(name="id", in="path", required="true", @OA\Schema(type="integer", minimum=0)) @@ -226,10 +218,9 @@ public function allQuotesApiAction(): Response * @OA\Property(property="", type="string") * ) * ) - * @param int $id - * @return JsonResponse * @codeCoverageIgnore */ + #[Route("/api/quote/{id}", name: "QuoteApiQuote", requirements: ["id" => "\d+"], methods: ["GET"])] public function singleQuotesApiAction(int $id): JsonResponse { $this->validateIsEnabled(); diff --git a/src/Controller/SimpleEditCounterController.php b/src/Controller/SimpleEditCounterController.php index 3e1a6d0e6..7f5413369 100644 --- a/src/Controller/SimpleEditCounterController.php +++ b/src/Controller/SimpleEditCounterController.php @@ -7,8 +7,9 @@ use App\Model\SimpleEditCounter; use App\Repository\SimpleEditCounterRepository; use OpenApi\Annotations as OA; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller handles the Simple Edit Counter tool. @@ -27,11 +28,10 @@ public function getIndexRoute(): string /** * The Simple Edit Counter search form. - * @Route("/sc", name="SimpleEditCounter") - * @Route("/sc/index.php", name="SimpleEditCounterIndexPhp") - * @Route("/sc/{project}", name="SimpleEditCounterProject") - * @return Response */ + #[Route(path: '/sc', name: 'SimpleEditCounter')] + #[Route(path: '/sc/index.php', name: 'SimpleEditCounterIndexPhp')] + #[Route(path: '/sc/{project}', name: 'SimpleEditCounterProject')] public function indexAction(): Response { // Redirect if project and username are given. @@ -73,25 +73,23 @@ private function prepareSimpleEditCounter(SimpleEditCounterRepository $simpleEdi /** * Display the results. - * @Route( - * "/sc/{project}/{username}/{namespace}/{start}/{end}", - * name="SimpleEditCounterResult", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace" = "|all|\d+", - * "start" = "|\d{4}-\d{2}-\d{2}", - * "end" = "|\d{4}-\d{2}-\d{2}", - * }, - * defaults={ - * "start"=false, - * "end"=false, - * "namespace"="all", - * } - * ) - * @param SimpleEditCounterRepository $simpleEditCounterRepo - * @return Response * @codeCoverageIgnore */ + #[Route( + '/sc/{project}/{username}/{namespace}/{start}/{end}', + name: 'SimpleEditCounterResult', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + 'namespace' => 'all', + ] + )] public function resultAction(SimpleEditCounterRepository $simpleEditCounterRepo): Response { $sec = $this->prepareSimpleEditCounter($simpleEditCounterRepo); @@ -107,22 +105,6 @@ public function resultAction(SimpleEditCounterRepository $simpleEditCounterRepo) /** * API endpoint for the Simple Edit Counter. - * @Route( - * "/api/user/simple_editcount/{project}/{username}/{namespace}/{start}/{end}", - * name="SimpleEditCounterApi", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "start" = "|\d{4}-\d{2}-\d{2}", - * "end" = "|\d{4}-\d{2}-\d{2}", - * "namespace" = "|all|\d+" - * }, - * defaults={ - * "start"=false, - * "end"=false, - * "namespace"="all", - * }, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Parameter(ref="#/components/parameters/Project") * @OA\Parameter(ref="#/components/parameters/UsernameOrIp") @@ -149,11 +131,25 @@ public function resultAction(SimpleEditCounterRepository $simpleEditCounterRepo) * @OA\Response(response=404, ref="#/components/responses/404") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param SimpleEditCounterRepository $simpleEditCounterRepository - * @return Response * @codeCoverageIgnore */ - public function simpleEditCounterApiAction(SimpleEditCounterRepository $simpleEditCounterRepository): Response + #[Route( + '/api/user/simple_editcount/{project}/{username}/{namespace}/{start}/{end}', + name: 'SimpleEditCounterApi', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: [ + 'start' => false, + 'end' => false, + 'namespace' => 'all', + ], + methods: ['GET'] + )] + public function simpleEditCounterApiAction(SimpleEditCounterRepository $simpleEditCounterRepository): JsonResponse { $this->recordApiUsage('user/simple_editcount'); $sec = $this->prepareSimpleEditCounter($simpleEditCounterRepository); diff --git a/src/Controller/TopEditsController.php b/src/Controller/TopEditsController.php index b2f753b96..d1515552d 100644 --- a/src/Controller/TopEditsController.php +++ b/src/Controller/TopEditsController.php @@ -11,7 +11,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * The Top Edits tool. @@ -57,12 +57,11 @@ public function restrictedApiActions(): array /** * Display the form. - * @Route("/topedits", name="topedits") - * @Route("/topedits", name="TopEdits") - * @Route("/topedits/index.php", name="TopEditsIndex") - * @Route("/topedits/{project}", name="TopEditsProject") - * @return Response */ + #[Route('/topedits', name: 'topedits')] + #[Route('/topedits', name: 'TopEdits')] + #[Route('/topedits/index.php', name: 'TopEditsIndex')] + #[Route('/topedits/{project}', name: 'TopEditsProject')] public function indexAction(): Response { // Redirect if at minimum project and username are provided. @@ -112,21 +111,19 @@ public function setUpTopEdits(TopEditsRepository $topEditsRepo, AutomatedEditsHe /** * List top edits by this user for all pages in a particular namespace. - * @Route("/topedits/{project}/{username}/{namespace}/{start}/{end}", - * name="TopEditsResultNamespace", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace" = "all", "start"=false, "end"=false} - * ) - * @param TopEditsRepository $topEditsRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return Response * @codeCoverageIgnore */ + #[Route( + '/topedits/{project}/{username}/{namespace}/{start}/{end}', + name: 'TopEditsResultNamespace', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: ['namespace' => 'all', 'start' => false, 'end' => false] + )] public function namespaceTopEditsAction( TopEditsRepository $topEditsRepo, AutomatedEditsHelper $autoEditsHelper @@ -150,23 +147,21 @@ public function namespaceTopEditsAction( /** * List top edits by this user for a particular page. - * @Route("/topedits/{project}/{username}/{namespace}/{page}/{start}/{end}", - * name="TopEditsResultPage", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|all|\d+", - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace"="all", "start"=false, "end"=false} - * ) - * @param TopEditsRepository $topEditsRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return Response * @codeCoverageIgnore * @todo Add pagination. */ + #[Route( + '/topedits/{project}/{username}/{namespace}/{page}/{start}/{end}', + name: 'TopEditsResultPage', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: ['namespace' => 'all', 'start' => false, 'end' => false] + )] public function singlePageTopEditsAction( TopEditsRepository $topEditsRepo, AutomatedEditsHelper $autoEditsHelper @@ -186,17 +181,6 @@ public function singlePageTopEditsAction( /** * Get the most-edited pages by a user. - * @Route("/api/user/top_edits/{project}/{username}/{namespace}/{start}/{end}", - * name="UserApiTopEditsNamespace", - * requirements={ - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|\d+|all", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace"="all", "start"=false, "end"=false}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="List the most-edited pages by a user in one or all namespaces.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -231,11 +215,20 @@ public function singlePageTopEditsAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param TopEditsRepository $topEditsRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore */ + #[Route( + '/api/user/top_edits/{project}/{username}/{namespace}/{start}/{end}', + name: 'UserApiTopEditsNamespace', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: ['namespace' => 'all', 'start' => false, 'end' => false], + methods: ['GET'] + )] public function namespaceTopEditsUserApiAction( TopEditsRepository $topEditsRepo, AutomatedEditsHelper $autoEditsHelper @@ -252,18 +245,6 @@ public function namespaceTopEditsUserApiAction( /** * Get the all edits made by a user to a specific page. - * @Route("/api/user/top_edits/{project}/{username}/{namespace}/{page}/{start}/{end}", - * name="UserApiTopEditsPage", - * requirements = { - * "username" = "(ipr-.+\/\d+[^\/])|([^\/]+)", - * "namespace"="|\d+|all", - * "page"="(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$", - * "start"="|\d{4}-\d{2}-\d{2}", - * "end"="|\d{4}-\d{2}-\d{2}", - * }, - * defaults={"namespace"="all", "start"=false, "end"=false}, - * methods={"GET"} - * ) * @OA\Tag(name="User API") * @OA\Get(description="Get all edits made by a user to a specific page.") * @OA\Parameter(ref="#/components/parameters/Project") @@ -299,12 +280,22 @@ public function namespaceTopEditsUserApiAction( * @OA\Response(response=501, ref="#/components/responses/501") * @OA\Response(response=503, ref="#/components/responses/503") * @OA\Response(response=504, ref="#/components/responses/504") - * @param TopEditsRepository $topEditsRepo - * @param AutomatedEditsHelper $autoEditsHelper - * @return JsonResponse * @codeCoverageIgnore * @todo Add pagination. */ + #[Route( + '/api/user/top_edits/{project}/{username}/{namespace}/{page}/{start}/{end}', + name: 'UserApiTopEditsPage', + requirements: [ + 'username' => '(ipr-.+\/\d+[^\/])|([^\/]+)', + 'namespace' => '|all|\d+', + 'page' => '(.+?)(?!\/(?:|\d{4}-\d{2}-\d{2})(?:\/(|\d{4}-\d{2}-\d{2}))?)?$', + 'start' => '|\d{4}-\d{2}-\d{2}', + 'end' => '|\d{4}-\d{2}-\d{2}', + ], + defaults: ['namespace' => 'all', 'start' => false, 'end' => false], + methods: ['GET'] + )] public function singlePageTopEditsUserApiAction( TopEditsRepository $topEditsRepo, AutomatedEditsHelper $autoEditsHelper diff --git a/src/Controller/XtoolsController.php b/src/Controller/XtoolsController.php index 8133e83bb..6e8a36167 100644 --- a/src/Controller/XtoolsController.php +++ b/src/Controller/XtoolsController.php @@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\HttpException; use Twig\Environment; @@ -39,24 +40,6 @@ */ abstract class XtoolsController extends AbstractController { - /** DEPENDENCIES */ - - protected CacheItemPoolInterface $cache; - protected Client $guzzle; - protected Environment $twig; - protected FlashBagInterface $flashBag; - protected I18nHelper $i18n; - protected ManagerRegistry $managerRegistry; - protected ProjectRepository $projectRepo; - protected UserRepository $userRepo; - protected PageRepository $pageRepo; - - /** @var bool Whether this is a WMF installation. */ - protected bool $isWMF; - - /** @var string The configured default project. */ - protected string $defaultProject; - /** OTHER CLASS PROPERTIES */ /** @var Request The request object. */ @@ -81,16 +64,16 @@ abstract class XtoolsController extends AbstractController protected ?Page $page = null; /** @var int|false Start date parsed from the Request. */ - protected $start = false; + protected int|false $start = false; /** @var int|false End date parsed from the Request. */ - protected $end = false; + protected int|false $end = false; /** @var int|string|null Namespace parsed from the Request, ID as int or 'all' for all namespaces. */ - protected $namespace; + protected int|string|null $namespace; /** @var int|false Unix timestamp. Pagination offset that substitutes for $end. */ - protected $offset = false; + protected int|false $offset = false; /** @var int|null Number of results to return. */ protected ?int $limit = 50; @@ -192,7 +175,6 @@ protected function maxLimit(): int * @param RequestStack $requestStack * @param ManagerRegistry $managerRegistry * @param CacheItemPoolInterface $cache - * @param FlashBagInterface $flashBag * @param Client $guzzle * @param I18nHelper $i18n * @param ProjectRepository $projectRepo @@ -205,31 +187,21 @@ protected function maxLimit(): int public function __construct( ContainerInterface $container, RequestStack $requestStack, - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - FlashBagInterface $flashBag, - Client $guzzle, - I18nHelper $i18n, - ProjectRepository $projectRepo, - UserRepository $userRepo, - PageRepository $pageRepo, - Environment $twig, - bool $isWMF, - string $defaultProject + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected I18nHelper $i18n, + protected ProjectRepository $projectRepo, + protected UserRepository $userRepo, + protected PageRepository $pageRepo, + protected Environment $twig, + /** @var bool Whether this is a WMF installation. */ + protected bool $isWMF, + /** @var string The configured default project. */ + protected string $defaultProject, ) { $this->container = $container; $this->request = $requestStack->getCurrentRequest(); - $this->managerRegistry = $managerRegistry; - $this->cache = $cache; - $this->flashBag = $flashBag; - $this->guzzle = $guzzle; - $this->i18n = $i18n; - $this->projectRepo = $projectRepo; - $this->userRepo = $userRepo; - $this->pageRepo = $pageRepo; - $this->twig = $twig; - $this->isWMF = $isWMF; - $this->defaultProject = $defaultProject; $this->params = $this->parseQueryParams(); // Parse out the name of the controller and action. @@ -577,7 +549,7 @@ private function handleHasManyEdits(User $user): void // Clear flash bag for API responses, since they get intercepted in ExceptionListener // and would otherwise be shown in subsequent requests. if ($this->isApi) { - $this->flashBag->clear(); + $this->getFlashBag()?->clear(); } throw new XtoolsHttpException( @@ -927,11 +899,11 @@ public function getFormattedApiResponse(array $data, int $responseCode = Respons ], $data); // Merge in flash messages, putting them at the top. - $flashes = $this->flashBag->peekAll(); + $flashes = $this->getFlashBag()?->peekAll() ?? []; $ret = array_merge($flashes, $ret); // Flashes now can be cleared after merging into the response. - $this->flashBag->clear(); + $this->getFlashBag()?->clear(); // Normalize path param values. $ret = self::normalizeApiProperties($ret); @@ -1042,6 +1014,18 @@ public function recordApiUsage(string $endpoint): void } } + /** + * Get the FlashBag instance from the current session, if available. + * @return ?FlashBagInterface + */ + public function getFlashBag(): ?FlashBagInterface + { + if ($this->request->getSession() instanceof FlashBagAwareSessionInterface) { + return $this->request->getSession()->getFlashBag(); + } + return null; + } + /** * Add a flash message. * @param string $type diff --git a/src/EventSubscriber/DisabledToolSubscriber.php b/src/EventSubscriber/DisabledToolSubscriber.php index 41bbbaf24..f4deb37d5 100644 --- a/src/EventSubscriber/DisabledToolSubscriber.php +++ b/src/EventSubscriber/DisabledToolSubscriber.php @@ -17,16 +17,12 @@ */ class DisabledToolSubscriber implements EventSubscriberInterface { - - protected ParameterBagInterface $parameterBag; - /** * Save the container for later use. * @param ParameterBagInterface $parameterBag */ - public function __construct(ParameterBagInterface $parameterBag) + public function __construct(protected ParameterBagInterface $parameterBag) { - $this->parameterBag = $parameterBag; } /** diff --git a/src/EventSubscriber/ExceptionListener.php b/src/EventSubscriber/ExceptionListener.php index f7123f09a..409029c0e 100644 --- a/src/EventSubscriber/ExceptionListener.php +++ b/src/EventSubscriber/ExceptionListener.php @@ -11,6 +11,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -26,10 +27,7 @@ */ class ExceptionListener { - protected Environment $templateEngine; - protected FlashBagInterface $flashBag; - protected I18nHelper $i18n; - protected LoggerInterface $logger; + protected ?FlashBagInterface $flashBag; /** @var string The environment. */ protected string $environment; @@ -38,21 +36,17 @@ class ExceptionListener * Constructor for the ExceptionListener. * @param Environment $templateEngine * @param LoggerInterface $logger - * @param FlashBagInterface $flashBag * @param I18nHelper $i18n * @param string $environment */ public function __construct( - Environment $templateEngine, - LoggerInterface $logger, - FlashBagInterface $flashBag, - I18nHelper $i18n, + protected Environment $templateEngine, + RequestStack $requestStack, + protected LoggerInterface $logger, + protected I18nHelper $i18n, string $environment = 'prod' ) { - $this->templateEngine = $templateEngine; - $this->logger = $logger; - $this->flashBag = $flashBag; - $this->i18n = $i18n; + $this->flashBag = $requestStack->getSession()?->getFlashBag(); $this->environment = $environment; } @@ -68,7 +62,7 @@ public function onKernelException(ExceptionEvent $event): void // We only care about the previous (original) exception, not the one Twig put on top of it. $prevException = $exception->getPrevious(); - $isApi = '/api/' === substr($event->getRequest()->getRequestUri(), 0, 5); + $isApi = str_starts_with($event->getRequest()->getRequestUri(), '/api/'); if ($exception instanceof XtoolsHttpException && !$isApi) { $response = $this->getXtoolsHttpResponse($exception); @@ -111,9 +105,9 @@ public function onKernelException(ExceptionEvent $event): void private function getXtoolsHttpResponse(XtoolsHttpException $exception) { if ($exception->isApi()) { - $this->flashBag->add('error', $exception->getMessage()); - $flashes = $this->flashBag->peekAll(); - $this->flashBag->clear(); + $this->flashBag?->add('error', $exception->getMessage()); + $flashes = $this->flashBag?->peekAll() ?? []; + $this->flashBag?->clear(); return new JsonResponse(array_merge( array_merge($flashes, FlattenException::createFromThrowable($exception)->toArray()), $exception->getParams() diff --git a/src/EventSubscriber/RateLimitSubscriber.php b/src/EventSubscriber/RateLimitSubscriber.php index 6d3e70180..f31e28a8f 100644 --- a/src/EventSubscriber/RateLimitSubscriber.php +++ b/src/EventSubscriber/RateLimitSubscriber.php @@ -13,7 +13,6 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; @@ -36,20 +35,7 @@ class RateLimitSubscriber implements EventSubscriberInterface 'showAction', ]; - protected CacheItemPoolInterface $cache; - protected I18nHelper $i18n; - protected LoggerInterface $crawlerLogger; - protected LoggerInterface $denylistLogger; - protected LoggerInterface $rateLimitLogger; - protected ParameterBagInterface $parameterBag; protected Request $request; - protected SessionInterface $session; - - /** @var int Number of requests allowed in time period */ - protected int $rateLimit; - - /** @var int Number of minutes during which $rateLimit requests are permitted. */ - protected int $rateDuration; /** @var string User agent string. */ protected string $userAgent; @@ -72,25 +58,18 @@ class RateLimitSubscriber implements EventSubscriberInterface * @param int $rateDuration */ public function __construct( - I18nHelper $i18n, - CacheItemPoolInterface $cache, - ParameterBagInterface $parameterBag, - RequestStack $requestStack, - LoggerInterface $crawlerLogger, - LoggerInterface $denylistLogger, - LoggerInterface $rateLimitLogger, - int $rateLimit, - int $rateDuration + protected I18nHelper $i18n, + protected CacheItemPoolInterface $cache, + protected ParameterBagInterface $parameterBag, + protected RequestStack $requestStack, + protected LoggerInterface $crawlerLogger, + protected LoggerInterface $denylistLogger, + protected LoggerInterface $rateLimitLogger, + /** @var int Number of requests allowed in time period */ + protected int $rateLimit, + /** @var int Number of minutes during which $rateLimit requests are permitted. */ + protected int $rateDuration ) { - $this->i18n = $i18n; - $this->cache = $cache; - $this->parameterBag = $parameterBag; - $this->session = $requestStack->getSession(); - $this->crawlerLogger = $crawlerLogger; - $this->denylistLogger = $denylistLogger; - $this->rateLimitLogger = $rateLimitLogger; - $this->rateLimit = $rateLimit; - $this->rateDuration = $rateDuration; } /** @@ -135,7 +114,7 @@ public function onKernelController(ControllerEvent $event): void return; } - $loggedIn = (bool)$this->session->get('logged_in_user'); + $loggedIn = (bool)$this->request->getSession()->get('logged_in_user'); $isApi = 'ApiAction' === substr($action, -9); // No rate limits on lightweight pages, logged in users, subrequests or API requests. diff --git a/src/Exception/BadGatewayException.php b/src/Exception/BadGatewayException.php index 4d0f2d181..fa9431be6 100644 --- a/src/Exception/BadGatewayException.php +++ b/src/Exception/BadGatewayException.php @@ -14,16 +14,13 @@ */ class BadGatewayException extends HttpException { - protected array $msgParams; - /** * @param string $msgKey i18n key * @param array $msgParams Params for i18n message, if applicable. * @param Throwable|null $e */ - public function __construct(string $msgKey, array $msgParams = [], ?Throwable $e = null) + public function __construct(string $msgKey, protected array $msgParams = [], ?Throwable $e = null) { - $this->msgParams = $msgParams; parent::__construct(Response::HTTP_BAD_GATEWAY, $msgKey, $e); } diff --git a/src/Exception/XtoolsHttpException.php b/src/Exception/XtoolsHttpException.php index ccc0e481f..0ef9a555c 100644 --- a/src/Exception/XtoolsHttpException.php +++ b/src/Exception/XtoolsHttpException.php @@ -12,15 +12,6 @@ */ class XtoolsHttpException extends HttpException { - /** @var string What URL to redirect to. */ - protected string $redirectUrl; - - /** @var array The params to pass in with the URL. */ - protected array $params; - - /** @var bool Whether the exception was thrown as part of an API request. */ - protected bool $api; - /** * XtoolsHttpException constructor. * @param string $message @@ -31,14 +22,14 @@ class XtoolsHttpException extends HttpException */ public function __construct( string $message, - string $redirectUrl, - array $params = [], - bool $api = false, + /** @var string What URL to redirect to. */ + protected string $redirectUrl, + /** @var array The params to pass in with the URL. */ + protected array $params = [], + /** @var bool Whether the exception was thrown as part of an API request. */ + protected bool $api = false, int $statusCode = Response::HTTP_NOT_FOUND ) { - $this->redirectUrl = $redirectUrl; - $this->params = $params; - $this->api = $api; parent::__construct($statusCode, $message); } diff --git a/src/Helper/AutomatedEditsHelper.php b/src/Helper/AutomatedEditsHelper.php index 43b4c43b2..fd54d3093 100644 --- a/src/Helper/AutomatedEditsHelper.php +++ b/src/Helper/AutomatedEditsHelper.php @@ -9,15 +9,12 @@ use MediaWiki\OAuthClient\Client; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Helper class for fetching semi-automated definitions. */ class AutomatedEditsHelper { - protected SessionInterface $session; - /** @var array The list of tools that are considered reverting. */ protected array $revertTools = []; @@ -31,11 +28,10 @@ class AutomatedEditsHelper * @param \GuzzleHttp\Client $guzzle */ public function __construct( - RequestStack $requestStack, + protected RequestStack $requestStack, protected CacheItemPoolInterface $cache, protected \GuzzleHttp\Client $guzzle ) { - $this->session = $requestStack->getSession(); } /** @@ -93,12 +89,12 @@ public function getConfig(bool $useSandbox = false): array 'titles' => 'MediaWiki:XTools-AutoEdits.json' . ($useSandbox ? '/sandbox' : ''), ]); - if ($useSandbox && $this->session->get('logged_in_user')) { + if ($useSandbox && $this->requestStack->getSession()->get('logged_in_user')) { // Request via OAuth to get around server-side caching. /** @var Client $client */ - $client = $this->session->get('oauth_client'); + $client = $this->requestStack->getSession()->get('oauth_client'); $resp = json_decode($client->makeOAuthCall( - $this->session->get('oauth_access_token'), + $this->requestStack->getSession()->get('oauth_access_token'), $uri )); } else { diff --git a/src/Helper/I18nHelper.php b/src/Helper/I18nHelper.php index 5ef69d847..66110fd3f 100644 --- a/src/Helper/I18nHelper.php +++ b/src/Helper/I18nHelper.php @@ -19,13 +19,11 @@ */ class I18nHelper { - private string $projectDir; protected ContainerInterface $container; protected Intuition $intuition; protected IntlDateFormatter $dateFormatter; protected NumberFormatter $numFormatter; protected NumberFormatter $percentFormatter; - protected RequestStack $requestStack; /** * Constructor for the I18nHelper. @@ -33,11 +31,9 @@ class I18nHelper * @param string $projectDir */ public function __construct( - RequestStack $requestStack, - string $projectDir + protected RequestStack $requestStack, + private readonly string $projectDir ) { - $this->requestStack = $requestStack; - $this->projectDir = $projectDir; } /** @@ -207,7 +203,7 @@ public function msgIfExists(?string $message, array $vars = []): string * @param int $decimals Number of decimals to format to. * @return string */ - public function numberFormat($number, int $decimals = 0): string + public function numberFormat(int|float $number, int $decimals = 0): string { $lang = $this->getLangForTranslatingNumerals(); if (!isset($this->numFormatter)) { @@ -226,7 +222,7 @@ public function numberFormat($number, int $decimals = 0): string * @param integer $precision Number of decimal places to show. * @return string Formatted percentage. */ - public function percentFormat($numerator, ?int $denominator = null, int $precision = 1): string + public function percentFormat(int|float $numerator, ?int $denominator = null, int $precision = 1): string { $lang = $this->getLangForTranslatingNumerals(); if (!isset($this->percentFormatter)) { @@ -255,7 +251,7 @@ public function percentFormat($numerator, ?int $denominator = null, int $precisi * @see http://userguide.icu-project.org/formatparse/datetime * @return string */ - public function dateFormat($datetime, string $pattern = 'yyyy-MM-dd HH:mm'): string + public function dateFormat(string|int|DateTime $datetime, string $pattern = 'yyyy-MM-dd HH:mm'): string { $lang = $this->getLangForTranslatingNumerals(); if (!isset($this->dateFormatter)) { diff --git a/src/Kernel.php b/src/Kernel.php index 6bcc733d9..433f634f0 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -5,62 +5,9 @@ namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; -use Symfony\Component\Config\Loader\LoaderInterface; -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; -use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; -/** - * Class Kernel. Generated by Symfony. - */ class Kernel extends BaseKernel { use MicroKernelTrait; - - private const CONFIG_EXTS = '.{php,xml,yaml,yml}'; - - public function __construct(string $environment, bool $debug) - { - date_default_timezone_set('UTC'); - parent::__construct($environment, $debug); - } - - public function registerBundles(): iterable - { - $contents = require $this->getProjectDir().'/config/bundles.php'; - foreach ($contents as $class => $envs) { - if ($envs[$this->environment] ?? $envs['all'] ?? false) { - yield new $class(); - } - } - } - - public function getProjectDir(): string - { - return \dirname(__DIR__); - } - - protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void - { - $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug); - $container->setParameter('container.dumper.inline_factories', true); - $confDir = $this->getProjectDir().'/config'; - - $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); - $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob'); - $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); - $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); - } - - protected function configureRoutes(RoutingConfigurator $routes): void - { - $routes->import('../config/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS); - $routes->import('../config/{routes}/*'.self::CONFIG_EXTS); - - if (is_file($this->getProjectDir().'/config/routes.yaml')) { - $routes->import('../config/routes.yaml'); - } - } } diff --git a/src/Model/AdminScore.php b/src/Model/AdminScore.php index 47fc6f34a..780187d8f 100644 --- a/src/Model/AdminScore.php +++ b/src/Model/AdminScore.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\AdminScoreRepository; +use App\Repository\Repository; use DateTime; /** @@ -41,15 +42,15 @@ class AdminScore extends Model /** * AdminScore constructor. - * @param AdminScoreRepository $repository + * @param Repository|AdminScoreRepository $repository * @param Project $project - * @param User $user + * @param ?User $user */ - public function __construct(AdminScoreRepository $repository, Project $project, User $user) - { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; + public function __construct( + protected Repository|AdminScoreRepository $repository, + protected Project $project, + protected ?User $user + ) { } /** diff --git a/src/Model/AdminStats.php b/src/Model/AdminStats.php index 4571b0747..c5ec2dbd0 100644 --- a/src/Model/AdminStats.php +++ b/src/Model/AdminStats.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\AdminStatsRepository; +use App\Repository\Repository; /** * AdminStats returns information about users with rights defined in admin_stats.yaml. @@ -24,35 +25,25 @@ class AdminStats extends Model /** @var string[] Usernames of users who are in the relevant user group (sysop for admins, etc.). */ private array $usersInGroup = []; - /** @var string Type that we're getting stats for (admin, patroller, steward, etc.). See admin_stats.yaml */ - private string $type; - - /** @var string[] Which actions to show ('block', 'protect', etc.) */ - private array $actions; - /** * AdminStats constructor. - * @param AdminStatsRepository $repository + * @param Repository|AdminStatsRepository $repository * @param Project $project - * @param int $start as UTC timestamp. - * @param int $end as UTC timestamp. - * @param string $group Which user group to get stats for. Refer to admin_stats.yaml for possible values. + * @param false|int $start as UTC timestamp. + * @param false|int $end as UTC timestamp. + * @param string $type Which user group to get stats for. Refer to admin_stats.yaml for possible values. * @param string[] $actions Which actions to query for ('block', 'protect', etc.). Null for all actions. */ public function __construct( - AdminStatsRepository $repository, - Project $project, - int $start, - int $end, - string $group, - array $actions + protected Repository|AdminStatsRepository $repository, + protected Project $project, + protected false|int $start, + protected false|int $end, + /** @var string Type that we're getting stats for (admin, patroller, steward, etc.). See admin_stats.yaml */ + private string $type, + /** @var string[] Which actions to show ('block', 'protect', etc.) */ + private array $actions ) { - $this->repository = $repository; - $this->project = $project; - $this->start = $start; - $this->end = $end; - $this->type = $group; - $this->actions = $actions; } /** diff --git a/src/Model/Authorship.php b/src/Model/Authorship.php index 0650a9ae4..d67d7fabc 100644 --- a/src/Model/Authorship.php +++ b/src/Model/Authorship.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\AuthorshipRepository; +use App\Repository\Repository; use DateTime; use GuzzleHttp\Exception\RequestException; @@ -39,20 +40,17 @@ class Authorship extends Model /** * Authorship constructor. - * @param AuthorshipRepository $repository - * @param Page $page The page to process. - * @param string|null $target Either a revision ID or date in YYYY-MM-DD format. Null to use latest revision. - * @param int|null $limit Max number of results. + * @param Repository|AuthorshipRepository $repository + * @param ?Page $page The page to process. + * @param ?string $target Either a revision ID or date in YYYY-MM-DD format. Null to use latest revision. + * @param ?int $limit Max number of results. */ public function __construct( - AuthorshipRepository $repository, - Page $page, + protected Repository|AuthorshipRepository $repository, + protected ?Page $page, ?string $target = null, - ?int $limit = null + protected ?int $limit = null ) { - $this->repository = $repository; - $this->page = $page; - $this->limit = $limit; $this->target = $this->getTargetRevId($target); } @@ -163,7 +161,7 @@ protected function getRevisionData(bool $returnRevId = false): ?array { try { $ret = $this->repository->getData($this->page, $this->target, $returnRevId); - } catch (RequestException $e) { + } catch (RequestException) { $this->data = [ 'error' => 'unknown', ]; @@ -297,7 +295,7 @@ private function countTokens(array $tokens): array $editor = $token['editor']; // IPs are prefixed with '0|', otherwise it's the user ID. - if ('0|' === substr($editor, 0, 2)) { + if (str_starts_with($editor, '0|')) { $editor = substr($editor, 2); } else { $userIds[] = $editor; diff --git a/src/Model/AutoEdits.php b/src/Model/AutoEdits.php index fbae27bb5..e516f7c1a 100644 --- a/src/Model/AutoEdits.php +++ b/src/Model/AutoEdits.php @@ -7,6 +7,7 @@ use App\Repository\AutoEditsRepository; use App\Repository\EditRepository; use App\Repository\PageRepository; +use App\Repository\Repository; use App\Repository\UserRepository; /** @@ -14,13 +15,6 @@ */ class AutoEdits extends Model { - protected EditRepository $editRepo; - protected PageRepository $pageRepo; - protected UserRepository $userRepo; - - /** @var null|string The tool we're searching for when fetching (semi-)automated edits. */ - protected ?string $tool; - /** @var Edit[] The list of non-automated contributions. */ protected array $nonAutomatedEdits; @@ -44,44 +38,34 @@ class AutoEdits extends Model /** * Constructor for the AutoEdits class. - * @param AutoEditsRepository $repository + * @param Repository|AutoEditsRepository $repository * @param EditRepository $editRepo * @param PageRepository $pageRepo * @param UserRepository $userRepo * @param Project $project - * @param User $user + * @param ?User $user * @param int|string $namespace Namespace ID or 'all' * @param false|int $start Start date as Unix timestamp. * @param false|int $end End date as Unix timestamp. - * @param null $tool The tool we're searching for when fetching (semi-)automated edits. + * @param ?string $tool The tool we're searching for when fetching (semi-)automated edits. * @param false|int $offset Unix timestamp. Used for pagination. * @param int|null $limit Number of results to return. */ public function __construct( - AutoEditsRepository $repository, - EditRepository $editRepo, - PageRepository $pageRepo, - UserRepository $userRepo, - Project $project, - User $user, - $namespace = 0, - $start = false, - $end = false, - $tool = null, - $offset = false, + protected Repository|AutoEditsRepository $repository, + protected EditRepository $editRepo, + protected PageRepository $pageRepo, + protected UserRepository $userRepo, + protected Project $project, + protected ?User $user, + protected int|string $namespace = 0, + protected false|int $start = false, + protected false|int $end = false, + /** @var ?string The tool we're searching for when fetching (semi-)automated edits. */ + protected ?string $tool = null, + protected false|int $offset = false, ?int $limit = self::RESULTS_PER_PAGE ) { - $this->repository = $repository; - $this->editRepo = $editRepo; - $this->pageRepo = $pageRepo; - $this->userRepo = $userRepo; - $this->project = $project; - $this->user = $user; - $this->namespace = $namespace; - $this->start = $start; - $this->end = $end; - $this->tool = $tool; - $this->offset = $offset; $this->limit = $limit ?? self::RESULTS_PER_PAGE; } diff --git a/src/Model/Blame.php b/src/Model/Blame.php index 1a02fe580..b184abbcf 100644 --- a/src/Model/Blame.php +++ b/src/Model/Blame.php @@ -5,15 +5,13 @@ namespace App\Model; use App\Repository\BlameRepository; +use App\Repository\Repository; /** * A Blame will search the given page for the given text and return the relevant revisions and authors. */ class Blame extends Authorship { - /** @var string Text to search for. */ - protected string $query; - /** @var array|null Matches, keyed by revision ID, each with keys 'edit' and 'tokens' . */ protected ?array $matches; @@ -22,19 +20,19 @@ class Blame extends Authorship /** * Blame constructor. - * @param BlameRepository $repository - * @param Page $page The page to process. + * @param Repository|BlameRepository $repository + * @param ?Page $page The page to process. * @param string $query Text to search for. * @param string|null $target Either a revision ID or date in YYYY-MM-DD format. Null to use latest revision. */ public function __construct( - BlameRepository $repository, - Page $page, - string $query, + protected Repository|BlameRepository $repository, + protected ?Page $page, + /** @var string Text to search for. */ + protected string $query, ?string $target = null ) { parent::__construct($repository, $page, $target); - $this->query = $query; } /** @@ -155,18 +153,18 @@ private function searchTokens(array $tokens): array // We first check if the first token of the query matches, because we want to allow for partial matches // (e.g. for query "barbaz", the tokens ["foobar","baz"] should match). - if (false !== strpos($newMatchSoFar, $firstQueryToken)) { + if (str_contains($newMatchSoFar, $firstQueryToken)) { // If the full query is in the new match, use it, otherwise use just the first token. This is because // the full match may exist across multiple tokens, but the first match is only a partial match. - $newMatchSoFar = false !== strpos($newMatchSoFar, $tokenizedQuery) + $newMatchSoFar = str_contains($newMatchSoFar, $tokenizedQuery) ? $newMatchSoFar : $firstQueryToken; } // Keep track of tokens that match. To allow partial matches, // we check the query against $newMatchSoFar and vice versa. - if (false !== strpos($tokenizedQuery, $newMatchSoFar) || - false !== strpos($newMatchSoFar, $tokenizedQuery) + if (str_contains($tokenizedQuery, $newMatchSoFar) || + str_contains($newMatchSoFar, $tokenizedQuery) ) { $matchSoFar = $newMatchSoFar; $matchDataSoFar[] = [ @@ -182,7 +180,7 @@ private function searchTokens(array $tokens): array // A full match was found, so merge $matchDataSoFar into $matchData, // and start over to see if there are more matches in the article. - if (false !== strpos($matchSoFar, $tokenizedQuery)) { + if (str_contains($matchSoFar, $tokenizedQuery)) { $matchData = array_merge($matchData, $matchDataSoFar); $matchDataSoFar = []; $matchSoFar = ''; diff --git a/src/Model/CategoryEdits.php b/src/Model/CategoryEdits.php index b05ebdcf4..77ce3f5d3 100644 --- a/src/Model/CategoryEdits.php +++ b/src/Model/CategoryEdits.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\CategoryEditsRepository; +use App\Repository\Repository; /** * CategoryEdits returns statistics about edits made by a user to pages in given categories. @@ -28,32 +29,26 @@ class CategoryEdits extends Model /** * Constructor for the CategoryEdits class. - * @param CategoryEditsRepository $repository + * @param Repository|CategoryEditsRepository $repository * @param Project $project - * @param User $user + * @param ?User $user * @param array $categories * @param int|false $start As Unix timestamp. * @param int|false $end As Unix timestamp. * @param int|false $offset As Unix timestamp. Used for pagination. */ public function __construct( - CategoryEditsRepository $repository, - Project $project, - User $user, + protected Repository|CategoryEditsRepository $repository, + protected Project $project, + protected ?User $user, array $categories, - $start = false, - $end = false, - $offset = false + protected int|false $start = false, + protected int|false $end = false, + protected int|false $offset = false ) { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; $this->categories = array_map(function ($category) { return str_replace(' ', '_', $category); }, $categories); - $this->start = $start; - $this->end = $end; - $this->offset = $offset; } /** diff --git a/src/Model/Edit.php b/src/Model/Edit.php index 4fef00278..7ffbe24a6 100644 --- a/src/Model/Edit.php +++ b/src/Model/Edit.php @@ -6,6 +6,7 @@ use App\Repository\EditRepository; use App\Repository\PageRepository; +use App\Repository\Repository; use App\Repository\UserRepository; use DateTime; use TypeError; @@ -20,8 +21,6 @@ class Edit extends Model public const DELETED_USER = 4; public const DELETED_RESTRICTED = 8; - protected UserRepository $userRepo; - /** @var int ID of the revision */ protected int $id; @@ -54,17 +53,17 @@ class Edit extends Model /** * Edit constructor. - * @param EditRepository $repository + * @param Repository|EditRepository $repository * @param UserRepository $userRepo - * @param Page $page + * @param ?Page $page * @param string[] $attrs Attributes, as retrieved by PageRepository::getRevisions() */ - public function __construct(EditRepository $repository, UserRepository $userRepo, Page $page, array $attrs = []) - { - $this->repository = $repository; - $this->userRepo = $userRepo; - $this->page = $page; - + public function __construct( + protected Repository|EditRepository $repository, + protected UserRepository $userRepo, + protected ?Page $page, + array $attrs = [] + ) { // Copy over supported attributes $this->id = isset($attrs['id']) ? (int)$attrs['id'] : (int)$attrs['rev_id']; diff --git a/src/Model/EditCounter.php b/src/Model/EditCounter.php index ac14a1992..614a97b17 100644 --- a/src/Model/EditCounter.php +++ b/src/Model/EditCounter.php @@ -7,6 +7,7 @@ use App\Helper\AutomatedEditsHelper; use App\Helper\I18nHelper; use App\Repository\EditCounterRepository; +use App\Repository\Repository; use DateInterval; use DatePeriod; use DateTime; @@ -16,9 +17,6 @@ */ class EditCounter extends Model { - protected I18nHelper $i18n; - protected UserRights $userRights; - /** @var int[] Revision and page counts etc. */ protected array $pairData; @@ -64,27 +62,21 @@ class EditCounter extends Model /** * EditCounter constructor. - * @param EditCounterRepository $repository + * @param Repository|EditCounterRepository $repository * @param I18nHelper $i18n * @param UserRights $userRights * @param Project $project The base project to count edits - * @param AutomatedEditsHelper - * @param User $user + * @param ?User $user + * @param ?AutomatedEditsHelper $autoEditsHelper */ public function __construct( - EditCounterRepository $repository, - I18nHelper $i18n, - UserRights $userRights, - Project $project, - User $user, - AutomatedEditsHelper $autoEditsHelper + protected Repository|EditCounterRepository $repository, + protected I18nHelper $i18n, + protected UserRights $userRights, + protected Project $project, + protected ?User $user, + protected ?AutomatedEditsHelper $autoEditsHelper ) { - $this->repository = $repository; - $this->i18n = $i18n; - $this->userRights = $userRights; - $this->project = $project; - $this->user = $user; - $this->autoEditsHelper = $autoEditsHelper; } /** @@ -387,7 +379,7 @@ public function getLongestBlockSeconds() } elseif ('reblock' === $block['log_action'] && -1 !== $lastBlock[1]) { // The last block was modified. // $lastBlock is left unchanged if its duration was indefinite. - + // If this reblock set the block to infinite, set lastBlock manually to infinite if (-1 === $duration) { $lastBlock[1] = -1; @@ -721,7 +713,7 @@ public function reviews(): int $reviewedArticle = $logCounts['pagetriage-curation-reviewed-article'] ?: 0; return ($reviewed + $reviewedRedirect + $reviewedArticle); } - + /** * Get the total number of accounts created by the user. * @return int diff --git a/src/Model/EditSummary.php b/src/Model/EditSummary.php index 49152b506..0e5f9b6dd 100644 --- a/src/Model/EditSummary.php +++ b/src/Model/EditSummary.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\EditSummaryRepository; +use App\Repository\Repository; use DateTime; /** @@ -12,9 +13,6 @@ */ class EditSummary extends Model { - /** @var int Number of edits from present to consider as 'recent'. */ - protected int $numEditsRecent; - /** * Counts of summaries, raw edits, and per-month breakdown. * Keys are underscored because this also is served in the API. @@ -37,30 +35,24 @@ class EditSummary extends Model /** * EditSummary constructor. * - * @param EditSummaryRepository $repository + * @param Repository|EditSummaryRepository $repository * @param Project $project The project we're working with. - * @param User $user The user to process. + * @param ?User $user The user to process. * @param int|string $namespace Namespace ID or 'all' for all namespaces. * @param int|false $start Start date as Unix timestamp. * @param int|false $end End date as Unix timestamp. * @param int $numEditsRecent Number of edits from present to consider as 'recent'. */ public function __construct( - EditSummaryRepository $repository, - Project $project, - User $user, - $namespace, - $start = false, - $end = false, - int $numEditsRecent = 150 + protected Repository|EditSummaryRepository $repository, + protected Project $project, + protected ?User $user, + protected int|string $namespace, + protected int|false $start = false, + protected int|false $end = false, + /** @var int Number of edits from present to consider as 'recent'. */ + protected int $numEditsRecent = 150 ) { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; - $this->namespace = $namespace; - $this->start = $start; - $this->end = $end; - $this->numEditsRecent = $numEditsRecent; } /** diff --git a/src/Model/GlobalContribs.php b/src/Model/GlobalContribs.php index 462b0400a..dacf0a79c 100644 --- a/src/Model/GlobalContribs.php +++ b/src/Model/GlobalContribs.php @@ -7,6 +7,7 @@ use App\Repository\EditRepository; use App\Repository\GlobalContribsRepository; use App\Repository\PageRepository; +use App\Repository\Repository; use App\Repository\UserRepository; /** @@ -14,10 +15,6 @@ */ class GlobalContribs extends Model { - protected EditRepository $editRepo; - protected PageRepository $pageRepo; - protected UserRepository $userRepo; - /** @var int Number of results per page. */ public const PAGE_SIZE = 50; @@ -29,11 +26,11 @@ class GlobalContribs extends Model /** * GlobalContribs constructor. - * @param GlobalContribsRepository $repository + * @param Repository|GlobalContribsRepository $repository * @param PageRepository $pageRepo * @param UserRepository $userRepo * @param EditRepository $editRepo - * @param User $user + * @param ?User $user * @param string|int|null $namespace Namespace ID or 'all'. * @param false|int $start As Unix timestamp. * @param false|int $end As Unix timestamp. @@ -41,26 +38,18 @@ class GlobalContribs extends Model * @param int|null $limit Number of results to return. */ public function __construct( - GlobalContribsRepository $repository, - PageRepository $pageRepo, - UserRepository $userRepo, - EditRepository $editRepo, - User $user, - $namespace = 'all', - $start = false, - $end = false, - $offset = false, + protected Repository|GlobalContribsRepository $repository, + protected PageRepository $pageRepo, + protected UserRepository $userRepo, + protected EditRepository $editRepo, + protected ?User $user, + string|int|null $namespace = 'all', + protected false|int $start = false, + protected false|int $end = false, + protected false|int $offset = false, ?int $limit = null ) { - $this->repository = $repository; - $this->pageRepo = $pageRepo; - $this->userRepo = $userRepo; - $this->editRepo = $editRepo; - $this->user = $user; $this->namespace = '' == $namespace ? 0 : $namespace; - $this->start = $start; - $this->end = $end; - $this->offset = $offset; $this->limit = $limit ?? self::PAGE_SIZE; } diff --git a/src/Model/LargestPages.php b/src/Model/LargestPages.php index 267484742..e94f929a5 100644 --- a/src/Model/LargestPages.php +++ b/src/Model/LargestPages.php @@ -5,21 +5,16 @@ namespace App\Model; use App\Repository\LargestPagesRepository; +use App\Repository\Repository; /** * A LargestPages provides a list of the largest pages on a project. */ class LargestPages extends Model { - /** @var string */ - protected string $includePattern; - - /** @var string */ - protected string $excludePattern; - /** * LargestPages constructor. - * @param LargestPagesRepository $repository + * @param Repository|LargestPagesRepository $repository * @param Project $project * @param string|int|null $namespace Namespace ID or 'all'. * @param string $includePattern Either regular expression (starts/ends with forward slash), @@ -28,17 +23,13 @@ class LargestPages extends Model * or a wildcard pattern with % as the wildcard symbol. */ public function __construct( - LargestPagesRepository $repository, - Project $project, - $namespace = 'all', - string $includePattern = '', - string $excludePattern = '' + protected Repository|LargestPagesRepository $repository, + protected Project $project, + string|int|null $namespace = 'all', + protected string $includePattern = '', + protected string $excludePattern = '' ) { - $this->repository = $repository; - $this->project = $project; $this->namespace = '' == $namespace ? 0 : $namespace; - $this->includePattern = $includePattern; - $this->excludePattern = $excludePattern; } /** diff --git a/src/Model/Model.php b/src/Model/Model.php index b41aa8c05..4f14c6c73 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -30,16 +30,16 @@ abstract class Model protected ?Page $page = null; /** @var int|string Which namespace we are querying for. 'all' for all namespaces. */ - protected $namespace; + protected int|string $namespace; /** @var false|int Start of time period as Unix timestamp. */ - protected $start; + protected false|int $start; /** @var false|int End of time period as Unix timestamp. */ - protected $end; + protected false|int $end; /** @var false|int Unix timestamp to offset results which acts as a substitute for $end */ - protected $offset; + protected false|int $offset = false; /** @var int|null Number of rows to fetch. */ protected ?int $limit = null; @@ -163,7 +163,7 @@ public function getLimit(): ?int * Get the offset timestamp as Unix timestamp. Used for pagination. * @return false|int */ - public function getOffset() + public function getOffset(): false|int { return $this->offset; } diff --git a/src/Model/Page.php b/src/Model/Page.php index 737e46794..0a537d0f0 100644 --- a/src/Model/Page.php +++ b/src/Model/Page.php @@ -6,8 +6,9 @@ use App\Exception\BadGatewayException; use App\Repository\PageRepository; +use App\Repository\Repository; use DateTime; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Result; use GuzzleHttp\Exception\ClientException; /** @@ -15,9 +16,6 @@ */ class Page extends Model { - /** @var string The page name as provided at instantiation. */ - protected string $unnormalizedPageName; - /** @var string[]|null Metadata about this page. */ protected ?array $pageInfo; @@ -38,15 +36,16 @@ class Page extends Model /** * Page constructor. - * @param PageRepository $repository + * @param Repository|PageRepository $repository * @param Project $project - * @param string $pageName + * @param string $unnormalizedPageName */ - public function __construct(PageRepository $repository, Project $project, string $pageName) - { - $this->repository = $repository; - $this->project = $project; - $this->unnormalizedPageName = $pageName; + public function __construct( + protected Repository|PageRepository $repository, + protected Project $project, + /** @var string The page name as provided at instantiation. */ + protected string $unnormalizedPageName + ) { } /** @@ -214,12 +213,12 @@ public function getWatchers(): ?int /** * Get the HTML content of the body of the page. - * @param DateTime|int $target If a DateTime object, the + * @param DateTime|int|null $target If a DateTime object, the * revision at that time will be returned. If an integer, it is * assumed to be the actual revision ID. * @return string */ - public function getHTMLContent($target = null): string + public function getHTMLContent(DateTime|int|null $target = null): string { if (is_a($target, 'DateTime')) { $target = $this->repository->getRevisionIdAtDate($this, $target); @@ -274,7 +273,7 @@ public function getWikidataId(): ?string * @param false|int $end * @return int */ - public function getNumRevisions(?User $user = null, $start = false, $end = false): int + public function getNumRevisions(?User $user = null, false|int $start = false, false|int $end = false): int { // If a user is given, we will not cache the result via instance variable. if (null !== $user) { @@ -308,8 +307,8 @@ public function getNumRevisions(?User $user = null, $start = false, $end = false */ public function getRevisions( ?User $user = null, - $start = false, - $end = false, + false|int $start = false, + false|int $end = false, ?int $limit = null, ?int $numRevisions = null ): array { @@ -346,15 +345,15 @@ public function getWikitext(): ?string * separate query is ran to get the nuber of revisions. * @param false|int $start * @param false|int $end - * @return ResultStatement + * @return Result */ public function getRevisionsStmt( ?User $user = null, ?int $limit = null, ?int $numRevisions = null, - $start = false, - $end = false - ): ResultStatement { + false|int $start = false, + false|int $end = false + ): Result { // If we have a limit, we need to know the total number of revisions so that PageRepo // will properly set the OFFSET. See PageRepository::getRevisionsStmt() for more info. if (isset($limit) && null === $numRevisions) { @@ -440,14 +439,14 @@ public function countLinksAndRedirects(): array * @param string|DateTime $end In the format YYYYMMDD * @return int|null Total pageviews or null if data is unavailable. */ - public function getPageviews($start, $end): ?int + public function getPageviews(string|DateTime $start, string|DateTime $end): ?int { try { $pageviews = $this->repository->getPageviews($this, $start, $end); - } catch (ClientException $e) { + } catch (ClientException) { // 404 means zero pageviews return 0; - } catch (BadGatewayException $e) { + } catch (BadGatewayException) { // Upstream error, so return null so the view can customize messaging. return null; } diff --git a/src/Model/PageAssessments.php b/src/Model/PageAssessments.php index 5bb5e7385..4aab71f6f 100644 --- a/src/Model/PageAssessments.php +++ b/src/Model/PageAssessments.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\PageAssessmentsRepository; +use App\Repository\Repository; /** * A PageAssessments is responsible for handling logic around @@ -36,13 +37,13 @@ class PageAssessments extends Model /** * Create a new PageAssessments. - * @param PageAssessmentsRepository $repository + * @param Repository|PageAssessmentsRepository $repository * @param Project $project */ - public function __construct(PageAssessmentsRepository $repository, Project $project) - { - $this->repository = $repository; - $this->project = $project; + public function __construct( + protected Repository|PageAssessmentsRepository $repository, + protected Project $project + ) { } /** @@ -118,7 +119,7 @@ public function getBadgeURL(?string $class, bool $filenameOnly = false): string * @param Page $page * @return string[]|false With keys 'value' and 'badge', or false if assessments are unsupported. */ - public function getAssessment(Page $page) + public function getAssessment(Page $page): array|false { if (!$this->isEnabled() || !$this->isSupportedNamespace($page->getNamespace())) { return false; diff --git a/src/Model/PageInfo.php b/src/Model/PageInfo.php index 0097279ff..43ed48d90 100644 --- a/src/Model/PageInfo.php +++ b/src/Model/PageInfo.php @@ -893,7 +893,7 @@ private function updateUserCounts(Edit $edit): void 'first' => $edit->getTimestamp(), 'firstId' => $edit->getId(), 'last' => null, - 'atbe' => null, + 'atbe' => 0, 'added' => 0, ]; } diff --git a/src/Model/PageInfoApi.php b/src/Model/PageInfoApi.php index 5656c3a71..d87b04cf4 100644 --- a/src/Model/PageInfoApi.php +++ b/src/Model/PageInfoApi.php @@ -8,6 +8,7 @@ use App\Helper\AutomatedEditsHelper; use App\Helper\I18nHelper; use App\Repository\PageInfoRepository; +use App\Repository\Repository; use DateTime; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -23,9 +24,6 @@ class PageInfoApi extends Model /** @var int Number of days of recent data to show for pageviews. */ public const PAGEVIEWS_OFFSET = 30; - protected AutomatedEditsHelper $autoEditsHelper; - protected I18nHelper $i18n; - /** @var int Number of revisions that belong to the page. */ protected int $numRevisions; @@ -52,27 +50,21 @@ class PageInfoApi extends Model /** * PageInfoApi constructor. - * @param PageInfoRepository $repository + * @param Repository|PageInfoRepository $repository * @param I18nHelper $i18n * @param AutomatedEditsHelper $autoEditsHelper - * @param Page $page The page to process. + * @param ?Page $page The page to process. * @param false|int $start Start date as Unix timestmap. * @param false|int $end End date as Unix timestamp. */ public function __construct( - PageInfoRepository $repository, - I18nHelper $i18n, - AutomatedEditsHelper $autoEditsHelper, - Page $page, - $start = false, - $end = false + protected Repository|PageInfoRepository $repository, + protected I18nHelper $i18n, + protected AutomatedEditsHelper $autoEditsHelper, + protected ?Page $page, + protected false|int $start = false, + protected false|int $end = false ) { - $this->repository = $repository; - $this->i18n = $i18n; - $this->autoEditsHelper = $autoEditsHelper; - $this->page = $page; - $this->start = $start; - $this->end = $end; } /** @@ -169,7 +161,7 @@ public function getProseStats(): ?array try { $html = $this->page->getHTMLContent($datetime); - } catch (BadGatewayException $e) { + } catch (BadGatewayException) { // Prose stats are non-critical, so handle the BadGatewayException gracefully in the views. return null; } @@ -292,10 +284,10 @@ public function getPageInfoApiData(Project $project, Page $page): array try { $info = $this->repository->getBasicEditingInfo($page); - } catch (ServiceUnavailableHttpException $e) { + } catch (ServiceUnavailableHttpException) { // No more open database connections. $data['error'] = 'Unable to fetch revision data. Please try again later.'; - } catch (HttpException $e) { + } catch (HttpException) { /** * The query most likely exceeded the maximum query time, * so we'll abort and give only info retrieved by the API. diff --git a/src/Model/Pages.php b/src/Model/Pages.php index d30b42074..16be3b6ae 100644 --- a/src/Model/Pages.php +++ b/src/Model/Pages.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Repository\PagesRepository; +use App\Repository\Repository; use DateTime; /** @@ -36,9 +37,9 @@ class Pages extends Model /** * Pages constructor. - * @param PagesRepository $repository + * @param Repository|PagesRepository $repository * @param Project $project - * @param User $user + * @param ?User $user * @param string|int $namespace Namespace ID or 'all'. * @param string $redirects One of the Pages::REDIR_ constants. * @param string $deleted One of the Pages::DEL_ constants. @@ -47,25 +48,19 @@ class Pages extends Model * @param int|false $offset Unix timestamp. Used for pagination. */ public function __construct( - PagesRepository $repository, - Project $project, - User $user, - $namespace = 0, + protected Repository|PagesRepository $repository, + protected Project $project, + protected ?User $user, + string|int $namespace = 0, string $redirects = self::REDIR_NONE, string $deleted = self::DEL_ALL, - $start = false, - $end = false, - $offset = false + protected int|false $start = false, + protected int|false $end = false, + protected int|false $offset = false ) { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace; - $this->start = $start; - $this->end = $end; $this->redirects = $redirects ?: self::REDIR_NONE; $this->deleted = $deleted ?: self::DEL_ALL; - $this->offset = $offset; } /** diff --git a/src/Model/Project.php b/src/Model/Project.php index d6f911fd8..fbb103cd0 100644 --- a/src/Model/Project.php +++ b/src/Model/Project.php @@ -11,9 +11,6 @@ class Project extends Model { protected PageAssessments $pageAssessments; - /** @var string The project name as supplied by the user. */ - protected string $nameUnnormalized; - /** @var string[]|null Basic metadata about the project */ protected ?array $metadata; @@ -28,11 +25,12 @@ class Project extends Model /** * Create a new Project. - * @param string $nameOrUrl The project's database name or URL. + * @param string $nameUnnormalized The project's database name or URL. */ - public function __construct(string $nameOrUrl) - { - $this->nameUnnormalized = $nameOrUrl; + public function __construct( + /** @var string The project name as supplied by the user. */ + protected string $nameUnnormalized + ) { } /** @@ -59,7 +57,7 @@ public function setPageAssessments(PageAssessments $pageAssessments): Project * @param int|string|null $nsId Namespace ID, null if checking if project has page assessments for any namespace. * @return bool */ - public function hasPageAssessments($nsId = null): bool + public function hasPageAssessments(int|string|null $nsId = null): bool { if (null !== $nsId && (int)$nsId > 0) { return $this->pageAssessments->isSupportedNamespace((int)$nsId); @@ -166,7 +164,7 @@ public function getUrl(bool $withTrailingSlash = true): string * is a Page object. * @return string */ - public function getUrlForPage($page, bool $useUnnormalizedPageTitle = false): string + public function getUrlForPage(Page|string $page, bool $useUnnormalizedPageTitle = false): string { if ($page instanceof Page) { $page = $page->getTitle($useUnnormalizedPageTitle); diff --git a/src/Model/SimpleEditCounter.php b/src/Model/SimpleEditCounter.php index 83fab03a5..326d999e6 100644 --- a/src/Model/SimpleEditCounter.php +++ b/src/Model/SimpleEditCounter.php @@ -4,6 +4,7 @@ namespace App\Model; +use App\Repository\Repository; use App\Repository\SimpleEditCounterRepository; /** @@ -28,24 +29,21 @@ class SimpleEditCounter extends Model /** * Constructor for the SimpleEditCounter class. + * @param Repository|SimpleEditCounterRepository $repository * @param Project $project - * @param User $user + * @param ?User $user * @param string|int|null $namespace Namespace ID or 'all'. * @param false|int $start As Unix timestamp. * @param false|int $end As Unix timestamp. */ public function __construct( - SimpleEditCounterRepository $repository, - Project $project, - User $user, - $namespace = 'all', - $start = false, - $end = false + protected Repository|SimpleEditCounterRepository $repository, + protected Project $project, + protected ?User $user, + string|int|null $namespace = 'all', + protected false|int $start = false, + protected false|int $end = false ) { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; - if ($this->user->getEditCount($this->project) > $this->user->maxEdits()) { $this->limited = true; $this->namespace = 'all'; @@ -53,8 +51,6 @@ public function __construct( $this->end = false; } else { $this->namespace = '' == $namespace ? 0 : $namespace; - $this->start = $start; - $this->end = $end; } } diff --git a/src/Model/TopEdits.php b/src/Model/TopEdits.php index 27ac23013..89a53be94 100644 --- a/src/Model/TopEdits.php +++ b/src/Model/TopEdits.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Helper\AutomatedEditsHelper; +use App\Repository\Repository; use App\Repository\TopEditsRepository; /** @@ -12,8 +13,6 @@ */ class TopEdits extends Model { - protected AutomatedEditsHelper $autoEditsHelper; - /** @var string[]|Edit[] Top edits, either to a page or across namespaces. */ protected array $topEdits = []; @@ -32,19 +31,16 @@ class TopEdits extends Model /** @var int Number of reverted top edits. */ protected int $totalReverted = 0; - /** @var int Which page of results to show. */ - protected int $pagination = 0; - private const DEFAULT_LIMIT_SINGLE_NAMESPACE = 1000; private const DEFAULT_LIMIT_ALL_NAMESPACES = 20; /** * TopEdits constructor. - * @param TopEditsRepository $repository + * @param Repository|TopEditsRepository $repository * @param AutomatedEditsHelper $autoEditsHelper * @param Project $project - * @param User $user - * @param Page|null $page + * @param ?User $user + * @param ?Page $page * @param string|int $namespace Namespace ID or 'all'. * @param int|false $start Start date as Unix timestamp. * @param int|false $end End date as Unix timestamp. @@ -53,26 +49,19 @@ class TopEdits extends Model * @param int $pagination Which page of results to show. */ public function __construct( - TopEditsRepository $repository, - AutomatedEditsHelper $autoEditsHelper, - Project $project, - User $user, - ?Page $page = null, - $namespace = 0, - $start = false, - $end = false, + protected Repository|TopEditsRepository $repository, + protected AutomatedEditsHelper $autoEditsHelper, + protected Project $project, + protected ?User $user, + protected ?Page $page = null, + string|int $namespace = 0, + protected int|false $start = false, + protected int|false $end = false, ?int $limit = null, - int $pagination = 0 + /** @var int Which page of results to show. */ + protected int $pagination = 0, ) { - $this->repository = $repository; - $this->autoEditsHelper = $autoEditsHelper; - $this->project = $project; - $this->user = $user; - $this->page = $page; $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace; - $this->start = $start; - $this->end = $end; - $this->pagination = $pagination; if (null !== $limit) { $this->limit = $limit; @@ -171,36 +160,13 @@ public function getNumTopEdits(): int */ public function getProjectTotals(int $ns): array { - if ($this->getNumPagesNamespace() > $this->limit) { - $projectTotals = $this->repository->getProjectTotals( - $this->project, - $this->user, - $ns, - $this->start, - $this->end - ); - } else { - $counts_tmp = []; - // List of pages for this namespace - $rows = $this->topEdits[$ns]; - foreach ($rows as $row) { - $num = $row["count"]; - // May be null or nonexistent for assessment-less pages - $titles = $row["pap_project_title"] ?? "{}"; - // Had to use json to pass multiple values in SQL select - foreach (json_decode($titles) as $projectName) { - $counts_tmp[$projectName] ??= 0; - $counts_tmp[$projectName] += $num; - } - } - arsort($counts_tmp); - $counts_tmp = array_slice($counts_tmp, 0, 10); - $projectTotals = []; - foreach ($counts_tmp as $project => $count) { - $projectTotals[] = [ "pap_project_title" => $project, "count" => $count ]; - } - } - return $projectTotals; + return $this->repository->getProjectTotals( + $this->project, + $this->user, + $ns, + $this->start, + $this->end + ); } /** diff --git a/src/Model/User.php b/src/Model/User.php index 475e3e9da..e13c7b8ba 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -4,6 +4,7 @@ namespace App\Model; +use App\Repository\Repository; use App\Repository\UserRepository; use DateTime; use Exception; @@ -21,9 +22,6 @@ class User extends Model /** @var int Maximum queryable range for IPv6. */ public const MAX_IPV6_CIDR = 32; - /** @var string The user's username. */ - protected string $username; - /** @var int[] Quick cache of edit counts, keyed by project domain. */ protected array $editCounts = []; @@ -32,13 +30,14 @@ class User extends Model /** * Create a new User given a username. - * @param UserRepository $repository + * @param Repository|UserRepository $repository * @param string $username */ - public function __construct(UserRepository $repository, string $username) - { - $this->repository = $repository; - if ('ipr-' === substr($username, 0, 4)) { + public function __construct( + protected Repository|UserRepository $repository, + protected string $username + ) { + if (str_starts_with($username, 'ipr-')) { $username = substr($username, 4); } $this->username = ucfirst(str_replace('_', ' ', trim($username))); @@ -312,7 +311,7 @@ public function existsGlobally(): bool */ public function isAdmin(Project $project): bool { - return false !== array_search('sysop', $this->getUserRights($project)); + return in_array('sysop', $this->getUserRights($project)); } /** @@ -341,6 +340,7 @@ public function isTemp(Project $project): bool * Does the given username match that of temporary accounts? * Based on https://w.wiki/BZQY from MediaWiki core (GPL-2.0-or-later) * @param Project $project + * @param string $username * @return bool */ public static function isTempUsername(Project $project, string $username): bool @@ -426,12 +426,16 @@ public function hasTooManyEdits(Project $project): bool * Get edit count within given timeframe and namespace * @param Project $project * @param int|string $namespace Namespace ID or 'all' for all namespaces - * @param int|false $start Start date as Unix timestamp. + * @param false|int $start Start date as Unix timestamp. * @param int|false $end End date as Unix timestamp. * @return int */ - public function countEdits(Project $project, $namespace = 'all', $start = false, $end = false): int - { + public function countEdits( + Project $project, + int|string $namespace = 'all', + false|int $start = false, + false|int $end = false + ): int { return $this->repository->countEdits($project, $this, $namespace, $start, $end); } @@ -443,7 +447,7 @@ public function isCurrentlyLoggedIn(): bool { try { $ident = $this->repository->getXtoolsUserInfo(); - } catch (Exception $exception) { + } catch (Exception) { return false; } return isset($ident->username) && $ident->username === $this->getUsername(); diff --git a/src/Model/UserRights.php b/src/Model/UserRights.php index 688e2570c..c120f1a65 100644 --- a/src/Model/UserRights.php +++ b/src/Model/UserRights.php @@ -5,6 +5,7 @@ namespace App\Model; use App\Helper\I18nHelper; +use App\Repository\Repository; use App\Repository\UserRightsRepository; use DateInterval; use DateTimeImmutable; @@ -15,8 +16,6 @@ */ class UserRights extends Model { - protected I18nHelper $i18n; - /** @var string[] Rights changes, keyed by timestamp then 'added' and 'removed'. */ protected array $rightsChanges; @@ -33,15 +32,17 @@ class UserRights extends Model protected bool $impossibleLogs = false; /** - * @param UserRightsRepository $repository - * @param User $user + * @param Repository|UserRightsRepository $repository + * @param Project $project + * @param User|null $user + * @param I18nHelper $i18n */ - public function __construct(UserRightsRepository $repository, Project $project, User $user, I18nHelper $i18n) - { - $this->repository = $repository; - $this->project = $project; - $this->user = $user; - $this->i18n = $i18n; + public function __construct( + protected Repository|UserRightsRepository $repository, + protected Project $project, + protected ?User $user, + protected I18nHelper $i18n + ) { } /** @@ -81,7 +82,7 @@ public function getRightsChanges(?int $limit = null): array * Checks the user rights log to see whether the user is an admin or used to be one. * @return string|false One of false (never an admin), 'current' or 'former'. */ - public function getAdminStatus() + public function getAdminStatus(): false|string { $rightsStates = $this->getRightsStates(); @@ -245,7 +246,7 @@ private function processRightsChanges(array $logData): array // Nothing was deleted. } else { $unserialized = @unserialize($row['log_params']); - + if (false !== $unserialized) { $old = $unserialized['4::oldgroups'] ?? $unserialized['oldGroups']; $new = $unserialized['5::newgroups'] ?? $unserialized['newGroups']; @@ -253,21 +254,21 @@ private function processRightsChanges(array $logData): array $removed = array_diff($old, $new); $oldMetadata = $unserialized['oldmetadata'] ?? $unserialized['oldMetadata'] ?? null; $newMetadata = $unserialized['newmetadata'] ?? $unserialized['newMetadata'] ?? null; - + // Check for changes only to expiry. // If such exists, treat it as added. Various issets are safeguards. if (empty($added) && empty($removed) && isset($oldMetadata) && isset($newMetadata)) { foreach ($old as $index => $right) { $oldExpiry = $oldMetadata[$index]['expiry'] ?? null; $newExpiry = $newMetadata[$index]['expiry'] ?? null; - + // Check if an expiry was added, removed, or modified. if ((null !== $oldExpiry && null === $newExpiry) || (null === $oldExpiry && null !== $newExpiry) || (null !== $oldExpiry && null !== $newExpiry) ) { $added[$index] = $right; - + // Remove the last auto-removal(s), which must exist. foreach (array_reverse($rightsChanges, true) as $timestamp => $change) { if (in_array($right, $change['removed']) && !in_array($right, $change['added']) && @@ -279,12 +280,12 @@ private function processRightsChanges(array $logData): array } } } - + // If a right was removed, remove any previously pending auto-removals. if (count($removed) > 0) { $this->unsetAutoRemoval($rightsChanges, $removed); } - + $this->setAutoRemovals($rightsChanges, $row, $unserialized, $added); } else { // This is the old school format that most likely contains @@ -295,14 +296,14 @@ private function processRightsChanges(array $logData): array $new = array_filter(array_map('trim', explode(',', (string)$new))); $added = array_diff($new, $old); $removed = array_diff($old, $new); - } catch (Exception $e) { + } catch (Exception) { // Really, really old school format that may be missing metadata // altogether. Here we'll just leave $added and $removed empty. $added = []; $removed = []; } } - + // Remove '(none)'. if (in_array('(none)', $added)) { array_splice($added, array_search('(none)', $added), 1); @@ -324,7 +325,7 @@ private function processRightsChanges(array $logData): array // Then append those that are in $added. // (Doesn't take care of duplicates, but that should be impossible.) $tempRights = array_merge($tempRights, $added); - + $rightsChanges[$row['log_timestamp']] = [ 'logId' => $row['log_id'], 'performer' => 'autopromote' === $row['log_action'] ? null : $row['performer'], @@ -415,7 +416,7 @@ public function hasImpossibleLogs(): bool * Get the timestamp of when the user became autoconfirmed. * @return string|false YmdHis format, or false if date is in the future or if AC status could not be determined. */ - private function getAutoconfirmedTimestamp() + private function getAutoconfirmedTimestamp(): false|string { static $acTimestamp = null; if (null !== $acTimestamp) { diff --git a/src/Monolog/WebProcessorMonolog.php b/src/Monolog/WebProcessorMonolog.php index f65864184..b645e5818 100644 --- a/src/Monolog/WebProcessorMonolog.php +++ b/src/Monolog/WebProcessorMonolog.php @@ -13,15 +13,12 @@ */ class WebProcessorMonolog { - protected RequestStack $requestStack; - /** * WebProcessorMonolog constructor. * @param RequestStack $requestStack */ - public function __construct(RequestStack $requestStack) + public function __construct(protected RequestStack $requestStack) { - $this->requestStack = $requestStack; } /** @@ -33,7 +30,7 @@ public function __invoke(array $record): array { try { $session = $this->requestStack->getSession(); - } catch (SessionNotFoundException $e) { + } catch (SessionNotFoundException) { return $record; } if (!$session->isStarted()) { diff --git a/src/Repository/AdminStatsRepository.php b/src/Repository/AdminStatsRepository.php index 23f36a40b..0cd6c4556 100644 --- a/src/Repository/AdminStatsRepository.php +++ b/src/Repository/AdminStatsRepository.php @@ -5,7 +5,6 @@ namespace App\Repository; use App\Model\Project; -use PDO; /** * AdminStatsRepository is responsible for retrieving data from the database @@ -95,8 +94,8 @@ private function getLogSqlParts(Project $project, string $type, array $requested // admin_stats.yaml gives us the log type and action as a string in the format of "type/action". [$logType, $logAction] = explode('/', $entry); - $logTypes[] = $keyTypes[] = $connection->quote($logType, PDO::PARAM_STR); - $logActions[] = $keyActions[] = $connection->quote($logAction, PDO::PARAM_STR); + $logTypes[] = $keyTypes[] = $connection->getDatabasePlatform()->quoteStringLiteral($logType); + $logActions[] = $keyActions[] = $connection->getDatabasePlatform()->quoteStringLiteral($logAction); } $keyTypes = implode(',', array_unique($keyTypes)); @@ -196,7 +195,7 @@ private function getUserGroupByLocality(array $res, array $permissions, bool $gl foreach (($global ? $res['globalgroups'] : $res['usergroups']) as $userGroup) { // If they are able to add and remove user groups, we'll treat them as having the 'userrights' permission. if (isset($userGroup['add']) || isset($userGroup['remove'])) { - array_push($userGroup['rights'], 'userrights'); + $userGroup['rights'][] = 'userrights'; $userGroup['rights'][] = 'userrights'; } if (count(array_intersect($userGroup['rights'], $permissions)) > 0) { diff --git a/src/Repository/AutoEditsRepository.php b/src/Repository/AutoEditsRepository.php index b1f96429c..deac0ca6f 100644 --- a/src/Repository/AutoEditsRepository.php +++ b/src/Repository/AutoEditsRepository.php @@ -23,8 +23,6 @@ */ class AutoEditsRepository extends UserRepository { - protected AutomatedEditsHelper $autoEditsHelper; - /** @var array List of automated tools, used for fetching the tool list and filtering it. */ private array $aeTools; @@ -44,21 +42,20 @@ class AutoEditsRepository extends UserRepository * @param int $queryTimeout * @param ProjectRepository $projectRepo * @param AutomatedEditsHelper $autoEditsHelper - * @param RequestStack $requestStack + * @param ?RequestStack $requestStack */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ProjectRepository $projectRepo, - AutomatedEditsHelper $autoEditsHelper, - RequestStack $requestStack + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ProjectRepository $projectRepo, + protected AutomatedEditsHelper $autoEditsHelper, + protected ?RequestStack $requestStack ) { - $this->autoEditsHelper = $autoEditsHelper; parent::__construct( $managerRegistry, $cache, @@ -96,7 +93,7 @@ public function getUseSandbox(): bool * @param int|string $namespace Namespace ID or 'all'. * @return array */ - public function getTools(Project $project, $namespace = 'all'): array + public function getTools(Project $project, int|string $namespace = 'all'): array { if (!isset($this->aeTools)) { $this->aeTools = $this->autoEditsHelper->getTools($project, $this->useSandbox); @@ -134,7 +131,7 @@ public function getInvalidTools(Project $project): array * Overrides Repository::setCache(), and will not call the parent (which sets the cache) if using the sandbox. * @inheritDoc */ - public function setCache(string $cacheKey, $value, $duration = 'PT20M') + public function setCache(string $cacheKey, $value, $duration = 'PT20M'): mixed { if ($this->useSandbox) { return $value; @@ -155,9 +152,9 @@ public function setCache(string $cacheKey, $value, $duration = 'PT20M') public function countAutomatedEdits( Project $project, User $user, - $namespace = 'all', - $start = false, - $end = false + string|int $namespace = 'all', + int|false $start = false, + int|false $end = false ): int { $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoeditcount'); if (!$this->useSandbox && $this->cache->hasItem($cacheKey)) { @@ -235,10 +232,10 @@ public function countAutomatedEdits( public function getNonAutomatedEdits( Project $project, User $user, - $namespace = 'all', - $start = false, - $end = false, - $offset = false, + string|int $namespace = 'all', + int|false $start = false, + int|false $end = false, + int|false $offset = false, int $limit = 50 ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_nonautoedits'); @@ -320,11 +317,11 @@ public function getNonAutomatedEdits( public function getAutomatedEdits( Project $project, User $user, - $namespace = 'all', - $start = false, - $end = false, + string|int $namespace = 'all', + int|false $start = false, + int|false $end = false, ?string $tool = null, - $offset = false, + int|false $offset = false, int $limit = 50 ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoedits'); @@ -420,8 +417,13 @@ public function getAutomatedEdits( * ], * ] */ - public function getToolCounts(Project $project, User $user, $namespace = 'all', $start = false, $end = false): array - { + public function getToolCounts( + Project $project, + User $user, + string|int $namespace = 'all', + int|false $start = false, + int|false $end = false + ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_autotoolcounts'); if (!$this->useSandbox && $this->cache->hasItem($cacheKey)) { return $this->cache->getItem($cacheKey)->get(); @@ -473,9 +475,9 @@ public function getToolCounts(Project $project, User $user, $namespace = 'all', private function getAutomatedCountsSql( Project $project, User $user, - $namespace, - $start = false, - $end = false + string|int $namespace, + int|false $start = false, + int|false $end = false ): string { $revDateConditions = $this->getDateConditions($start, $end); @@ -501,7 +503,7 @@ private function getAutomatedCountsSql( foreach ($tools as $toolName => $values) { [$condTool, $commentJoin, $tagJoin] = $this->getInnerAutomatedCountsSql($project, $toolName, $values); - $toolName = $conn->quote($toolName, PDO::PARAM_STR); + $toolName = $conn->getDatabasePlatform()->quoteStringLiteral($toolName); // No regex or tag provided for this tool. This can happen for tag-only tools that are in the global // configuration, but no local tag exists on the said project. @@ -543,7 +545,7 @@ protected function getInnerAutomatedCountsSql(Project $project, string $toolName if (isset($values['regex'])) { $commentTable = $project->getTableName('comment', 'revision'); $commentJoin = "LEFT OUTER JOIN $commentTable ON rev_comment_id = comment_id"; - $regex = $conn->quote($values['regex'], PDO::PARAM_STR); + $regex = $conn->getDatabasePlatform()->quoteStringLiteral($values['regex']); $condTool = "comment_text REGEXP $regex"; } if (isset($values['tags'])) { @@ -581,7 +583,7 @@ protected function getInnerAutomatedCountsSql(Project $project, string $toolName private function getToolRegexAndTags( Project $project, ?string $tool = null, - $namespace = null + int|string|null $namespace = null ): array { $tools = $this->getTools($project); $regexes = []; diff --git a/src/Repository/BlameRepository.php b/src/Repository/BlameRepository.php index d3f09a882..e5f918107 100644 --- a/src/Repository/BlameRepository.php +++ b/src/Repository/BlameRepository.php @@ -18,9 +18,6 @@ */ class BlameRepository extends AuthorshipRepository { - protected EditRepository $editRepo; - protected UserRepository $userRepo; - /** * @param ManagerRegistry $managerRegistry * @param CacheItemPoolInterface $cache @@ -33,18 +30,16 @@ class BlameRepository extends AuthorshipRepository * @param UserRepository $userRepo */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - EditRepository $editRepo, - UserRepository $userRepo + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected EditRepository $editRepo, + protected UserRepository $userRepo ) { - $this->editRepo = $editRepo; - $this->userRepo = $userRepo; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } diff --git a/src/Repository/CategoryEditsRepository.php b/src/Repository/CategoryEditsRepository.php index c68f1614b..00cc86ad8 100644 --- a/src/Repository/CategoryEditsRepository.php +++ b/src/Repository/CategoryEditsRepository.php @@ -8,9 +8,9 @@ use App\Model\Edit; use App\Model\Project; use App\Model\User; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Result; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; use Psr\Cache\CacheItemPoolInterface; @@ -25,11 +25,6 @@ */ class CategoryEditsRepository extends Repository { - protected AutomatedEditsHelper $autoEditsHelper; - protected EditRepository $editRepo; - protected PageRepository $pageRepo; - protected UserRepository $userRepo; - /** * @param ManagerRegistry $managerRegistry * @param CacheItemPoolInterface $cache @@ -44,22 +39,18 @@ class CategoryEditsRepository extends Repository * @param UserRepository $userRepo */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - AutomatedEditsHelper $autoEditsHelper, - EditRepository $editRepo, - PageRepository $pageRepo, - UserRepository $userRepo + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected AutomatedEditsHelper $autoEditsHelper, + protected EditRepository $editRepo, + protected PageRepository $pageRepo, + protected UserRepository $userRepo ) { - $this->autoEditsHelper = $autoEditsHelper; - $this->editRepo = $editRepo; - $this->pageRepo = $pageRepo; - $this->userRepo = $userRepo; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } @@ -76,8 +67,8 @@ public function countCategoryEdits( Project $project, User $user, array $categories, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): int { $cacheKey = $this->getCacheKey(func_get_args(), 'user_categoryeditcount'); if ($this->cache->hasItem($cacheKey)) { @@ -122,8 +113,8 @@ public function getCategoryCounts( Project $project, User $user, array $categories, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_categorycounts'); if ($this->cache->hasItem($cacheKey)) { @@ -180,9 +171,9 @@ public function getCategoryEdits( Project $project, User $user, array $categories, - $start = false, - $end = false, - $offset = false + int|false $start = false, + int|false $end = false, + int|false $offset = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_categoryedits'); if ($this->cache->hasItem($cacheKey)) { @@ -232,14 +223,14 @@ public function getCategoryEdits( * @param Project $project * @param User $user * @param string[] $categories - * @return ResultStatement + * @return Result */ private function executeStmt( string $sql, Project $project, User $user, array $categories - ): ResultStatement { + ): Result { if ($user->isIpRange()) { [$hexStart, $hexEnd] = IPUtils::parseRange($user->getUsername()); $params = [ @@ -250,7 +241,7 @@ private function executeStmt( $types = [ ParameterType::STRING, ParameterType::STRING, - Connection::PARAM_STR_ARRAY, + ArrayParameterType::STRING, ]; } else { $params = [ @@ -259,7 +250,7 @@ private function executeStmt( ]; $types = [ ParameterType::STRING, - Connection::PARAM_STR_ARRAY, + ArrayParameterType::STRING, ]; } diff --git a/src/Repository/EditCounterRepository.php b/src/Repository/EditCounterRepository.php index 40706ad08..4de02d050 100644 --- a/src/Repository/EditCounterRepository.php +++ b/src/Repository/EditCounterRepository.php @@ -20,31 +20,24 @@ */ class EditCounterRepository extends Repository { - protected AutoEditsRepository $autoEditsRepo; - protected ProjectRepository $projectRepo; - public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ProjectRepository $projectRepo, - AutoEditsRepository $autoEditsRepo + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ProjectRepository $projectRepo, + protected AutoEditsRepository $autoEditsRepo ) { - $this->projectRepo = $projectRepo; - $this->autoEditsRepo = $autoEditsRepo; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } /** * Get data about revisions, pages, etc. - * @param Project $project The project. - * @param User $user The user. * @return string[] With keys: 'deleted', 'live', 'total', '24h', '7d', '30d', - * '365d', 'small', 'large', 'with_comments', and 'minor_edits', ... + * '365d', 'small', 'large', 'with_comments', and 'minor_edits', ... */ public function getPairData(Project $project, User $user): array { @@ -147,8 +140,6 @@ public function getPairData(Project $project, User $user): array /** * Get log totals for a user. - * @param Project $project The project. - * @param User $user The user. * @return int[] Keys are "-" strings, values are counts. */ public function getLogCounts(Project $project, User $user): array @@ -228,9 +219,6 @@ public function getLogCounts(Project $project, User $user): array /** * Get counts of files moved, and files moved/uploaded on Commons. * Local file uploads are counted in getLogCounts() since we're querying the same rows anyway. - * @param Project $project - * @param User $user - * @return array */ public function getFileCounts(Project $project, User $user): array { @@ -276,8 +264,6 @@ public function getFileCounts(Project $project, User $user): array /** * Get count of files moved and uploaded on Commons. - * @param User $user - * @return array */ protected function getFileCountsCommons(User $user): array { @@ -364,9 +350,6 @@ public function getFirstAndLatestActions(Project $project, User $user): array /** * Get data for all blocks set on the given user. - * @param Project $project - * @param User $user - * @return array */ public function getBlocksReceived(Project $project, User $user): array { @@ -387,9 +370,6 @@ public function getBlocksReceived(Project $project, User $user): array /** * Get the number of times the user was thanked. - * @param Project $project - * @param User $user - * @return int */ public function getThanksReceived(Project $project, User $user): int { @@ -408,7 +388,7 @@ public function getThanksReceived(Project $project, User $user): int return $this->setCache($cacheKey, (int)$this->executeProjectsQuery($project, $sql, [ 'username' => $username, - ])->fetchColumn()); + ])->fetchOne()); } /** @@ -439,22 +419,16 @@ public function getNamespaceTotals(Project $project, User $user): array $whereClause = 'ipc_hex BETWEEN :startIp AND :endIp'; } - $sql = "SELECT page_namespace AS `namespace`, COUNT(rev_id) AS `total` + $sql = "SELECT page_namespace, COUNT(rev_id) FROM $pageTable p JOIN $revisionTable r ON (r.rev_page = p.page_id) $ipcJoin WHERE $whereClause - GROUP BY `namespace`"; + GROUP BY page_namespace"; - $results = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); - - $namespaceTotals = array_combine(array_map(function ($e) { - return $e['namespace']; - }, $results), array_map(function ($e) { - return (int)$e['total']; - }, $results)); + $results = $this->executeProjectsQuery($project, $sql, $params)->fetchAllKeyValue(); // Cache and return. - return $this->setCache($cacheKey, $namespaceTotals); + return $this->setCache($cacheKey, $results); } /** @@ -501,7 +475,7 @@ public function getMonthCounts(Project $project, User $user): array WHERE $whereClause GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), `namespace`"; - $totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); + $totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAllAssociative(); // Cache and return. return $this->setCache($cacheKey, $totals); @@ -509,9 +483,6 @@ public function getMonthCounts(Project $project, User $user): array /** * Get data for the timecard chart, with totals grouped by day and to the nearest two-hours. - * @param Project $project - * @param User $user - * @return string[][] */ public function getTimeCard(Project $project, User $user): array { @@ -551,7 +522,7 @@ public function getTimeCard(Project $project, User $user): array WHERE $whereClause GROUP BY DAYOFWEEK($column), $xCalc"; - $totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); + $totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAllAssociative(); // Cache and return. return $this->setCache($cacheKey, $totals); diff --git a/src/Repository/EditRepository.php b/src/Repository/EditRepository.php index 9c9584279..284fa0652 100644 --- a/src/Repository/EditRepository.php +++ b/src/Repository/EditRepository.php @@ -20,22 +20,17 @@ */ class EditRepository extends Repository { - protected AutomatedEditsHelper $autoEditsHelper; - protected PageRepository $pageRepo; - public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - AutomatedEditsHelper $autoEditsHelper, - PageRepository $pageRepo + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected AutomatedEditsHelper $autoEditsHelper, + protected PageRepository $pageRepo ) { - $this->autoEditsHelper = $autoEditsHelper; - $this->pageRepo = $pageRepo; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } diff --git a/src/Repository/EditSummaryRepository.php b/src/Repository/EditSummaryRepository.php index 0d5c58fd7..36ee02c52 100644 --- a/src/Repository/EditSummaryRepository.php +++ b/src/Repository/EditSummaryRepository.php @@ -6,7 +6,7 @@ use App\Model\Project; use App\Model\User; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Result; use Wikimedia\IPUtils; /** @@ -24,15 +24,15 @@ class EditSummaryRepository extends UserRepository * @param string|int $namespace Namespace ID or 'all' for all namespaces. * @param int|false $start Start date as Unix timestamp. * @param int|false $end End date as Unix timestamp. - * @return ResultStatement + * @return Result */ public function getRevisions( Project $project, User $user, - $namespace, - $start = false, - $end = false - ): ResultStatement { + string|int $namespace, + int|false $start = false, + int|false $end = false + ): Result { $revisionTable = $project->getTableName('revision'); $commentTable = $project->getTableName('comment'); $pageTable = $project->getTableName('page'); @@ -78,9 +78,9 @@ public function prepareData( array $processRow, Project $project, User $user, - $namespace, - $start = false, - $end = false + int|string $namespace, + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey([$project, $user, $namespace, $start, $end], 'edit_summary_usage'); if ($this->cache->hasItem($cacheKey)) { diff --git a/src/Repository/GlobalContribsRepository.php b/src/Repository/GlobalContribsRepository.php index 26b4ba9e9..b7ed2e708 100644 --- a/src/Repository/GlobalContribsRepository.php +++ b/src/Repository/GlobalContribsRepository.php @@ -8,7 +8,6 @@ use App\Model\User; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; -use PDO; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; @@ -20,24 +19,22 @@ */ class GlobalContribsRepository extends Repository { - protected ProjectRepository $projectRepo; /** @var Project CentralAuth project (meta.wikimedia for WMF installation). */ protected Project $caProject; public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ProjectRepository $projectRepo, + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ProjectRepository $projectRepo, string $centralAuthProject ) { $this->caProject = new Project($centralAuthProject); - $this->projectRepo = $projectRepo; $this->caProject->setRepository($this->projectRepo); parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } @@ -47,7 +44,7 @@ public function __construct( * @see GlobalContribsRepository::globalEditCountsFromCentralAuth() * @see GlobalContribsRepository::globalEditCountsFromDatabases() * @param User $user The user. - * @return mixed[] Elements are arrays with 'project' (Project), and 'total' (int). Null if anon (too slow). + * @return ?array Elements are arrays with 'project' (Project), and 'total' (int). Null if anon (too slow). */ public function globalEditCounts(User $user): ?array { @@ -221,11 +218,11 @@ public function getDbNamesAndActorIds(User $user, ?array $dbNames = null): array public function getRevisions( array $dbNames, User $user, - $namespace = 'all', - $start = false, - $end = false, + int|string $namespace = 'all', + int|false $start = false, + int|false $end = false, int $limit = 31, // One extra to know whether there should be another page. - $offset = false + int|false $offset = false ): array { // Check cache. $cacheKey = $this->getCacheKey(func_get_args(), 'gc_revisions'); @@ -235,15 +232,15 @@ public function getRevisions( // Just need any Connection to use the ->quote() method. $quoteConn = $this->getProjectsConnection('s1'); - $username = $quoteConn->quote($user->getUsername(), PDO::PARAM_STR); + $username = $quoteConn->getDatabasePlatform()->quoteStringLiteral($user->getUsername()); // IP range handling. $startIp = ''; $endIp = ''; if ($user->isIpRange()) { [$startIp, $endIp] = IPUtils::parseRange($user->getUsername()); - $startIp = $quoteConn->quote($startIp, PDO::PARAM_STR); - $endIp = $quoteConn->quote($endIp, PDO::PARAM_STR); + $startIp = $quoteConn->getDatabasePlatform()->quoteStringLiteral($startIp); + $endIp = $quoteConn->getDatabasePlatform()->quoteStringLiteral($endIp); } // Fetch actor IDs (for IP ranges, it strips trailing zeros and uses a LIKE query). diff --git a/src/Repository/LargestPagesRepository.php b/src/Repository/LargestPagesRepository.php index b6371fa5e..51e2f937e 100644 --- a/src/Repository/LargestPagesRepository.php +++ b/src/Repository/LargestPagesRepository.php @@ -18,8 +18,6 @@ */ class LargestPagesRepository extends Repository { - protected PageRepository $pageRepo; - /** * @param ManagerRegistry $managerRegistry * @param CacheItemPoolInterface $cache @@ -31,16 +29,15 @@ class LargestPagesRepository extends Repository * @param PageRepository $pageRepo */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - PageRepository $pageRepo + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected PageRepository $pageRepo ) { - $this->pageRepo = $pageRepo; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } @@ -76,8 +73,12 @@ private function getLikeSql(string &$includePattern, string &$excludePattern): s * or a wildcard pattern with % as the wildcard symbol. * @return array */ - public function getData(Project $project, $namespace, string $includePattern, string $excludePattern): array - { + public function getData( + Project $project, + int|string $namespace, + string $includePattern, + string $excludePattern + ): array { $pageTable = $project->getTableName('page'); $where = ''; diff --git a/src/Repository/PageInfoRepository.php b/src/Repository/PageInfoRepository.php index cd3474fee..5abf4ad08 100644 --- a/src/Repository/PageInfoRepository.php +++ b/src/Repository/PageInfoRepository.php @@ -9,7 +9,6 @@ use App\Model\Page; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; -use PDO; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; @@ -21,39 +20,23 @@ */ class PageInfoRepository extends AutoEditsRepository { - protected EditRepository $editRepo; - protected UserRepository $userRepo; - /** @var int Maximum number of revisions to process, as configured via APP_MAX_PAGE_REVISIONS */ protected int $maxPageRevisions; - /** - * @param ManagerRegistry $managerRegistry - * @param CacheItemPoolInterface $cache - * @param Client $guzzle - * @param LoggerInterface $logger - * @param ParameterBagInterface $parameterBag - * @param bool $isWMF - * @param int $queryTimeout - * @param EditRepository $editRepo - * @param UserRepository $userRepo - */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - EditRepository $editRepo, - UserRepository $userRepo, - ProjectRepository $projectRepo, - AutomatedEditsHelper $autoEditsHelper, - RequestStack $requestStack + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected EditRepository $editRepo, + protected UserRepository $userRepo, + protected ProjectRepository $projectRepo, + protected AutomatedEditsHelper $autoEditsHelper, + protected ?RequestStack $requestStack ) { - $this->editRepo = $editRepo; - $this->userRepo = $userRepo; parent::__construct( $managerRegistry, $cache, @@ -100,7 +83,7 @@ public function getEdit(Page $page, array $revision): Edit * @param bool $count Return a count rather than the full set of rows. * @return array with rows with keys 'count', 'username' and 'current'. */ - public function getBotData(Page $page, $start, $end, ?int $limit, bool $count = false): array + public function getBotData(Page $page, false|int $start, false|int $end, ?int $limit, bool $count = false): array { $cacheKey = $this->getCacheKey(func_get_args(), 'page_bot_data'); if ($this->cache->hasItem($cacheKey)) { @@ -166,7 +149,7 @@ public function getBotData(Page $page, $start, $end, ?int $limit, bool $count = * @param false|int $end * @return string[] each entry with keys 'log_action', 'log_type' and 'timestamp'. */ - public function getLogEvents(Page $page, $start, $end): array + public function getLogEvents(Page $page, false|int $start, false|int $end): array { $cacheKey = $this->getCacheKey(func_get_args(), 'page_logevents'); if ($this->cache->hasItem($cacheKey)) { @@ -265,8 +248,8 @@ public function getSubpageCount(Page $page): int */ public function getTopEditorsByEditCount( Page $page, - $start = false, - $end = false, + false|int $start = false, + false|int $end = false, int $limit = 20, bool $noBots = false ): array { @@ -322,7 +305,7 @@ public function getTopEditorsByEditCount( * @param Page $page The page. * @return string[]|false false if the page was not found. */ - public function getBasicEditingInfo(Page $page) + public function getBasicEditingInfo(Page $page): array|false { $cacheKey = $this->getCacheKey(func_get_args(), 'page_basicinfo'); if ($this->cache->hasItem($cacheKey)) { @@ -405,11 +388,11 @@ public function getBasicEditingInfo(Page $page) /** * Get counts of (semi-)automated tools that were used to edit the page. * @param Page $page - * @param $start - * @param $end + * @param false|int $start + * @param false|int $end * @return array */ - public function getAutoEditsCounts(Page $page, $start, $end): array + public function getAutoEditsCounts(Page $page, false|int $start, false|int $end): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoeditcount'); if ($this->cache->hasItem($cacheKey)) { @@ -427,7 +410,7 @@ public function getAutoEditsCounts(Page $page, $start, $end): array foreach ($tools as $toolName => $values) { [$condTool, $commentJoin, $tagJoin] = $this->getInnerAutomatedCountsSql($project, $toolName, $values); - $toolName = $conn->quote($toolName, PDO::PARAM_STR); + $toolName = $conn->getDatabasePlatform()->quoteStringLiteral($toolName); // No regex or tag provided for this tool. This can happen for tag-only tools that are in the global // configuration, but no local tag exists on the said project. diff --git a/src/Repository/PageRepository.php b/src/Repository/PageRepository.php index 998a38c7d..e0ed13547 100644 --- a/src/Repository/PageRepository.php +++ b/src/Repository/PageRepository.php @@ -9,7 +9,7 @@ use App\Model\Project; use App\Model\User; use DateTime; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Result; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ServerException; @@ -111,8 +111,8 @@ public function getPagesWikitext(Project $project, array $pageTitles): array public function getRevisions( Page $page, ?User $user = null, - $start = false, - $end = false, + false|int $start = false, + false|int $end = false, ?int $limit = null, ?int $numRevisions = null ): array { @@ -138,16 +138,16 @@ public function getRevisions( * a separate query is ran to get the number of revisions. * @param false|int $start * @param false|int $end - * @return ResultStatement + * @return Result */ public function getRevisionsStmt( Page $page, ?User $user = null, ?int $limit = null, ?int $numRevisions = null, - $start = false, - $end = false - ): ResultStatement { + false|int $start = false, + false|int $end = false + ): Result { $revTable = $this->getTableName( $page->getProject()->getDatabaseName(), 'revision', @@ -215,8 +215,12 @@ public function getRevisionsStmt( * @param false|int $end * @return int */ - public function getNumRevisions(Page $page, ?User $user = null, $start = false, $end = false): int - { + public function getNumRevisions( + Page $page, + ?User $user = null, + false|int $start = false, + false|int $end = false + ): int { $cacheKey = $this->getCacheKey(func_get_args(), 'page_numrevisions'); if ($this->cache->hasItem($cacheKey)) { return $this->cache->getItem($cacheKey)->get(); @@ -272,22 +276,19 @@ public function getCheckWikiErrors(Page $page): array // Page title without underscores (str_replace just to be sure) $pageTitle = str_replace('_', ' ', $page->getTitle()); - $conn = $this->getToolsConnection(); - return $conn->executeQuery($sql, [ + return $this->getToolsConnection()->executeQuery($sql, [ 'dbName' => $dbName, 'title' => $pageTitle, ])->fetchAllAssociative(); } /** - * Get or count all wikidata items for the given page, - * not just languages of sister projects + * Get or count all wikidata items for the given page, not just languages of sister projects. * @param Page $page * @param bool $count Set to true to get only a COUNT - * @return string[]|int Records as returend by the DB, - * or raw COUNT of the records. + * @return string[]|int Records as returned by the DB, or raw COUNT of the records. */ - public function getWikidataItems(Page $page, bool $count = false) + public function getWikidataItems(Page $page, bool $count = false): array|int { if (!$page->getWikidataId()) { return $count ? 0 : []; @@ -319,18 +320,18 @@ public function countLinksAndRedirects(Page $page): array $linkTargetTable = $page->getProject()->getTableName('linktarget'); $redirectTable = $page->getProject()->getTableName('redirect'); - $sql = "SELECT COUNT(*) AS value, 'links_ext' AS type + $sql = "SELECT 'links_ext_count' AS type, COUNT(*) AS value FROM $externalLinksTable WHERE el_from = :id UNION - SELECT COUNT(*) AS value, 'links_out' AS type + SELECT 'links_out_count' AS type, COUNT(*) AS value FROM $pageLinksTable WHERE pl_from = :id UNION - SELECT COUNT(*) AS value, 'links_in' AS type + SELECT 'links_in_count' AS type, COUNT(*) AS value FROM $pageLinksTable JOIN $linkTargetTable ON lt_id = pl_target_id WHERE lt_namespace = :namespace AND lt_title = :title UNION - SELECT COUNT(*) AS value, 'redirects' AS type + SELECT 'redirects_count' AS type, COUNT(*) AS value FROM $redirectTable WHERE rd_namespace = :namespace AND rd_title = :title"; $params = [ @@ -339,15 +340,7 @@ public function countLinksAndRedirects(Page $page): array 'namespace' => $page->getNamespace(), ]; - $res = $this->executeProjectsQuery($page->getProject(), $sql, $params); - $data = []; - - // Transform to associative array by 'type' - foreach ($res as $row) { - $data[$row['type'] . '_count'] = (int)$row['value']; - } - - return $data; + return $this->executeProjectsQuery($page->getProject(), $sql, $params)->fetchAllKeyValue(); } /** @@ -369,7 +362,7 @@ public function countWikidataItems(Page $page): int * @return string[][][] * @throws BadGatewayException */ - public function getPageviews(Page $page, $start, $end): array + public function getPageviews(Page $page, string|DateTime $start, string|DateTime $end): array { // Pull from cache for each call during the same request. // FIXME: This is fine for now as we only fetch pageviews for one page at a time, @@ -413,7 +406,7 @@ public function getPageviews(Page $page, $start, $end): array /** * Get the full HTML content of the the page. * @param Page $page - * @param int|null $revId What revision to query for. + * @param ?int $revId What revision to query for. * @return string * @throws BadGatewayException */ diff --git a/src/Repository/PagesRepository.php b/src/Repository/PagesRepository.php index 8c8e7c777..e0c8cec7f 100644 --- a/src/Repository/PagesRepository.php +++ b/src/Repository/PagesRepository.php @@ -30,11 +30,11 @@ class PagesRepository extends UserRepository public function countPagesCreated( Project $project, User $user, - $namespace, + string|int $namespace, string $redirects, string $deleted, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'num_user_pages_created'); if ($this->cache->hasItem($cacheKey)) { @@ -89,13 +89,13 @@ public function countPagesCreated( public function getPagesCreated( Project $project, User $user, - $namespace, + string|int $namespace, string $redirects, string $deleted, - $start = false, - $end = false, + int|false $start = false, + int|false $end = false, ?int $limit = 1000, - $offset = false + int|false $offset = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_pages_created'); if ($this->cache->hasItem($cacheKey)) { @@ -171,7 +171,7 @@ private function getWasRedirectClause(string $redirects, string $deleted): strin * @param string $redirects One of the Pages::REDIR_ constants. * @return string[] With keys 'namespaceRev', 'namespaceArc' and 'redirects' */ - private function getNamespaceRedirectAndDeletedPagesConditions($namespace, string $redirects): array + private function getNamespaceRedirectAndDeletedPagesConditions(string|int $namespace, string $redirects): array { $conditions = [ 'namespaceArc' => '', @@ -210,9 +210,9 @@ private function getPagesCreatedInnerSql( Project $project, array $conditions, string $deleted, - $start, - $end, - $offset = false, + int|false $start, + int|false $end, + int|false $offset = false, bool $count = false ): string { $pageTable = $project->getTableName('page'); @@ -304,10 +304,10 @@ private function getPagesCreatedInnerSql( public function getAssessmentCounts( Project $project, User $user, - $namespace, + int|string $namespace, string $redirects, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_pages_created_assessments'); if ($this->cache->hasItem($cacheKey)) { @@ -373,10 +373,10 @@ public function getAssessmentCounts( public function getWikiprojectCounts( Project $project, User $user, - $namespace, + int|string $namespace, string $redirects, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'user_pages_created_wikiprojects'); if ($this->cache->hasItem($cacheKey)) { diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php index 48ddfe5b4..93084a725 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/ProjectRepository.php @@ -6,7 +6,7 @@ use App\Model\PageAssessments; use App\Model\Project; -use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\Persistence\ManagerRegistry; use Exception; use GuzzleHttp\Client; @@ -20,26 +20,12 @@ */ class ProjectRepository extends Repository { - protected PageAssessmentsRepository $assessmentsRepo; - /** @var string[] Basic metadata if XTools is in single-wiki mode. */ protected array $singleBasicInfo; /** @var string The cache key for the 'all project' metadata. */ protected string $cacheKeyAllProjects = 'allprojects'; - /** @var string The configured default project. */ - protected string $defaultProject; - - /** @var bool Whether XTools is configured to run on a single wiki or not. */ - protected bool $singleWiki; - - /** @var array Projects that have opted into showing restricted stats to everyone. */ - protected array $optedIn; - - /** @var string The project's API path. */ - protected string $apiPath; - /** * @param ManagerRegistry $managerRegistry * @param CacheItemPoolInterface $cache @@ -55,24 +41,23 @@ class ProjectRepository extends Repository * @param string $apiPath */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - PageAssessmentsRepository $assessmentsRepo, - string $defaultProject, - bool $singleWiki, - array $optedIn, - string $apiPath + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected PageAssessmentsRepository $assessmentsRepo, + /** @var string The configured default project. */ + protected string $defaultProject, + /** @var bool Whether XTools is configured to run on a single wiki or not. */ + protected bool $singleWiki, + /** @var array Projects that have opted into showing restricted stats to everyone. */ + protected array $optedIn, + /** @var string The project's API path. */ + protected string $apiPath, ) { - $this->assessmentsRepo = $assessmentsRepo; - $this->defaultProject = $defaultProject; - $this->singleWiki = $singleWiki; - $this->optedIn = $optedIn; - $this->apiPath = $apiPath; parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); } @@ -268,7 +253,7 @@ public function getMetadata(string $projectUrl): ?array 'formatversion' => '2', ], ])->getBody()->getContents(), true); - } catch (Exception $e) { + } catch (Exception) { return null; } @@ -414,7 +399,7 @@ public function getUsersInGroups(Project $project, array $groups = [], array $gl WHERE ug_group IN (?) GROUP BY user_name, ug_group"; $users = $this->getProjectsConnection($project) - ->executeQuery($sql, [$groups], [Connection::PARAM_STR_ARRAY]) + ->executeQuery($sql, [$groups], [ArrayParameterType::STRING]) ->fetchAllAssociative(); if (count($globalGroups) > 0 && $this->isWMF) { @@ -424,7 +409,7 @@ public function getUsersInGroups(Project $project, array $groups = [], array $gl WHERE gug_group IN (?) GROUP BY user_name, user_group"; $globalUsers = $this->getProjectsConnection('centralauth') - ->executeQuery($sql, [$globalGroups], [Connection::PARAM_STR_ARRAY]) + ->executeQuery($sql, [$globalGroups], [ArrayParameterType::STRING]) ->fetchAllAssociative(); $users = array_merge($users, $globalUsers); diff --git a/src/Repository/Repository.php b/src/Repository/Repository.php index f42c4853c..6987769cf 100644 --- a/src/Repository/Repository.php +++ b/src/Repository/Repository.php @@ -8,9 +8,9 @@ use App\Model\Project; use DateInterval; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Result; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; use GuzzleHttp\Exception\ConnectException; @@ -29,64 +29,40 @@ */ abstract class Repository { - protected CacheItemPoolInterface $cache; - protected Client $guzzle; - protected LoggerInterface $logger; - protected ManagerRegistry $managerRegistry; - protected ParameterBagInterface $parameterBag; - /** @var Connection The database connection to the meta database. */ private Connection $metaConnection; /** @var Connection The database connection to other tools' databases. */ private Connection $toolsConnection; - /** @var bool Whether this is configured as a WMF installation. */ - protected bool $isWMF; - - /** @var int */ - protected int $queryTimeout; - - /** @var RequestStack|null */ - protected ?RequestStack $requestStack; - /** @var string Prefix URL for where the dblists live. Will be followed by i.e. 's1.dblist' */ public const DBLISTS_URL = 'https://noc.wikimedia.org/conf/dblists/'; /** * Create a new Repository. - * @param ManagerRegistry $managerRegistry - * @param Client $guzzle - * @param LoggerInterface $logger - * @param ParameterBagInterface $parameterBag - * @param bool $isWMF - * @param int $queryTimeout - * @param RequestStack|null $requestStack */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ?RequestStack $requestStack = null + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ?RequestStack $requestStack = null ) { - $this->managerRegistry = $managerRegistry; - $this->cache = $cache; - $this->guzzle = $guzzle; - $this->logger = $logger; - $this->parameterBag = $parameterBag; - $this->isWMF = $isWMF; - $this->queryTimeout = $queryTimeout; - $this->requestStack = $requestStack; } /*************** * CONNECTIONS * ***************/ + private function getConnection(string $name): Connection + { + /** @type Connection */ + return $this->managerRegistry->getConnection($name); + } + /** * Get the database connection for the 'meta' database. * @return Connection @@ -106,7 +82,7 @@ protected function getMetaConnection(): Connection * @return Connection * @codeCoverageIgnore */ - protected function getProjectsConnection($project): Connection + protected function getProjectsConnection(Project|string $project): Connection { if (is_string($project)) { if (1 === preg_match('/^s\d+$/', $project)) { @@ -120,7 +96,7 @@ protected function getProjectsConnection($project): Connection $slice = $this->getDbList()[$project->getDatabaseName()]; } - return $this->managerRegistry->getConnection('toolforge_'.$slice); + return $this->getConnection('toolforge_'.$slice); } /** @@ -131,7 +107,7 @@ protected function getProjectsConnection($project): Connection protected function getToolsConnection(): Connection { if (!isset($this->toolsConnection)) { - $this->toolsConnection = $this->managerRegistry->getConnection('toolsdb'); + $this->toolsConnection = $this->getConnection('toolsdb'); } return $this->toolsConnection; } @@ -154,7 +130,7 @@ protected function getDbList(): array $exists = true; $i = 0; - while ($exists) { + while (true) { $i += 1; $response = $this->guzzle->request('GET', self::DBLISTS_URL."s$i.dblist", ['http_errors' => false]); $exists = in_array( @@ -335,7 +311,7 @@ private function getCacheKeyFromArg($arg): string * @param string $duration Valid DateInterval string. * @return mixed The given $value. */ - public function setCache(string $cacheKey, $value, string $duration = 'PT20M') + public function setCache(string $cacheKey, mixed $value, string $duration = 'PT20M'): mixed { $cacheItem = $this->cache ->getItem($cacheKey) @@ -359,9 +335,9 @@ public function setCache(string $cacheKey, $value, string $duration = 'PT20M') * @return string */ public function getDateConditions( - $start, - $end, - $offset = false, + false|int $start, + false|int $end, + false|int $offset = false, string $tableAlias = '', string $field = 'rev_timestamp' ) : string { @@ -392,16 +368,16 @@ public function getDateConditions( * @param array $params Parameters to bound to the prepared query. * @param int|null $timeout Maximum statement time in seconds. null will use the * default specified by the APP_QUERY_TIMEOUT env variable. - * @return ResultStatement + * @return Result * @throws DriverException * @codeCoverageIgnore */ public function executeProjectsQuery( - $project, + Project|string $project, string $sql, array $params = [], ?int $timeout = null - ): ResultStatement { + ): Result { try { $timeout = $timeout ?? $this->queryTimeout; $sql = "SET STATEMENT max_statement_time = $timeout FOR\n".$sql; @@ -417,17 +393,18 @@ public function executeProjectsQuery( * @param QueryBuilder $qb * @param int|null $timeout Maximum statement time in seconds. null will use the * default specified by the APP_QUERY_TIMEOUT env variable. - * @return ResultStatement + * @return Result * @throws HttpException * @throws DriverException * @codeCoverageIgnore */ - public function executeQueryBuilder(QueryBuilder $qb, ?int $timeout = null): ResultStatement + public function executeQueryBuilder(QueryBuilder $qb, ?int $timeout = null): Result { try { $timeout = $timeout ?? $this->queryTimeout; $sql = "SET STATEMENT max_statement_time = $timeout FOR\n".$qb->getSQL(); - return $qb->getConnection()->executeQuery($sql, $qb->getParameters(), $qb->getParameterTypes()); + // FIXME + return $qb->executeQuery($sql, $qb->getParameters(), $qb->getParameterTypes()); } catch (DriverException $e) { $this->handleDriverError($e, $timeout); } @@ -448,9 +425,9 @@ private function handleDriverError(DriverException $e, ?int $timeout): void $timeout = $this->queryTimeout; } - if (1226 === $e->getErrorCode()) { + if (1226 === $e->getCode()) { throw new ServiceUnavailableHttpException(30, 'error-service-overload', null, 503); - } elseif (in_array($e->getErrorCode(), [2006, 2013])) { + } elseif (in_array($e->getCode(), [2006, 2013])) { // FIXME: Attempt to reestablish connection on 2006 error (MySQL server has gone away). throw new HttpException( Response::HTTP_GATEWAY_TIMEOUT, @@ -459,7 +436,7 @@ private function handleDriverError(DriverException $e, ?int $timeout): void [], Response::HTTP_GATEWAY_TIMEOUT ); - } elseif (1969 == $e->getErrorCode()) { + } elseif (1969 == $e->getCode()) { throw new HttpException( Response::HTTP_GATEWAY_TIMEOUT, 'error-query-timeout', diff --git a/src/Repository/SimpleEditCounterRepository.php b/src/Repository/SimpleEditCounterRepository.php index 816f939f0..c7a4b5fc4 100644 --- a/src/Repository/SimpleEditCounterRepository.php +++ b/src/Repository/SimpleEditCounterRepository.php @@ -24,8 +24,13 @@ class SimpleEditCounterRepository extends Repository * @param int|false $end Unix timestamp. * @return string[] Counts, each row with keys 'source' and 'value'. */ - public function fetchData(Project $project, User $user, $namespace = 'all', $start = false, $end = false): array - { + public function fetchData( + Project $project, + User $user, + int|string $namespace = 'all', + int|false $start = false, + int|false $end = false + ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'simple_editcount'); if ($this->cache->hasItem($cacheKey)) { return $this->cache->getItem($cacheKey)->get(); @@ -52,9 +57,9 @@ public function fetchData(Project $project, User $user, $namespace = 'all', $sta private function fetchDataNormal( Project $project, User $user, - $namespace = 'all', - $start = false, - $end = false + int|string $namespace = 'all', + int|false $start = false, + int|false $end = false, ): array { $userTable = $project->getTableName('user'); $pageTable = $project->getTableName('page'); @@ -117,9 +122,9 @@ private function fetchDataNormal( private function fetchDataIpRange( Project $project, User $user, - $namespace = 'all', - $start = false, - $end = false + int|string $namespace = 'all', + int|false $start = false, + int|false $end = false ): array { $ipcTable = $project->getTableName('ip_changes'); $revTable = $project->getTableName('revision', ''); diff --git a/src/Repository/TopEditsRepository.php b/src/Repository/TopEditsRepository.php index 54c98e482..15fcbc12c 100644 --- a/src/Repository/TopEditsRepository.php +++ b/src/Repository/TopEditsRepository.php @@ -10,12 +10,10 @@ use App\Model\User; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; -use PDO; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Wikimedia\IPUtils; /** @@ -26,37 +24,19 @@ */ class TopEditsRepository extends UserRepository { - protected EditRepository $editRepo; - protected UserRepository $userRepo; - - /** - * @param ManagerRegistry $managerRegistry - * @param CacheItemPoolInterface $cache - * @param Client $guzzle - * @param LoggerInterface $logger - * @param ParameterBagInterface $parameterBag - * @param bool $isWMF - * @param int $queryTimeout - * @param ProjectRepository $projectRepo - * @param EditRepository $editRepo - * @param UserRepository $userRepo - * @param SessionInterface $session - */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ProjectRepository $projectRepo, - EditRepository $editRepo, - UserRepository $userRepo, - RequestStack $requestStack + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ProjectRepository $projectRepo, + protected EditRepository $editRepo, + protected UserRepository $userRepo, + protected ?RequestStack $requestStack ) { - $this->editRepo = $editRepo; - $this->userRepo = $userRepo; parent::__construct( $managerRegistry, $cache, @@ -96,8 +76,8 @@ public function getTopEditsNamespace( Project $project, User $user, int $namespace = 0, - $start = false, - $end = false, + int|false $start = false, + int|false $end = false, int $limit = 1000, int $pagination = 0 ): array { @@ -122,16 +102,6 @@ public function getTopEditsNamespace( LIMIT 1 ) AS pa_class" : ''; - $paProjectsTable = $project->getTableName('page_assessments_projects'); - $paProjectsSelect = $hasPageAssessments - ? ", ( - SELECT JSON_ARRAYAGG(pap_project_title) - FROM $paTable - JOIN $paProjectsTable - ON pa_project_id = pap_project_id - WHERE pa_page_id = page_id - ) AS pap_project_title" - : ''; $ipcJoin = ''; $whereClause = 'rev_actor = :actorId'; @@ -147,7 +117,6 @@ public function getTopEditsNamespace( $sql = "SELECT page_namespace AS `namespace`, page_title, page_is_redirect AS `redirect`, COUNT(page_title) AS `count` $paSelect - $paProjectsSelect FROM $pageTable JOIN $revisionTable ON page_id = rev_page @@ -176,8 +145,13 @@ public function getTopEditsNamespace( * @param int|false $end End date as Unix timestamp. * @return mixed */ - public function countPagesNamespace(Project $project, User $user, $namespace, $start = false, $end = false) - { + public function countPagesNamespace( + Project $project, + User $user, + int|string $namespace, + int|false $start = false, + int|false $end = false + ) { // Set up cache. $cacheKey = $this->getCacheKey(func_get_args(), 'topedits_count_ns'); if ($this->cache->hasItem($cacheKey)) { @@ -220,13 +194,14 @@ public function countPagesNamespace(Project $project, User $user, $namespace, $s * @param int $ns * @param int|false $start * @param int|false $end + * @return array */ public function getProjectTotals( Project $project, User $user, int $ns, - $start = false, - $end = false + int|false $start = false, + int|false $end = false ): array { $cacheKey = $this->getCacheKey(func_get_args(), 'top_edits_wikiprojects'); if ($this->cache->hasItem($cacheKey)) { @@ -285,8 +260,8 @@ public function getProjectTotals( public function getTopEditsAllNamespaces( Project $project, User $user, - $start = false, - $end = false, + int|false $start = false, + int|false $end = false, int $limit = 10 ): array { // Set up cache. @@ -309,17 +284,6 @@ public function getTopEditsAllNamespaces( LIMIT 1 ) AS pa_class" : ''; - $paProjectsTable = $project->getTableName('page_assessments_projects'); - $paProjectsSelect = $hasPageAssessments - ? ", ( - SELECT JSON_ARRAYAGG(pap_project_title) - FROM $pageAssessmentsTable - JOIN $paProjectsTable - ON pa_project_id = pap_project_id - WHERE pa_page_id = e.page_id - ) AS pap_project_title" - : ''; - $ipcJoin = ''; $whereClause = 'rev_actor = :actorId'; @@ -332,7 +296,7 @@ public function getTopEditsAllNamespaces( } $sql = "SELECT c.page_namespace AS `namespace`, e.page_title, - c.page_is_redirect AS `redirect`, c.count $paSelect $paProjectsSelect + c.page_is_redirect AS `redirect`, c.count $paSelect FROM ( SELECT b.page_namespace, b.page_is_redirect, b.rev_page, b.count @@ -369,7 +333,7 @@ public function getTopEditsAllNamespaces( * @return string[][] Each row with keys 'id', 'timestamp', 'minor', 'length', * 'length_change', 'reverted', 'user_id', 'username', 'comment', 'parent_comment' */ - public function getTopEditsPage(Page $page, User $user, $start = false, $end = false): array + public function getTopEditsPage(Page $page, User $user, int|false $start = false, int|false $end = false): array { // Set up cache. $cacheKey = $this->getCacheKey(func_get_args(), 'topedits_page'); @@ -404,8 +368,8 @@ public function getTopEditsPage(Page $page, User $user, $start = false, $end = f private function queryTopEditsPage( Page $page, User $user, - $start = false, - $end = false, + int|false $start = false, + int|false $end = false, bool $childRevs = false ): array { $project = $page->getProject(); @@ -446,8 +410,8 @@ private function queryTopEditsPage( $childLimit = 'LIMIT 1'; } - $userId = $this->getProjectsConnection($project)->quote($user->getId($page->getProject()), PDO::PARAM_STR); - $username = $this->getProjectsConnection($project)->quote($user->getUsername(), PDO::PARAM_STR); + $userId = $user->getId($page->getProject()); + $username = $this->getProjectsConnection($project)->quote($user->getUsername()); // IP range handling. $ipcJoin = ''; diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 59786950c..e2fbe3036 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -6,7 +6,7 @@ use App\Model\Project; use App\Model\User; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Result; use Doctrine\Persistence\ManagerRegistry; use GuzzleHttp\Client; use Psr\Cache\CacheItemPoolInterface; @@ -21,31 +21,17 @@ */ class UserRepository extends Repository { - protected ProjectRepository $projectRepo; - - /** - * @param ManagerRegistry $managerRegistry - * @param CacheItemPoolInterface $cache - * @param Client $guzzle - * @param LoggerInterface $logger - * @param ParameterBagInterface $parameterBag - * @param bool $isWMF - * @param int $queryTimeout - * @param ProjectRepository $projectRepo - * @param RequestStack $requestStack - */ public function __construct( - ManagerRegistry $managerRegistry, - CacheItemPoolInterface $cache, - Client $guzzle, - LoggerInterface $logger, - ParameterBagInterface $parameterBag, - bool $isWMF, - int $queryTimeout, - ProjectRepository $projectRepo, - RequestStack $requestStack + protected ManagerRegistry $managerRegistry, + protected CacheItemPoolInterface $cache, + protected Client $guzzle, + protected LoggerInterface $logger, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected int $queryTimeout, + protected ProjectRepository $projectRepo, + protected ?RequestStack $requestStack ) { - $this->projectRepo = $projectRepo; parent::__construct( $managerRegistry, $cache, @@ -86,7 +72,7 @@ public function getIdAndRegistration(string $databaseName, string $username) * Get the user's actor ID. * @param string $databaseName * @param string $username - * @return int|null + * @return ?int */ public function getActorId(string $databaseName, string $username): ?int { @@ -128,7 +114,7 @@ public function getEditCount(string $databaseName, string $username): int $sql = "SELECT user_editcount FROM $userTable WHERE user_name = :username LIMIT 1"; $resultQuery = $this->executeProjectsQuery($databaseName, $sql, ['username' => $username]); - return (int)$this->setCache($cacheKey, $resultQuery->fetchColumn()); + return (int)$this->setCache($cacheKey, $resultQuery->fetchOne()); } /** @@ -172,8 +158,13 @@ public function countActiveBlocks(Project $project, User $user): int * @param int|false $end End date as Unix timestamp. * @return int */ - public function countEdits(Project $project, User $user, $namespace = 'all', $start = false, $end = false): int - { + public function countEdits( + Project $project, + User $user, + int|string $namespace = 'all', + int|false $start = false, + int|false $end = false + ): int { $cacheKey = $this->getCacheKey(func_get_args(), 'user_editcount'); if ($this->cache->hasItem($cacheKey)) { return (int)$this->cache->getItem($cacheKey)->get(); @@ -214,7 +205,7 @@ public function countEdits(Project $project, User $user, $namespace = 'all', $st * Get information about the currently-logged in user. * @return array|object|null null if not logged in. */ - public function getXtoolsUserInfo() + public function getXtoolsUserInfo(): object|array|null { return $this->requestStack->getSession()->get('logged_in_user'); } @@ -243,7 +234,7 @@ public function maxEdits(): int * @param int|string $namespace Namespace ID or 'all' for all namespaces. * @return array [page join clause, page namespace clause] */ - protected function getPageAndNamespaceSql(Project $project, $namespace): array + protected function getPageAndNamespaceSql(Project $project, int|string $namespace): array { if ('all' === $namespace) { return [null, null]; @@ -278,15 +269,15 @@ public function getUserConditions(bool $dateFiltering = false): array * @param User $user * @param int|string|null $namespace Namespace ID, or 'all'/null for all namespaces. * @param array $extraParams Will get merged in the params array used for binding values. - * @return ResultStatement + * @return Result */ protected function executeQuery( string $sql, Project $project, User $user, - $namespace = 'all', + int|string|null $namespace = 'all', array $extraParams = [] - ): ResultStatement { + ): Result { $params = ['actorId' => $user->getActorId($project)]; if ('all' !== $namespace) { diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 946924b07..93a5961dc 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -24,48 +24,19 @@ */ class AppExtension extends AbstractExtension { - protected I18nHelper $i18n; - protected ParameterBagInterface $parameterBag; - protected ProjectRepository $projectRepo; - protected RequestStack $requestStack; - protected UrlGeneratorInterface $urlGenerator; - - protected bool $isWMF; - protected int $replagThreshold; - protected bool $singleWiki; - /** @var float Duration of the current HTTP request in seconds. */ protected float $requestTime; - /** - * Constructor, with the I18nHelper through dependency injection. - * @param RequestStack $requestStack - * @param I18nHelper $i18n - * @param UrlGeneratorInterface $generator - * @param ProjectRepository $projectRepo - * @param ParameterBagInterface $parameterBag - * @param bool $isWMF - * @param bool $singleWiki - * @param int $replagThreshold - */ public function __construct( - RequestStack $requestStack, - I18nHelper $i18n, - UrlGeneratorInterface $generator, - ProjectRepository $projectRepo, - ParameterBagInterface $parameterBag, - bool $isWMF, - bool $singleWiki, - int $replagThreshold + protected RequestStack $requestStack, + protected I18nHelper $i18n, + protected UrlGeneratorInterface $urlGenerator, + protected ProjectRepository $projectRepo, + protected ParameterBagInterface $parameterBag, + protected bool $isWMF, + protected bool $singleWiki, + protected int $replagThreshold ) { - $this->requestStack = $requestStack; - $this->i18n = $i18n; - $this->urlGenerator = $generator; - $this->projectRepo = $projectRepo; - $this->parameterBag = $parameterBag; - $this->isWMF = $isWMF; - $this->singleWiki = $singleWiki; - $this->replagThreshold = $replagThreshold; } /*********************************** FUNCTIONS ***********************************/ @@ -455,7 +426,7 @@ public function quote(): string * Get the currently logged in user's details. * @return string[]|object|null */ - public function loggedInUser() + public function loggedInUser(): array|object|null { return $this->requestStack->getSession()->get('logged_in_user'); } @@ -502,7 +473,7 @@ public function getFilters(): array * @param int $decimals Number of decimals to format to. * @return string */ - public function numberFormat($number, int $decimals = 0): string + public function numberFormat(int|float $number, int $decimals = 0): string { return $this->i18n->numberFormat($number, $decimals); } @@ -541,7 +512,7 @@ public function sizeFormat(int $bytes, int $precision = 2): string * @see http://userguide.icu-project.org/formatparse/datetime * @return string */ - public function dateFormat($datetime, string $pattern = 'yyyy-MM-dd HH:mm'): string + public function dateFormat(string|int|DateTime $datetime, string $pattern = 'yyyy-MM-dd HH:mm'): string { return $this->i18n->dateFormat($datetime, $pattern); } @@ -575,7 +546,7 @@ public function capitalizeFirst(string $str): string * @param integer $precision Number of decimal places to show. * @return string Formatted percentage. */ - public function percentFormat($numerator, ?int $denominator = null, int $precision = 1): string + public function percentFormat(int|float $numerator, ?int $denominator = null, int $precision = 1): string { return $this->i18n->percentFormat($numerator, $denominator, $precision); } @@ -586,7 +557,7 @@ public function percentFormat($numerator, ?int $denominator = null, int $precisi * @param User|string $user User object or username as a string. * @return bool */ - public function isUserAnon(Project $project, $user): bool + public function isUserAnon(Project $project, User|string $user): bool { if ($user instanceof User) { $username = $user->getUsername(); @@ -602,7 +573,7 @@ public function isUserAnon(Project $project, $user): bool * @param string[] $namespaces List of available namespaces as retrieved from Project::getNamespaces(). * @return string Namespace name */ - public function nsName($namespace, array $namespaces): string + public function nsName(int|string $namespace, array $namespaces): string { if ('all' === $namespace) { return $this->i18n->msg('all'); @@ -663,7 +634,7 @@ public function diffFormat(?int $size): string * @return string|array Examples: '30 seconds', '2 minutes', '15 hours', '500 days', * or [30, 'num-seconds'] (etc.) if $translate is false. */ - public function formatDuration(int $seconds, bool $translate = true) + public function formatDuration(int $seconds, bool $translate = true): string|array { [$val, $key] = $this->getDurationMessageKey($seconds); diff --git a/symfony.lock b/symfony.lock index 5384528e5..cc9cb1787 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,13 +1,4 @@ { - "doctrine/annotations": { - "version": "1.14", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "1.10", - "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" - } - }, "doctrine/deprecations": { "version": "1.0", "recipe": { @@ -147,20 +138,18 @@ ] }, "symfony/framework-bundle": { - "version": "4.4", + "version": "6.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "4.4", - "ref": "24eb45d1355810154890460e6a05c0ca27318fe7" + "version": "6.4", + "ref": "32126346f25e1cee607cc4aa6783d46034920554" }, "files": [ - "config/bootstrap.php", "config/packages/cache.yaml", "config/packages/framework.yaml", - "config/packages/test/framework.yaml", "config/preload.php", - "config/routes/dev/framework.yaml", + "config/routes/framework.yaml", "config/services.yaml", "public/index.php", "src/Controller/.gitignore", diff --git a/templates/pages/result.html.twig b/templates/pages/result.html.twig index 41baf8720..bebccc097 100644 --- a/templates/pages/result.html.twig +++ b/templates/pages/result.html.twig @@ -300,3 +300,4 @@ {% endif %} {% endblock %} + diff --git a/templates/topedits/result_namespace.wikitext.twig b/templates/topedits/result_namespace.wikitext.twig index acb20c808..674b8e906 100644 --- a/templates/topedits/result_namespace.wikitext.twig +++ b/templates/topedits/result_namespace.wikitext.twig @@ -41,13 +41,13 @@ |} {% if showPageAssessment %} {% set totals = te.projectTotals(ns) %} -{% if totals|keys|length > 0 %} +{% if totals|length > 0 %} {| class="wikitable sortable" |+ {{ msg('top-wikiprojects') }} ! {{ msg('wikiproject') }} !! {{ msg('count') }} -{% for wikiproject, count in totals %} +{% for row in totals %} |- -| {{ wikiproject }} || {{ count|num_format }} +| {{ row.pap_project_title }} || {% verbatim %}{{FORMATNUM:{% endverbatim %}{{ row.count }}}} {% endfor %} |} {% endif %} diff --git a/tests/Controller/EditCounterControllerTest.php b/tests/Controller/EditCounterControllerTest.php index 1d63deb91..e0cd3f1b8 100644 --- a/tests/Controller/EditCounterControllerTest.php +++ b/tests/Controller/EditCounterControllerTest.php @@ -39,7 +39,6 @@ public function testIndexPages(): void '/ec-yearcounts', '/ec-monthcounts', '/ec-rightschanges', - '/ec-latestglobal', ]; foreach ($routes as $route) { @@ -130,7 +129,6 @@ public function testResultPages(): void '/ec-monthcounts/en.wikipedia/Example', '/ec-monthcounts/en.wikipedia/Example?format=wikitext', '/ec-rightschanges/en.wikipedia/Example', - '/ec-latestglobal/en.wikipedia/Example', ]); } diff --git a/tests/Controller/OverridableXtoolsController.php b/tests/Controller/OverridableXtoolsController.php index f0810b167..6bd9074bf 100644 --- a/tests/Controller/OverridableXtoolsController.php +++ b/tests/Controller/OverridableXtoolsController.php @@ -64,7 +64,6 @@ public function __construct( $requestStack, $managerRegistry, $cache, - $flashBag, $guzzle, $i18n, $projectRepo, diff --git a/tests/Model/ModelTest.php b/tests/Model/ModelTest.php index 3ec168f43..a9ea968fa 100644 --- a/tests/Model/ModelTest.php +++ b/tests/Model/ModelTest.php @@ -41,7 +41,7 @@ public function testBasics(): void self::assertEquals($end, $model->getEndDate()); self::assertTrue($model->hasDateRange()); self::assertNull($model->getLimit()); - self::assertNull($model->getOffset()); + self::assertFalse($model->getOffset()); self::assertNull($model->getOffsetISO()); } } diff --git a/tests/Model/PageTest.php b/tests/Model/PageTest.php index bd5bfda2d..9344e0120 100644 --- a/tests/Model/PageTest.php +++ b/tests/Model/PageTest.php @@ -229,7 +229,7 @@ public function testUsersEdits(): void */ public function testErrors(): void { - $this->markTestSkipped( 'Broken until T413013 is fixed' ); + $this->markTestSkipped('Broken until T413013 is fixed'); $checkWikiErrors = [ [ 'error' => '61', @@ -320,11 +320,11 @@ private function getRealPageRepository(): PageRepository { static::createClient(); return new PageRepository( - self::$container->get('doctrine'), - self::$container->get('cache.app'), - self::$container->get('eight_points_guzzle.client.xtools'), + static::getContainer()->get('doctrine'), + static::getContainer()->get('cache.app'), + static::getContainer()->get('eight_points_guzzle.client.xtools'), $this->createMock(LoggerInterface::class), - self::$container->get('parameter_bag'), + static::getContainer()->get('parameter_bag'), true, 30 );