[TASK] TCA tree refactoring 00/50700/8
authorChristian Kuhn <lolli@schwarzbu.ch>
Thu, 17 Nov 2016 17:51:13 +0000 (18:51 +0100)
committerTymoteusz Motylewski <t.motylewski@gmail.com>
Fri, 18 Nov 2016 14:53:25 +0000 (15:53 +0100)
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>
16 files changed:
typo3/sysext/backend/Classes/Controller/SelectTreeController.php
typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php
typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
typo3/sysext/backend/Classes/Form/FormDataCompiler.php
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php
typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js
typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php
typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php

index 01a25f6..a0f6423 100644 (file)
@@ -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'];
-    }
 }
index 5e8d491..c8b7cc8 100644 (file)
@@ -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'],
index d1ae3ff..4af7e77 100644 (file)
@@ -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) . '"';
index 24fff34..ff8729c 100644 (file)
@@ -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:
index e78cb7a..dc8a85d 100644 (file)
@@ -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'];
index 2d807f4..4cfb47a 100644 (file)
@@ -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(
index 054fd30..e0f2843 100644 (file)
@@ -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'] = [];
         }
index ada1c46..b408267 100644 (file)
@@ -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)) {
index eede711..92fed04 100644 (file)
@@ -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;
@@ -187,31 +189,6 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide
     }
 
     /**
-     * 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
      *
      * @param array $fieldConfig
index a596eae..c9f5c76 100644 (file)
@@ -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 (file)
index 0000000..5dddc63
--- /dev/null
@@ -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());
+    }
+}
index 2193388..89ef3b0 100644 (file)
@@ -61,9 +61,44 @@ class DatabaseRecordTypeValueTest extends UnitTestCase
     /**
      * @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',
index cd8557c..e904e76 100644 (file)
@@ -51,6 +51,23 @@ class InitializeProcessedTcaTest extends UnitTestCase
     /**
      * @test
      */
+    public function addDataKeepsGivenProcessedTca()
+    {
+        $input = [
+            'tableName' => 'aTable',
+            'processedTca' => [
+                'columns' => [
+                    'afield' => [],
+                ],
+            ],
+        ];
+        $expected = $input;
+        $this->assertEquals($expected, $this->subject->addData($input));
+    }
+
+    /**
+     * @test
+     */
     public function addDataThrowsExceptionIfGlobalTableTcaIsNotSet()
     {
         $input = [
index 0f48598..bda0637 100644 (file)
@@ -67,6 +67,53 @@ class TcaFlexPrepareTest extends UnitTestCase
     /**
      * @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
+     */
     public function addDataSetsParsedDataStructureArray()
     {
         $input = [
index 35787a8..f3d66dd 100644 (file)
@@ -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);
index 823d18b..d77facf 100644 (file)
@@ -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';