diff --git a/.phpcq.lock b/.phpcq.lock index 2d695cc..58f5971 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.50.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.50.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.50.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.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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b4c27..77aebc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Changelog [Unreleased] ------------ +### Deprecated + + - Legacy content elements `bs_gridStart`, `bs_gridStop` and `bs_gridSeparator` are deprecated. + Use `bs_grid_wrapper` instead. + +### Added + +- Add support for nested fragments +- Bundle configuration option `enable_legacy_elements` (default: `true`) to disable legacy content elements. + 3.0.5 (2025-03-02) ------------------ diff --git a/README.md b/README.md index a185717..5925acb 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,11 @@ This extension provides Bootstrap 5 grid tools for Contao CMS. Features -------- - - Manage grid definition in your theme settings - - Content elements - - Form elements - - Grid module - - Import/Export with your theme settings - +- Manage grid definition in your theme settings +- Content elements +- Form elements +- Grid module +- Import/Export with your theme settings Changelog --------- @@ -32,7 +31,6 @@ Requirements - PHP ^8.1 - Contao ^4.13 || ^5.3 - Install ------- @@ -73,3 +71,34 @@ class AppKernel } ``` + +Migration +--------- + +To automatically migrate your grid from Start- and Stop-Wrappers to nested fragments, you have to enable the migration +via the bundle configuration. Create or extend the file `config/packages/contao_bootstrap_grid.yaml` in your Symfony +application: + +```yaml +contao_bootstrap_grid: + enable_wrapper_migration: true +``` + +Afterwards you can run the migration in the Contao Manager or via CLI: + +```bash +$ php vendor/bin/contao-console contao:migrate +``` + +Deprecated +---------- + +The legacy content elements `bs_gridStart`, `bs_gridStop` and `bs_gridSeparator` are deprecated and will be removed in +a future major version. Use `bs_grid_wrapper` instead. + +To disable the legacy elements now, set the following configuration: + +```yaml +contao_bootstrap_grid: + enable_legacy_elements: false +``` diff --git a/psalm.xml b/psalm.xml index 4499dac..dc16451 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,7 @@ + diff --git a/src/Component/ContentElement/GridSeparatorElementController.php b/src/Component/ContentElement/GridSeparatorElementController.php index b0bcc83..124ca15 100644 --- a/src/Component/ContentElement/GridSeparatorElementController.php +++ b/src/Component/ContentElement/GridSeparatorElementController.php @@ -21,7 +21,17 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -/** @ContentElement("bs_gridSeparator", category="bs_grid", template="ce_bs_gridSeparator") */ +use function sprintf; +use function trigger_error; + +use const E_USER_DEPRECATED; + +/** + * @deprecated Use GridWrapperElementController with bs_grid_wrapper instead. + * Will be removed in a future major version. + * + * @ContentElement("bs_gridSeparator", category="bs_grid", template="ce_bs_gridSeparator") + */ final class GridSeparatorElementController extends AbstractGridElementController { public function __construct( @@ -34,6 +44,15 @@ public function __construct( TranslatorInterface $translator, private readonly RepositoryManager $repositories, ) { + trigger_error( + sprintf( + 'Content element "%s" is deprecated. Use "%s" instead. Will be removed in a future major version.', + 'bs_gridSeparator', + 'bs_grid_wrapper', + ), + E_USER_DEPRECATED, + ); + parent::__construct( $templateRenderer, $scopeMatcher, @@ -110,6 +129,10 @@ protected function getIterator(ContentModel $model): GridIterator|null */ protected function getParent(ContentModel $model): ContentModel|null { + if ($model->ptable === 'tl_content') { + return $this->repositories->getRepository(ContentModel::class)->find($model->pid); + } + return $this->repositories->getRepository(ContentModel::class)->find((int) $model->bs_grid_parent); } } diff --git a/src/Component/ContentElement/GridStartElementController.php b/src/Component/ContentElement/GridStartElementController.php index c26ef45..b37b714 100644 --- a/src/Component/ContentElement/GridStartElementController.php +++ b/src/Component/ContentElement/GridStartElementController.php @@ -5,17 +5,63 @@ namespace ContaoBootstrap\Grid\Component\ContentElement; use Contao\ContentModel; +use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; use Contao\CoreBundle\ServiceAnnotation\ContentElement; use Contao\Model; +use ContaoBootstrap\Core\Helper\ColorRotate; use ContaoBootstrap\Grid\Exception\GridNotFound; use ContaoBootstrap\Grid\GridIterator; +use ContaoBootstrap\Grid\GridProvider; +use Netzmacht\Contao\Toolkit\Response\ResponseTagger; +use Netzmacht\Contao\Toolkit\Routing\RequestScopeMatcher; +use Netzmacht\Contao\Toolkit\View\Template\TemplateRenderer; use Override; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatorInterface; -/** @ContentElement("bs_gridStart", category="bs_grid", template="ce_bs_gridStart") */ +use function sprintf; +use function trigger_error; + +use const E_USER_DEPRECATED; + +/** + * @deprecated Use GridWrapperElementController with bs_grid_wrapper instead. + * Will be removed in a future major version. + * + * @ContentElement("bs_gridStart", category="bs_grid", template="ce_bs_gridStart") + */ final class GridStartElementController extends AbstractGridElementController { + public function __construct( + TemplateRenderer $templateRenderer, + RequestScopeMatcher $scopeMatcher, + ResponseTagger $responseTagger, + TokenChecker $tokenChecker, + GridProvider $gridProvider, + ColorRotate $colorRotate, + TranslatorInterface $translator, + ) { + trigger_error( + sprintf( + 'Content element "%s" is deprecated. Use "%s" instead. Will be removed in a future major version.', + 'bs_gridStart', + 'bs_grid_wrapper', + ), + E_USER_DEPRECATED, + ); + + parent::__construct( + $templateRenderer, + $scopeMatcher, + $responseTagger, + $tokenChecker, + $gridProvider, + $colorRotate, + $translator, + ); + } + /** {@inheritDoc} */ #[Override] protected function preGenerate( diff --git a/src/Component/ContentElement/GridStopElementController.php b/src/Component/ContentElement/GridStopElementController.php index 59f694c..6a2ff6f 100644 --- a/src/Component/ContentElement/GridStopElementController.php +++ b/src/Component/ContentElement/GridStopElementController.php @@ -21,7 +21,17 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -/** @ContentElement("bs_gridStop", category="bs_grid", template="ce_bs_gridStop") */ +use function sprintf; +use function trigger_error; + +use const E_USER_DEPRECATED; + +/** + * @deprecated Use GridWrapperElementController with bs_grid_wrapper instead. + * Will be removed in a future major version. + * + * @ContentElement("bs_gridStop", category="bs_grid", template="ce_bs_gridStop") + */ final class GridStopElementController extends AbstractGridElementController { public function __construct( @@ -34,6 +44,15 @@ public function __construct( TranslatorInterface $translator, private readonly RepositoryManager $repositories, ) { + trigger_error( + sprintf( + 'Content element "%s" is deprecated. Use "%s" instead. Will be removed in a future major version.', + 'bs_gridStop', + 'bs_grid_wrapper', + ), + E_USER_DEPRECATED, + ); + parent::__construct( $templateRenderer, $scopeMatcher, diff --git a/src/Component/ContentElement/GridWrapperElementController.php b/src/Component/ContentElement/GridWrapperElementController.php index 75450e6..96fe5e9 100644 --- a/src/Component/ContentElement/GridWrapperElementController.php +++ b/src/Component/ContentElement/GridWrapperElementController.php @@ -29,10 +29,16 @@ public function __construct( #[Override] protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response { - $template->iterator = $this->getIterator($model); - $template->name = $model->bs_grid_name; - $template->color = $this->colorRotate->getColor('ce:' . $model->id); - $template->isBackend = $this->isBackendScope($request); + if ($this->isBackendScope($request)) { + $template->setName('backend/grid_wildcard'); + + $template->set('title', $model->bs_grid_name); + $template->set('color', $this->colorRotate->getColor('ce:' . $model->id)); + + return $template->getResponse(); + } + + $template->set('iterator', $this->getIterator($model)); return $template->getResponse(); } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..02106bd --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,30 @@ +getRootNode() + ->children() + ->booleanNode('enable_wrapper_migration') + ->defaultFalse() + ->end() + ->booleanNode('enable_legacy_elements') + ->defaultTrue() + ->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/ContaoBootstrapGridExtension.php b/src/DependencyInjection/ContaoBootstrapGridExtension.php index 998871b..985f6e8 100644 --- a/src/DependencyInjection/ContaoBootstrapGridExtension.php +++ b/src/DependencyInjection/ContaoBootstrapGridExtension.php @@ -18,6 +18,14 @@ final class ContaoBootstrapGridExtension extends Extension #[Override] public function load(array $configs, ContainerBuilder $container): void { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter( + 'contao_bootstrap.grid.enable_wrapper_migration', + $config['enable_wrapper_migration'], + ); + $loader = new YamlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config'), @@ -26,5 +34,11 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('config.yaml'); $loader->load('services.yaml'); $loader->load('listeners.yaml'); + + if (! $config['enable_legacy_elements']) { + return; + } + + $loader->load('legacy.yaml'); } } diff --git a/src/Listener/Dca/ContentListener.php b/src/Listener/Dca/ContentListener.php index 66498f1..f944bda 100644 --- a/src/Listener/Dca/ContentListener.php +++ b/src/Listener/Dca/ContentListener.php @@ -8,6 +8,7 @@ use Contao\Config; use Contao\ContentModel; use Contao\Controller; +use Contao\CoreBundle\DataContainer\PaletteManipulator; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\CoreBundle\Image\ImageSizes; use Contao\Database\Result; @@ -70,6 +71,22 @@ public function initializeDca(): void ]; } + public function updatePaletteOnNestedParent(DataContainer $dataContainer): void + { + $input = $this->framework->getAdapter(Input::class); + $currentRecord = $dataContainer->getCurrentRecord(); + + if ($input->get('act') !== 'edit' || $currentRecord === null) { + return; + } + + if ($currentRecord['type'] !== 'bs_gridSeparator' || $currentRecord['ptable'] !== 'tl_content') { + return; + } + + PaletteManipulator::create()->removeField('bs_grid_parent')->applyToPalette('bs_gridSeparator', 'tl_content'); + } + /** * Get all grid parent options. * diff --git a/src/Migration/GridWrapperMigration.php b/src/Migration/GridWrapperMigration.php new file mode 100644 index 0000000..d7411bc --- /dev/null +++ b/src/Migration/GridWrapperMigration.php @@ -0,0 +1,138 @@ +enableMigration === false) { + return false; + } + + $schemaManager = $this->connection->createSchemaManager(); + if (! $schemaManager->tablesExist(['tl_bs_grid', 'tl_content'])) { + return false; + } + + $queryBuilder = $this->connection->createQueryBuilder(); + + 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; + } + + #[Override] + 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; + } + + return $carry; + }, []); + + $elementCount = array_sum( + array_map( + static fn (array $gridContainer) => count($gridContainer['elements']), + $gridContainers, + ), + ); + + $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']], + ); + } + + if ($gridContainer['stop_id'] === null) { + continue; + } + + $this->connection->delete('tl_content', ['id' => $gridContainer['stop_id']]); + } + }); + + return $this->createResult( + true, + 'Migrated ' . count($gridContainers) . ' grid containers and ' . $elementCount . ' elements.', + ); + } +} diff --git a/src/Resources/config/legacy.yaml b/src/Resources/config/legacy.yaml new file mode 100644 index 0000000..36ee1ae --- /dev/null +++ b/src/Resources/config/legacy.yaml @@ -0,0 +1,32 @@ +services: + ContaoBootstrap\Grid\Component\ContentElement\GridStartElementController: + arguments: + - '@netzmacht.contao_toolkit.template_renderer' + - '@netzmacht.contao_toolkit.routing.scope_matcher' + - '@netzmacht.contao_toolkit.response_tagger' + - '@contao.security.token_checker' + - '@contao_bootstrap.grid.grid_provider' + - '@contao_bootstrap.core.helper.color_rotate' + - '@translator' + + ContaoBootstrap\Grid\Component\ContentElement\GridSeparatorElementController: + arguments: + - '@netzmacht.contao_toolkit.template_renderer' + - '@netzmacht.contao_toolkit.routing.scope_matcher' + - '@netzmacht.contao_toolkit.response_tagger' + - '@contao.security.token_checker' + - '@contao_bootstrap.grid.grid_provider' + - '@contao_bootstrap.core.helper.color_rotate' + - '@translator' + - '@netzmacht.contao_toolkit.repository_manager' + + ContaoBootstrap\Grid\Component\ContentElement\GridStopElementController: + arguments: + - '@netzmacht.contao_toolkit.template_renderer' + - '@netzmacht.contao_toolkit.routing.scope_matcher' + - '@netzmacht.contao_toolkit.response_tagger' + - '@contao.security.token_checker' + - '@contao_bootstrap.grid.grid_provider' + - '@contao_bootstrap.core.helper.color_rotate' + - '@translator' + - '@netzmacht.contao_toolkit.repository_manager' diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index b49788c..995708d 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -18,38 +18,6 @@ services: - '@contao_bootstrap.grid.grid_provider' - '@contao_bootstrap.core.helper.color_rotate' - ContaoBootstrap\Grid\Component\ContentElement\GridStartElementController: - arguments: - - '@netzmacht.contao_toolkit.template_renderer' - - '@netzmacht.contao_toolkit.routing.scope_matcher' - - '@netzmacht.contao_toolkit.response_tagger' - - '@contao.security.token_checker' - - '@contao_bootstrap.grid.grid_provider' - - '@contao_bootstrap.core.helper.color_rotate' - - '@translator' - - ContaoBootstrap\Grid\Component\ContentElement\GridSeparatorElementController: - arguments: - - '@netzmacht.contao_toolkit.template_renderer' - - '@netzmacht.contao_toolkit.routing.scope_matcher' - - '@netzmacht.contao_toolkit.response_tagger' - - '@contao.security.token_checker' - - '@contao_bootstrap.grid.grid_provider' - - '@contao_bootstrap.core.helper.color_rotate' - - '@translator' - - '@netzmacht.contao_toolkit.repository_manager' - - ContaoBootstrap\Grid\Component\ContentElement\GridStopElementController: - arguments: - - '@netzmacht.contao_toolkit.template_renderer' - - '@netzmacht.contao_toolkit.routing.scope_matcher' - - '@netzmacht.contao_toolkit.response_tagger' - - '@contao.security.token_checker' - - '@contao_bootstrap.grid.grid_provider' - - '@contao_bootstrap.core.helper.color_rotate' - - '@translator' - - '@netzmacht.contao_toolkit.repository_manager' - ContaoBootstrap\Grid\Component\ContentElement\GalleryElementController: arguments: - '@netzmacht.contao_toolkit.template_renderer' @@ -104,3 +72,10 @@ services: - '@database_connection' tags: - { name: 'contao.migration' } + + ContaoBootstrap\Grid\Migration\GridWrapperMigration: + arguments: + - '@database_connection' + - '%contao_bootstrap.grid.enable_wrapper_migration%' + tags: + - { name: 'contao.migration' } diff --git a/src/Resources/contao/dca/tl_content.php b/src/Resources/contao/dca/tl_content.php index c9193db..80cffcd 100644 --- a/src/Resources/contao/dca/tl_content.php +++ b/src/Resources/contao/dca/tl_content.php @@ -13,6 +13,11 @@ 'initializeDca', ]; +$GLOBALS['TL_DCA']['tl_content']['config']['onload_callback'][] = [ + 'contao_bootstrap.grid.listeners.dca.content', + 'updatePaletteOnNestedParent', +]; + $GLOBALS['TL_DCA']['tl_content']['config']['oncopy_callback'][] = [ ContentFixParentRelationListener::class, 'onCopy', diff --git a/src/Resources/contao/templates/twig/backend/grid_wildcard.html.twig b/src/Resources/contao/templates/twig/backend/grid_wildcard.html.twig new file mode 100644 index 0000000..5c4bae3 --- /dev/null +++ b/src/Resources/contao/templates/twig/backend/grid_wildcard.html.twig @@ -0,0 +1,7 @@ +{% trans_default_domain 'contao_modules' %} + +
+ ### {{ ('CTE.bootstrap')|trans }}: {{ ('CTE.' ~ type ~ '.0')|trans }} ### +
+ {{ title }} +
diff --git a/src/Resources/contao/templates/twig/content_element/bs_grid_wrapper.html.twig b/src/Resources/contao/templates/twig/content_element/bs_grid_wrapper.html.twig index 199f371..2999c98 100644 --- a/src/Resources/contao/templates/twig/content_element/bs_grid_wrapper.html.twig +++ b/src/Resources/contao/templates/twig/content_element/bs_grid_wrapper.html.twig @@ -1,34 +1,22 @@ {% extends "@Contao/content_element/_base.html.twig" %} {% block content %} - {% if isBackend %} {% if iterator is not null %} - {% for fragment in nested_fragments %} -
{{ name }} [{{ iterator.current }}]
- {{ content_element(fragment) }} - {% endfor %} +
+ {% for fragment in nested_fragments %} + {% for reset in iterator.resets %} +
+ {% endfor %} +
+ {{ content_element(fragment) }} +
+ {{ iterator.next() }} + {% endfor %} +
{% else %} + {{ 'ERR.bsGridParentMissing'|trans({}, 'contao_default') }} {% for fragment in nested_fragments %} {{ content_element(fragment) }} {% endfor %} {% endif %} - {% else %} - {% if iterator is not null %} -
- {% for fragment in nested_fragments %} - {% for reset in iterator.resets %} -
- {% endfor %} -
- {{ content_element(fragment) }} -
- {% endfor %} -
- {% else %} - {{ 'ERR.bsGridParentMissing'|trans({}, 'contao_default') }} - {% for fragment in nested_fragments %} - {{ content_element(fragment) }} - {% endfor %} - {% endif %} - {% endif %} {% endblock %}