From f75022af08ebda59490ebc63af8aed3b7b2b53f7 Mon Sep 17 00:00:00 2001 From: "c.boelter" Date: Thu, 7 May 2026 17:12:27 +0200 Subject: [PATCH 1/2] rework migration to handle also separators and nested grids --- .phpcq.lock | 2 +- src/Migration/GridWrapperMigration.php | 432 ++++++++++++++++++++----- 2 files changed, 345 insertions(+), 89 deletions(-) diff --git a/.phpcq.lock b/.phpcq.lock index 58f5971..a940923 100644 --- a/.phpcq.lock +++ b/.phpcq.lock @@ -1 +1 @@ -{"plugins":{"doctrine-coding-standard":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/doctrine-coding-standard/doctrine-coding-standard-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"composer":{"doctrine/coding-standard":"^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0 || ^14.0"}},"checksum":{"type":"sha-512","value":"00fab498a6575bf07930e078fd616c0481714570bc1c61ebae4fa277d64c0cb28575aba9190c9731c7bda9f97f57c113516e1eb2920c3b9b7b295e0078be3159"},"tools":{},"composerLock":"{\n \"_readme\": [\n \"This file locks the dependencies of your project to a known state\",\n \"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies\",\n \"This file is @generated automatically\"\n ],\n \"content-hash\": \"40b84043be9c25e6feb38a9e014f1a9f\",\n \"packages\": [\n {\n \"name\": \"dealerdirect/phpcodesniffer-composer-installer\",\n \"version\": \"v1.2.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/composer-installer.git\",\n \"reference\": \"845eb62303d2ca9b289ef216356568ccc075ffd1\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1\",\n \"reference\": \"845eb62303d2ca9b289ef216356568ccc075ffd1\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"composer-plugin-api\": \"^2.2\",\n \"php\": \">=5.4\",\n \"squizlabs/php_codesniffer\": \"^3.1.0 || ^4.0\"\n },\n \"require-dev\": {\n \"composer/composer\": \"^2.2\",\n \"ext-json\": \"*\",\n \"ext-zip\": \"*\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.4.0\",\n \"phpcompatibility/php-compatibility\": \"^9.0 || ^10.0.0@dev\",\n \"yoast/phpunit-polyfills\": \"^1.0\"\n },\n \"type\": \"composer-plugin\",\n \"extra\": {\n \"class\": \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\Plugin\"\n },\n \"autoload\": {\n \"psr-4\": {\n \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\\": \"src/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Franck Nijhof\",\n \"email\": \"opensource@frenck.dev\",\n \"homepage\": \"https://frenck.dev\",\n \"role\": \"Open source developer\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/composer-installer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer Standards Composer Installer Plugin\",\n \"keywords\": [\n \"PHPCodeSniffer\",\n \"PHP_CodeSniffer\",\n \"code quality\",\n \"codesniffer\",\n \"composer\",\n \"installer\",\n \"phpcbf\",\n \"phpcs\",\n \"plugin\",\n \"qa\",\n \"quality\",\n \"standard\",\n \"standards\",\n \"style guide\",\n \"stylecheck\",\n \"tests\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/composer-installer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/composer-installer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/composer-installer\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2025-11-11T04:32:07+00:00\"\n },\n {\n \"name\": \"doctrine/coding-standard\",\n \"version\": \"14.0.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/doctrine/coding-standard.git\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/doctrine/coding-standard/zipball/897a7dc209e49ee6cf04e689c41112df17967130\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.6.2 || ^0.7 || ^1.0.0\",\n \"php\": \"^7.4 || ^8.0\",\n \"slevomat/coding-standard\": \"^8.23\",\n \"squizlabs/php_codesniffer\": \"^4\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Benjamin Eberlei\",\n \"email\": \"kontakt@beberlei.de\"\n },\n {\n \"name\": \"Steve Müller\",\n \"email\": \"st.mueller@dzh-online.de\"\n }\n ],\n \"description\": \"The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.\",\n \"homepage\": \"https://www.doctrine-project.org/projects/coding-standard.html\",\n \"keywords\": [\n \"checks\",\n \"code\",\n \"coding\",\n \"cs\",\n \"dev\",\n \"doctrine\",\n \"rules\",\n \"sniffer\",\n \"sniffs\",\n \"standard\",\n \"style\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/doctrine/coding-standard/issues\",\n \"source\": \"https://github.com/doctrine/coding-standard/tree/14.0.0\"\n },\n \"time\": \"2025-09-21T18:21:47+00:00\"\n },\n {\n \"name\": \"phpstan/phpdoc-parser\",\n \"version\": \"2.3.2\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/phpstan/phpdoc-parser.git\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"php\": \"^7.4 || ^8.0\"\n },\n \"require-dev\": {\n \"doctrine/annotations\": \"^2.0\",\n \"nikic/php-parser\": \"^5.3.0\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.2\",\n \"phpstan/extension-installer\": \"^1.0\",\n \"phpstan/phpstan\": \"^2.0\",\n \"phpstan/phpstan-phpunit\": \"^2.0\",\n \"phpstan/phpstan-strict-rules\": \"^2.0\",\n \"phpunit/phpunit\": \"^9.6\",\n \"symfony/process\": \"^5.2\"\n },\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"PHPStan\\\\PhpDocParser\\\\\": [\n \"src/\"\n ]\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"PHPDoc parser with support for nullable, intersection and generic types\",\n \"support\": {\n \"issues\": \"https://github.com/phpstan/phpdoc-parser/issues\",\n \"source\": \"https://github.com/phpstan/phpdoc-parser/tree/2.3.2\"\n },\n \"time\": \"2026-01-25T14:56:51+00:00\"\n },\n {\n \"name\": \"slevomat/coding-standard\",\n \"version\": \"8.28.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/slevomat/coding-standard.git\",\n \"reference\": \"66151cfbd25b50e8becd9f809fb704f01fd4d6f2\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/slevomat/coding-standard/zipball/66151cfbd25b50e8becd9f809fb704f01fd4d6f2\",\n \"reference\": \"66151cfbd25b50e8becd9f809fb704f01fd4d6f2\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.7 || ^1.2.0\",\n \"php\": \"^7.4 || ^8.0\",\n \"phpstan/phpdoc-parser\": \"^2.3.2\",\n \"squizlabs/php_codesniffer\": \"^4.0.1\"\n },\n \"require-dev\": {\n \"phing/phing\": \"3.0.1|3.1.2\",\n \"php-parallel-lint/php-parallel-lint\": \"1.4.0\",\n \"phpstan/phpstan\": \"2.1.42\",\n \"phpstan/phpstan-deprecation-rules\": \"2.0.4\",\n \"phpstan/phpstan-phpunit\": \"2.0.16\",\n \"phpstan/phpstan-strict-rules\": \"2.0.10\",\n \"phpunit/phpunit\": \"9.6.34|10.5.63|11.4.4|11.5.50|12.5.14\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"8.x-dev\"\n }\n },\n \"autoload\": {\n \"psr-4\": {\n \"SlevomatCodingStandard\\\\\": \"SlevomatCodingStandard/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.\",\n \"keywords\": [\n \"dev\",\n \"phpcs\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/slevomat/coding-standard/issues\",\n \"source\": \"https://github.com/slevomat/coding-standard/tree/8.28.1\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/kukulich\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://tidelift.com/funding/github/packagist/slevomat/coding-standard\",\n \"type\": \"tidelift\"\n }\n ],\n \"time\": \"2026-03-22T17:22:38+00:00\"\n },\n {\n \"name\": \"squizlabs/php_codesniffer\",\n \"version\": \"4.0.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer.git\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"ext-simplexml\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"php\": \">=7.2.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31\"\n },\n \"bin\": [\n \"bin/phpcbf\",\n \"bin/phpcs\"\n ],\n \"type\": \"library\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"BSD-3-Clause\"\n ],\n \"authors\": [\n {\n \"name\": \"Greg Sherwood\",\n \"role\": \"Former lead\"\n },\n {\n \"name\": \"Juliette Reinders Folmer\",\n \"role\": \"Current lead\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"keywords\": [\n \"phpcs\",\n \"standards\",\n \"static analysis\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"wiki\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2025-11-10T16:43:36+00:00\"\n }\n ],\n \"packages-dev\": [],\n \"aliases\": [],\n \"minimum-stability\": \"stable\",\n \"stability-flags\": {},\n \"prefer-stable\": false,\n \"prefer-lowest\": false,\n \"platform\": {},\n \"platform-dev\": {},\n \"plugin-api-version\": \"2.6.0\"\n}\n"},"composer-normalize":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-normalize/composer-normalize-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-json":"*"},"tool":{"composer-normalize":"^2.1"}},"checksum":{"type":"sha-512","value":"d9abda440b85d501c58abf9c81bf76f417594b397129215ffa8b777e9bb5e5eda37d7661d661db3c8d11c24f20345bc6fbe56f013b3b9435d459d2b94f086e0f"},"tools":{"composer-normalize":{"version":"2.51.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar","requirements":{"php":{"php":"~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0","ext-json":"*"}},"checksum":null,"signature":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar.asc"}},"composerLock":null},"composer-require-checker":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-require-checker/composer-require-checker-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0"},"tool":{"composer-require-checker":"^3.8 || ^4.0"}},"checksum":{"type":"sha-512","value":"d5415bddfe024c5749d894034583882aee4e5c3e1087815d9fdd81cb5e71630f631a0e35de0ff84b97fbbf738c16ece5f83bd8c00695913eb846aa6f04577dc2"},"tools":{"composer-require-checker":{"version":"4.24.0","url":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.24.0/composer-require-checker.phar","requirements":{"php":{"php":"~8.4.0 || ~8.5.0","ext-phar":"*"}},"checksum":null,"signature":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.24.0/composer-require-checker.phar.asc"}},"composerLock":null},"phpcpd":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcpd/phpcpd-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcpd":"^6.0"}},"checksum":{"type":"sha-512","value":"1189ce0bf3fade4cb4241f1d96f915ef8fc7651f4450dc79fdf464ee3d6be3009316f0d423ce2d4af9d76ad50807b7fdf4d77bfa6d9ee2c91d6eda32ea214433"},"tools":{"phpcpd":{"version":"6.0.3","url":"https://phar.phpunit.de/phpcpd-6.0.3.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*"}},"checksum":{"type":"sha-256","value":"2cbaea7cfda1bb4299d863eb075e977c3f49055dd16d88529fae5150d48a84cb"},"signature":"https://phar.phpunit.de/phpcpd-6.0.3.phar.asc"}},"composerLock":null},"phploc":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phploc/phploc-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*","ext-json":"*"},"tool":{"phploc":"^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"}},"checksum":{"type":"sha-512","value":"f67b02d494796adf553cb3dd13ec06c1cb8e53c799954061749424251379541637538199afb3afa3c7a01cabd1cb6f1c53eb621f015dff9644c6c7cbf10c56d1"},"tools":{"phploc":{"version":"7.0.2","url":"https://phar.phpunit.de/phploc-7.0.2.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*"}},"checksum":{"type":"sha-256","value":"3d59778ec86faf25fd00e3a329b2f9ad4a3c751ca91601ea7dab70f887b0bf46"},"signature":"https://phar.phpunit.de/phploc-7.0.2.phar.asc"}},"composerLock":null},"phpmd":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpmd/phpmd-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpmd":"^2.6.1"}},"checksum":{"type":"sha-512","value":"f22280a6dec8dbdd2ec1d83b294f23237fe32c34f4a298e52038e0a7a0074d541635b2b488b1a6098a42d8418a6cd8eb804406ea82b91e362be2b5d11a0915b0"},"tools":{"phpmd":{"version":"2.15.0","url":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar","requirements":{"php":{"php":">=5.3.9","ext-xml":"*"}},"checksum":null,"signature":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar.asc"}},"composerLock":null},"psalm":{"api-version":"1.0.0","version":"1.3.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/psalm/psalm-1.3.0.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"tool":{"psalm":"^3.0 || ^4.0 || ^5.0 || ^6.0"}},"checksum":{"type":"sha-512","value":"4a550c9226d7bca582d7c10bd87cce01190c96398936b1613421640c83df62ed1c6e0d44c1b39635414ea8cf4a892a6458d27590793238add24e7cb5547e6ffd"},"tools":{"psalm":{"version":"6.16.1","url":"https://github.com/vimeo/psalm/releases/download/6.16.1/psalm.phar","requirements":{"php":{"php":"~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0","ext-SimpleXML":"*","ext-ctype":"*","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-tokenizer":"*"}},"checksum":null,"signature":"https://github.com/vimeo/psalm/releases/download/6.16.1/psalm.phar.asc"}},"composerLock":null},"phpcs":{"api-version":"1.0.0","version":"1.2.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcs/phpcs-1.2.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcs":"^4.0 || ^3.0 || ^2.0","phpcbf":"^4.0 || ^3.0 || ^2.0"}},"checksum":{"type":"sha-512","value":"03f1c6c2d94b79d0e8cbd42996382e0d100c7e07f84c3138fa3a8b394e814ec18ce05cbbd257e527913219b2264f062522e4cf3e3bd402b907b9437d96982b44"},"tools":{"phpcs":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar.asc"},"phpcbf":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar.asc"}},"composerLock":null}},"tools":[]} \ No newline at end of file +{"plugins":{"doctrine-coding-standard":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/doctrine-coding-standard/doctrine-coding-standard-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"composer":{"doctrine/coding-standard":"^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0 || ^14.0"}},"checksum":{"type":"sha-512","value":"00fab498a6575bf07930e078fd616c0481714570bc1c61ebae4fa277d64c0cb28575aba9190c9731c7bda9f97f57c113516e1eb2920c3b9b7b295e0078be3159"},"tools":{},"composerLock":"{\n \"_readme\": [\n \"This file locks the dependencies of your project to a known state\",\n \"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies\",\n \"This file is @generated automatically\"\n ],\n \"content-hash\": \"40b84043be9c25e6feb38a9e014f1a9f\",\n \"packages\": [\n {\n \"name\": \"dealerdirect/phpcodesniffer-composer-installer\",\n \"version\": \"v1.2.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/composer-installer.git\",\n \"reference\": \"963f0c67bffde0eac41b56be71ac0e8ba132f0bd\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd\",\n \"reference\": \"963f0c67bffde0eac41b56be71ac0e8ba132f0bd\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"composer-plugin-api\": \"^2.2\",\n \"php\": \">=5.4\",\n \"squizlabs/php_codesniffer\": \"^3.1.0 || ^4.0\"\n },\n \"require-dev\": {\n \"composer/composer\": \"^2.2\",\n \"ext-json\": \"*\",\n \"ext-zip\": \"*\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.4.0\",\n \"phpcompatibility/php-compatibility\": \"^9.0 || ^10.0.0@dev\",\n \"yoast/phpunit-polyfills\": \"^1.0\"\n },\n \"type\": \"composer-plugin\",\n \"extra\": {\n \"class\": \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\Plugin\"\n },\n \"autoload\": {\n \"psr-4\": {\n \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\\": \"src/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Franck Nijhof\",\n \"email\": \"opensource@frenck.dev\",\n \"homepage\": \"https://frenck.dev\",\n \"role\": \"Open source developer\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/composer-installer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer Standards Composer Installer Plugin\",\n \"keywords\": [\n \"PHPCodeSniffer\",\n \"PHP_CodeSniffer\",\n \"code quality\",\n \"codesniffer\",\n \"composer\",\n \"installer\",\n \"phpcbf\",\n \"phpcs\",\n \"plugin\",\n \"qa\",\n \"quality\",\n \"standard\",\n \"standards\",\n \"style guide\",\n \"stylecheck\",\n \"tests\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/composer-installer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/composer-installer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/composer-installer\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2026-05-06T08:26:05+00:00\"\n },\n {\n \"name\": \"doctrine/coding-standard\",\n \"version\": \"14.0.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/doctrine/coding-standard.git\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/doctrine/coding-standard/zipball/897a7dc209e49ee6cf04e689c41112df17967130\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.6.2 || ^0.7 || ^1.0.0\",\n \"php\": \"^7.4 || ^8.0\",\n \"slevomat/coding-standard\": \"^8.23\",\n \"squizlabs/php_codesniffer\": \"^4\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Benjamin Eberlei\",\n \"email\": \"kontakt@beberlei.de\"\n },\n {\n \"name\": \"Steve Müller\",\n \"email\": \"st.mueller@dzh-online.de\"\n }\n ],\n \"description\": \"The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.\",\n \"homepage\": \"https://www.doctrine-project.org/projects/coding-standard.html\",\n \"keywords\": [\n \"checks\",\n \"code\",\n \"coding\",\n \"cs\",\n \"dev\",\n \"doctrine\",\n \"rules\",\n \"sniffer\",\n \"sniffs\",\n \"standard\",\n \"style\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/doctrine/coding-standard/issues\",\n \"source\": \"https://github.com/doctrine/coding-standard/tree/14.0.0\"\n },\n \"time\": \"2025-09-21T18:21:47+00:00\"\n },\n {\n \"name\": \"phpstan/phpdoc-parser\",\n \"version\": \"2.3.2\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/phpstan/phpdoc-parser.git\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"php\": \"^7.4 || ^8.0\"\n },\n \"require-dev\": {\n \"doctrine/annotations\": \"^2.0\",\n \"nikic/php-parser\": \"^5.3.0\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.2\",\n \"phpstan/extension-installer\": \"^1.0\",\n \"phpstan/phpstan\": \"^2.0\",\n \"phpstan/phpstan-phpunit\": \"^2.0\",\n \"phpstan/phpstan-strict-rules\": \"^2.0\",\n \"phpunit/phpunit\": \"^9.6\",\n \"symfony/process\": \"^5.2\"\n },\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"PHPStan\\\\PhpDocParser\\\\\": [\n \"src/\"\n ]\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"PHPDoc parser with support for nullable, intersection and generic types\",\n \"support\": {\n \"issues\": \"https://github.com/phpstan/phpdoc-parser/issues\",\n \"source\": \"https://github.com/phpstan/phpdoc-parser/tree/2.3.2\"\n },\n \"time\": \"2026-01-25T14:56:51+00:00\"\n },\n {\n \"name\": \"slevomat/coding-standard\",\n \"version\": \"8.29.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/slevomat/coding-standard.git\",\n \"reference\": \"81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/slevomat/coding-standard/zipball/81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\",\n \"reference\": \"81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.7 || ^1.2.1\",\n \"php\": \"^7.4 || ^8.0\",\n \"phpstan/phpdoc-parser\": \"^2.3.2\",\n \"squizlabs/php_codesniffer\": \"^4.0.1\"\n },\n \"require-dev\": {\n \"phing/phing\": \"3.0.1|3.1.2\",\n \"php-parallel-lint/php-parallel-lint\": \"1.4.0\",\n \"phpstan/phpstan\": \"2.1.54\",\n \"phpstan/phpstan-deprecation-rules\": \"2.0.4\",\n \"phpstan/phpstan-phpunit\": \"2.0.16\",\n \"phpstan/phpstan-strict-rules\": \"2.0.11\",\n \"phpunit/phpunit\": \"9.6.34|10.5.63|11.4.4|11.5.55|12.5.24\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"8.x-dev\"\n }\n },\n \"autoload\": {\n \"psr-4\": {\n \"SlevomatCodingStandard\\\\\": \"SlevomatCodingStandard/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.\",\n \"keywords\": [\n \"dev\",\n \"phpcs\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/slevomat/coding-standard/issues\",\n \"source\": \"https://github.com/slevomat/coding-standard/tree/8.29.0\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/kukulich\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://tidelift.com/funding/github/packagist/slevomat/coding-standard\",\n \"type\": \"tidelift\"\n }\n ],\n \"time\": \"2026-05-07T05:48:08+00:00\"\n },\n {\n \"name\": \"squizlabs/php_codesniffer\",\n \"version\": \"4.0.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer.git\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"ext-simplexml\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"php\": \">=7.2.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31\"\n },\n \"bin\": [\n \"bin/phpcbf\",\n \"bin/phpcs\"\n ],\n \"type\": \"library\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"BSD-3-Clause\"\n ],\n \"authors\": [\n {\n \"name\": \"Greg Sherwood\",\n \"role\": \"Former lead\"\n },\n {\n \"name\": \"Juliette Reinders Folmer\",\n \"role\": \"Current lead\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"keywords\": [\n \"phpcs\",\n \"standards\",\n \"static analysis\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"wiki\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2025-11-10T16:43:36+00:00\"\n }\n ],\n \"packages-dev\": [],\n \"aliases\": [],\n \"minimum-stability\": \"stable\",\n \"stability-flags\": {},\n \"prefer-stable\": false,\n \"prefer-lowest\": false,\n \"platform\": {},\n \"platform-dev\": {},\n \"plugin-api-version\": \"2.6.0\"\n}\n"},"composer-normalize":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-normalize/composer-normalize-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-json":"*"},"tool":{"composer-normalize":"^2.1"}},"checksum":{"type":"sha-512","value":"d9abda440b85d501c58abf9c81bf76f417594b397129215ffa8b777e9bb5e5eda37d7661d661db3c8d11c24f20345bc6fbe56f013b3b9435d459d2b94f086e0f"},"tools":{"composer-normalize":{"version":"2.51.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar","requirements":{"php":{"php":"~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0","ext-json":"*"}},"checksum":null,"signature":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar.asc"}},"composerLock":null},"composer-require-checker":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-require-checker/composer-require-checker-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0"},"tool":{"composer-require-checker":"^3.8 || ^4.0"}},"checksum":{"type":"sha-512","value":"d5415bddfe024c5749d894034583882aee4e5c3e1087815d9fdd81cb5e71630f631a0e35de0ff84b97fbbf738c16ece5f83bd8c00695913eb846aa6f04577dc2"},"tools":{"composer-require-checker":{"version":"4.18.0","url":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.18.0/composer-require-checker.phar","requirements":{"php":{"php":"~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0","ext-phar":"*"}},"checksum":null,"signature":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.18.0/composer-require-checker.phar.asc"}},"composerLock":null},"phpcpd":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcpd/phpcpd-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcpd":"^6.0"}},"checksum":{"type":"sha-512","value":"1189ce0bf3fade4cb4241f1d96f915ef8fc7651f4450dc79fdf464ee3d6be3009316f0d423ce2d4af9d76ad50807b7fdf4d77bfa6d9ee2c91d6eda32ea214433"},"tools":{"phpcpd":{"version":"6.0.3","url":"https://phar.phpunit.de/phpcpd-6.0.3.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*"}},"checksum":{"type":"sha-256","value":"2cbaea7cfda1bb4299d863eb075e977c3f49055dd16d88529fae5150d48a84cb"},"signature":"https://phar.phpunit.de/phpcpd-6.0.3.phar.asc"}},"composerLock":null},"phploc":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phploc/phploc-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*","ext-json":"*"},"tool":{"phploc":"^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"}},"checksum":{"type":"sha-512","value":"f67b02d494796adf553cb3dd13ec06c1cb8e53c799954061749424251379541637538199afb3afa3c7a01cabd1cb6f1c53eb621f015dff9644c6c7cbf10c56d1"},"tools":{"phploc":{"version":"7.0.2","url":"https://phar.phpunit.de/phploc-7.0.2.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*"}},"checksum":{"type":"sha-256","value":"3d59778ec86faf25fd00e3a329b2f9ad4a3c751ca91601ea7dab70f887b0bf46"},"signature":"https://phar.phpunit.de/phploc-7.0.2.phar.asc"}},"composerLock":null},"phpmd":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpmd/phpmd-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpmd":"^2.6.1"}},"checksum":{"type":"sha-512","value":"f22280a6dec8dbdd2ec1d83b294f23237fe32c34f4a298e52038e0a7a0074d541635b2b488b1a6098a42d8418a6cd8eb804406ea82b91e362be2b5d11a0915b0"},"tools":{"phpmd":{"version":"2.15.0","url":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar","requirements":{"php":{"php":">=5.3.9","ext-xml":"*"}},"checksum":null,"signature":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar.asc"}},"composerLock":null},"psalm":{"api-version":"1.0.0","version":"1.3.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/psalm/psalm-1.3.0.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"tool":{"psalm":"^3.0 || ^4.0 || ^5.0 || ^6.0"}},"checksum":{"type":"sha-512","value":"4a550c9226d7bca582d7c10bd87cce01190c96398936b1613421640c83df62ed1c6e0d44c1b39635414ea8cf4a892a6458d27590793238add24e7cb5547e6ffd"},"tools":{"psalm":{"version":"6.5.0","url":"https://github.com/vimeo/psalm/releases/download/6.5.0/psalm.phar","requirements":{"php":{"php":"~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0","ext-SimpleXML":"*","ext-ctype":"*","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-tokenizer":"*"}},"checksum":null,"signature":"https://github.com/vimeo/psalm/releases/download/6.5.0/psalm.phar.asc"}},"composerLock":null},"phpcs":{"api-version":"1.0.0","version":"1.2.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcs/phpcs-1.2.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcs":"^4.0 || ^3.0 || ^2.0","phpcbf":"^4.0 || ^3.0 || ^2.0"}},"checksum":{"type":"sha-512","value":"03f1c6c2d94b79d0e8cbd42996382e0d100c7e07f84c3138fa3a8b394e814ec18ce05cbbd257e527913219b2264f062522e4cf3e3bd402b907b9437d96982b44"},"tools":{"phpcs":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar.asc"},"phpcbf":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar.asc"}},"composerLock":null}},"tools":[]} \ No newline at end of file diff --git a/src/Migration/GridWrapperMigration.php b/src/Migration/GridWrapperMigration.php index d7411bc..01a8f70 100644 --- a/src/Migration/GridWrapperMigration.php +++ b/src/Migration/GridWrapperMigration.php @@ -7,11 +7,9 @@ use Contao\CoreBundle\Migration\AbstractMigration; use Contao\CoreBundle\Migration\MigrationResult; use Doctrine\DBAL\Connection; +use Exception; use Override; -use function array_map; -use function array_reduce; -use function array_sum; use function count; final class GridWrapperMigration extends AbstractMigration @@ -20,6 +18,7 @@ public function __construct(private readonly Connection $connection, private rea { } + /** @throws Exception */ #[Override] public function shouldRun(): bool { @@ -28,111 +27,368 @@ public function shouldRun(): bool } $schemaManager = $this->connection->createSchemaManager(); + if (! $schemaManager->tablesExist(['tl_bs_grid', 'tl_content'])) { return false; } - $queryBuilder = $this->connection->createQueryBuilder(); + if (! $schemaManager->tablesExist(['tl_content'])) { + return false; + } + + $count = $this->connection->fetchOne( + "SELECT COUNT(*) FROM tl_content WHERE type IN ('bs_gridStart', 'bs_gridStop', 'bs_gridSeparator')", + ); - return $queryBuilder - ->select('COUNT(tc.id) as count') - ->from('tl_content', 'tc') - ->where($queryBuilder->expr()->eq('tc.type', ':type')) - ->setParameter('type', 'bs_gridStart') - ->executeQuery() - ->fetchOne() > 0; + return $count > 0; } - #[Override] + /** @throws Exception */ public function run(): MigrationResult { - $sql = <<<'SQL' - SELECT - grid_start.id AS grid_start_id, - grid_start.pid AS pid, - grid_start.ptable AS ptable, - el.id AS element_id, - el.type AS element_type, - el.sorting AS element_sorting, - ( - SELECT grid_stop.id - FROM tl_content grid_stop - WHERE grid_stop.pid = grid_start.pid - AND grid_stop.ptable = grid_start.ptable - AND grid_stop.type = 'bs_gridStop' - AND grid_stop.sorting > grid_start.sorting - ORDER BY grid_stop.sorting ASC - LIMIT 1 - ) AS grid_stop_id - FROM tl_content grid_start - LEFT JOIN tl_content el - ON el.pid = grid_start.pid - AND el.ptable = grid_start.ptable - AND el.sorting > grid_start.sorting - AND el.sorting < ( - SELECT MIN(grid_stop.sorting) - FROM tl_content grid_stop - WHERE grid_stop.pid = grid_start.pid - AND grid_stop.ptable = grid_start.ptable - AND grid_stop.type = 'bs_gridStop' - AND grid_stop.sorting > grid_start.sorting - ) - WHERE grid_start.type = 'bs_gridStart' - ORDER BY grid_start.pid, grid_start.ptable, grid_start.sorting, el.sorting -SQL; - - $contentElements = $this->connection->executeQuery($sql)->fetchAllAssociative(); - - $gridContainers = array_reduce($contentElements, static function (array $carry, array $row) { - $startId = $row['grid_start_id']; - - $carry[$startId] ??= [ - 'start_id' => $startId, - 'stop_id' => $row['grid_stop_id'], - 'elements' => [], - ]; - - if ($row['element_id'] !== null) { - $carry[$startId]['elements'][] = $row; + $this->connection->transactional(function (): void { + $roots = $this->connection->fetchAllAssociative( + "SELECT DISTINCT pid, ptable FROM tl_content + WHERE type IN ('bs_gridStart', 'bs_gridStop', 'bs_gridSeparator')", + ); + + foreach ($roots as $root) { + $elements = $this->fetchElements((int) $root['pid'], $root['ptable']); + $this->processElements($elements, (int) $root['pid'], $root['ptable']); } + }); + + return $this->createResult(true); + } + + /** + * @return array> + * + * @throws Exception + */ + private function fetchElements(int $pid, string $ptable): array + { + return $this->connection->fetchAllAssociative( + 'SELECT * FROM tl_content WHERE pid = :pid AND ptable = :ptable ORDER BY sorting ASC', + ['pid' => $pid, 'ptable' => $ptable], + ); + } + + /** + * Main recursive processor. Works with an index pointer through the flat list. + * + * @param array> $elements + * + * @throws Exception + */ + private function processElements(array $elements, int $pid, string $ptable): void + { + $idx = 0; + $count = count($elements); + + while ($idx < $count) { + $element = $elements[$idx]; + + match ($element['type']) { + 'bs_gridStart' => $idx = $this->handleGridStart($elements, $idx, $pid, $ptable), + 'bs_gridSeparator' => $idx = $this->handleSeparator($elements, $idx, $pid, $ptable), + 'bs_gridStop' => $idx = $this->handleGridStop($elements, $idx), + default => $idx++, + }; + } + } - return $carry; - }, []); + /** + * Handles a bs_gridStart: converts it to bs_grid_wrapper, + * collects all children until matching bs_gridStop and moves them into the wrapper. + * If there are bs_gridSeparators at the top level, elements before the first separator + * are wrapped in an implicit element_group. + * + * @param array> $elements + * + * @throws Exception + */ + private function handleGridStart(array $elements, int $index): int + { + $startElement = $elements[$index]; + $wrapperId = (int) $startElement['id']; - $elementCount = array_sum( - array_map( - static fn (array $gridContainer) => count($gridContainer['elements']), - $gridContainers, - ), + $this->connection->executeStatement( + "UPDATE tl_content SET type = 'bs_grid_wrapper' WHERE id = :id", + ['id' => $wrapperId], ); - $this->connection->transactional(function () use ($gridContainers): void { - foreach ($gridContainers as $gridContainer) { - $this->connection->update( - 'tl_content', - ['type' => 'bs_grid_wrapper'], - ['id' => $gridContainer['start_id']], - ); - - foreach ($gridContainer['elements'] as $element) { - $this->connection->update( - 'tl_content', - ['pid' => $gridContainer['start_id'], 'ptable' => 'tl_content'], - ['id' => $element['element_id']], - ); + // Collect all children until matching bs_gridStop + [$children, $nextIndex] = $this->collectUntilMatchingStop($elements, $index + 1); + + // Move children into wrapper with fresh sorting + $this->moveElementsToParent($children, $wrapperId, 'tl_content'); + + // Only wrap leading elements in an implicit group if there are top-level separators + $this->wrapLeadingElementsInGroup($wrapperId, $startElement); + + // Now recursively process the children inside the wrapper + $movedElements = $this->fetchElements($wrapperId, 'tl_content'); + $this->processElements($movedElements, $wrapperId, 'tl_content'); + + return $nextIndex; + } + + /** + * Wraps all elements before the first bs_gridSeparator inside a wrapper + * into a newly created element_group. + * If there is no top-level bs_gridSeparator, nothing is done. + * + * @param array $referenceElement Used to copy tstamp for the new row + * + * @throws Exception + */ + private function wrapLeadingElementsInGroup(int $wrapperPid, array $referenceElement): void + { + $children = $this->fetchElements($wrapperPid, 'tl_content'); + + // If there is no bs_gridSeparator at depth 0, elements stay directly in the wrapper + if (! $this->hasTopLevelSeparator($children)) { + return; + } + + // Collect elements before the first bs_gridSeparator at depth 0 + $leadingElements = []; + $depth = 0; + + foreach ($children as $child) { + $type = $child['type']; + + if ($type === 'bs_gridStart') { + $depth++; + $leadingElements[] = $child; + } elseif ($type === 'bs_gridStop') { + if ($depth === 0) { + break; } - if ($gridContainer['stop_id'] === null) { - continue; + $depth--; + $leadingElements[] = $child; + } elseif ($type === 'bs_gridSeparator') { + if ($depth === 0) { + // Stop here, do not include the separator itself + break; } - $this->connection->delete('tl_content', ['id' => $gridContainer['stop_id']]); + $leadingElements[] = $child; + } else { + $leadingElements[] = $child; } - }); + } - return $this->createResult( - true, - 'Migrated ' . count($gridContainers) . ' grid containers and ' . $elementCount . ' elements.', + // Nothing to wrap (grid starts directly with a separator) + if (empty($leadingElements)) { + return; + } + + // Create a new element_group as first child of the wrapper + // sorting = 64 so it sits before the existing children which start at 128 + $this->connection->executeStatement( + "INSERT INTO tl_content (pid, ptable, sorting, tstamp, type) +VALUES (:pid, :ptable, :sorting, :tstamp, 'element_group')", + [ + 'pid' => $wrapperPid, + 'ptable' => 'tl_content', + 'sorting' => 64, + 'tstamp' => $referenceElement['tstamp'], + ], + ); + + $groupId = (int) $this->connection->lastInsertId(); + + $this->moveElementsToParent($leadingElements, $groupId, 'tl_content'); + } + + /** + * Checks whether there is at least one bs_gridSeparator at depth 0 in the given element list. + * + * @param array> $elements + */ + private function hasTopLevelSeparator(array $elements): bool + { + $depth = 0; + + foreach ($elements as $element) { + $type = $element['type']; + + if ($type === 'bs_gridStart') { + $depth++; + } elseif ($type === 'bs_gridStop') { + $depth--; + } elseif ($type === 'bs_gridSeparator' && $depth === 0) { + return true; + } + } + + return false; + } + + /** + * Handles a bs_gridSeparator: converts it to element_group, + * collects all following siblings until next bs_gridSeparator or bs_gridStop + * and moves them into the group. + * + * @param array> $elements + * + * @throws Exception + */ + private function handleSeparator(array $elements, int $index): int + { + $separatorElement = $elements[$index]; + $groupId = (int) $separatorElement['id']; + + $this->connection->executeStatement( + "UPDATE tl_content SET type = 'element_group' WHERE id = :id", + ['id' => $groupId], + ); + + // Collect following siblings until next separator or stop (depth-aware) + [$children, $nextIndex] = $this->collectUntilNextSeparatorOrStop($elements, $index + 1); + + // Move collected elements into the element_group + $this->moveElementsToParent($children, $groupId, 'tl_content'); + + // Recursively process elements inside the group (e.g. nested grids inside a group) + $movedElements = $this->fetchElements($groupId, 'tl_content'); + $this->processElements($movedElements, $groupId, 'tl_content'); + + return $nextIndex; + } + + /** + * Handles a bs_gridStop: deletes it and advances the index. + * + * @param array> $elements + * + * @throws Exception + */ + private function handleGridStop(array $elements, int $index): int + { + $this->deleteElement((int) $elements[$index]['id']); + + return $index + 1; + } + + /** + * Collects all elements from $startIndex until the matching bs_gridStop (depth-aware). + * The matching bs_gridStop itself is deleted and NOT included in the result. + * + * @param array> $elements + * + * @return array{0: array>, 1: int} + * + * @throws Exception + */ + private function collectUntilMatchingStop(array $elements, int $startIndex): array + { + $children = []; + $depth = 1; + $idx = $startIndex; + $count = count($elements); + + while ($idx < $count && $depth > 0) { + $type = $elements[$idx]['type']; + + if ($type === 'bs_gridStart') { + $depth++; + $children[] = $elements[$idx]; + } elseif ($type === 'bs_gridStop') { + $depth--; + if ($depth === 0) { + $this->deleteElement((int) $elements[$idx]['id']); + $idx++; + break; + } + + $children[] = $elements[$idx]; + } else { + $children[] = $elements[$idx]; + } + + $idx++; + } + + return [$children, $idx]; + } + + /** + * Collects elements from $startIndex until the next bs_gridSeparator or bs_gridStop at depth 0. + * Nested grids are included as a block (depth-aware). + * + * @param array> $elements + * + * @return array{0: array>, 1: int} + */ + private function collectUntilNextSeparatorOrStop(array $elements, int $startIndex): array + { + $children = []; + $depth = 0; + $idx = $startIndex; + $count = count($elements); + + while ($idx < $count) { + $type = $elements[$idx]['type']; + + if ($type === 'bs_gridStart') { + $depth++; + $children[] = $elements[$idx]; + } elseif ($type === 'bs_gridStop') { + if ($depth === 0) { + break; + } + + $depth--; + $children[] = $elements[$idx]; + } elseif ($type === 'bs_gridSeparator') { + if ($depth === 0) { + break; + } + + $children[] = $elements[$idx]; + } else { + $children[] = $elements[$idx]; + } + + $idx++; + } + + return [$children, $idx]; + } + + /** + * @param array> $elements + * + * @throws Exception + */ + private function moveElementsToParent(array $elements, int $newPid, string $newPtable): void + { + $sorting = 128; + + foreach ($elements as $element) { + $this->connection->executeStatement( + 'UPDATE tl_content SET pid = :pid, ptable = :ptable, sorting = :sorting WHERE id = :id', + [ + 'pid' => $newPid, + 'ptable' => $newPtable, + 'sorting' => $sorting, + 'id' => $element['id'], + ], + ); + $sorting += 128; + } + } + + /** @throws Exception */ + private function deleteElement(int $elementId): void + { + $this->connection->executeStatement( + 'DELETE FROM tl_content WHERE id = :id', + ['id' => $elementId], ); } } From 954a2aa56579afde3084f0dcec77cd42d97ff8a5 Mon Sep 17 00:00:00 2001 From: "c.boelter" Date: Thu, 7 May 2026 17:13:37 +0200 Subject: [PATCH 2/2] remove phpcq.lock --- .phpcq.lock | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpcq.lock diff --git a/.phpcq.lock b/.phpcq.lock deleted file mode 100644 index a940923..0000000 --- a/.phpcq.lock +++ /dev/null @@ -1 +0,0 @@ -{"plugins":{"doctrine-coding-standard":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/doctrine-coding-standard/doctrine-coding-standard-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"composer":{"doctrine/coding-standard":"^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0 || ^14.0"}},"checksum":{"type":"sha-512","value":"00fab498a6575bf07930e078fd616c0481714570bc1c61ebae4fa277d64c0cb28575aba9190c9731c7bda9f97f57c113516e1eb2920c3b9b7b295e0078be3159"},"tools":{},"composerLock":"{\n \"_readme\": [\n \"This file locks the dependencies of your project to a known state\",\n \"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies\",\n \"This file is @generated automatically\"\n ],\n \"content-hash\": \"40b84043be9c25e6feb38a9e014f1a9f\",\n \"packages\": [\n {\n \"name\": \"dealerdirect/phpcodesniffer-composer-installer\",\n \"version\": \"v1.2.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/composer-installer.git\",\n \"reference\": \"963f0c67bffde0eac41b56be71ac0e8ba132f0bd\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd\",\n \"reference\": \"963f0c67bffde0eac41b56be71ac0e8ba132f0bd\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"composer-plugin-api\": \"^2.2\",\n \"php\": \">=5.4\",\n \"squizlabs/php_codesniffer\": \"^3.1.0 || ^4.0\"\n },\n \"require-dev\": {\n \"composer/composer\": \"^2.2\",\n \"ext-json\": \"*\",\n \"ext-zip\": \"*\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.4.0\",\n \"phpcompatibility/php-compatibility\": \"^9.0 || ^10.0.0@dev\",\n \"yoast/phpunit-polyfills\": \"^1.0\"\n },\n \"type\": \"composer-plugin\",\n \"extra\": {\n \"class\": \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\Plugin\"\n },\n \"autoload\": {\n \"psr-4\": {\n \"PHPCSStandards\\\\Composer\\\\Plugin\\\\Installers\\\\PHPCodeSniffer\\\\\": \"src/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Franck Nijhof\",\n \"email\": \"opensource@frenck.dev\",\n \"homepage\": \"https://frenck.dev\",\n \"role\": \"Open source developer\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/composer-installer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer Standards Composer Installer Plugin\",\n \"keywords\": [\n \"PHPCodeSniffer\",\n \"PHP_CodeSniffer\",\n \"code quality\",\n \"codesniffer\",\n \"composer\",\n \"installer\",\n \"phpcbf\",\n \"phpcs\",\n \"plugin\",\n \"qa\",\n \"quality\",\n \"standard\",\n \"standards\",\n \"style guide\",\n \"stylecheck\",\n \"tests\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/composer-installer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/composer-installer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/composer-installer\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2026-05-06T08:26:05+00:00\"\n },\n {\n \"name\": \"doctrine/coding-standard\",\n \"version\": \"14.0.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/doctrine/coding-standard.git\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/doctrine/coding-standard/zipball/897a7dc209e49ee6cf04e689c41112df17967130\",\n \"reference\": \"897a7dc209e49ee6cf04e689c41112df17967130\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.6.2 || ^0.7 || ^1.0.0\",\n \"php\": \"^7.4 || ^8.0\",\n \"slevomat/coding-standard\": \"^8.23\",\n \"squizlabs/php_codesniffer\": \"^4\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"authors\": [\n {\n \"name\": \"Benjamin Eberlei\",\n \"email\": \"kontakt@beberlei.de\"\n },\n {\n \"name\": \"Steve Müller\",\n \"email\": \"st.mueller@dzh-online.de\"\n }\n ],\n \"description\": \"The Doctrine Coding Standard is a set of PHPCS rules applied to all Doctrine projects.\",\n \"homepage\": \"https://www.doctrine-project.org/projects/coding-standard.html\",\n \"keywords\": [\n \"checks\",\n \"code\",\n \"coding\",\n \"cs\",\n \"dev\",\n \"doctrine\",\n \"rules\",\n \"sniffer\",\n \"sniffs\",\n \"standard\",\n \"style\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/doctrine/coding-standard/issues\",\n \"source\": \"https://github.com/doctrine/coding-standard/tree/14.0.0\"\n },\n \"time\": \"2025-09-21T18:21:47+00:00\"\n },\n {\n \"name\": \"phpstan/phpdoc-parser\",\n \"version\": \"2.3.2\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/phpstan/phpdoc-parser.git\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"reference\": \"a004701b11273a26cd7955a61d67a7f1e525a45a\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"php\": \"^7.4 || ^8.0\"\n },\n \"require-dev\": {\n \"doctrine/annotations\": \"^2.0\",\n \"nikic/php-parser\": \"^5.3.0\",\n \"php-parallel-lint/php-parallel-lint\": \"^1.2\",\n \"phpstan/extension-installer\": \"^1.0\",\n \"phpstan/phpstan\": \"^2.0\",\n \"phpstan/phpstan-phpunit\": \"^2.0\",\n \"phpstan/phpstan-strict-rules\": \"^2.0\",\n \"phpunit/phpunit\": \"^9.6\",\n \"symfony/process\": \"^5.2\"\n },\n \"type\": \"library\",\n \"autoload\": {\n \"psr-4\": {\n \"PHPStan\\\\PhpDocParser\\\\\": [\n \"src/\"\n ]\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"PHPDoc parser with support for nullable, intersection and generic types\",\n \"support\": {\n \"issues\": \"https://github.com/phpstan/phpdoc-parser/issues\",\n \"source\": \"https://github.com/phpstan/phpdoc-parser/tree/2.3.2\"\n },\n \"time\": \"2026-01-25T14:56:51+00:00\"\n },\n {\n \"name\": \"slevomat/coding-standard\",\n \"version\": \"8.29.0\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/slevomat/coding-standard.git\",\n \"reference\": \"81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/slevomat/coding-standard/zipball/81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\",\n \"reference\": \"81fce13c4ef4b53a03e5cfa6ce36afc191c1598e\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"dealerdirect/phpcodesniffer-composer-installer\": \"^0.7 || ^1.2.1\",\n \"php\": \"^7.4 || ^8.0\",\n \"phpstan/phpdoc-parser\": \"^2.3.2\",\n \"squizlabs/php_codesniffer\": \"^4.0.1\"\n },\n \"require-dev\": {\n \"phing/phing\": \"3.0.1|3.1.2\",\n \"php-parallel-lint/php-parallel-lint\": \"1.4.0\",\n \"phpstan/phpstan\": \"2.1.54\",\n \"phpstan/phpstan-deprecation-rules\": \"2.0.4\",\n \"phpstan/phpstan-phpunit\": \"2.0.16\",\n \"phpstan/phpstan-strict-rules\": \"2.0.11\",\n \"phpunit/phpunit\": \"9.6.34|10.5.63|11.4.4|11.5.55|12.5.24\"\n },\n \"type\": \"phpcodesniffer-standard\",\n \"extra\": {\n \"branch-alias\": {\n \"dev-master\": \"8.x-dev\"\n }\n },\n \"autoload\": {\n \"psr-4\": {\n \"SlevomatCodingStandard\\\\\": \"SlevomatCodingStandard/\"\n }\n },\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"MIT\"\n ],\n \"description\": \"Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.\",\n \"keywords\": [\n \"dev\",\n \"phpcs\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/slevomat/coding-standard/issues\",\n \"source\": \"https://github.com/slevomat/coding-standard/tree/8.29.0\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/kukulich\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://tidelift.com/funding/github/packagist/slevomat/coding-standard\",\n \"type\": \"tidelift\"\n }\n ],\n \"time\": \"2026-05-07T05:48:08+00:00\"\n },\n {\n \"name\": \"squizlabs/php_codesniffer\",\n \"version\": \"4.0.1\",\n \"source\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer.git\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\"\n },\n \"dist\": {\n \"type\": \"zip\",\n \"url\": \"https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5\",\n \"reference\": \"0525c73950de35ded110cffafb9892946d7771b5\",\n \"shasum\": \"\"\n },\n \"require\": {\n \"ext-simplexml\": \"*\",\n \"ext-tokenizer\": \"*\",\n \"ext-xmlwriter\": \"*\",\n \"php\": \">=7.2.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31\"\n },\n \"bin\": [\n \"bin/phpcbf\",\n \"bin/phpcs\"\n ],\n \"type\": \"library\",\n \"notification-url\": \"https://packagist.org/downloads/\",\n \"license\": [\n \"BSD-3-Clause\"\n ],\n \"authors\": [\n {\n \"name\": \"Greg Sherwood\",\n \"role\": \"Former lead\"\n },\n {\n \"name\": \"Juliette Reinders Folmer\",\n \"role\": \"Current lead\"\n },\n {\n \"name\": \"Contributors\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors\"\n }\n ],\n \"description\": \"PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.\",\n \"homepage\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"keywords\": [\n \"phpcs\",\n \"standards\",\n \"static analysis\"\n ],\n \"support\": {\n \"issues\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/issues\",\n \"security\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy\",\n \"source\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer\",\n \"wiki\": \"https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki\"\n },\n \"funding\": [\n {\n \"url\": \"https://github.com/PHPCSStandards\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://github.com/jrfnl\",\n \"type\": \"github\"\n },\n {\n \"url\": \"https://opencollective.com/php_codesniffer\",\n \"type\": \"open_collective\"\n },\n {\n \"url\": \"https://thanks.dev/u/gh/phpcsstandards\",\n \"type\": \"thanks_dev\"\n }\n ],\n \"time\": \"2025-11-10T16:43:36+00:00\"\n }\n ],\n \"packages-dev\": [],\n \"aliases\": [],\n \"minimum-stability\": \"stable\",\n \"stability-flags\": {},\n \"prefer-stable\": false,\n \"prefer-lowest\": false,\n \"platform\": {},\n \"platform-dev\": {},\n \"plugin-api-version\": \"2.6.0\"\n}\n"},"composer-normalize":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-normalize/composer-normalize-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-json":"*"},"tool":{"composer-normalize":"^2.1"}},"checksum":{"type":"sha-512","value":"d9abda440b85d501c58abf9c81bf76f417594b397129215ffa8b777e9bb5e5eda37d7661d661db3c8d11c24f20345bc6fbe56f013b3b9435d459d2b94f086e0f"},"tools":{"composer-normalize":{"version":"2.51.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar","requirements":{"php":{"php":"~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0","ext-json":"*"}},"checksum":null,"signature":"https://github.com/ergebnis/composer-normalize/releases/download/2.51.0/composer-normalize.phar.asc"}},"composerLock":null},"composer-require-checker":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/composer-require-checker/composer-require-checker-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0"},"tool":{"composer-require-checker":"^3.8 || ^4.0"}},"checksum":{"type":"sha-512","value":"d5415bddfe024c5749d894034583882aee4e5c3e1087815d9fdd81cb5e71630f631a0e35de0ff84b97fbbf738c16ece5f83bd8c00695913eb846aa6f04577dc2"},"tools":{"composer-require-checker":{"version":"4.18.0","url":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.18.0/composer-require-checker.phar","requirements":{"php":{"php":"~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0","ext-phar":"*"}},"checksum":null,"signature":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.18.0/composer-require-checker.phar.asc"}},"composerLock":null},"phpcpd":{"api-version":"1.0.0","version":"1.1.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcpd/phpcpd-1.1.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcpd":"^6.0"}},"checksum":{"type":"sha-512","value":"1189ce0bf3fade4cb4241f1d96f915ef8fc7651f4450dc79fdf464ee3d6be3009316f0d423ce2d4af9d76ad50807b7fdf4d77bfa6d9ee2c91d6eda32ea214433"},"tools":{"phpcpd":{"version":"6.0.3","url":"https://phar.phpunit.de/phpcpd-6.0.3.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*"}},"checksum":{"type":"sha-256","value":"2cbaea7cfda1bb4299d863eb075e977c3f49055dd16d88529fae5150d48a84cb"},"signature":"https://phar.phpunit.de/phpcpd-6.0.3.phar.asc"}},"composerLock":null},"phploc":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phploc/phploc-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*","ext-json":"*"},"tool":{"phploc":"^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"}},"checksum":{"type":"sha-512","value":"f67b02d494796adf553cb3dd13ec06c1cb8e53c799954061749424251379541637538199afb3afa3c7a01cabd1cb6f1c53eb621f015dff9644c6c7cbf10c56d1"},"tools":{"phploc":{"version":"7.0.2","url":"https://phar.phpunit.de/phploc-7.0.2.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*"}},"checksum":{"type":"sha-256","value":"3d59778ec86faf25fd00e3a329b2f9ad4a3c751ca91601ea7dab70f887b0bf46"},"signature":"https://phar.phpunit.de/phploc-7.0.2.phar.asc"}},"composerLock":null},"phpmd":{"api-version":"1.0.0","version":"1.0.2.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpmd/phpmd-1.0.2.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpmd":"^2.6.1"}},"checksum":{"type":"sha-512","value":"f22280a6dec8dbdd2ec1d83b294f23237fe32c34f4a298e52038e0a7a0074d541635b2b488b1a6098a42d8418a6cd8eb804406ea82b91e362be2b5d11a0915b0"},"tools":{"phpmd":{"version":"2.15.0","url":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar","requirements":{"php":{"php":">=5.3.9","ext-xml":"*"}},"checksum":null,"signature":"https://github.com/phpmd/phpmd/releases/download/2.15.0/phpmd.phar.asc"}},"composerLock":null},"psalm":{"api-version":"1.0.0","version":"1.3.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/psalm/psalm-1.3.0.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"tool":{"psalm":"^3.0 || ^4.0 || ^5.0 || ^6.0"}},"checksum":{"type":"sha-512","value":"4a550c9226d7bca582d7c10bd87cce01190c96398936b1613421640c83df62ed1c6e0d44c1b39635414ea8cf4a892a6458d27590793238add24e7cb5547e6ffd"},"tools":{"psalm":{"version":"6.5.0","url":"https://github.com/vimeo/psalm/releases/download/6.5.0/psalm.phar","requirements":{"php":{"php":"~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0","ext-SimpleXML":"*","ext-ctype":"*","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-tokenizer":"*"}},"checksum":null,"signature":"https://github.com/vimeo/psalm/releases/download/6.5.0/psalm.phar.asc"}},"composerLock":null},"phpcs":{"api-version":"1.0.0","version":"1.2.1.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcs/phpcs-1.2.1.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcs":"^4.0 || ^3.0 || ^2.0","phpcbf":"^4.0 || ^3.0 || ^2.0"}},"checksum":{"type":"sha-512","value":"03f1c6c2d94b79d0e8cbd42996382e0d100c7e07f84c3138fa3a8b394e814ec18ce05cbbd257e527913219b2264f062522e4cf3e3bd402b907b9437d96982b44"},"tools":{"phpcs":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcs.phar.asc"},"phpcbf":{"version":"4.0.1","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar","requirements":{"php":{"php":">=7.2.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/4.0.1/phpcbf.phar.asc"}},"composerLock":null}},"tools":[]} \ No newline at end of file