diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php index 8ae7bdd3fbc5a57ce443c44feff376c9e60b0269..08def5278569ad8722b466f662c5234eeec47c31 100644 --- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php +++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php @@ -496,11 +496,19 @@ class EditDocumentController $fieldGroups = [$fieldGroups]; } foreach ($fieldGroups as $fields) { - $this->columnsOnly[$table] = array_merge($this->columnsOnly[$table], (is_array($fields) ? $fields : GeneralUtility::trimExplode(',', $fields, true))); + $this->columnsOnly['__hiddenGeneratorFields'][$table] = array_merge( + $this->columnsOnly['__hiddenGeneratorFields'][$table] ?? [], + (is_array($fields) ? $fields : GeneralUtility::trimExplode(',', $fields, true)) + ); } } } - $this->columnsOnly[$table] = array_unique($this->columnsOnly[$table]); + if (!empty($this->columnsOnly['__hiddenGeneratorFields'][$table])) { + $this->columnsOnly['__hiddenGeneratorFields'][$table] = array_diff( + array_unique($this->columnsOnly['__hiddenGeneratorFields'][$table]), + $this->columnsOnly[$table] + ); + } } } } @@ -1185,6 +1193,9 @@ class EditDocumentController // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer if (!empty($this->columnsOnly[$table])) { $formData['fieldListToRender'] = implode(',', $this->columnsOnly[$table]); + if (!empty($this->columnsOnly['__hiddenGeneratorFields'][$table])) { + $formData['hiddenFieldListToRender'] = implode(',', $this->columnsOnly['__hiddenGeneratorFields'][$table]); + } } $formData['renderType'] = 'outerWrapContainer'; diff --git a/typo3/sysext/backend/Classes/Form/Container/ListOfFieldsContainer.php b/typo3/sysext/backend/Classes/Form/Container/ListOfFieldsContainer.php index 4c5fe37bf41233aeca3656f379fdcc014bbdbb19..afa2165cc422409af892ea754e7baebefb19aedd 100644 --- a/typo3/sysext/backend/Classes/Form/Container/ListOfFieldsContainer.php +++ b/typo3/sysext/backend/Classes/Form/Container/ListOfFieldsContainer.php @@ -24,6 +24,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; * This is an entry container called from FormEngine to handle a * list of specific fields. Access rights are checked here and globalOption array * is prepared for further processing of single fields by PaletteAndSingleContainer. + * + * Using "hiddenFieldListToRender" it's also possible to render additional fields as + * hidden fields, which is e.g. used for the "generatorFields" of TCA type "slug". */ class ListOfFieldsContainer extends AbstractContainer { @@ -34,20 +37,41 @@ class ListOfFieldsContainer extends AbstractContainer */ public function render(): array { - $fieldListToRender = $this->data['fieldListToRender']; - $recordTypeValue = $this->data['recordTypeValue']; + $options = $this->data; + $options['fieldsArray'] = $this->sanitizeFieldList($this->data['fieldListToRender']); - $fieldListToRender = array_unique(GeneralUtility::trimExplode(',', $fieldListToRender, true)); + if ($this->data['hiddenFieldListToRender'] ?? false) { + $hiddenFieldList = array_diff( + $this->sanitizeFieldList($this->data['hiddenFieldListToRender']), + $options['fieldsArray'] + ); + if ($hiddenFieldList !== []) { + $hiddenFieldList = implode(',', $hiddenFieldList); + $hiddenPaletteName = 'hiddenFieldsPalette' . md5($hiddenFieldList); + $options['processedTca']['palettes'][$hiddenPaletteName] = [ + 'isHiddenPalette' => true, + 'showitem' => $hiddenFieldList, + ]; + $options['fieldsArray'][] = '--palette--;;' . $hiddenPaletteName; + } + } - $fieldsByShowitem = $this->data['processedTca']['types'][$recordTypeValue]['showitem']; + $options['renderType'] = 'paletteAndSingleContainer'; + return $this->nodeFactory->create($options)->render(); + } + + protected function sanitizeFieldList(string $fieldList): array + { + $fields = array_unique(GeneralUtility::trimExplode(',', $fieldList, true)); + $fieldsByShowitem = $this->data['processedTca']['types'][$this->data['recordTypeValue']]['showitem']; $fieldsByShowitem = GeneralUtility::trimExplode(',', $fieldsByShowitem, true); - $finalFieldsList = []; - foreach ($fieldListToRender as $fieldName) { + $allowedFields = []; + foreach ($fields as $fieldName) { foreach ($fieldsByShowitem as $fieldByShowitem) { $fieldByShowitemArray = $this->explodeSingleFieldShowItemConfiguration($fieldByShowitem); if ($fieldByShowitemArray['fieldName'] === $fieldName) { - $finalFieldsList[] = implode(';', $fieldByShowitemArray); + $allowedFields[] = implode(';', $fieldByShowitemArray); break; } if ($fieldByShowitemArray['fieldName'] === '--palette--' @@ -59,18 +83,14 @@ class ListOfFieldsContainer extends AbstractContainer foreach ($paletteFields as $paletteField) { $paletteFieldArray = $this->explodeSingleFieldShowItemConfiguration($paletteField); if ($paletteFieldArray['fieldName'] === $fieldName) { - $finalFieldsList[] = implode(';', $paletteFieldArray); + $allowedFields[] = implode(';', $paletteFieldArray); break; } } } } } - - $options = $this->data; - $options['fieldsArray'] = $finalFieldsList; - $options['renderType'] = 'paletteAndSingleContainer'; - return $this->nodeFactory->create($options)->render(); + return $allowedFields; } protected function getLanguageService(): LanguageService diff --git a/typo3/sysext/backend/Tests/Unit/Controller/EditDocumentControllerTest.php b/typo3/sysext/backend/Tests/Unit/Controller/EditDocumentControllerTest.php index e63b8051d2ca479e84441ccd2d4990479f219914..63948594447387aa7e7105c760d867a44c4b2d86 100644 --- a/typo3/sysext/backend/Tests/Unit/Controller/EditDocumentControllerTest.php +++ b/typo3/sysext/backend/Tests/Unit/Controller/EditDocumentControllerTest.php @@ -43,14 +43,15 @@ final class EditDocumentControllerTest extends UnitTestCase ]; $editDocumentControllerMock->_call('addSlugFieldsToColumnsOnly', $queryParams); - self::assertEquals($result, array_values($editDocumentControllerMock->_get('columnsOnly')[$tableName])); + self::assertEquals($selectedFields, array_values($editDocumentControllerMock->_get('columnsOnly')[$tableName] ?? [])); + self::assertEquals($result, array_values($editDocumentControllerMock->_get('columnsOnly')['__hiddenGeneratorFields'][$tableName] ?? [])); } public static function slugDependentFieldsAreAddedToColumnsOnlyDataProvider(): array { return [ 'fields in string' => [ - ['fo', 'bar', 'slug', 'title'], + ['title'], ['fo', 'bar', 'slug'], 'fake', [ @@ -65,7 +66,7 @@ final class EditDocumentControllerTest extends UnitTestCase ], ], 'fields in string and array' => [ - ['slug', 'fo', 'title', 'nav_title', 'other_field'], + ['nav_title', 'other_field'], ['slug', 'fo', 'title'], 'fake', [ @@ -80,7 +81,7 @@ final class EditDocumentControllerTest extends UnitTestCase ], ], 'unique fields' => [ - ['slug', 'fo', 'title', 'some_field'], + ['some_field'], ['slug', 'fo', 'title'], 'fake', [ @@ -95,7 +96,7 @@ final class EditDocumentControllerTest extends UnitTestCase ], ], 'fields as comma-separated list' => [ - ['slug', 'fo', 'title', 'nav_title', 'some_field'], + ['nav_title', 'some_field'], ['slug', 'fo', 'title'], 'fake', [ @@ -110,7 +111,7 @@ final class EditDocumentControllerTest extends UnitTestCase ], ], 'no slug field given' => [ - ['slug', 'fo'], + [], ['slug', 'fo'], 'fake', [ @@ -168,8 +169,10 @@ final class EditDocumentControllerTest extends UnitTestCase ]; $editDocumentControllerMock->_call('addSlugFieldsToColumnsOnly', $queryParams); - self::assertEquals(['aField', 'aTitle'], array_values($editDocumentControllerMock->_get('columnsOnly')['aTable'])); - self::assertEquals(['bField', 'bTitle'], array_values($editDocumentControllerMock->_get('columnsOnly')['bTable'])); + self::assertEquals(['aField'], array_values($editDocumentControllerMock->_get('columnsOnly')['aTable'])); + self::assertEquals(['aTitle'], array_values($editDocumentControllerMock->_get('columnsOnly')['__hiddenGeneratorFields']['aTable'])); + self::assertEquals(['bField'], array_values($editDocumentControllerMock->_get('columnsOnly')['bTable'])); + self::assertEquals(['bTitle'], array_values($editDocumentControllerMock->_get('columnsOnly')['__hiddenGeneratorFields']['bTable'])); } public static function resolvePreviewRecordIdDataProvider(): array diff --git a/typo3/sysext/backend/Tests/Unit/Form/Container/ListOfFieldsContainerTest.php b/typo3/sysext/backend/Tests/Unit/Form/Container/ListOfFieldsContainerTest.php index c133c965293d3c3cf067254f9312a3c203cfdc5c..c89d337e0f09281a74f8094b71138386bf634854 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/Container/ListOfFieldsContainerTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/Container/ListOfFieldsContainerTest.php @@ -180,4 +180,54 @@ final class ListOfFieldsContainerTest extends UnitTestCase $subject->setData($input); $subject->render(); } + + #[Test] + public function renderAddsHiddenFields(): void + { + $nodeFactoryMock = $this->createMock(NodeFactory::class); + $paletteAndSingleContainerMock = $this->createMock(PaletteAndSingleContainer::class); + $paletteAndSingleContainerMock->expects(self::atLeastOnce())->method('render')->withAnyParameters()->willReturn([]); + + $input = [ + 'tableName' => 'aTable', + 'recordTypeValue' => 'aType', + 'processedTca' => [ + 'types' => [ + 'aType' => [ + 'showitem' => '--palette--;;aPalette,uniqueField,hiddenField,--palette--;;bPalette,', + ], + ], + 'palettes' => [ + 'aPalette' => [ + 'showitem' => 'aField', + ], + 'bPalette' => [ + 'showitem' => 'hiddenInPalette', + ], + ], + ], + 'fieldListToRender' => 'aField,uniqueField', + 'hiddenFieldListToRender' => 'hiddenField,uniqueField,iDontExist,hiddenInPalette', + ]; + + $expected = $input; + $expected['renderType'] = 'paletteAndSingleContainer'; + $expected['processedTca']['palettes']['hiddenFieldsPalette' . md5('hiddenField;;,hiddenInPalette;;')] = [ + 'isHiddenPalette' => true, + 'showitem' => 'hiddenField;;,hiddenInPalette;;', + ]; + // "uniqueField" is onl rendered once and "iDontExist" is not rendered at all + $expected['fieldsArray'] = [ + 'aField;;', + 'uniqueField;;', + '--palette--;;' . 'hiddenFieldsPalette' . md5('hiddenField;;,hiddenInPalette;;'), + ]; + + $nodeFactoryMock->method('create')->with($expected)->willReturn($paletteAndSingleContainerMock); + + $subject = new ListOfFieldsContainer(); + $subject->injectNodeFactory($nodeFactoryMock); + $subject->setData($input); + $subject->render(); + } }