diff --git a/.composer-require-checker.json b/.composer-require-checker.json new file mode 100644 index 0000000..e287bac --- /dev/null +++ b/.composer-require-checker.json @@ -0,0 +1,28 @@ +{ + "symbol-whitelist": [ + "array", "bool", "false", "int", "null", "self", "static", "parent", "string", "true", "void", "mixed", + "ampersand", "array_insert", "array_is_assoc", "nl2br_html5", "TL_ERROR", + "BackendTemplate", + "ContaoCommunityAlliance\\Contao\\Bindings\\ContaoEvents", + "ContaoCommunityAlliance\\Contao\\Bindings\\Events\\Widget\\GetAttributesFromDcaEvent", + "Contao\\ManagerBundle\\ContaoManagerBundle", + "Contao\\ManagerPlugin\\Bundle\\BundlePluginInterface", + "Contao\\ManagerPlugin\\Bundle\\Config\\BundleConfig", + "Contao\\ManagerPlugin\\Bundle\\Parser\\ParserInterface", + "Doctrine\\DBAL\\ArrayParameterType", + "Doctrine\\DBAL\\Connection", + "Doctrine\\DBAL\\Platforms\\AbstractPlatform", + "FrontendUser", + "Psr\\EventDispatcher\\EventDispatcherInterface", + "Symfony\\Component\\Config\\FileLocator", + "Symfony\\Component\\DependencyInjection\\ContainerBuilder", + "Symfony\\Component\\DependencyInjection\\Extension\\Extension", + "Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader", + "Symfony\\Component\\Filesystem\\Filesystem", + "Symfony\\Component\\HttpFoundation\\Request", + "Symfony\\Component\\HttpFoundation\\RequestStack", + "Symfony\\Component\\HttpKernel\\Bundle\\Bundle", + "Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface", + "Symfony\\Contracts\\Translation\\TranslatorInterface" + ] +} diff --git a/.github/workflows/diagnostics.yml b/.github/workflows/diagnostics.yml index 78e3812..bddb943 100644 --- a/.github/workflows/diagnostics.yml +++ b/.github/workflows/diagnostics.yml @@ -1,50 +1,75 @@ -name: DC General Contao Frontend +name: Code Quality Diagnostics on: - push: pull_request: + push: + branches: jobs: build: runs-on: ubuntu-latest + name: 'PHP: ${{ matrix.php }} Contao: ${{ matrix.contao }}' strategy: + fail-fast: false matrix: - php: [8.1] - contao: [~4.13.0] + php: [ '8.1', '8.2' ] + contao: [ '~4.13.0' ] + phpcq_install: [ 'update' ] + output: [ '-o github-action -o default' ] steps: - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Pull source - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - name: Pull source + uses: actions/checkout@v3 - # see https://github.com/shivammathur/setup-php - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Setup PHP. + - name: Setup PHP with PECL extension uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - coverage: none - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Cache composer cache directory - uses: actions/cache@v1 + # setup caches + - name: Cache composer cache directory + uses: actions/cache@v3 env: cache-name: composer-cache-dir with: path: ~/.cache/composer - key: ${{ runner.os }}-build-${{ env.cache-name }} + key: ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }} - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Cache vendor directory + - name: Cache vendor directory uses: actions/cache@v3 env: - cache-name: composer-vendor + cache-name: vendor with: path: vendor - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.contao }}-build-${{ env.cache-name }}-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-${{ matrix.php }}-${{ matrix.contao }}-build-${{ env.cache-name }}- - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Install composer dependencies - run: composer update --prefer-dist --no-interaction --no-suggest + - name: Cache phpcq directory + uses: actions/cache@v3 + env: + cache-name: phpcq + with: + path: .phpcq + key: ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }}-${{ hashFiles('**/.phpcq.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.php }}-build-${{ env.cache-name }}- + + # install dependencies and tools + - name: Install composer dependencies + run: | + composer require contao/core-bundle ${{ matrix.contao }} --no-update + composer install + - name: Install phpcq toolchain + run: ./vendor/bin/phpcq ${{ matrix.phpcq_install }} -v - - name: PHP ${{ matrix.php }} ${{ matrix.contao }} Run tests - run: ant -keep-going + # run tests + - name: Run tests + run: ./vendor/bin/phpcq run -v ${{ matrix.output }} + + - name: Upload build directory to artifact + uses: actions/upload-artifact@v3 + if: ${{ success() }} || ${{ failure() }} + with: + name: phpcq-builds-php-${{ matrix.php }}-${{ matrix.contao }} + path: .phpcq/build/ diff --git a/.gitignore b/.gitignore index 343d80a..91a097f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .DS_Store Thumbs.db -# IDEs +# IDEs .buildpath .project .settings/ @@ -14,3 +14,4 @@ nbproject/ # composer related vendor/ composer.lock +.phpcq/ diff --git a/.phpcq.lock b/.phpcq.lock new file mode 100644 index 0000000..52ac3b1 --- /dev/null +++ b/.phpcq.lock @@ -0,0 +1 @@ +{"plugins":{"phpunit":{"api-version":"1.0.0","version":"1.0.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpunit/phpunit-1.0.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0"},"tool":{"phpunit":"^6.0 || ^7.0 || ^8.0 || ^9.0"}},"checksum":{"type":"sha-512","value":"c73f15658e3ba62665f09492ec91c3a6a715760bfaa88473a987538439fff442540148e086e46a6aa18ce55a3ea2fbf76caaa581384cb84a38859fcc609ae7e4"},"tools":{"phpunit":{"version":"9.6.20","url":"https://phar.phpunit.de/phpunit-9.6.20.phar","requirements":{"php":{"php":">=7.3","ext-dom":"*","ext-json":"*","ext-libxml":"*","ext-mbstring":"*","ext-xml":"*","ext-xmlwriter":"*"}},"checksum":{"type":"sha-256","value":"4b49fdf48a3fdb5f4f4b5b18c43a542e75787edaf6a6ab39713f579d0a018210"},"signature":"https://phar.phpunit.de/phpunit-9.6.20.phar.asc"}},"composerLock":null},"psalm":{"api-version":"1.0.0","version":"1.2.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/psalm/psalm-1.2.0.0.php","signature":null,"requirements":{"php":{"php":"^7.4 || ^8.0","ext-dom":"*"},"tool":{"psalm":"^3.0 || ^4.0 || ^5.0"}},"checksum":{"type":"sha-512","value":"4a550c9226d7bca582d7c10bd87cce01190c96398936b1613421640c83df62ed1c6e0d44c1b39635414ea8cf4a892a6458d27590793238add24e7cb5547e6ffd"},"tools":{"psalm":{"version":"5.25.0","url":"https://github.com/vimeo/psalm/releases/download/5.25.0/psalm.phar","requirements":{"php":{"php":"^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.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/5.25.0/psalm.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.11.0","url":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.11.0/composer-require-checker.phar","requirements":{"php":{"php":"~8.2.0 || ~8.3.0","ext-phar":"*"}},"checksum":null,"signature":"https://github.com/maglnet/ComposerRequireChecker/releases/download/4.11.0/composer-require-checker.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},"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},"phpcs":{"api-version":"1.0.0","version":"1.2.0.0","type":"php-file","url":"https://phpcq.github.io/repository/plugin/phpcs/phpcs-1.2.0.0.php","signature":null,"requirements":{"php":{"php":"^7.3 || ^8.0","ext-dom":"*"},"tool":{"phpcs":"^3.0 || ^2.0","phpcbf":"^3.0 || ^2.0"}},"checksum":{"type":"sha-512","value":"b6ed00306e76068a6af5e3b1dec837724f9e1900ef1049ce88e7ce195b0583524ca33a73613fba13244307a7ca853b6ddaa14ded69f651c3f184ac130bd1aaad"},"tools":{"phpcs":{"version":"3.10.2","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.10.2/phpcs.phar","requirements":{"php":{"php":">=5.4.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.10.2/phpcs.phar.asc"},"phpcbf":{"version":"3.10.2","url":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.10.2/phpcbf.phar","requirements":{"php":{"php":">=5.4.0","ext-simplexml":"*","ext-tokenizer":"*","ext-xmlwriter":"*"}},"checksum":null,"signature":"https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/download/3.10.2/phpcbf.phar.asc"}},"composerLock":null},"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.43.0","url":"https://github.com/ergebnis/composer-normalize/releases/download/2.43.0/composer-normalize.phar","requirements":{"php":{"php":"~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0","ext-json":"*"}},"checksum":null,"signature":"https://github.com/ergebnis/composer-normalize/releases/download/2.43.0/composer-normalize.phar.asc"}},"composerLock":null}},"tools":[]} \ No newline at end of file diff --git a/.phpcq.yaml.dist b/.phpcq.yaml.dist index 5cf7a38..236f0aa 100644 --- a/.phpcq.yaml.dist +++ b/.phpcq.yaml.dist @@ -19,7 +19,7 @@ phpcq: requirements: composer-require-checker: signed: false - version: ^3.3 + version: ^4.0 phpmd: version: ^1.0 signed: false @@ -51,6 +51,7 @@ phpcq: - D2CCAC42F6295E7D # PHP_CodeSniffer - 31C7E470E2138192 + - A978220305CD5C32 # Composer normalize - C00543248C87FB13 # phpmd diff --git a/build.xml b/build.xml deleted file mode 100644 index 6c03cfd..0000000 --- a/build.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/composer.json b/composer.json index d6d7eb2..71b490e 100644 --- a/composer.json +++ b/composer.json @@ -1,59 +1,64 @@ { - "name":"contao-community-alliance/dc-general-contao-frontend", - "description":"DcGeneral frontend editing feature", - "keywords":["contao", "dc-general"], - "type":"contao-bundle", - "homepage":"https://c-c-a.org/", - "license":"LGPL-3.0-or-later", - "authors":[ + "name": "contao-community-alliance/dc-general-contao-frontend", + "description": "DcGeneral frontend editing feature", + "license": "LGPL-3.0-or-later", + "type": "contao-bundle", + "keywords": [ + "contao", + "dc-general", + "frontend" + ], + "authors": [ { - "name":"Christian Schiffler", - "email":"c.schiffler@cyberspectrum.de", - "homepage":"https://www.cyberspectrum.de", - "role":"Developer" + "name": "Christian Schiffler", + "email": "c.schiffler@cyberspectrum.de", + "homepage": "https://www.cyberspectrum.de", + "role": "Developer" }, { - "name":"Stefan Heimes", - "email":"stefan_heimes@hotmail.com", - "role":"Developer" + "name": "Stefan Heimes", + "email": "stefan_heimes@hotmail.com", + "role": "Developer" } ], + "homepage": "https://c-c-a.org/", "support": { - "email":"info@contao-community-alliance.org", - "issues":"https://github.com/contao-community-alliance/dc-general-contao-frontend/issues", - "wiki":"https://github.com/contao-community-alliance/dc-general-contao-frontend/wiki", - "irc":"irc://irc.freenode.org/contao.cca", - "source":"https://github.com/contao-community-alliance/dc-general-contao-frontend" + "email": "info@contao-community-alliance.org", + "issues": "https://github.com/contao-community-alliance/dc-general-contao-frontend/issues", + "wiki": "https://github.com/contao-community-alliance/dc-general-contao-frontend/wiki", + "source": "https://github.com/contao-community-alliance/dc-general-contao-frontend" }, "require": { - "php": "^8.0", - "contao-community-alliance/dc-general": "^2.3@dev", - "contao-community-alliance/url-builder": "~1.3", - "contao-community-alliance/translator": "^2.3", - "contao/core-bundle": "^4.13.0, <5.0", + "php": "^8.1", + "contao-community-alliance/dc-general": "^2.3", + "contao-community-alliance/translator": "^2.4.2", + "contao-community-alliance/url-builder": "^1.3", + "contao/core-bundle": "^4.13.0 <5.0", "symfony/event-dispatcher": "^5.4" }, "require-dev": { - "contao/manager-plugin": "^2.8", - "phpcq/runner-bootstrap": "^1.0@dev" + "contao/manager-bundle": "^4.13.0 <5.0", + "phpcq/runner-bootstrap": "^1.0@dev", + "roave/security-advisories": "dev-latest" }, "autoload": { "psr-4": { "ContaoCommunityAlliance\\DcGeneral\\ContaoFrontend\\": "src" } }, - "extra":{ - "contao-manager-plugin": "ContaoCommunityAlliance\\DcGeneral\\ContaoFrontend\\ContaoManager\\Plugin", - "branch-alias": { - "dev-feature/2.3.0": "2.3.x-dev" - } - }, "config": { "allow-plugins": { "contao-components/installer": false, + "contao/manager-plugin": false, "ocramius/package-versions": false, - "contao/manager-plugin": false + "php-http/discovery": true + } + }, + "extra": { + "branch-alias": { + "dev-feature/2.3.0": "2.3.x-dev", + "dev-hotfix/update_phpcq": "2.3.x-dev" }, - "sort-packages": true + "contao-manager-plugin": "ContaoCommunityAlliance\\DcGeneral\\ContaoFrontend\\ContaoManager\\Plugin" } } diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..abd61c3 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/CcaDcGeneralContaoFrontendBundle.php b/src/CcaDcGeneralContaoFrontendBundle.php index d059949..928272c 100644 --- a/src/CcaDcGeneralContaoFrontendBundle.php +++ b/src/CcaDcGeneralContaoFrontendBundle.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2017 Contao Community Alliance. + * (c) 2015-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,7 +12,8 @@ * * @package contao-community-alliance/dc-general-contao-frontend * @author Richard Henkenjohann - * @copyright 2015-2017 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -26,5 +27,4 @@ */ class CcaDcGeneralContaoFrontendBundle extends Bundle { - } diff --git a/src/DependencyInjection/CcaDcGeneralContaoFrontendExtension.php b/src/DependencyInjection/CcaDcGeneralContaoFrontendExtension.php index 842a824..06059d3 100644 --- a/src/DependencyInjection/CcaDcGeneralContaoFrontendExtension.php +++ b/src/DependencyInjection/CcaDcGeneralContaoFrontendExtension.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2017 Contao Community Alliance. + * (c) 2015-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,7 +12,8 @@ * * @package contao-community-alliance/dc-general-contao-frontend * @author Richard Henkenjohann - * @copyright 2015-2017 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -29,11 +30,10 @@ */ class CcaDcGeneralContaoFrontendExtension extends Extension { - /** * The files to load. * - * @var array + * @var string[] */ private static $files = [ 'listeners.yml', @@ -43,7 +43,7 @@ class CcaDcGeneralContaoFrontendExtension extends Extension /** * {@inheritDoc} */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); foreach (static::$files as $file) { diff --git a/src/Event/BuildWidgetEvent.php b/src/Event/BuildWidgetEvent.php index 7c6ccec..05cd3ac 100644 --- a/src/Event/BuildWidgetEvent.php +++ b/src/Event/BuildWidgetEvent.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,13 +12,15 @@ * * @package contao-community-alliance/dc-general-contao-frontend * @author Christian Schiffler - * @copyright 2015 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event; +use Contao\Widget; use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\Properties\PropertyInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; @@ -39,17 +41,15 @@ class BuildWidgetEvent extends AbstractModelAwareEvent /** * The instantiated widget. * - * @var \Widget + * @var Widget|null */ - protected $widget; + protected $widget = null; /** * Create a new event. * * @param EnvironmentInterface $environment The environment instance in use. - * * @param ModelInterface $model The model holding the data for the widget that shall be instantiated. - * * @param PropertyInterface $property The property for which the widget shall be instantiated. */ public function __construct( @@ -65,7 +65,7 @@ public function __construct( /** * Stores the widget instance into the event. * - * @param \Widget $widget The widget instance. + * @param Widget|null $widget The widget instance. * * @return BuildWidgetEvent */ @@ -79,7 +79,7 @@ public function setWidget($widget) /** * Retrieve the widget instance from the event. * - * @return \Widget + * @return Widget|null */ public function getWidget() { diff --git a/src/Event/DcGeneralFrontendEvents.php b/src/Event/DcGeneralFrontendEvents.php index 0f6f6a4..9576b95 100644 --- a/src/Event/DcGeneralFrontendEvents.php +++ b/src/Event/DcGeneralFrontendEvents.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015 Contao Community Alliance. + * (c) 2015-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,7 +12,8 @@ * * @package contao-community-alliance/dc-general-contao-frontend * @author Christian Schiffler - * @copyright 2015 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -29,12 +30,12 @@ class DcGeneralFrontendEvents * * @see ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\BuildWidgetEvent */ - const BUILD_WIDGET = 'dc-general.contao-frontend.build-widget'; + public const BUILD_WIDGET = 'dc-general.contao-frontend.build-widget'; /** * This event is being emitted when the edit mask has encountered a submit and post action shall be performed. * * @see ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\HandleSubmitEvent */ - const HANDLE_SUBMIT = 'dc-general.contao-frontend.handle-submit'; + public const HANDLE_SUBMIT = 'dc-general.contao-frontend.handle-submit'; } diff --git a/src/FrontendEditor.php b/src/FrontendEditor.php index 4b47f0b..bd19e4d 100644 --- a/src/FrontendEditor.php +++ b/src/FrontendEditor.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,7 +15,7 @@ * @author Richard Henkenjohann * @author Sven Baumann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -27,6 +27,7 @@ use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent; use ContaoCommunityAlliance\DcGeneral\Factory\DcGeneralFactory; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; use ContaoCommunityAlliance\Translator\TranslatorInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -40,27 +41,26 @@ class FrontendEditor * * @var EventDispatcherInterface */ - private $dispatcher; + private EventDispatcherInterface $dispatcher; /** * The translator. * * @var TranslatorInterface */ - private $translator; + private TranslatorInterface $translator; /** * The already populated environments. * * @var EnvironmentInterface[] */ - private static $environments = []; + private static array $environments = []; /** * Create a new instance. * * @param EventDispatcherInterface $dispatcher The event dispatcher. - * * @param TranslatorInterface $translator The translator. */ public function __construct(EventDispatcherInterface $dispatcher, TranslatorInterface $translator) @@ -80,22 +80,28 @@ public function __construct(EventDispatcherInterface $dispatcher, TranslatorInte public function editFor($containerName, $defaultAction = 'showAll'): string { $environment = $this->createDcGeneral($containerName); - $actionName = $environment->getInputProvider()->getParameter('act') ?: $defaultAction; - $action = new Action($actionName); - $event = new ActionEvent($environment, $action); + + $inputProvider = $environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + + $actionName = $inputProvider->getParameter('act') ?: $defaultAction; + assert(\is_string($actionName)); + + $action = new Action($actionName); + $event = new ActionEvent($environment, $action); // If the action parameter is not set, it is set. So that the action parameter can be used everywhere. - if (false === ($hasActionName = $environment->getInputProvider()->hasParameter('act'))) { - $environment->getInputProvider()->setParameter('act', $actionName); + if (false === ($hasActionName = $inputProvider->hasParameter('act'))) { + $inputProvider->setParameter('act', $actionName); } $this->dispatcher->dispatch($event, DcGeneralEvents::ACTION); if (false === $hasActionName) { - $environment->getInputProvider()->unsetParameter('act'); + $inputProvider->unsetParameter('act'); } - if (!$result = $event->getResponse()) { + if (null === ($result = $event->getResponse())) { return 'Action ' . $action->getName() . ' is not supported yet.'; } diff --git a/src/Listener/HandleSubmitListener.php b/src/Listener/HandleSubmitListener.php index 3dc9c8d..cce04eb 100644 --- a/src/Listener/HandleSubmitListener.php +++ b/src/Listener/HandleSubmitListener.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2018 Contao Community Alliance. + * (c) 2015-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,8 @@ * @package contao-community-alliance/dc-general-contao-frontend * @author Christian Schiffler * @author Richard Henkenjohann - * @copyright 2015-2018 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -25,6 +26,7 @@ use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\HandleSubmitEvent; use ContaoCommunityAlliance\DcGeneral\Data\ModelId; use ContaoCommunityAlliance\UrlBuilder\UrlBuilder; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; /** @@ -32,7 +34,6 @@ */ class HandleSubmitListener { - /** * The request scope determinator. * @@ -75,7 +76,10 @@ public function handleEvent(HandleSubmitEvent $event): void return; } - $currentUrl = $this->requestStack->getCurrentRequest()->getUri(); + $currentRequest = $this->requestStack->getCurrentRequest(); + assert($currentRequest instanceof Request); + + $currentUrl = $currentRequest->getUri(); switch ($event->getButtonName()) { case 'save': diff --git a/src/Resources/config/listeners.yml b/src/Resources/config/listeners.yml index afd0d78..723936e 100644 --- a/src/Resources/config/listeners.yml +++ b/src/Resources/config/listeners.yml @@ -1,10 +1,11 @@ services: cca.dc-general.contao_frontend.view.build_widget_listener: class: ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View\DefaultWidgetBuilder - tags: - - { name: kernel.event_listener, event: 'dc-general.contao-frontend.build-widget', method: handleEvent } arguments: - '@cca.dc-general.scope-matcher' + - '@translator' + tags: + - { name: kernel.event_listener, event: 'dc-general.contao-frontend.build-widget', method: handleEvent } cca.dc-general.contao_frontend.handle_submit_listener: class: ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Listener\HandleSubmitListener diff --git a/src/Resources/contao/templates/dcfe_general_edit.html5 b/src/Resources/contao/templates/dcfe_general_edit.html5 index 0e4d400..bb015a8 100644 --- a/src/Resources/contao/templates/dcfe_general_edit.html5 +++ b/src/Resources/contao/templates/dcfe_general_edit.html5 @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2021 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,40 +13,45 @@ * @package contao-community-alliance/dc-general-contao-frontend * @author Christian Schiffler * @author Ingolf Steinhardt - * @copyright 2015-2021 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ + +use Contao\StringUtil; +use Contao\System; + +$requestToken = System::getContainer()->get('contao.csrf.token_manager')->getDefaultTokenValue(); ?>

subHeadline; ?>

-getMessages(); ?> +getMessages() ?>
- - - error): ?>

', $this->error); ?>

+ + + error): ?>

', $this->error) ?>

fieldsets as $arrFieldset): ?> -
- - +
+ +
-
- +
+
- editButtons); ?> + editButtons) ?>
diff --git a/src/Resources/contao/templates/widgets/form_upload-on-steroids.html5 b/src/Resources/contao/templates/widgets/form_upload-on-steroids.html5 index b0ad124..f909006 100644 --- a/src/Resources/contao/templates/widgets/form_upload-on-steroids.html5 +++ b/src/Resources/contao/templates/widgets/form_upload-on-steroids.html5 @@ -17,7 +17,7 @@
    files as $key => $file): ?> -
  • +
  • showThumbnail): ?> ', @@ -26,7 +26,7 @@ $file['thumbnail']['height'], $file['name'] ) ?> -

    +
    @@ -34,20 +34,16 @@
    deselect): ?>
    - - trans('MSC.reset') ?> +
    delete): ?>
    - - trans('MSC.delete') ?> +
    diff --git a/src/View/ActionHandler/CopyHandler.php b/src/View/ActionHandler/CopyHandler.php index 589b83f..1cfb3fa 100644 --- a/src/View/ActionHandler/CopyHandler.php +++ b/src/View/ActionHandler/CopyHandler.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2023 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,7 @@ * @package contao-community-alliance/dc-general-contao-frontend * @author Richard Henkenjohann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2023 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -24,7 +24,10 @@ use Contao\CoreBundle\Exception\RedirectResponseException; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait; +use ContaoCommunityAlliance\DcGeneral\Controller\ControllerInterface; +use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; use ContaoCommunityAlliance\DcGeneral\Data\ModelId; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent; @@ -32,11 +35,16 @@ use ContaoCommunityAlliance\DcGeneral\Event\PreDuplicateModelEvent; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; use ContaoCommunityAlliance\DcGeneral\Exception\NotCreatableException; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; use ContaoCommunityAlliance\UrlBuilder\UrlBuilder; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * This class handles the copy actions in the frontend. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CopyHandler { @@ -47,13 +55,12 @@ class CopyHandler * * @var RequestStack */ - private $requestStack; + private RequestStack $requestStack; /** * CopyHandler constructor. * * @param RequestScopeDeterminator $scopeDeterminator The request mode determinator. - * * @param RequestStack $requestStack The current request stack. */ public function __construct(RequestScopeDeterminator $scopeDeterminator, RequestStack $requestStack) @@ -75,7 +82,10 @@ public function __construct(RequestScopeDeterminator $scopeDeterminator, Request */ public function handleEvent(ActionEvent $event): void { - if (!$this->scopeDeterminator->currentScopeIsFrontend()) { + if ( + null === ($scopeDeterminator = $this->scopeDeterminator) + || !$scopeDeterminator->currentScopeIsFrontend() + ) { return; } @@ -89,7 +99,7 @@ public function handleEvent(ActionEvent $event): void } // Only run when no response given yet. - if (null !== $event->getResponse()) { + if ('' !== $event->getResponse()) { return; } @@ -109,28 +119,46 @@ public function handleEvent(ActionEvent $event): void */ public function process(EnvironmentInterface $environment): void { - $dispatcher = $environment->getEventDispatcher(); - $definition = $environment->getDataDefinition(); + $dispatcher = $environment->getEventDispatcher(); + assert($dispatcher instanceof EventDispatcherInterface); + + $definition = $environment->getDataDefinition(); + assert($definition instanceof ContainerInterface); + $basicDefinition = $definition->getBasicDefinition(); - $currentUrl = $this->requestStack->getCurrentRequest()->getUri(); + + $currentRequest = $this->requestStack->getCurrentRequest() ; + assert($currentRequest instanceof Request); + + $currentUrl = $currentRequest->getUri(); if (!$basicDefinition->isCreatable()) { throw new NotCreatableException('DataContainer ' . $definition->getName() . ' is not creatable'); } + // We only support flat tables, sorry. if (BasicDefinitionInterface::MODE_HIERARCHICAL === $basicDefinition->getMode()) { return; } - $modelId = ModelId::fromSerialized($environment->getInputProvider()->getParameter('source')); + + $inputProvider = $environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + + $modelId = ModelId::fromSerialized((string) $inputProvider->getParameter('source')); $dataProvider = $environment->getDataProvider(); - $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); + assert($dataProvider instanceof DataProviderInterface); + + $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); if (null === $model) { throw new PageNotFoundException('Model not found: ' . $modelId->getSerialized()); } - $copyModel = $environment->getController()->createClonedModel($model); + $controller = $environment->getController(); + assert($controller instanceof ControllerInterface); + + $copyModel = $controller->createClonedModel($model); // Dispatch pre duplicate event. $copyEvent = new PreDuplicateModelEvent($environment, $copyModel, $model); @@ -138,6 +166,8 @@ public function process(EnvironmentInterface $environment): void // Save the copy. $provider = $environment->getDataProvider($copyModel->getProviderName()); + assert($provider instanceof DataProviderInterface); + $provider->save($copyModel); // Dispatch post duplicate event. diff --git a/src/View/ActionHandler/CreateHandler.php b/src/View/ActionHandler/CreateHandler.php index 0b3cc81..ae5c858 100644 --- a/src/View/ActionHandler/CreateHandler.php +++ b/src/View/ActionHandler/CreateHandler.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2019 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,7 +14,7 @@ * @author Christian Schiffler * @author Ingolf Steinhardt * @author Richard Henkenjohann - * @copyright 2015-2019 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -24,6 +24,8 @@ use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait; use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View\EditMask; +use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent; @@ -32,7 +34,7 @@ use ContaoCommunityAlliance\DcGeneral\Exception\NotCreatableException; /** - * This class handles the create actions in the frontend. + * This class handles the actions of create in the frontend. */ class CreateHandler { @@ -60,26 +62,25 @@ public function __construct(RequestScopeDeterminator $scopeDeterminator) */ public function handleEvent(ActionEvent $event): void { - if (!$this->scopeDeterminator->currentScopeIsFrontend()) { + if ( + null === ($scopeDeterminator = $this->scopeDeterminator) + || !$scopeDeterminator->currentScopeIsFrontend() + ) { return; } - $environment = $event->getEnvironment(); - $action = $event->getAction(); - - // Only handle if we do not have a manual sorting or we know where to insert. + // Only handle if we do not have a manual sorting, or we know where to insert. // Manual sorting is handled by clipboard. - if ('create' !== $action->getName()) { + if ('create' !== $event->getAction()->getName()) { return; } // Only run when no response given yet. - if (null !== $event->getResponse()) { + if ('' !== $event->getResponse()) { return; } - $response = $this->process($environment); - if ($response !== false) { + if ('' !== ($response = $this->process($event->getEnvironment()))) { $event->setResponse($response); } } @@ -89,13 +90,15 @@ public function handleEvent(ActionEvent $event): void * * @param EnvironmentInterface $environment The environment. * - * @return string|bool + * @return string * * @throws NotCreatableException If the data container is not editable, closed. */ public function process(EnvironmentInterface $environment) { - $definition = $environment->getDataDefinition(); + $definition = $environment->getDataDefinition(); + assert($definition instanceof ContainerInterface); + $basicDefinition = $definition->getBasicDefinition(); if (!$basicDefinition->isCreatable()) { @@ -103,22 +106,26 @@ public function process(EnvironmentInterface $environment) } // We only support flat tables, sorry. if (BasicDefinitionInterface::MODE_HIERARCHICAL === $basicDefinition->getMode()) { - return false; + return ''; } $dataProvider = $environment->getDataProvider(); - $properties = $definition->getPropertiesDefinition()->getProperties(); - $model = $dataProvider->getEmptyModel(); - $clone = $dataProvider->getEmptyModel(); + assert($dataProvider instanceof DataProviderInterface); + + $properties = $definition->getPropertiesDefinition()->getProperties(); + $model = $dataProvider->getEmptyModel(); + $clone = $dataProvider->getEmptyModel(); // If some of the fields have a default value, set it. foreach ($properties as $property) { $propName = $property->getName(); - if ($property->getDefaultValue() !== null) { - $model->setProperty($propName, $property->getDefaultValue()); - $clone->setProperty($propName, $property->getDefaultValue()); + if ((null === $property->getDefaultValue()) || !$dataProvider->fieldExists($propName)) { + continue; } + + $clone->setProperty($propName, $property->getDefaultValue()); + $model->setProperty($propName, $property->getDefaultValue()); } return (new EditMask($environment, $model, $clone, null, null))->execute(); diff --git a/src/View/ActionHandler/DeleteHandler.php b/src/View/ActionHandler/DeleteHandler.php index 946d144..22350ae 100644 --- a/src/View/ActionHandler/DeleteHandler.php +++ b/src/View/ActionHandler/DeleteHandler.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,7 @@ * @package contao-community-alliance/dc-general-contao-frontend * @author Richard Henkenjohann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -24,7 +24,9 @@ use Contao\CoreBundle\Exception\RedirectResponseException; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait; +use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; use ContaoCommunityAlliance\DcGeneral\Data\ModelId; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent; @@ -32,10 +34,15 @@ use ContaoCommunityAlliance\DcGeneral\Event\PreDeleteModelEvent; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; use ContaoCommunityAlliance\DcGeneral\Exception\NotDeletableException; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; /** - * This class handles the edit actions in the frontend. + * This class handles the actions of edit in the frontend. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DeleteHandler { @@ -46,13 +53,12 @@ class DeleteHandler * * @var RequestStack */ - private $requestStack; + private RequestStack $requestStack; /** * DeleteHandler constructor. * * @param RequestScopeDeterminator $scopeDeterminator The request mode determinator. - * * @param RequestStack $requestStack The current request stack. */ public function __construct(RequestScopeDeterminator $scopeDeterminator, RequestStack $requestStack) @@ -74,7 +80,10 @@ public function __construct(RequestScopeDeterminator $scopeDeterminator, Request */ public function handleEvent(ActionEvent $event): void { - if (!$this->scopeDeterminator->currentScopeIsFrontend()) { + if ( + null === ($scopeDeterminator = $this->scopeDeterminator) + || !$scopeDeterminator->currentScopeIsFrontend() + ) { return; } @@ -106,17 +115,24 @@ public function handleEvent(ActionEvent $event): void */ public function process(EnvironmentInterface $environment): void { - $definition = $environment->getDataDefinition(); + $definition = $environment->getDataDefinition(); + assert($definition instanceof ContainerInterface); + $basicDefinition = $definition->getBasicDefinition(); if (!$basicDefinition->isDeletable()) { throw new NotDeletableException('DataContainer ' . $definition->getName() . ' is not deletable'); } - $modelId = ModelId::fromSerialized($environment->getInputProvider()->getParameter('id')); + $inputProvider = $environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + + $modelId = ModelId::fromSerialized($inputProvider->getParameter('id')); $dataProvider = $environment->getDataProvider(); - $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); + assert($dataProvider instanceof DataProviderInterface); + + $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); if (null === $model) { throw new PageNotFoundException('Model not found: ' . $modelId->getSerialized()); @@ -124,14 +140,20 @@ public function process(EnvironmentInterface $environment): void // Trigger event before the model will be deleted. $event = new PreDeleteModelEvent($environment, $model); - $environment->getEventDispatcher()->dispatch($event, $event::NAME); + $eventDispatcher = $environment->getEventDispatcher(); + assert($eventDispatcher instanceof EventDispatcherInterface); + + $eventDispatcher->dispatch($event, $event::NAME); $dataProvider->delete($model); // Trigger event after the model is deleted. $event = new PostDeleteModelEvent($environment, $model); - $environment->getEventDispatcher()->dispatch($event, $event::NAME); + $eventDispatcher->dispatch($event, $event::NAME); + + $currentRequest = $this->requestStack->getCurrentRequest(); + assert($currentRequest instanceof Request); - throw new RedirectResponseException($this->requestStack->getCurrentRequest()->headers->get('referer')); + throw new RedirectResponseException($currentRequest->headers->get('referer') ?? ''); } } diff --git a/src/View/ActionHandler/EditHandler.php b/src/View/ActionHandler/EditHandler.php index 0527acc..b6bd869 100644 --- a/src/View/ActionHandler/EditHandler.php +++ b/src/View/ActionHandler/EditHandler.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,7 +15,7 @@ * @author Richard Henkenjohann * @author Sven Baumann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -26,16 +26,21 @@ use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminator; use ContaoCommunityAlliance\DcGeneral\Contao\RequestScopeDeterminatorAwareTrait; use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View\EditMask; +use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; use ContaoCommunityAlliance\DcGeneral\Data\ModelId; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\BasicDefinitionInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Event\ActionEvent; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralInvalidArgumentException; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; use ContaoCommunityAlliance\DcGeneral\Exception\NotEditableException; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; /** - * This class handles the edit actions in the frontend. + * This class handles the actions of edit in the frontend. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class EditHandler { @@ -64,14 +69,16 @@ public function __construct(RequestScopeDeterminator $scopeDeterminator) */ public function handleEvent(ActionEvent $event): void { - if (!$this->scopeDeterminator->currentScopeIsFrontend()) { + if ( + null === ($scopeDeterminator = $this->scopeDeterminator) + || !$scopeDeterminator->currentScopeIsFrontend() + ) { return; } - $action = $event->getAction(); // Only handle if we do not have a manual sorting or we know where to insert. // Manual sorting is handled by clipboard. - if ('edit' !== $action->getName()) { + if ('edit' !== $event->getAction()->getName()) { return; } @@ -80,8 +87,7 @@ public function handleEvent(ActionEvent $event): void return; } - $response = $this->process($event->getEnvironment()); - if (false !== $response) { + if ('' !== ($response = $this->process($event->getEnvironment()))) { $event->setResponse($response); } } @@ -91,14 +97,16 @@ public function handleEvent(ActionEvent $event): void * * @param EnvironmentInterface $environment The environment. * - * @return string|bool + * @return string * * @throws PageNotFoundException If item to edit was not found. * @throws NotEditableException When the definition is not editable. */ public function process(EnvironmentInterface $environment) { - $definition = $environment->getDataDefinition(); + $definition = $environment->getDataDefinition(); + assert($definition instanceof ContainerInterface); + $basicDefinition = $definition->getBasicDefinition(); if (!$basicDefinition->isEditable()) { @@ -107,13 +115,18 @@ public function process(EnvironmentInterface $environment) // We only support flat tables, sorry. if (BasicDefinitionInterface::MODE_HIERARCHICAL === $basicDefinition->getMode()) { - return false; + return ''; } - $modelId = ModelId::fromSerialized($environment->getInputProvider()->getParameter('id')); + $inputProvider = $environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + + $modelId = ModelId::fromSerialized($inputProvider->getParameter('id')); $dataProvider = $environment->getDataProvider(); - $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); + assert($dataProvider instanceof DataProviderInterface); + + $model = $dataProvider->fetch($dataProvider->getEmptyConfig()->setId($modelId->getId())); if (null === $model) { throw new PageNotFoundException('Model not found: ' . $modelId->getSerialized()); diff --git a/src/View/DefaultWidgetBuilder.php b/src/View/DefaultWidgetBuilder.php index ec9594b..04ec1d6 100644 --- a/src/View/DefaultWidgetBuilder.php +++ b/src/View/DefaultWidgetBuilder.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,13 +14,15 @@ * @author Christian Schiffler * @author Richard Henkenjohann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View; +use Contao\System; +use Contao\Widget; use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents; use ContaoCommunityAlliance\Contao\Bindings\Events\Widget\GetAttributesFromDcaEvent; use ContaoCommunityAlliance\DcGeneral\Contao\Compatibility\DcCompat; @@ -30,30 +32,44 @@ use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\ManipulateWidgetEvent; use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\BuildWidgetEvent; use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\Properties\PropertyInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Widget Builder to build Contao frontend widgets. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DefaultWidgetBuilder { - /** * The request scope determinator. * * @var RequestScopeDeterminator */ - private $scopeDeterminator; + private RequestScopeDeterminator $scopeDeterminator; + + /** + * The translator. + * + * @var TranslatorInterface + */ + private TranslatorInterface $translator; /** * DefaultWidgetBuilder constructor. * * @param RequestScopeDeterminator $scopeDeterminator The request scope determinator. + * @param TranslatorInterface $translator The translator. */ - public function __construct(RequestScopeDeterminator $scopeDeterminator) + public function __construct(RequestScopeDeterminator $scopeDeterminator, TranslatorInterface $translator) { $this->scopeDeterminator = $scopeDeterminator; + $this->translator = $translator; } /** @@ -79,12 +95,10 @@ public function handleEvent(BuildWidgetEvent $event) * Build a widget for a given property. * * @param EnvironmentInterface $environment The environment. - * * @param PropertyInterface $property The property. - * * @param ModelInterface $model The current model. * - * @return \Widget + * @return Widget|null * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -94,11 +108,17 @@ public function buildWidget( PropertyInterface $property, ModelInterface $model ) { - $dispatcher = $environment->getEventDispatcher(); + $dispatcher = $environment->getEventDispatcher(); + assert($dispatcher instanceof EventDispatcherInterface); + $propertyName = $property->getName(); $propExtra = $property->getExtra(); - $defName = $environment->getDataDefinition()->getName(); - $strClass = $this->getWidgetClass($property); + + $dataDefinition = $environment->getDataDefinition(); + assert($dataDefinition instanceof ContainerInterface); + + $defName = $dataDefinition->getName(); + $strClass = $this->getWidgetClass($property); if (null === $strClass) { return null; @@ -109,14 +129,16 @@ public function buildWidget( ->setProperty($propertyName) ->setValue($model->getProperty($propertyName)); - $dispatcher->dispatch($event,$event::NAME); + $dispatcher->dispatch($event, $event::NAME); $varValue = $event->getValue(); - $propExtra['required'] = ($varValue == '') && !empty($propExtra['mandatory']); + $propExtra['required'] = ($varValue === '') && !empty($propExtra['mandatory']); $propExtra['tableless'] = true; - if (isset($propExtra['readonly']) + if ( + isset($propExtra['readonly']) && $propExtra['readonly'] - && \in_array($property->getWidgetType(), ['checkbox', 'select'], true)) { + && \in_array($property->getWidgetType(), ['checkbox', 'select'], true) + ) { $propExtra['disabled'] = true; } @@ -148,15 +170,21 @@ public function buildWidget( $propExtra['class'] = $this->addCssClass($propExtra['class'], $propExtra['tl_class']); } - $arrConfig = array( + // If no description present, pass as string instead of array. + $label = $this->translator->trans($property->getLabel(), [], $defName); + if ('' !== $description = $property->getDescription()) { + $label = [ + $label, + $this->translator->trans($description, [], $defName), + ]; + } + + $arrConfig = [ 'inputType' => $property->getWidgetType(), - 'label' => array( - $property->getLabel(), - $property->getDescription() - ), + 'label' => $label, 'options' => $this->getOptionsForWidget($environment, $property, $model), 'eval' => $propExtra, - ); + ]; if (isset($propExtra['reference'])) { $arrConfig['reference'] = $propExtra['reference']; @@ -181,6 +209,7 @@ public function buildWidget( } $widget = new $strClass($preparedConfig, new DcCompat($environment, $model, $propertyName)); + assert($widget instanceof Widget); $widget->currentRecord = $model->getId(); @@ -195,7 +224,7 @@ public function buildWidget( * * @param PropertyInterface $property The property to get the widget class name for. * - * @return string + * @return class-string|null * * @SuppressWarnings(PHPMD.Superglobals) * @SuppressWarnings(PHPMD.CamelCaseVariableName) @@ -206,8 +235,8 @@ private function getWidgetClass(PropertyInterface $property) return null; } - $className = $GLOBALS['TL_FFL'][$property->getWidgetType()]; - if (!class_exists($className)) { + $className = (string) $GLOBALS['TL_FFL'][$property->getWidgetType()]; + if (!\class_exists($className)) { return null; } @@ -218,12 +247,10 @@ private function getWidgetClass(PropertyInterface $property) * Get special labels. * * @param EnvironmentInterface $environment The environment. - * * @param PropertyInterface $propInfo The property for which the options shall be retrieved. - * * @param ModelInterface $model The model. * - * @return string[] + * @return string[]|null */ private function getOptionsForWidget( EnvironmentInterface $environment, @@ -231,8 +258,10 @@ private function getOptionsForWidget( ModelInterface $model ) { $dispatcher = $environment->getEventDispatcher(); - $options = $propInfo->getOptions(); - $event = new GetPropertyOptionsEvent($environment, $model); + assert($dispatcher instanceof EventDispatcherInterface); + + $options = $propInfo->getOptions(); + $event = new GetPropertyOptionsEvent($environment, $model); $event->setPropertyName($propInfo->getName()); $event->setOptions($options); $dispatcher->dispatch($event, GetPropertyOptionsEvent::NAME); @@ -255,7 +284,7 @@ private function getOptionsForWidget( */ private function addCssClass($classString, $class) { - if (null !== $classString) { + if ('' !== $classString) { $classString .= ' ' . $class; } else { $classString = $class; diff --git a/src/View/EditMask.php b/src/View/EditMask.php index 50ff8b8..6a22693 100644 --- a/src/View/EditMask.php +++ b/src/View/EditMask.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2023 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,7 +15,7 @@ * @author Richard Henkenjohann * @author Sven Baumann * @author Ingolf Steinhardt - * @copyright 2015-2023 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -26,7 +26,9 @@ use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\HandleSubmitEvent; use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetEditMaskSubHeadlineEvent; use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\GetEditModeButtonsEvent; +use ContaoCommunityAlliance\DcGeneral\Controller\ControllerInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\DataProviderInformationInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Definition\PropertiesDefinitionInterface; use ContaoCommunityAlliance\DcGeneral\DataDefinition\Palette\PaletteInterface; use ContaoCommunityAlliance\DcGeneral\Data\DataProviderInterface; @@ -40,6 +42,7 @@ use ContaoCommunityAlliance\DcGeneral\Event\PrePersistModelEvent; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralInvalidArgumentException; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; +use ContaoCommunityAlliance\DcGeneral\InputProviderInterface; use ContaoCommunityAlliance\Translator\TranslatorInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -47,6 +50,9 @@ * This class manages the displaying of the edit/create mask containing the widgets. * * It also handles the persisting of the model. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ class EditMask { @@ -55,49 +61,49 @@ class EditMask * * @var EnvironmentInterface */ - private $environment; + private EnvironmentInterface $environment; /** * The event dispatcher. * * @var EventDispatcherInterface */ - private $dispatcher; + private EventDispatcherInterface $dispatcher; /** * Retrieve the translation manager to use. * * @var TranslatorInterface */ - private $translator; + private TranslatorInterface $translator; /** * The data definition from the environment. * * @var ContainerInterface */ - private $definition; + private ContainerInterface $definition; /** * The data provider of the model being edited. * - * @var DataProviderInterface + * @var DataProviderInformationInterface */ - private $modelProvider; + private DataProviderInformationInterface $modelProvider; /** * The model to be manipulated. * * @var ModelInterface */ - private $model; + private ModelInterface $model; /** * The original model from the database. * * @var ModelInterface */ - private $originalModel; + private ModelInterface $originalModel; /** * The method to be executed before the model is persisted. @@ -118,29 +124,31 @@ class EditMask * * @var array */ - private $errors = []; + private array $errors = []; /** * Create the edit mask. * * @param EnvironmentInterface $environment The view in use. - * * @param ModelInterface $model The model with the current data. - * * @param ModelInterface $originalModel The data from the original data. - * - * @param callable $preFunction The function to call before saving an item. - * - * @param callable $postFunction The function to call after saving an item. + * @param callable|null $preFunction The function to call before saving an item. + * @param callable|null $postFunction The function to call after saving an item. */ public function __construct($environment, $model, $originalModel, $preFunction, $postFunction) { - $providerName = $model->getProviderName(); - $this->environment = $environment; - $this->translator = $environment->getTranslator(); - $this->dispatcher = $environment->getEventDispatcher(); - $this->definition = $environment->getDataDefinition(); - $this->modelProvider = $this->definition->getDataProviderDefinition()->getInformation($providerName); + $providerName = $model->getProviderName(); + $this->environment = $environment; + $translator = $environment->getTranslator(); + assert($translator instanceof TranslatorInterface); + $this->translator = $translator; + $dispatcher = $environment->getEventDispatcher(); + assert($dispatcher instanceof EventDispatcherInterface); + $this->dispatcher = $dispatcher; + $dataDefinition = $environment->getDataDefinition(); + assert($dataDefinition instanceof ContainerInterface); + $this->definition = $dataDefinition; + $this->modelProvider = $dataDefinition->getDataProviderDefinition()->getInformation($providerName); $this->model = $model; $this->originalModel = $originalModel; $this->preFunction = $preFunction; @@ -153,12 +161,13 @@ public function __construct($environment, $model, $originalModel, $preFunction, * @return string * * @throws DcGeneralRuntimeException If the data container is not editable, closed. - * * @throws DcGeneralInvalidArgumentException If an unknown property is encountered in the palette. */ public function execute() { - $inputProvider = $this->environment->getInputProvider(); + $inputProvider = $this->environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + $palettesDefinition = $this->definition->getPalettesDefinition(); $isSubmitted = ($inputProvider->getValue('FORM_SUBMIT') === $this->definition->getName()); $isAutoSubmit = ($inputProvider->getValue('SUBMIT_TYPE') === 'auto'); @@ -172,12 +181,14 @@ public function execute() $palette = $palettesDefinition->findPalette($this->model); $propertyValues = $this->processInput($widgetManager); - if ($isSubmitted && $propertyValues) { + if ($isSubmitted && null !== $propertyValues) { // Pass 2: Determine the real palette we want to work on if we have some data submitted. $palette = $palettesDefinition->findPalette($this->model, $propertyValues); // Update the model - the model might add some more errors to the propertyValueBag via exceptions. - $this->environment->getController()->updateModelFromPropertyBag($this->model, $propertyValues); + $controller = $this->environment->getController(); + assert($controller instanceof ControllerInterface); + $controller->updateModelFromPropertyBag($this->model, $propertyValues); } $fieldSets = $this->buildFieldSet($widgetManager, $palette, $propertyValues); @@ -210,7 +221,7 @@ public function execute() * * @return void */ - private function enforceModelRelationship() + private function enforceModelRelationship(): void { $event = new EnforceModelRelationshipEvent($this->environment, $this->model); @@ -224,13 +235,14 @@ private function enforceModelRelationship() * * @return null|PropertyValueBag */ - private function processInput($widgetManager) + private function processInput($widgetManager): ?PropertyValueBag { $input = $this->environment->getInputProvider(); + assert($input instanceof InputProviderInterface); if ($input->getValue('FORM_SUBMIT') === $this->definition->getName()) { $propertyValues = new PropertyValueBag(); - $propertyNames = array_intersect( + $propertyNames = \array_intersect( $this->definition->getPropertiesDefinition()->getPropertyNames(), (array) $input->getValue('FORM_INPUTS') ); @@ -254,7 +266,7 @@ private function processInput($widgetManager) * * @return void */ - private function handlePrePersist() + private function handlePrePersist(): void { if (null !== $this->preFunction) { \call_user_func($this->preFunction, $this->environment, $this->model, $this->originalModel); @@ -271,7 +283,7 @@ private function handlePrePersist() * * @return void */ - private function handlePostPersist() + private function handlePostPersist(): void { if (null !== $this->postFunction) { \call_user_func($this->postFunction, $this->environment, $this->model, $this->originalModel); @@ -294,16 +306,19 @@ private function handlePostPersist() * * @return string */ - private function translateLabel($transString, $parameters = []) + private function translateLabel(string $transString, array $parameters = []): string { $translator = $this->translator; - if ($transString !== ($label = - $translator->translate($transString, $this->definition->getName(), $parameters))) { + if ( + $transString !== ($label = + $translator->translate($transString, $this->definition->getName(), $parameters)) + ) { return $label; } - if ($transString !== ($label = - $translator->translate('MSC.'.$transString, $this->definition->getName(), $parameters)) + if ( + $transString !== ($label = + $translator->translate('MSC.' . $transString, $this->definition->getName(), $parameters)) ) { return $label; } @@ -317,12 +332,12 @@ private function translateLabel($transString, $parameters = []) * * @return string[] */ - private function getEditButtons() + private function getEditButtons(): array { $button = ''; $buttons = []; - $buttons['save'] = sprintf( + $buttons['save'] = \sprintf( $button, 'save', 'save', @@ -332,7 +347,7 @@ private function getEditButtons() ); if ($this->definition->getBasicDefinition()->isCreatable()) { - $buttons['saveNcreate'] = sprintf( + $buttons['saveNcreate'] = \sprintf( $button, 'saveNcreate', 'saveNcreate', @@ -353,11 +368,9 @@ private function getEditButtons() /** * Build the field sets. * - * @param WidgetManager $widgetManager The widget manager in use. - * - * @param PaletteInterface $palette The palette to use. - * - * @param PropertyValueBag $propertyValues The property values. + * @param WidgetManager $widgetManager The widget manager in use. + * @param PaletteInterface $palette The palette to use. + * @param PropertyValueBag|null $propertyValues The property values. * * @return array * @@ -366,10 +379,17 @@ private function getEditButtons() * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function buildFieldSet($widgetManager, $palette, $propertyValues) - { + private function buildFieldSet( + WidgetManager $widgetManager, + PaletteInterface $palette, + ?PropertyValueBag $propertyValues + ): array { $propertyDefinitions = $this->definition->getPropertiesDefinition(); - $isAutoSubmit = ($this->environment->getInputProvider()->getValue('SUBMIT_TYPE') === 'auto'); + + $inputProvider = $this->environment->getInputProvider(); + assert($inputProvider instanceof InputProviderInterface); + + $isAutoSubmit = ($inputProvider->getValue('SUBMIT_TYPE') === 'auto'); $fieldSets = []; $errors = []; @@ -392,7 +412,8 @@ private function buildFieldSet($widgetManager, $palette, $propertyValues) $this->ensurePropertyExists($propertyName, $propertyDefinitions); // If this property is invalid, fetch the error. - if ((!$isAutoSubmit) + if ( + (!$isAutoSubmit) && $propertyValues && $propertyValues->hasPropertyValue($propertyName) && $propertyValues->isPropertyValueInvalid($propertyName) @@ -401,12 +422,13 @@ private function buildFieldSet($widgetManager, $palette, $propertyValues) } $fields[] = $widgetManager->renderWidget($propertyName, $isAutoSubmit, $propertyValues); - $hidden[] = sprintf('', $propertyName); + $hidden[] = \sprintf('', $propertyName); } + $fieldSet = []; $fieldSet['label'] = $legendName; $fieldSet['class'] = $first ? 'tl_tbox' : 'tl_box'; - $fieldSet['palette'] = implode('', $hidden) . implode('', $fields); + $fieldSet['palette'] = \implode('', $hidden) . \implode('', $fields); $fieldSet['legend'] = $legend->getName(); $fieldSets[] = $fieldSet; @@ -424,18 +446,17 @@ private function buildFieldSet($widgetManager, $palette, $propertyValues) * Ensure a property is defined in the data definition and raise an exception if it is unknown. * * @param string $property The property name to check. - * * @param PropertiesDefinitionInterface $propertyDefinitions The property definitions. * * @return void * * @throws DcGeneralInvalidArgumentException When the property is not registered in the definition. */ - private function ensurePropertyExists($property, $propertyDefinitions) + private function ensurePropertyExists(string $property, PropertiesDefinitionInterface $propertyDefinitions): void { if (!$propertyDefinitions->hasProperty($property)) { throw new DcGeneralInvalidArgumentException( - sprintf( + \sprintf( 'Property %s is mentioned in palette but not defined in propertyDefinition.', $property ) @@ -450,24 +471,31 @@ private function ensurePropertyExists($property, $propertyDefinitions) * * @return void */ - private function storeVersion(ModelInterface $model) + private function storeVersion(ModelInterface $model): void { if (!$this->modelProvider->isVersioningEnabled()) { return; } - $environment = $this->environment; - $modelId = $model->getId(); - $dataProvider = $environment->getDataProvider($this->model->getProviderName()); + $environment = $this->environment; + $modelId = $model->getId(); + $dataProvider = $environment->getDataProvider($this->model->getProviderName()); + assert($dataProvider instanceof DataProviderInterface); + $currentVersion = $dataProvider->getActiveVersion($modelId); + $version = $dataProvider->getVersion($modelId, $currentVersion); + assert($version instanceof ModelInterface); // Compare version and current record. - if (!$currentVersion - || !$dataProvider->sameModels($model, $dataProvider->getVersion($modelId, $currentVersion)) + if ( + !$currentVersion + || !$dataProvider->sameModels($model, $version) ) { $user = \FrontendUser::getInstance(); $username = '(frontend anonymous)'; + + /** @psalm-suppress DeprecatedMethod */ if ($user->authenticate()) { - $username = $user->username; + $username = $user->username ?? ''; } $dataProvider->saveVersion($model, $username); @@ -483,10 +511,12 @@ private function storeVersion(ModelInterface $model) * * @return void */ - private function handleSubmit($buttons) + private function handleSubmit(array $buttons): void { $inputProvider = $this->environment->getInputProvider(); - foreach (array_keys($buttons) as $button) { + assert($inputProvider instanceof InputProviderInterface); + + foreach (\array_keys($buttons) as $button) { if ($inputProvider->hasValue($button)) { $event = new HandleSubmitEvent($this->environment, $this->model, $button); @@ -500,25 +530,25 @@ private function handleSubmit($buttons) /** * Determine the headline to use. * - * @return string. + * @return string|null * * @deprecated This is deprecated since 2.3 and will be removed in 3.0. */ - private function getHeadline(): string + private function getHeadline(): ?string { // @codingStandardsIgnoreStart @\trigger_error(__CLASS__ . '::' . __METHOD__ . ' is deprecated - use getSubHeadline()!', E_USER_DEPRECATED); // @codingStandardsIgnoreEnd - $this->getSubHeadline(); + return $this->getSubHeadline(); } /** * Determine the headline to use. * - * @return string. + * @return string|null */ - private function getSubHeadline(): string + private function getSubHeadline(): ?string { $event = new GetEditMaskSubHeadlineEvent($this->environment, $this->model); @@ -540,9 +570,10 @@ private function doPersist() $this->handlePrePersist(); - // TO DO: manual sorting property handling is not enabled here as it originates from the backend defininiton. + // TO DO: manual sorting property handling is not enabled here as it originates from the backend definition. // Save the model. $dataProvider = $this->environment->getDataProvider($this->model->getProviderName()); + assert($dataProvider instanceof DataProviderInterface); $dataProvider->save($this->model); diff --git a/src/View/ViewTemplate.php b/src/View/ViewTemplate.php index ca30a94..2453630 100644 --- a/src/View/ViewTemplate.php +++ b/src/View/ViewTemplate.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,27 +12,31 @@ * * @package contao-community-alliance/dc-general-contao-frontend * @author Christian Schiffler - * @copyright 2015 Contao Community Alliance. + * @author Ingolf Steinhardt + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\View; +use Contao\BackendTemplate; use ContaoCommunityAlliance\DcGeneral\View\ViewTemplateInterface; use ContaoCommunityAlliance\Translator\TranslatorInterface; /** * This class is used for the contao frontend view as template. + * + * @psalm-suppress PropertyNotSetInConstructor */ -class ViewTemplate extends \BackendTemplate implements ViewTemplateInterface, TranslatorInterface +class ViewTemplate extends BackendTemplate implements ViewTemplateInterface, TranslatorInterface { /** * The translator. * * @var TranslatorInterface */ - protected $translator; + protected TranslatorInterface $translator; /** * Get the translator. @@ -61,9 +65,9 @@ public function setTranslator(TranslatorInterface $translator) /** * {@inheritDoc} */ - public function setData($data) + public function setData($arrData) { - parent::setData($data); + parent::setData($arrData); return $this; } @@ -91,11 +95,7 @@ public function get($name) */ public function translate($string, $domain = null, array $parameters = [], $locale = null) { - if ($this->translator) { - return $this->translator->translate($string, $domain, $parameters, $locale); - } - - return $string; + return $this->translator->translate($string, $domain, $parameters, $locale); } /** @@ -103,10 +103,33 @@ public function translate($string, $domain = null, array $parameters = [], $loca */ public function translatePluralized($string, $number, $domain = null, array $parameters = [], $locale = null) { - if ($this->translator) { - return $this->translator->translatePluralized($string, $number, $domain, $parameters, $locale); - } + return $this->translator->translatePluralized($string, $number, $domain, $parameters, $locale); + } + + // @codingStandardsIgnoreStart + /** + * {@inheritDoc} + */ + public function getData() + { + return parent::getData(); + } - return $string; + /** + * {@inheritDoc} + */ + public function parse() + { + return parent::parse(); + } + + /** + * {@inheritDoc} + */ + public function output() + { + /** @psalm-suppress DeprecatedMethod */ + parent::output(); } + // @codingStandardsIgnoreEnd } diff --git a/src/View/WidgetManager.php b/src/View/WidgetManager.php index b3da44d..d42002e 100644 --- a/src/View/WidgetManager.php +++ b/src/View/WidgetManager.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2015-2022 Contao Community Alliance. + * (c) 2015-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,7 +14,7 @@ * @author Christian Schiffler * @author Richard Henkenjohann * @author Ingolf Steinhardt - * @copyright 2015-2022 Contao Community Alliance. + * @copyright 2015-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * @filesource */ @@ -23,19 +23,25 @@ use Contao\FormTextArea; use Contao\Input; +use Contao\Widget; use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\BuildWidgetEvent; use ContaoCommunityAlliance\DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent; use ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Event\DcGeneralFrontendEvents; +use ContaoCommunityAlliance\DcGeneral\Controller\ControllerInterface; use ContaoCommunityAlliance\DcGeneral\Data\ModelInterface; use ContaoCommunityAlliance\DcGeneral\Data\PropertyValueBag; +use ContaoCommunityAlliance\DcGeneral\DataDefinition\ContainerInterface; use ContaoCommunityAlliance\DcGeneral\EnvironmentInterface; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralInvalidArgumentException; use ContaoCommunityAlliance\DcGeneral\Exception\DcGeneralRuntimeException; +use Psr\EventDispatcher\EventDispatcherInterface; /** * Class WidgetManager. * * This class is responsible for creating widgets and processing data through them. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class WidgetManager { @@ -44,20 +50,19 @@ class WidgetManager * * @var EnvironmentInterface */ - protected $environment; + protected EnvironmentInterface $environment; /** * The model for which widgets shall be generated. * * @var ModelInterface */ - protected $model; + protected ModelInterface $model; /** * Create a new instance. * * @param EnvironmentInterface $environment The environment in use. - * * @param ModelInterface $model The model for which widgets shall be generated. */ public function __construct(EnvironmentInterface $environment, ModelInterface $model) @@ -69,20 +74,23 @@ public function __construct(EnvironmentInterface $environment, ModelInterface $m /** * Retrieve the instance of a widget for the given property. * - * @param string $property Name of the property for which the widget shall be retrieved. - * - * @param PropertyValueBag $valueBag The input values to use (optional). + * @param string $property Name of the property for which the widget shall be retrieved. + * @param PropertyValueBag|null $valueBag The input values to use (optional). * - * @return \Widget + * @return \Widget|null * - * @throws DcGeneralRuntimeException When No widget could be build. + * @throws DcGeneralRuntimeException When No widget could be built. * @throws DcGeneralInvalidArgumentException When property is not defined in the property definitions. */ public function getWidget($property, PropertyValueBag $valueBag = null) { - $environment = $this->getEnvironment(); - $dispatcher = $environment->getEventDispatcher(); - $propertyDefinitions = $environment->getDataDefinition()->getPropertiesDefinition(); + $environment = $this->getEnvironment(); + $dispatcher = $environment->getEventDispatcher(); + assert($dispatcher instanceof EventDispatcherInterface); + $dataDefinition = $environment->getDataDefinition(); + assert($dataDefinition instanceof ContainerInterface); + + $propertyDefinitions = $dataDefinition->getPropertiesDefinition(); if (!$propertyDefinitions->hasProperty($property)) { throw new DcGeneralInvalidArgumentException( @@ -95,7 +103,11 @@ public function getWidget($property, PropertyValueBag $valueBag = null) if ($valueBag) { $values = new PropertyValueBag($valueBag->getArrayCopy()); - $this->environment->getController()->updateModelFromPropertyBag($model, $values); + + $controller = $this->environment->getController(); + assert($controller instanceof ControllerInterface); + + $controller->updateModelFromPropertyBag($model, $values); } $propertyDefinition = $propertyDefinitions->getProperty($property); @@ -104,7 +116,7 @@ public function getWidget($property, PropertyValueBag $valueBag = null) $dispatcher->dispatch($event, DcGeneralFrontendEvents::BUILD_WIDGET); if (!$event->getWidget()) { throw new DcGeneralRuntimeException( - sprintf('Widget was not build for property %s::%s.', $this->model->getProviderName(), $property) + \sprintf('Widget was not build for property %s::%s.', $this->model->getProviderName(), $property) ); } @@ -115,14 +127,12 @@ public function getWidget($property, PropertyValueBag $valueBag = null) * Render the widget for the named property. * * @param string $property The name of the property for which the widget shall be rendered. - * * @param bool $ignoreErrors Flag if the error property of the widget shall get cleared prior rendering. - * * @param PropertyValueBag $valueBag The input values to use (optional). * * @return string * - * @throws DcGeneralRuntimeException For unknown properties. + * @throws DcGeneralRuntimeException or unknown properties. */ public function renderWidget($property, $ignoreErrors = false, PropertyValueBag $valueBag = null) { @@ -141,7 +151,8 @@ public function renderWidget($property, $ignoreErrors = false, PropertyValueBag $reflection->setAccessible(true); $reflection->setValue($widget, str_replace('error', '', $reflection->getValue($widget))); } else { - if ($valueBag && $valueBag->hasPropertyValue($property) + if ( + $valueBag && $valueBag->hasPropertyValue($property) && $valueBag->isPropertyValueInvalid($property) ) { foreach ($valueBag->getPropertyValueErrors($property) as $error) { @@ -160,12 +171,12 @@ public function renderWidget($property, $ignoreErrors = false, PropertyValueBag * * @return void */ - public function processInput(PropertyValueBag $valueBag) + public function processInput(PropertyValueBag $valueBag): void { $post = $this->hijackPost($valueBag); // Now get and validate the widgets. - foreach (array_keys($valueBag->getArrayCopy()) as $property) { + foreach (\array_keys($valueBag->getArrayCopy()) as $property) { $this->processProperty($valueBag, $property); } @@ -176,7 +187,6 @@ public function processInput(PropertyValueBag $valueBag) * Process a single property. * * @param PropertyValueBag $valueBag The value bag to update. - * * @param string $property The property to process. * * @return void @@ -184,12 +194,13 @@ public function processInput(PropertyValueBag $valueBag) * @throws DcGeneralRuntimeException When No widget could be build. * @throws DcGeneralInvalidArgumentException When property is not defined in the property definitions. */ - private function processProperty(PropertyValueBag $valueBag, $property) + private function processProperty(PropertyValueBag $valueBag, string $property): void { // NOTE: the passed input values are RAW DATA from the input provider - aka widget known values and not // native data as in the model. - // Therefore we do not need to decode them but MUST encode them. + // Therefore, we do not need to decode them but MUST encode them. $widget = $this->getWidget($property, $valueBag); + assert($widget instanceof Widget); $widget->validate(); if ($widget->hasErrors()) { @@ -209,6 +220,7 @@ private function processProperty(PropertyValueBag $valueBag, $property) try { // See https://github.com/contao/contao/blob/7e6bacd4e/core-bundle/src/Resources/contao/forms/FormTextArea.php#L147 if ($widget instanceof FormTextArea) { + /** @psalm-suppress UndefinedMagicPropertyFetch */ $valueBag->setPropertyValue($property, $this->encodeValue($property, $widget->rawValue, $valueBag)); return; } @@ -233,7 +245,7 @@ private function processProperty(PropertyValueBag $valueBag, $property) * @SuppressWarnings(PHPMD.Superglobals) * @SuppressWarnings(PHPMD.CamelCaseVariableName) */ - private function hijackPost(PropertyValueBag $valueBag) + private function hijackPost(PropertyValueBag $valueBag): array { $post = $_POST; $_POST = []; @@ -257,7 +269,7 @@ private function hijackPost(PropertyValueBag $valueBag) * @SuppressWarnings(PHPMD.Superglobals) * @SuppressWarnings(PHPMD.CamelCaseVariableName) */ - private function restorePost($post) + private function restorePost(array $post): void { $_POST = $post; Input::resetCache(); @@ -268,7 +280,7 @@ private function restorePost($post) * * @return EnvironmentInterface */ - private function getEnvironment() + private function getEnvironment(): EnvironmentInterface { return $this->environment; } @@ -282,7 +294,7 @@ private function getEnvironment() * * @return mixed */ - private function encodeValue($property, $value, PropertyValueBag $valueBag) + private function encodeValue(string $property, mixed $value, PropertyValueBag $valueBag): mixed { $environment = $this->getEnvironment(); @@ -291,7 +303,10 @@ private function encodeValue($property, $value, PropertyValueBag $valueBag) ->setProperty($property) ->setValue($value); - $environment->getEventDispatcher()->dispatch($event, EncodePropertyValueFromWidgetEvent::NAME); + $dispatcher = $environment->getEventDispatcher(); + assert($dispatcher instanceof EventDispatcherInterface); + + $dispatcher->dispatch($event, EncodePropertyValueFromWidgetEvent::NAME); return $event->getValue(); } diff --git a/src/Widgets/UploadOnSteroids.php b/src/Widgets/UploadOnSteroids.php index 0e234f6..780b83a 100644 --- a/src/Widgets/UploadOnSteroids.php +++ b/src/Widgets/UploadOnSteroids.php @@ -3,7 +3,7 @@ /** * This file is part of contao-community-alliance/dc-general-contao-frontend. * - * (c) 2016-2023 Contao Community Alliance. + * (c) 2016-2024 Contao Community Alliance. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,7 @@ * @package contao-community-alliance/dc-general-contao-frontend * @author Sven Baumann * @author Ingolf Steinhardt - * @copyright 2016-2023 Contao Community Alliance. + * @copyright 2016-2024 Contao Community Alliance. * @license https://github.com/contao-community-alliance/dc-general-contao-frontend/blob/master/LICENSE LGPL-3.0 * * @filesource @@ -22,6 +22,7 @@ namespace ContaoCommunityAlliance\DcGeneral\ContaoFrontend\Widgets; use Contao\Controller; +use Contao\CoreBundle\Image\ImageFactory; use Contao\CoreBundle\Slug\Slug as SlugGenerator; use Contao\CoreBundle\Framework\Adapter; use Contao\Dbafs; @@ -31,8 +32,12 @@ use Contao\Input; use Contao\StringUtil; use Contao\System; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -60,6 +65,11 @@ * @property string sortBy * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @psalm-suppress PropertyNotSetInConstructor + * @psalm-suppress UndefinedThisPropertyFetch + * @psalm-suppress UndefinedThisPropertyAssignment */ class UploadOnSteroids extends FormFileUpload { @@ -85,92 +95,93 @@ class UploadOnSteroids extends FormFileUpload protected $strPrefix = 'widget widget-upload widget-upload-on-steroids'; /** - * Image sizes. + * Image sizes as serialisized string. * - * @var array + * @var string */ - protected $imageSize; + protected string $imageSize; /** * The translator. * - * @var TranslatorInterface + * @var TranslatorInterface|null */ - protected TranslatorInterface $translator; + protected ?TranslatorInterface $translator = null; /** * The input provider. * - * @var Adapter|Input + * @var Adapter */ - protected $inputProvider; + protected ?Adapter $inputProvider = null; /** * The file model. * - * @var Adapter|FilesModel + * @var Adapter|null */ - private $filesModel; + private ?Adapter $filesModel = null; /** * The filesystem. * - * @var Filesystem + * @var Filesystem|null */ - private $filesystem; + private ?Filesystem $filesystem = null; /** * The slug generator. * - * @var SlugGenerator + * @var SlugGenerator|null */ - private $slugGenerator; + private ?SlugGenerator $slugGenerator = null; /** * {@inheritDoc} */ - public function __set($key, $value) + public function __set($strKey, $varValue) { - if (\in_array( - $key, - [ - 'deselect', - 'delete', - 'extendFolder', - 'normalizeExtendFolder', - 'normalizeFilename', - 'prefixFilename', - 'postfixFilename', - 'files', - 'showThumbnail', - 'multiple', - 'imageSize', - 'sortBy' - ] - )) { - $this->arrConfiguration[$key] = $value; + if ( + \in_array( + $strKey, + [ + 'deselect', + 'delete', + 'extendFolder', + 'normalizeExtendFolder', + 'normalizeFilename', + 'prefixFilename', + 'postfixFilename', + 'files', + 'showThumbnail', + 'multiple', + 'imageSize', + 'sortBy' + ] + ) + ) { + $this->arrConfiguration[$strKey] = $varValue; return; } - parent::__set($key, $value); + parent::__set($strKey, $varValue); } /** * {@inheritDoc} */ - public function parse($attributes = null) + public function parse($arrAttributes = null) { $this->addIsDeletable(); $this->addIsDeselectable(); $this->addIsMultiple(); $this->addShowThumbnail(); - $this->getImageSize(); $this->addFiles($this->sortBy); $this->value = \implode(',', \array_map('\Contao\StringUtil::binToUuid', (array) $this->value)); - return parent::parse($attributes); + return parent::parse($arrAttributes); } /** @@ -182,7 +193,7 @@ public function parse($attributes = null) */ public function parseFilename(string $filename): string { - if (empty($filename) || !\is_string($filename)) { + if (empty($filename)) { return $filename; } @@ -192,7 +203,7 @@ public function parseFilename(string $filename): string /** * {@inheritDoc} */ - public function validate() + public function validate(): void { $inputName = $this->name; @@ -201,8 +212,9 @@ public function validate() } if ($this->extendFolder) { + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ $uploadFolder = $this->filesModel()->findByUuid($this->uploadFolder); - $uploadFolderPath = $uploadFolder->path . DIRECTORY_SEPARATOR . $this->extendFolder; + $uploadFolderPath = (string) $uploadFolder?->path . DIRECTORY_SEPARATOR . $this->extendFolder; $newUploadFolder = null; @@ -211,11 +223,12 @@ public function validate() $newUploadFolder = Dbafs::addResource($uploadFolderPath); } - if (!$newUploadFolder) { + if (null === $newUploadFolder) { + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ $newUploadFolder = $this->filesModel()->findByPath($uploadFolderPath); } - $this->uploadFolder = $newUploadFolder->uuid; + $this->uploadFolder = $newUploadFolder?->uuid ?? ''; } $this->validateSingleUpload(); @@ -229,6 +242,7 @@ public function validate() * * @return void * + * @throws \Exception * @SuppressWarnings(PHPMD.Superglobals) */ private function validateSingleUpload(): void @@ -238,7 +252,7 @@ private function validateSingleUpload(): void } $inputName = $this->name; - $_FILES[$inputName]['name'] = $this->parseFilename($_FILES[$inputName]['name']); + $_FILES[$inputName]['name'] = $this->parseFilename($_FILES[$inputName]['name'] ?? ''); parent::validate(); @@ -259,6 +273,7 @@ private function validateSingleUpload(): void * * @return void * + * @throws \Exception * @SuppressWarnings(PHPMD.Superglobals) */ private function validateMultipleUpload(): void @@ -268,7 +283,7 @@ private function validateMultipleUpload(): void } $inputName = $this->name; - $values = \array_map('\Contao\StringUtil::binToUuid', $this->value); + $values = \array_map('\Contao\StringUtil::binToUuid', (array) $this->value); $files = []; $inputFiles = $this->getMultipleUploadedFiles(); @@ -317,7 +332,7 @@ private function getMultipleUploadedFiles(): array $files = []; foreach ($_FILES[$this->name] as $propertyName => $values) { - foreach ($values as $key => $value) { + foreach ((array) $values as $key => $value) { $files[$key][$propertyName] = $value; } } @@ -328,28 +343,29 @@ private function getMultipleUploadedFiles(): array /** * Deselect the file, if is mark for deselect. * - * @param string $inputName The input nanme. + * @param string $inputName The input name. * * @return void */ - private function deselectFile(string $inputName) + private function deselectFile(string $inputName): void { - if (!$this->deselect + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ + if ( + !$this->deselect || $this->hasErrors() - || !($post = $this->inputProvider()->post($inputName)) - || !isset($post['reset'][0]) + || [] === ($post = (array) ($this->getCurrentRequest()?->request->get($inputName . '__reset') ?? [])) ) { return; } - if (!$this->multiple && (StringUtil::binToUuid($this->value) === $post['reset'][0])) { + if (!$this->multiple && (StringUtil::binToUuid($this->value) === $post[0])) { $this->value = ''; return; } - $values = \array_map('\Contao\StringUtil::binToUuid', $this->value); - $diffValues = \array_values(\array_diff($values, $post['reset'])); + $values = \array_map('\Contao\StringUtil::binToUuid', (array) $this->value); + $diffValues = \array_values(\array_diff($values, $post)); $this->value = \array_map('\Contao\StringUtil::uuidToBin', $diffValues); } @@ -363,36 +379,37 @@ private function deselectFile(string $inputName) * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function deleteFile(string $inputName) + private function deleteFile(string $inputName): void { - if (!$this->delete + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ + if ( + !$this->delete || $this->hasErrors() - || !($post = $this->inputProvider()->post($inputName)) - || !isset($post['delete'][0]) + || [] === ($post = (array) ($this->getCurrentRequest()?->request->get($inputName . '__delete') ?? [])) ) { return; } - if (!$this->multiple && (StringUtil::binToUuid($this->value) === $post['delete'][0])) { - $this->value = ''; - + if (!$this->multiple && (StringUtil::binToUuid($this->value) === $post[0])) { + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ $file = $this->filesModel()->findByUuid($this->value); - if ($file) { - $this->filesystem->remove($file->path); + if (null !== $file) { + $this->filesystem()->remove($file->path); $file->delete(); + Dbafs::deleteResource($file->path); } - - Dbafs::deleteResource($file->path); + $this->value = ''; return; } - $values = \array_map('\Contao\StringUtil::binToUuid', $this->value); - $diffValues = \array_values(\array_diff($values, $post['delete'])); + $values = \array_map('\Contao\StringUtil::binToUuid', (array) $this->value); + $diffValues = \array_values(\array_diff($values, $post)); - foreach ($post['delete'] as $delete) { - $file = $this->filesModel()->findByUuid(StringUtil::uuidToBin($delete)); - if (!$file) { + foreach ($post as $delete) { + /** @psalm-suppress InternalMethod - Class ContaoFramework is internal, not the getAdapter() method. */ + $file = $this->filesModel()->findByUuid(StringUtil::uuidToBin((string) $delete)); + if (null === $file) { continue; } @@ -414,8 +431,8 @@ private function deleteFile(string $inputName) private function convertFilename(string $filename): string { $fileInfo = \pathinfo($filename); - $extension = $fileInfo['extension']; - $filename = $fileInfo['filename']; + $extension = $fileInfo['extension'] ?? ''; + $filename = $fileInfo['filename'] ?? ''; if ($this->normalizeFilename) { $extension = $this->slugGenerator()->generate($extension, $this->getSlugOptions()); @@ -438,20 +455,20 @@ private function preOrPostFixFilename(string $filename): string return $filename; } - // We save the default delimeter '-' at prefix and postfix + // We save the default delimiter '-' at prefix and postfix // see https://github.com/ausi/slug-generator/issues/34. $prefix = $this->prefixFilename; if ($this->prefixFilename && $this->normalizeFilename) { $prefix = \str_repeat('-', \strspn($this->prefixFilename, '-')) . - $this->slugGenerator()->generate($this->prefixFilename, $this->getSlugOptions()) . - \str_repeat('-', \strspn(\strrev($this->prefixFilename), '-')); + $this->slugGenerator()->generate($this->prefixFilename, $this->getSlugOptions()) . + \str_repeat('-', \strspn(\strrev($this->prefixFilename), '-')); } $postfix = $this->postfixFilename; if ($this->postfixFilename && $this->normalizeFilename) { $postfix = \str_repeat('-', \strspn($this->postfixFilename, '-')) . - $this->slugGenerator()->generate($this->postfixFilename, $this->getSlugOptions()) . - \str_repeat('-', \strspn(\strrev($this->postfixFilename), '-')); + $this->slugGenerator()->generate($this->postfixFilename, $this->getSlugOptions()) . + \str_repeat('-', \strspn(\strrev($this->postfixFilename), '-')); } return $prefix . $filename . $postfix; @@ -464,9 +481,10 @@ private function preOrPostFixFilename(string $filename): string * * @return void * + * @throws Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function addFiles($sortBy) + private function addFiles($sortBy): void { $this->files = []; @@ -476,10 +494,7 @@ private function addFiles($sortBy) /** @var Connection $connection */ $connection = self::getContainer()->get('database_connection'); - - $platform = $connection->getDatabasePlatform(); - - $builder = $connection->createQueryBuilder(); + $builder = $connection->createQueryBuilder(); switch ($sortBy) { case 'name_desc': @@ -501,25 +516,25 @@ private function addFiles($sortBy) $builder ->select( - $platform->quoteIdentifier('id'), - $platform->quoteIdentifier('pid'), - $platform->quoteIdentifier('tstamp'), - $platform->quoteIdentifier('uuid'), - $platform->quoteIdentifier('type'), - $platform->quoteIdentifier('path'), - $platform->quoteIdentifier('extension'), - $platform->quoteIdentifier('hash'), - $platform->quoteIdentifier('found'), - $platform->quoteIdentifier('name'), - $platform->quoteIdentifier('importantPartX'), - $platform->quoteIdentifier('importantPartY'), - $platform->quoteIdentifier('importantPartWidth'), - $platform->quoteIdentifier('importantPartHeight'), - $platform->quoteIdentifier('meta') + 't.id', + 't.pid', + 't.tstamp', + 't.uuid', + 't.type', + 't.path', + 't.extension', + 't.hash', + 't.found', + 't.name', + 't.importantPartX', + 't.importantPartY', + 't.importantPartWidth', + 't.importantPartHeight', + 't.meta' ) - ->from($platform->quoteIdentifier('tl_files')) - ->where($builder->expr()->in($platform->quoteIdentifier('uuid'), ':uuids')) - ->setParameter('uuids', (array) $this->value, Connection::PARAM_STR_ARRAY); + ->from('tl_files', 't') + ->where($builder->expr()->in('t.uuid', ':uuids')) + ->setParameter('uuids', (array) $this->value, ArrayParameterType::STRING); $statement = $builder->executeQuery(); if (!$statement->rowCount()) { @@ -537,9 +552,14 @@ private function addFiles($sortBy) $fileList = []; $container = System::getContainer(); $projectDir = $container->getParameter('kernel.project_dir'); + assert(\is_string($projectDir)); + $imageFactory = $container->get('contao.image.image_factory'); + assert($imageFactory instanceof ImageFactory); foreach ($statement->fetchAllAssociative() as $file) { - $objFile = FilesModel::findByUuid($file['uuid']); - $src = $container->get('contao.image.image_factory') + if (null === ($objFile = FilesModel::findByUuid($file['uuid']))) { + continue; + } + $src = $imageFactory ->create($projectDir . '/' . rawurldecode($objFile->path), $this->imageSize) ->getUrl($projectDir); $objThumbnailFile = new File(rawurldecode($src)); @@ -616,34 +636,31 @@ private function addIsMultiple(): void return; } - $this->prefix .= $this->multiple ? ' is-multiple' : ''; + $this->prefix .= ' is-multiple'; $this->addAttribute('multiple', 'multiple'); } - /** - * Get the input provider. - * - * @return Adapter|Input - */ - private function inputProvider(): Adapter + private function getCurrentRequest(): ?Request { - if (!$this->inputProvider) { - $this->inputProvider = self::getContainer()->get('contao.framework')->getAdapter(Input::class); + $requestStack = \Contao\System::getContainer()->get('request_stack'); + if (!$requestStack instanceof RequestStack) { + return null; } - - return $this->inputProvider; + return $requestStack->getCurrentRequest(); } /** * Get the files model. * - * @return Adapter|FilesModel + * @return Adapter */ private function filesModel(): Adapter { - if (!$this->filesModel) { - $this->filesModel = self::getContainer()->get('contao.framework')->getAdapter(FilesModel::class); + if (null === $this->filesModel) { + $filesModel = self::getContainer()->get('contao.framework')?->getAdapter(FilesModel::class); + assert($filesModel instanceof Adapter); + $this->filesModel = $filesModel; } return $this->filesModel; @@ -654,10 +671,12 @@ private function filesModel(): Adapter * * @return Filesystem */ - private function filesystem() + private function filesystem(): Filesystem { - if (!$this->filesystem) { - $this->filesystem = self::getContainer()->get('filesystem'); + if (null === $this->filesystem) { + $filesystem = self::getContainer()->get('filesystem'); + assert($filesystem instanceof Filesystem); + $this->filesystem = $filesystem; } return $this->filesystem; @@ -668,10 +687,12 @@ private function filesystem() * * @return SlugGenerator */ - private function slugGenerator() + private function slugGenerator(): SlugGenerator { - if (!$this->slugGenerator) { - $this->slugGenerator = System::getContainer()->get('contao.slug'); + if (null === $this->slugGenerator) { + $slugGenerator = System::getContainer()->get('contao.slug'); + assert($slugGenerator instanceof SlugGenerator); + $this->slugGenerator = $slugGenerator; } return $this->slugGenerator; @@ -694,20 +715,12 @@ protected function getSlugOptions(): array */ private function translator(): TranslatorInterface { - if (!$this->filesystem) { - $this->filesystem = self::getContainer()->get('translator'); + if (null === $this->translator) { + $translator = self::getContainer()->get('translator'); + assert($translator instanceof TranslatorInterface); + $this->translator = $translator; } - return $this->filesystem; - } - - /** - * Get the image sizes. - * - * @return void - */ - private function getImageSize(): void - { - $this->imageSize = StringUtil::deserialize($this->imageSize, true); + return $this->translator; } }