From f24a203deb4b2615296d27b30a1a7cea18711be4 Mon Sep 17 00:00:00 2001 From: Andreas Kienast <a.fernandez@scripting-base.de> Date: Thu, 7 Mar 2024 20:07:27 +0100 Subject: [PATCH] [BUGFIX] Prepare `allowed` and `disallowed` in more deeply nested TCA TCA for usage of `sys_file` may configure their allowed extensions by an array (e.g. EXT:bootstrap_package, "Media" content element) which failed as the Element Browser took only strings into account. The class `TcaPreparation` now takes `columnsOverrides` and their respective `overrideChildTca` configuration into account. Resolves: #103316 Releases: main, 12.4 Change-Id: Ie173a0e007ab44bcb9f090a5b7ae8b7eedd9d45a Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83296 Reviewed-by: Garvin Hicking <gh@faktor-e.de> Reviewed-by: Nikita Hovratov <nikita.h@live.de> Tested-by: Garvin Hicking <gh@faktor-e.de> Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de> Tested-by: Nikita Hovratov <nikita.h@live.de> Tested-by: core-ci <typo3@b13.com> Tested-by: Andreas Kienast <a.fernandez@scripting-base.de> --- .../Configuration/Tca/TcaPreparation.php | 84 ++++- .../Configuration/Tca/TcaPreparationTest.php | 357 ++++++++++++++++++ 2 files changed, 421 insertions(+), 20 deletions(-) diff --git a/typo3/sysext/core/Classes/Configuration/Tca/TcaPreparation.php b/typo3/sysext/core/Classes/Configuration/Tca/TcaPreparation.php index f27ffc4604cb..83e2bcef785b 100644 --- a/typo3/sysext/core/Classes/Configuration/Tca/TcaPreparation.php +++ b/typo3/sysext/core/Classes/Configuration/Tca/TcaPreparation.php @@ -170,6 +170,37 @@ class TcaPreparation protected function configureFileReferences(array $tca): array { + $prepareFileExtensionLambda = static function (array $config): array { + if (!empty($allowed = ($config['allowed'] ?? null))) { + $config['allowed'] = self::prepareFileExtensions($allowed); + } + if (!empty($disallowed = ($config['disallowed'] ?? null))) { + $config['disallowed'] = self::prepareFileExtensions($disallowed); + } + return $config; + }; + + $migrateOverrideChildTcaExtensions = static function ($overrideChildTcaConfig) use ($prepareFileExtensionLambda): array { + if (is_array($overrideChildTcaConfig['columns'] ?? null)) { + foreach ($overrideChildTcaConfig['columns'] as &$overrideChildTcaColumnConfig) { + if (!isset($overrideChildTcaColumnConfig['config'])) { + continue; + } + $overrideChildTcaColumnConfig['config'] = $prepareFileExtensionLambda($overrideChildTcaColumnConfig['config']); + } + unset($overrideChildTcaColumnConfig); + } + if (is_array($overrideChildTcaConfig['types'] ?? null)) { + foreach ($overrideChildTcaConfig['types'] as &$overrideChildTcaTypeConfig) { + if (!isset($overrideChildTcaTypeConfig['config'])) { + continue; + } + $overrideChildTcaTypeConfig['config'] = $prepareFileExtensionLambda($overrideChildTcaTypeConfig['config']); + } + } + return $overrideChildTcaConfig; + }; + foreach ($tca as $table => &$tableDefinition) { if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { continue; @@ -183,27 +214,40 @@ class TcaPreparation // dedicated TCA type. However a lot of underlying code in DataHandler and // friends relies on those keys, especially "foreign_table" and "foreign_selector". // @todo Check which of those values can be removed since only used by FormEngine - $fieldConfig['config'] = array_replace_recursive( - $fieldConfig['config'], - [ - 'foreign_table' => 'sys_file_reference', - 'foreign_field' => 'uid_foreign', - 'foreign_sortby' => 'sorting_foreign', - 'foreign_table_field' => 'tablenames', - 'foreign_match_fields' => [ - 'fieldname' => $fieldName, - 'tablenames' => $table, - ], - 'foreign_label' => 'uid_local', - 'foreign_selector' => 'uid_local', - ] - ); - - if (!empty(($allowed = ($fieldConfig['config']['allowed'] ?? null)))) { - $fieldConfig['config']['allowed'] = self::prepareFileExtensions($allowed); + $fieldConfig['config'] = array_replace_recursive($fieldConfig['config'], [ + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => $fieldName, + 'tablenames' => $table, + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + ]); + $fieldConfig['config'] = $prepareFileExtensionLambda($fieldConfig['config']); + if (is_array($fieldConfig['config']['overrideChildTca'] ?? null)) { + $fieldConfig['config']['overrideChildTca'] = $migrateOverrideChildTcaExtensions($fieldConfig['config']['overrideChildTca']); } - if (!empty(($disallowed = ($fieldConfig['config']['disallowed'] ?? null)))) { - $fieldConfig['config']['disallowed'] = self::prepareFileExtensions($disallowed); + } + unset($fieldConfig); + + if (is_array($tableDefinition['types'] ?? null)) { + foreach ($tableDefinition['types'] as &$typeConfig) { + if (!isset($typeConfig['columnsOverrides']) || !is_array($typeConfig['columnsOverrides'])) { + continue; + } + foreach ($typeConfig['columnsOverrides'] as &$columnsOverridesConfig) { + if (!isset($columnsOverridesConfig['config']) || !is_array($columnsOverridesConfig['config'])) { + continue; + } + + $columnsOverridesConfig['config'] = $prepareFileExtensionLambda($columnsOverridesConfig['config']); + if (is_array($columnsOverridesConfig['config']['overrideChildTca'] ?? null)) { + $columnsOverridesConfig['config']['overrideChildTca'] = $migrateOverrideChildTcaExtensions($columnsOverridesConfig['config']['overrideChildTca']); + } + } } } } diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Tca/TcaPreparationTest.php b/typo3/sysext/core/Tests/Unit/Configuration/Tca/TcaPreparationTest.php index c9214c653dc8..3050e60b5c17 100644 --- a/typo3/sysext/core/Tests/Unit/Configuration/Tca/TcaPreparationTest.php +++ b/typo3/sysext/core/Tests/Unit/Configuration/Tca/TcaPreparationTest.php @@ -304,6 +304,363 @@ final class TcaPreparationTest extends UnitTestCase ]); } + public static function configureFileReferencesDataProvider(): \Generator + { + yield 'allowed and disallowed in config' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'allowed' => ['foo', 'bar'], + 'disallowed' => ['baz', 'bencer'], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'allowed' => 'foo,bar', + 'disallowed' => 'baz,bencer', + ], + ], + ], + ], + ], + ]; + yield 'allowed and disallowed in config/overrideChildTca' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'overrideChildTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'allowed' => ['foo', 'bar'], + 'disallowed' => ['baz', 'bencer'], + ], + ], + ], + ], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'overrideChildTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'allowed' => 'foo,bar', + 'disallowed' => 'baz,bencer', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]; + yield 'allowed and disallowed in columnsOverride/config' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'allowed' => ['foo', 'bar'], + 'disallowed' => ['baz', 'bencer'], + ], + ], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'allowed' => 'foo,bar', + 'disallowed' => 'baz,bencer', + ], + ], + ], + ], + ], + ], + ], + ]; + yield 'columnsOverride without config' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [], + ], + ], + ], + ], + ], + ]; + yield 'allowed and disallowed in columnsOverride/overrideChildTca/columns' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'overrideChildTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'allowed' => ['foo', 'bar'], + 'disallowed' => ['baz', 'bencer'], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'overrideChildTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'allowed' => 'foo,bar', + 'disallowed' => 'baz,bencer', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]; + yield 'allowed and disallowed in columnsOverride/overrideChildTca/types' => [ + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'overrideChildTca' => [ + 'types' => [ + 'aType' => [ + 'config' => [ + 'allowed' => ['foo', 'bar'], + 'disallowed' => ['baz', 'bencer'], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + [ + 'aTable' => [ + 'columns' => [ + 'foo' => [ + 'config' => [ + 'type' => 'file', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'foo', + 'tablenames' => 'aTable', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + ], + ], + ], + 'types' => [ + 'aType' => [ + 'columnsOverrides' => [ + 'aField' => [ + 'config' => [ + 'overrideChildTca' => [ + 'types' => [ + 'aType' => [ + 'config' => [ + 'allowed' => 'foo,bar', + 'disallowed' => 'baz,bencer', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]; + } + + #[DataProvider('configureFileReferencesDataProvider')] + #[Test] + public function configureFileReferences(array $input, array $expected): void + { + self::assertEquals($expected, (new TcaPreparation())->prepare($input)); + } + #[Test] public function prepareFileExtensionsReplacesPlaceholders(): void { -- GitLab