From df0dcddf8a4954e0075f6f5ed0cf355c39ba6d26 Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Thu, 17 Nov 2016 18:51:13 +0100 Subject: [PATCH] [TASK] TCA tree refactoring The patch refactors the TCA tree form engine data calculation. The tree now works with "new" (not yet persisted) records, even if the record has types in combination with flex forms. For instance, a new ext:news tt_content element now renders the category tree within flex forms, even in new tt_content records that have not been saved. The TCA tree no longer fetches all items when opening a record initially, but defers that to the ajax request. This gives a massive performance improvement on initial load of a record if the displayed TCA tree is bigger. The ajax request itself now compiles only data of the requested TCA field, also resulting in a significant performance improvement. As example, ext:styleguide "elements select" is now rendered much quicker and the single ajax calls per tree are reduced from about 4 seconds to less than a second each with my test data. Change-Id: If3c4c1779f5fe1510ffc13d1c9f1151bddab13e9 Resolves: #78744 Releases: master Reviewed-on: https://review.typo3.org/50700 Reviewed-by: Thomas Maroschik <tmaroschik@dfau.de> Tested-by: Thomas Maroschik <tmaroschik@dfau.de> Reviewed-by: Tymoteusz Motylewski <t.motylewski@gmail.com> Tested-by: Tymoteusz Motylewski <t.motylewski@gmail.com> --- .../Controller/SelectTreeController.php | 101 +++++++++++------- .../Container/FlexFormElementContainer.php | 2 - .../Form/Element/SelectTreeElement.php | 10 +- .../backend/Classes/Form/FormDataCompiler.php | 6 +- .../DatabaseRecordTypeValue.php | 5 + .../InitializeProcessedTca.php | 21 ++-- .../Form/FormDataProvider/TcaFlexPrepare.php | 25 +++-- .../Form/FormDataProvider/TcaFlexProcess.php | 3 + .../FormDataProvider/TcaSelectTreeItems.php | 99 +++++++---------- .../FormEngine/Element/SelectTreeElement.js | 4 +- .../Controller/SelectTreeControllerTest.php | 55 ++++++++++ .../DatabaseRecordTypeValueTest.php | 48 +++++++++ .../InitializeProcessedTcaTest.php | 17 +++ .../FormDataProvider/TcaFlexPrepareTest.php | 47 ++++++++ .../TcaSelectTreeItemsTest.php | 3 +- .../Backend/Page/AddPageInPageModuleCest.php | 1 - 16 files changed, 318 insertions(+), 129 deletions(-) create mode 100644 typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php diff --git a/typo3/sysext/backend/Classes/Controller/SelectTreeController.php b/typo3/sysext/backend/Classes/Controller/SelectTreeController.php index 01a25f69ebd8..a0f6423a6b2b 100644 --- a/typo3/sysext/backend/Classes/Controller/SelectTreeController.php +++ b/typo3/sysext/backend/Classes/Controller/SelectTreeController.php @@ -18,6 +18,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Form\FormDataCompiler; use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord; +use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -35,25 +36,76 @@ class SelectTreeController public function fetchDataAction(ServerRequestInterface $request, ResponseInterface $response) { $tableName = $request->getQueryParams()['table']; - if (!$this->getBackendUser()->check('tables_select', $tableName)) { - return $response; + $fieldName = $request->getQueryParams()['field']; + + // Prepare processedTca: Remove all column definitions except the one that contains + // our tree definition. This way only this field is calculated, everything else is ignored. + if (!isset($GLOBALS['TCA'][$tableName]) || !is_array($GLOBALS['TCA'][$tableName])) { + throw new \RuntimeException( + 'TCA for table ' . $tableName . ' not found', + 1479386729 + ); } + $processedTca = $GLOBALS['TCA'][$tableName]; + if (!isset($processedTca['columns'][$fieldName]) || !is_array($processedTca['columns'][$fieldName])) { + throw new \RuntimeException( + 'TCA for table ' . $tableName . ' and field ' . $fieldName . ' not found', + 1479386990 + ); + } + + // Force given record type and set showitem to our field only + $recordTypeValue = $request->getQueryParams()['record_type_value']; + $processedTca['types'][$recordTypeValue]['showitem'] = $fieldName; + // Unset all columns except our field + $processedTca['columns'] = [ + $fieldName => $processedTca['columns'][$fieldName], + ]; + + $flexFormPath = []; + if ($processedTca['columns'][$fieldName]['config']['type'] === 'flex') { + $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); + $dataStructureIdentifier = json_encode($request->getQueryParams()['flex_form_datastructure_identifier']); + $dataStructure = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier); + // Try to reduce given data structure down to the relevant element only + $flexFormPath = $request->getQueryParams()['flex_form_path']; + $fieldPattern = 'data[' . $tableName . ']['; + $flexFormPath = str_replace($fieldPattern, '', $flexFormPath); + $flexFormPath = substr($flexFormPath, 0, -1); + $flexFormPath = explode('][', $flexFormPath); + if (isset($dataStructure['sheets'][$flexFormPath[3]]['ROOT']['el'][$flexFormPath[5]])) { + $dataStructure = [ + 'sheets' => [ + $flexFormPath[3] => [ + 'ROOT' => [ + 'type' => 'array', + 'el' => [ + $flexFormPath[5] => $dataStructure['sheets'][$flexFormPath[3]]['ROOT']['el'][$flexFormPath[5]], + ], + ], + ], + ], + ]; + } + $processedTca['columns'][$fieldName]['config']['ds'] = $dataStructure; + $processedTca['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier; + } + $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class); $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup); - $formDataCompilerInput = [ 'tableName' => $request->getQueryParams()['table'], 'vanillaUid' => (int)$request->getQueryParams()['uid'], 'command' => $request->getQueryParams()['command'], + 'processedTca' => $processedTca, + 'recordTypeValue' => $recordTypeValue, + 'selectTreeCompileItems' => true, ]; - - $fieldName = $request->getQueryParams()['field']; $formData = $formDataCompiler->compile($formDataCompilerInput); if ($formData['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') { - $flexFormFieldName = $request->getQueryParams()['flex_form_field_name']; - $value = $this->searchForFieldInFlexStructure($formData['processedTca']['columns'][$fieldName]['config'], $flexFormFieldName); - $treeData = $value['config']['treeData']; + $treeData = $formData['processedTca']['columns'][$fieldName]['config']['ds'] + ['sheets'][$flexFormPath[3]]['ROOT']['el'][$flexFormPath[5]]['config']['treeData']; } else { $treeData = $formData['processedTca']['columns'][$fieldName]['config']['treeData']; } @@ -62,37 +114,4 @@ class SelectTreeController $response->getBody()->write($json); return $response; } - - /** - * A workaround for flexforms - there is no easy way to get flex field by key, so we need to search for it - * - * @todo remove me once flexforms are refactored - * - * @param array $array - * @param string $needle - * @return array - */ - protected function searchForFieldInFlexStructure(array $array, $needle) - { - $needle = trim($needle); - $iterator = new \RecursiveArrayIterator($array); - $recursive = new \RecursiveIteratorIterator( - $iterator, - \RecursiveIteratorIterator::SELF_FIRST - ); - foreach ($recursive as $key => $value) { - if ($key === $needle) { - return $value; - } - } - return []; - } - - /** - * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication - */ - protected function getBackendUser() - { - return $GLOBALS['BE_USER']; - } } diff --git a/typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php b/typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php index 5e8d4911732a..c8b7cc842fff 100644 --- a/typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php +++ b/typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php @@ -79,8 +79,6 @@ class FlexFormElementContainer extends AbstractContainer // Set up options for single element $fakeParameterArray = [ 'fieldConf' => [ - // @todo review this field during flex refactoring - 'flexFormFieldName' => $flexFormFieldName, 'label' => $languageService->sL(trim($flexFormFieldArray['label'])), 'config' => $flexFormFieldArray['config'], 'children' => $flexFormFieldArray['children'], diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php index d1ae3ff5711f..4af7e775f0c6 100644 --- a/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php @@ -76,7 +76,12 @@ class SelectTreeElement extends AbstractFormElement $heightInPx = $height * $this->itemHeight; $treeWrapperId = 'tree_' . $formElementId; - $flexFormFieldName = !empty($parameterArray['fieldConf']['flexFormFieldName']) ? htmlspecialchars($parameterArray['fieldConf']['flexFormFieldName']) : ''; + $fieldName = $this->data['fieldName']; + $flexDataStructureIdentifier = ''; + if ($this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'flex') { + $flexDataStructureIdentifier = $this->data['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier']; + } + $html = []; $html[] = '<div class="typo3-tceforms-tree">'; $html[] = ' <input class="treeRecord" type="hidden"'; @@ -85,8 +90,9 @@ class SelectTreeElement extends AbstractFormElement $html[] = ' data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'; $html[] = ' data-table="' . htmlspecialchars($this->data['tableName']) . '"'; $html[] = ' data-field="' . htmlspecialchars($this->data['fieldName']) . '"'; - $html[] = ' data-flex-form-field-name="' . $flexFormFieldName . '"'; + $html[] = ' data-flex-form-datastructure-identifier="' . htmlspecialchars($flexDataStructureIdentifier) . '"'; $html[] = ' data-uid="' . (int)$this->data['vanillaUid'] . '"'; + $html[] = ' data-recordtypevalue="' . $this->data['recordTypeValue'] . '"'; $html[] = ' data-command="' . htmlspecialchars($this->data['command']) . '"'; $html[] = ' data-read-only="' . $readOnly . '"'; $html[] = ' data-tree-exclusive-keys="' . htmlspecialchars($exclusiveKeys) . '"'; diff --git a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php index 24fff348aff5..ff8729cf9475 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php +++ b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php @@ -190,7 +190,7 @@ class FormDataCompiler // can be shown. This array holds those additional language records, Array key is sys_language_uid. 'additionalLanguageRows' => [], // The tca record type value of the record. Forced to string, there can be "named" type values. - 'recordTypeValue' => '0', + 'recordTypeValue' => '', // TCA of table with processed fields. After processing, this array contains merged and resolved // array data, items were resolved, only used types are set, renderTypes are set. 'processedTca' => [], @@ -204,6 +204,10 @@ class FormDataCompiler // itemsProcFunc need to have this data at hand to do their job. 'flexParentDatabaseRow' => [], + // If true, TcaSelectTreeItems data provider will compile tree items. This is false by default since + // on opening a record items are not calculated but are fetch in an ajax request, see SelectTreeController. + 'selectTreeCompileItems' => false, + // BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items // This array is "flat", an inline structure with parent uid 1 having firstChild uid 2 having secondChild uid 3 // firstChild and secondChild are not nested. If an uid is set it means "record is expanded", example: diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php index e78cb7a11511..dc8a85dc128f 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php @@ -46,6 +46,11 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface ); } + // Guard clause to suppress any calculation if record type value has been set from outside already + if ($result['recordTypeValue'] !== '') { + return $result; + } + $recordTypeValue = '0'; if (!empty($result['processedTca']['ctrl']['type'])) { $tcaTypeField = $result['processedTca']['ctrl']['type']; diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php index 2d807f4fd27c..4cfb47adf60d 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php @@ -30,16 +30,19 @@ class InitializeProcessedTca implements FormDataProviderInterface */ public function addData(array $result) { - if ( - !isset($GLOBALS['TCA'][$result['tableName']]) - || !is_array($GLOBALS['TCA'][$result['tableName']]) - ) { - throw new \UnexpectedValueException( - 'TCA for table ' . $result['tableName'] . ' not found', - 1437914223 - ); + if (empty($result['processedTca'])) { + if ( + !isset($GLOBALS['TCA'][$result['tableName']]) + || !is_array($GLOBALS['TCA'][$result['tableName']]) + ) { + throw new \UnexpectedValueException( + 'TCA for table ' . $result['tableName'] . ' not found', + 1437914223 + ); + } + + $result['processedTca'] = $GLOBALS['TCA'][$result['tableName']]; } - $result['processedTca'] = $GLOBALS['TCA'][$result['tableName']]; if (!is_array($result['processedTca']['columns'])) { throw new \UnexpectedValueException( diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php index 054fd306432f..e0f2843b3714 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php @@ -63,16 +63,21 @@ class TcaFlexPrepare implements FormDataProviderInterface */ protected function initializeDataStructure(array $result, $fieldName) { - $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); - $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier( - $result['processedTca']['columns'][$fieldName], - $result['tableName'], - $fieldName, - $result['databaseRow'] - ); - // Add the identifier to TCA to use it later during rendering - $result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier; - $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier); + if (!isset($result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'])) { + $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); + $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier( + $result['processedTca']['columns'][$fieldName], + $result['tableName'], + $fieldName, + $result['databaseRow'] + ); + // Add the identifier to TCA to use it later during rendering + $result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier; + $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier); + } else { + // Assume the data structure has been given from outside if the data structure identifier is already set. + $dataStructureArray = $result['processedTca']['columns'][$fieldName]['config']['ds']; + } if (!isset($dataStructureArray['meta']) || !is_array($dataStructureArray['meta'])) { $dataStructureArray['meta'] = []; } diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php index ada1c46063fa..b40826741017 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php @@ -468,6 +468,7 @@ class TcaFlexProcess implements FormDataProviderInterface $singleFieldName => $singleFieldConfiguration, ], ], + 'selectTreeCompileItems' => false, 'flexParentDatabaseRow' => $result['databaseRow'], ]; $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment); @@ -518,6 +519,8 @@ class TcaFlexProcess implements FormDataProviderInterface 'columns' => [], ], 'flexParentDatabaseRow' => $result['databaseRow'], + // Whether to compile TCA tree items - inherit from parent + 'selectTreeCompileItems' => $result['selectTreeCompileItems'], ]; if (!empty($tcaNewColumns)) { diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php index eede7112f17c..92fed04939be 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php @@ -46,42 +46,8 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide continue; } - $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName); $fieldConfig['config']['maxitems'] = $this->sanitizeMaxItems($fieldConfig['config']['maxitems']); - $pageTsConfigAddItems = $this->addItemsFromPageTsConfig($result, $fieldName, []); - $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']); - $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']); - $staticItems = $fieldConfig['config']['items'] + $pageTsConfigAddItems; - - $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']); - $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems); - - $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); - $fieldConfig['config']['items'] = $pageTsConfigAddItems + $fieldConfig['config']['items']; - $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); - - $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']); - $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']); - $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']); - - // Resolve "itemsProcFunc" - if (!empty($fieldConfig['config']['itemsProcFunc'])) { - $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']); - // itemsProcFunc must not be used anymore - unset($fieldConfig['config']['itemsProcFunc']); - } - - // Translate labels - $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName); - - $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems); - $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); - $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues); - - // Keys may contain table names, so a numeric array is created - $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']); - // A couple of tree specific config parameters can be overwritten via page TS. // Pick those that influence the data fetching and write them into the config // given to the tree data provider @@ -102,7 +68,44 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide } } - $fieldConfig['config']['treeData'] = $this->renderTree($result, $fieldConfig, $fieldName, $staticItems); + if ($result['selectTreeCompileItems']) { + $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName); + + $pageTsConfigAddItems = $this->addItemsFromPageTsConfig($result, $fieldName, []); + $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']); + $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']); + $staticItems = $fieldConfig['config']['items'] + $pageTsConfigAddItems; + + $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']); + $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems); + + $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); + $fieldConfig['config']['items'] = $pageTsConfigAddItems + $fieldConfig['config']['items']; + $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); + + $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']); + $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']); + $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']); + + // Resolve "itemsProcFunc" + if (!empty($fieldConfig['config']['itemsProcFunc'])) { + $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']); + // itemsProcFunc must not be used anymore + unset($fieldConfig['config']['itemsProcFunc']); + } + + // Translate labels + $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName); + + $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems); + $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); + $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues); + + // Keys may contain table names, so a numeric array is created + $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']); + + $fieldConfig['config']['treeData'] = $this->renderTree($result, $fieldConfig, $fieldName, $staticItems); + } $result['processedTca']['columns'][$fieldName] = $fieldConfig; } @@ -151,7 +154,6 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide $treeConfig = [ 'items' => $treeItems, - 'selectedNodes' => $this->prepareSelectedNodes($fieldConfig['config']['items'], $result['databaseRow'][$fieldName]) ]; return $treeConfig; @@ -186,31 +188,6 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide return $additionalItems; } - /** - * Make sure to only keep the selected nodes that are really available in the database and for the user - * (e.g. after permissions etc) - * - * @param array $itemArray - * @param array $databaseValues - * @return array - * @todo: this is ugly - should be removed with the tree rewrite - */ - protected function prepareSelectedNodes(array $itemArray, array $databaseValues) - { - $selectedNodes = []; - if (!empty($databaseValues)) { - foreach ($databaseValues as $selectedNode) { - foreach ($itemArray as $possibleSelectBoxItem) { - if ((string)$possibleSelectBoxItem[1] === (string)$selectedNode) { - $selectedNodes[] = $selectedNode; - } - } - } - } - - return $selectedNodes; - } - /** * Determines whether the current field is a valid target for this DataProvider * diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js index a596eae76b56..c9f5c769b186 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js @@ -33,7 +33,9 @@ define(['jquery', 'TYPO3/CMS/Backend/FormEngine/Element/SelectTree'], function ( table: treeInput.data('table'), field: treeInput.data('field'), uid: treeInput.data('uid'), - flex_form_field_name: treeInput.data('flex-form-field-name'), + record_type_value: treeInput.data('recordtypevalue'), + flex_form_datastructure_identifier: treeInput.data('flex-form-datastructure-identifier'), + flex_form_path: treeInput.data('formengine-input-name'), command: treeInput.data('command') }; var $wrapper = treeInput.parent().siblings('.svg-tree-wrapper'); diff --git a/typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php b/typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php new file mode 100644 index 000000000000..5dddc632b5ac --- /dev/null +++ b/typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php @@ -0,0 +1,55 @@ +<?php +namespace TYPO3\CMS\Backend\Tests\Unit\Controller; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Controller\SelectTreeController; +use TYPO3\CMS\Core\Tests\UnitTestCase; + +/** + * Test case + */ +class SelectTreeControllerTest extends UnitTestCase +{ + /** + * @test + */ + public function fetchDataActionThrowsExceptionIfTcaOfTableDoesNotExist() + { + $requestProphecy = $this->prophesize(ServerRequestInterface::class); + $responseProphecy = $this->prophesize(ResponseInterface::class); + $this->expectException(\RuntimeException::class); + $this->expectExceptionCode(1479386729); + (new SelectTreeController())->fetchDataAction($requestProphecy->reveal(), $responseProphecy->reveal()); + } + + /** + * @test + */ + public function fetchDataActionThrowsExceptionIfTcaOfTableFieldDoesNotExist() + { + $responseProphecy = $this->prophesize(ResponseInterface::class); + $requestProphecy = $this->prophesize(ServerRequestInterface::class); + $requestProphecy->getQueryParams()->shouldBeCalled()->willReturn([ + 'table' => 'aTable', + 'field' => 'aField', + ]); + $GLOBALS['TCA']['aTable']['columns'] = []; + $this->expectException(\RuntimeException::class); + $this->expectExceptionCode(1479386990); + (new SelectTreeController())->fetchDataAction($requestProphecy->reveal(), $responseProphecy->reveal()); + } +} diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php index 2193388c8fa3..89ef3b0889fd 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php @@ -58,12 +58,47 @@ class DatabaseRecordTypeValueTest extends UnitTestCase $this->subject->addData($input); } + /** + * @test + */ + public function addDataKeepsExistingTcaRecordTypeValue() + { + $input = [ + 'recordTypeValue' => 'egon', + 'processedTca' => [ + 'types' => [ + '1' => 'foo', + ], + ], + ]; + $expected = $input; + $this->assertSame($expected, $this->subject->addData($input)); + } + + /** + * @test + */ + public function addDataKeepsExistingTcaRecordTypeValueWithValueZero() + { + $input = [ + 'recordTypeValue' => 0, + 'processedTca' => [ + 'types' => [ + '1' => 'foo', + ], + ], + ]; + $expected = $input; + $this->assertSame($expected, $this->subject->addData($input)); + } + /** * @test */ public function addDataSetsRecordTypeValueToHistoricalOneIfTypeZeroIsNotDefined() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'types' => [ '1' => 'foo', @@ -81,6 +116,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToZero() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'types' => [ '0' => 'foo', @@ -100,6 +136,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataThrowsExceptionIfTypePointsToANotExistingField() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'notExists', @@ -125,6 +162,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToValueOfDatabaseField() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'aField', @@ -150,6 +188,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToZeroIfValueOfDatabaseFieldIsNotDefinedInTca() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'aField', @@ -175,6 +214,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToZeroIfValueOfDatabaseFieldIsEmptyString() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'aField', @@ -200,6 +240,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataThrowsExceptionIfValueTypesNotExistsAndNoFallbackExists() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'aField', @@ -225,6 +266,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToValueOfDefaultLanguageRecordIfConfiguredAsExclude() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'languageField' => 'sys_language_uid', @@ -260,6 +302,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToValueOfDefaultLanguageRecordIfConfiguredAsMergeIfNotBlank() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'languageField' => 'sys_language_uid', @@ -295,6 +338,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsRecordTypeValueToValueOfLocalizedRecordIfConfiguredAsMergeIfNotBlankButNotBlank() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'languageField' => 'sys_language_uid', @@ -330,6 +374,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataThrowsExceptionForForeignTypeConfigurationNotAsSelectOrGroup() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'localField:foreignField', @@ -359,6 +404,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataThrowsExceptionForForeignTypeIfPointerConfigurationHasNoTable() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'localField:foreignField', @@ -391,6 +437,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsTypeValueFromForeignTableRecord() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'localField:foreignField', @@ -434,6 +481,7 @@ class DatabaseRecordTypeValueTest extends UnitTestCase public function addDataSetsTypeValueFromNestedTcaGroupField() { $input = [ + 'recordTypeValue' => '', 'processedTca' => [ 'ctrl' => [ 'type' => 'uid_local:type', diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php index cd8557cc92ba..e904e76d554b 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php @@ -48,6 +48,23 @@ class InitializeProcessedTcaTest extends UnitTestCase $this->assertEquals($expected, $result['processedTca']); } + /** + * @test + */ + public function addDataKeepsGivenProcessedTca() + { + $input = [ + 'tableName' => 'aTable', + 'processedTca' => [ + 'columns' => [ + 'afield' => [], + ], + ], + ]; + $expected = $input; + $this->assertEquals($expected, $this->subject->addData($input)); + } + /** * @test */ diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php index 0f48598c424a..bda0637ad862 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php @@ -64,6 +64,53 @@ class TcaFlexPrepareTest extends UnitTestCase parent::tearDown(); } + /** + * @test + */ + public function addDataKeepsExistingDataStructure() + { + $input = [ + 'systemLanguageRows' => [], + 'tableName' => 'aTableName', + 'databaseRow' => [ + 'aField' => [ + 'data' => [], + 'meta' => [], + ], + ], + 'processedTca' => [ + 'columns' => [ + 'aField' => [ + 'config' => [ + 'type' => 'flex', + 'dataStructureIdentifier' => '{"type":"tca","tableName":"aTableName","fieldName":"aField","dataStructureKey":"default"}', + 'ds' => [ + 'sheets' => [ + 'sDEF' => [ + 'ROOT' => [ + 'type' => 'array', + 'el' => [ + 'aFlexField' => [ + 'label' => 'aFlexFieldLabel', + 'config' => [ + 'type' => 'input', + ], + ], + ], + ], + ], + ], + 'meta' => [], + ], + ], + ], + ], + ], + ]; + $expected = $input; + $this->assertEquals($expected, $this->subject->addData($input)); + } + /** * @test */ diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php index 35787a8f801b..f3d66dd20a87 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php @@ -175,13 +175,13 @@ class TcaSelectTreeItemsTest extends UnitTestCase ], ], ], + 'selectTreeCompileItems' => true, ]; $expected = $input; $expected['databaseRow']['aField'] = ['1']; $expected['processedTca']['columns']['aField']['config']['treeData'] = [ 'items' => [['fake', 'tree', 'data']], - 'selectedNodes' => [] ]; $this->assertEquals($expected, $this->subject->addData($input)); } @@ -247,6 +247,7 @@ class TcaSelectTreeItemsTest extends UnitTestCase ], ], ], + 'selectTreeCompileItems' => true, ]; $this->subject->addData($input); diff --git a/typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php b/typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php index 823d18b1b95e..d77facf0619f 100644 --- a/typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php +++ b/typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php @@ -65,7 +65,6 @@ class AddPageInPageModuleCest // Check empty $I->amGoingTo('check empty error'); - $I->click($saveButton); $I->wait(2); $editControllerDiv = '#EditDocumentController > div'; $generalTab = $editControllerDiv . ' > div:nth-child(1) > ul > li'; -- GitLab