diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
index 75ad4f6f1fc6914b369bba492cfc8c89b276d7ab..2708c5078911d08c99a1fcacb849f331cb379892 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
@@ -16,9 +16,6 @@ namespace TYPO3\CMS\Backend\Form\Element;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Tree\TableConfiguration\ExtJsArrayTreeRenderer;
-use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
-use TYPO3\CMS\Core\Tree\TableConfiguration\TreeDataProviderFactory;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -29,101 +26,108 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 class SelectTreeElement extends AbstractFormElement
 {
+
+    /**
+     * Default height of the tree in pixels.
+     *
+     * @const
+     */
+    const DEFAULT_HEIGHT = 280;
+
+    /**
+     * Default width of the tree in pixels.
+     *
+     * @const
+     */
+    const DEFAULT_WIDTH = 280;
+
     /**
      * Render tree widget
      *
      * @return array As defined in initializeResultArray() of AbstractNode
+     * @see AbstractNode::initializeResultArray()
      */
     public function render()
     {
-        $table = $this->data['tableName'];
-        $field = $this->data['fieldName'];
-        $row = $this->data['databaseRow'];
+        $resultArray = $this->initializeResultArray();
         $parameterArray = $this->data['parameterArray'];
+        $formElementId = md5($parameterArray['itemFormElName']);
 
         // Field configuration from TCA:
         $config = $parameterArray['fieldConf']['config'];
 
-        $possibleSelectboxItems = $config['items'];
+        $resultArray['extJSCODE'] .= LF . $this->generateJavascript($formElementId);
 
-        $selectedNodes = $parameterArray['itemFormElValue'];
+        $html = [];
+        $html[] = '<div class="typo3-tceforms-tree">';
+        $html[] = '    <input class="treeRecord" type="hidden"';
+        $html[] = '           ' . $this->getValidationDataAsDataAttribute($parameterArray['fieldConf']['config']);
+        $html[] = '           data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
+        $html[] = '           data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
+        $html[] = '           name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
+        $html[] = '           id="treeinput' . $formElementId . '"';
+        $html[] = '           value="' . htmlspecialchars(implode(',', $config['treeData']['selectedNodes'])) . '"';
+        $html[] = '    />';
+        $html[] = '</div>';
+        $html[] = '<div id="tree_' . $formElementId . '"></div>';
 
-        $selectedNodesForApi = array();
-        foreach ($selectedNodes as $selectedNode) {
-            // @todo: this is ugly - the "old" pipe based value|label syntax is re-created here at the moment
-            foreach ($possibleSelectboxItems as $possibleSelectboxItem) {
-                if ((string)$possibleSelectboxItem[1] === (string)$selectedNode) {
-                    $selectedNodesForApi[] = $selectedNode . '|' . rawurlencode($possibleSelectboxItem[0]);
-                }
-            }
-        }
+        $resultArray['html'] = implode(LF, $html);
 
-        $allowedUids = array();
-        foreach ($possibleSelectboxItems as $item) {
-            if ((int)$item[1] > 0) {
-                $allowedUids[] = $item[1];
-            }
-        }
-        $treeDataProvider = TreeDataProviderFactory::getDataProvider($config, $table, $field, $row);
-        $treeDataProvider->setSelectedList(implode(',', $selectedNodes));
-        $treeDataProvider->setItemWhiteList($allowedUids);
-        $treeDataProvider->initializeTreeData();
-        $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
-        $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
-        $tree->setDataProvider($treeDataProvider);
-        $tree->setNodeRenderer($treeRenderer);
-        $treeData = $tree->render();
-        $itemArray = array();
-
-        /**
-         * @todo: Small bug here: In the past, this was the "not processed list" of default items, but now it is
-         * @todo: a full list of elements. This needs to be fixed later, so "additional" default items are shown again.
-        if (is_array($config['items'])) {
-            foreach ($config['items'] as $additionalItem) {
-                if ($additionalItem[1] !== '--div--') {
-                    $item = new \stdClass();
-                    $item->uid = $additionalItem[1];
-                    $item->text = $this->getLanguageService()->sL($additionalItem[0]);
-                    $item->selectable = TRUE;
-                    $item->leaf = TRUE;
-                    $item->checked = in_array($additionalItem[1], $selectedNodes);
-                    if (file_exists(PATH_typo3 . $additionalItem[3])) {
-                        $item->icon = $additionalItem[3];
-                    } elseif (trim($additionalItem[3]) !== '') {
-                        $item->iconCls = IconUtility::getSpriteIconClasses($additionalItem[3]);
-                    }
-                    $itemArray[] = $item;
-                }
-            }
+        // Wizards:
+        if (empty($config['readOnly'])) {
+            $resultArray['html'] = $this->renderWizards(
+                [$resultArray['html']],
+                $config['wizards'],
+                $this->data['tableName'],
+                $this->data['databaseRow'],
+                $this->data['fieldName'],
+                $parameterArray,
+                $parameterArray['itemFormElName'],
+                BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
+            );
         }
-        */
 
-        $itemArray[] = $treeData;
-        $id = md5($parameterArray['itemFormElName']);
+        return $resultArray;
+    }
+
+    /**
+     * Generates the Ext JS tree JavaScript code.
+     *
+     * @param string $formElementId The HTML element ID of the tree select field.
+     * @return string
+     */
+    protected function generateJavascript($formElementId)
+    {
+        $table = $this->data['tableName'];
+        $field = $this->data['fieldName'];
+        $parameterArray = $this->data['parameterArray'];
+        $config = $parameterArray['fieldConf']['config'];
+
+        $selectedNodes = $this->data['databaseRow'][$this->data['fieldName']];
+
+        $disabled = !empty($config['readOnly']) ? 'true' : 'false';
+        $maxItems = $config['maxitems'] ? (int)$config['maxitems'] : 99999;
+        $exclusiveKeys = !empty($config['exclusiveKeys']) ? $config['exclusiveKeys'] : '';
+
+        $appearance = !empty($config['treeConfig']['appearance']) ? $config['treeConfig']['appearance'] : [];
+        $width = isset($appearance['width']) ? (int)$appearance['width'] : static::DEFAULT_WIDTH;
         if (isset($config['size']) && (int)$config['size'] > 0) {
             $height = (int)$config['size'] * 20;
         } else {
-            $height = 280;
+            $height = static::DEFAULT_HEIGHT;
         }
+        $showHeader = !empty($appearance['showHeader']);
+        $expanded = !empty($appearance['expandAll']);
+        $allowRecursiveMode = !empty($appearance['allowRecursiveMode']) ? 'true' : 'false';
+
         $autoSizeMax = null;
         if (isset($config['autoSizeMax']) && (int)$config['autoSizeMax'] > 0) {
             $autoSizeMax = (int)$config['autoSizeMax'] * 20;
         }
-        $header = false;
-        $expanded = false;
-        $width = 280;
-        $appearance = $config['treeConfig']['appearance'];
-        if (is_array($appearance)) {
-            $header = (bool)$appearance['showHeader'];
-            $expanded = (bool)$appearance['expandAll'];
-            if (isset($appearance['width'])) {
-                $width = (int)$appearance['width'];
-            }
-        }
-        $onChange = '';
-        if ($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) {
-            $onChange = $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
-        }
+
+        $onChange = !empty($parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) ? $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] : '';
+        $onChange .= !empty($parameterArray['fieldChangeFunc']['alert']) ? $parameterArray['fieldChangeFunc']['alert'] : '';
+
         // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
         // This is used for eg. "type" fields and others configured with "requestUpdate"
         if (
@@ -138,97 +142,79 @@ class SelectTreeElement extends AbstractFormElement
                 $onChange .= 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
             }
         }
-        $html = '
-			<div class="typo3-tceforms-tree">
-				<input class="treeRecord" type="hidden" '
-                    .  $this->getValidationDataAsDataAttribute($config)
-                    . ' data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
-                    . ' data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
-                    . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars(implode(',', $selectedNodesForApi)) . '" />
-			</div>
-			<div id="tree_' . $id . '">
-
-			</div>';
 
-        // Wizards:
-        if (empty($config['readOnly'])) {
-            $html = $this->renderWizards(
-                array($html),
-                $config['wizards'],
-                $table,
-                $row,
-                $field,
-                $parameterArray,
-                $parameterArray['itemFormElName'],
-                BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
-            );
+        $javascript = [];
+        $javascript[] = 'Ext.onReady(function() {';
+        $javascript[] = '    TYPO3.Components.Tree.StandardTreeItemData["' . $formElementId . '"] = ' . json_encode($config['treeData']['items']) . ';';
+        $javascript[] = '    var tree' . $formElementId . ' = new TYPO3.Components.Tree.StandardTree({';
+        $javascript[] = '        id: "' . $formElementId . '",';
+        $javascript[] = '        stateful: true,';
+        $javascript[] = '        stateId: "tcaTrees." + this.ucId,';
+        $javascript[] = '        stateEvents: [],';
+        $javascript[] = '        showHeader: ' . (int)$showHeader . ',';
+        $javascript[] = '        onChange: ' . GeneralUtility::quoteJSvalue($onChange) . ',';
+        $javascript[] = '        countSelectedNodes: ' . count($selectedNodes) . ',';
+        $javascript[] = '        width: ' . $width . ',';
+        $javascript[] = '        rendering: false,';
+        $javascript[] = '        listeners: {';
+        $javascript[] = '            click: function(node, event) {';
+        $javascript[] = '                if (typeof(node.attributes.checked) == "boolean") {';
+        $javascript[] = '                    node.attributes.checked = ! node.attributes.checked;';
+        $javascript[] = '                    node.getUI().toggleCheck(node.attributes.checked);';
+        $javascript[] = '                }';
+        $javascript[] = '            },';
+        $javascript[] = '            dblclick: function(node, event) {';
+        $javascript[] = '                if (typeof(node.attributes.checked) == "boolean") {';
+        $javascript[] = '                    node.attributes.checked = ! node.attributes.checked;';
+        $javascript[] = '                    node.getUI().toggleCheck(node.attributes.checked);';
+        $javascript[] = '                }';
+        $javascript[] = '            },';
+        $javascript[] = '            checkchange: TYPO3.Components.Tree.TcaCheckChangeHandler,';
+        $javascript[] = '            collapsenode: function(node) {';
+        $javascript[] = '                if (node.id !== "root" && !this.rendering) {';
+        $javascript[] = '                    top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);';
+        $javascript[] = '                }';
+        $javascript[] = '            },';
+        $javascript[] = '            expandnode: function(node) {';
+        $javascript[] = '                if (node.id !== "root" && !this.rendering) {';
+        $javascript[] = '                    top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);';
+        $javascript[] = '                }';
+        $javascript[] = '            },';
+        $javascript[] = '            beforerender: function(treeCmp) {';
+        $javascript[] = '                this.rendering = true';
+        $javascript[] = '                // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.';
+        $javascript[] = '                if (Ext.fly(treeCmp.getId())) {';
+        $javascript[] = '                    return false;';
+        $javascript[] = '                }';
+        $javascript[] = '            },';
+        $javascript[] = '            afterrender: function(treeCmp) {';
+        if ($expanded) {
+            $javascript[] = '                treeCmp.expandAll();';
         }
-        $resultArray = $this->initializeResultArray();
-        $resultArray['extJSCODE'] .= LF .
-            'Ext.onReady(function() {
-			TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . json_encode($itemArray) . ';
-			var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
-				id: "' . $id . '",
-				showHeader: ' . (int)$header . ',
-				onChange: ' . GeneralUtility::quoteJSvalue($onChange) . ',
-				countSelectedNodes: ' . count($selectedNodes) . ',
-				width: ' . (int)$width . ',
-				rendering: false,
-				listeners: {
-					click: function(node, event) {
-						if (typeof(node.attributes.checked) == "boolean") {
-							node.attributes.checked = ! node.attributes.checked;
-							node.getUI().toggleCheck(node.attributes.checked);
-						}
-					},
-					dblclick: function(node, event) {
-						if (typeof(node.attributes.checked) == "boolean") {
-							node.attributes.checked = ! node.attributes.checked;
-							node.getUI().toggleCheck(node.attributes.checked);
-						}
-					},
-					checkchange: TYPO3.Components.Tree.TcaCheckChangeHandler,
-					collapsenode: function(node) {
-						if (node.id !== "root" && !this.rendering) {
-							top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
-						}
-					},
-					expandnode: function(node) {
-						if (node.id !== "root" && !this.rendering) {
-							top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
-						}
-					},
-					beforerender: function(treeCmp) {
-					    this.rendering = true;
-						// Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
-						if (Ext.fly(treeCmp.getId())) {
-							return false;
-						}
-					},
-					afterrender: function(treeCmp) {
-					    ' . ($expanded ? 'treeCmp.expandAll();' : '') . '
-					    this.rendering = false;
-					}
-				},
-				tcaMaxItems: ' . ($config['maxitems'] ? (int)$config['maxitems'] : 99999) . ',
-				tcaSelectRecursiveAllowed: ' . ($appearance['allowRecursiveMode'] ? 'true' : 'false') . ',
-				tcaSelectRecursive: false,
-				tcaExclusiveKeys: "' . ($config['exclusiveKeys'] ? $config['exclusiveKeys'] : '') . '",
-				ucId: "' . md5(($table . '|' . $field)) . '",
-				selModel: TYPO3.Components.Tree.EmptySelectionModel,
-				disabled: ' . ($config['readOnly'] ? 'true' : 'false') . '
-			});' . LF .
-            ($autoSizeMax
-                ? 'tree' . $id . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";'
-                : 'tree' . $id . '.height = ' . $height . ';'
-            ) . LF .
-            'window.setTimeout(function() {
-				tree' . $id . '.render("tree_' . $id . '");
-			}, 200);
-		});';
-        $resultArray['html'] = $html;
+        $javascript[] = '                this.rendering = false;';
+        $javascript[] = '            }';
+        $javascript[] = '        },';
+        $javascript[] = '        tcaMaxItems: ' . $maxItems . ',';
+        $javascript[] = '        tcaSelectRecursiveAllowed: ' . $allowRecursiveMode . ',';
+        $javascript[] = '        tcaSelectRecursive: false,';
+        $javascript[] = '        tcaExclusiveKeys: "' . $exclusiveKeys . '",';
+        $javascript[] = '        ucId: "' . md5(($table . '|' . $field)) . '",';
+        $javascript[] = '        selModel: TYPO3.Components.Tree.EmptySelectionModel,';
+        $javascript[] = '        disabled: ' . $disabled;
+        $javascript[] = '    });';
 
-        return $resultArray;
+        if ($autoSizeMax) {
+            $javascript[] = '    tree' . $formElementId . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";';
+        } else {
+            $javascript[] = '    tree' . $formElementId . '.height = ' . $height . ';';
+        }
+
+        $javascript[] = '    window.setTimeout(function() {';
+        $javascript[] = '        tree' . $formElementId . '.render("tree_' . $formElementId . '");';
+        $javascript[] = '    }, 200);';
+        $javascript[] = '});';
+
+        return implode(LF, $javascript);
     }
 
     /**
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
index ab39477783929de3e19968dd3b7f17d991cac3ae..4526c21d1c1da31a90514dab0f4c2d5d5fe1bebc 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
@@ -14,9 +14,20 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
+use TYPO3\CMS\Backend\Module\ModuleLoader;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Lang\LanguageService;
 
 /**
@@ -89,6 +100,8 @@ abstract class AbstractItemProvider
      * TCEFORMS.aTable.aField[.types][.aType].addItems.aValue = aLabel,
      * with type specific options merged by pageTsConfig already
      *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
      * @param array $result result array
      * @param string $fieldName Current handle field name
      * @param array $items Incoming items
@@ -120,6 +133,1104 @@ abstract class AbstractItemProvider
         return $items;
     }
 
+    /**
+     * TCA config "special" evaluation. Add them to $items
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     * @throws \UnexpectedValueException
+     */
+    protected function addItemsFromSpecial(array $result, $fieldName, array $items)
+    {
+        // Guard
+        if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
+            || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
+        ) {
+            return $items;
+        }
+
+        $languageService = $this->getLanguageService();
+        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+
+        $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
+        if ($special === 'tables') {
+            foreach ($GLOBALS['TCA'] as $currentTable => $_) {
+                if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
+                    // Hide "admin only" tables
+                    continue;
+                }
+                $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
+                $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, []);
+                $helpText = [];
+                $languageService->loadSingleTableDescription($currentTable);
+                // @todo: check if this actually works, currently help texts are missing
+                $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
+                if (!empty($helpTextArray['description'])) {
+                    $helpText['description'] = $helpTextArray['description'];
+                }
+                $items[] = [$label, $currentTable, $icon, $helpText];
+            }
+        } elseif ($special === 'pagetypes') {
+            if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
+                && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
+            ) {
+                $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
+                foreach ($specialItems as $specialItem) {
+                    if (!is_array($specialItem) || $specialItem[1] === '--div--') {
+                        // Skip non arrays and divider items
+                        continue;
+                    }
+                    $label = $specialItem[0];
+                    $value = $specialItem[1];
+                    $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $specialItem[1]]);
+                    $items[] = [$label, $value, $icon];
+                }
+            }
+        } elseif ($special === 'exclude') {
+            $excludeArrays = $this->getExcludeFields();
+            foreach ($excludeArrays as $excludeArray) {
+                list($theTable, $theFullField) = explode(':', $excludeArray[1]);
+                // If the field comes from a FlexForm, the syntax is more complex
+                $theFieldParts = explode(';', $theFullField);
+                $theField = array_pop($theFieldParts);
+                // Add header if not yet set for table:
+                if (!array_key_exists($theTable, $items)) {
+                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($theTable, []);
+                    $items[$theTable] = [
+                        $GLOBALS['TCA'][$theTable]['ctrl']['title'],
+                        '--div--',
+                        $icon
+                    ];
+                }
+                // Add help text
+                $helpText = [];
+                $languageService->loadSingleTableDescription($theTable);
+                $helpTextArray = $GLOBALS['TCA_DESCR'][$theTable]['columns'][$theFullField];
+                if (!empty($helpTextArray['description'])) {
+                    $helpText['description'] = $helpTextArray['description'];
+                }
+                // Item configuration:
+                // @todo: the title calculation does not work well for flex form fields, see unit tests
+                $items[] = [
+                    rtrim($languageService->sL($GLOBALS['TCA'][$theTable]['columns'][$theField]['label']), ':') . ' (' . $theField . ')',
+                    $excludeArray[1],
+                    'empty-empty',
+                    $helpText
+                ];
+            }
+        } elseif ($special === 'explicitValues') {
+            $theTypes = $this->getExplicitAuthFieldValues();
+            $icons = [
+                'ALLOW' => 'status-status-permission-granted',
+                'DENY' => 'status-status-permission-denied'
+            ];
+            // Traverse types:
+            foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
+                if (is_array($theTypeArrays['items'])) {
+                    // Add header:
+                    $items[] = [
+                        $theTypeArrays['tableFieldLabel'],
+                        '--div--',
+                    ];
+                    // Traverse options for this field:
+                    foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
+                        // Add item to be selected:
+                        $items[] = [
+                            '[' . $itemContent[2] . '] ' . $itemContent[1],
+                            $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
+                            $icons[$itemContent[0]]
+                        ];
+                    }
+                }
+            }
+        } elseif ($special === 'languages') {
+            // @todo: This should probably use the data provided by DatabaseSystemLanguageRows sitting in $result['systemLanguageRows']
+            /** @var TranslationConfigurationProvider $translationConfigurationProvider */
+            $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
+            $languages = $translationConfigurationProvider->getSystemLanguages();
+            foreach ($languages as $language) {
+                if ($language['uid'] !== -1) {
+                    $items[] = [
+                        0 => $language['title'] . ' [' . $language['uid'] . ']',
+                        1 => $language['uid'],
+                        2 => $language['flagIcon']
+                    ];
+                }
+            }
+        } elseif ($special === 'custom') {
+            $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
+            if (is_array($customOptions)) {
+                foreach ($customOptions as $coKey => $coValue) {
+                    if (is_array($coValue['items'])) {
+                        // Add header:
+                        $items[] = [
+                            $languageService->sL($coValue['header']),
+                            '--div--'
+                        ];
+                        // Traverse items:
+                        foreach ($coValue['items'] as $itemKey => $itemCfg) {
+                            $icon = 'empty-empty';
+                            $helpText = [];
+                            if (!empty($itemCfg[2])) {
+                                $helpText['description'] = $languageService->sL($itemCfg[2]);
+                            }
+                            $items[] = [
+                                $languageService->sL($itemCfg[0]),
+                                $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
+                                $icon,
+                                $helpText
+                            ];
+                        }
+                    }
+                }
+            }
+        } elseif ($special === 'modListGroup' || $special === 'modListUser') {
+            $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
+            $loadModules->load($GLOBALS['TBE_MODULES']);
+            $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
+            if (is_array($modList)) {
+                foreach ($modList as $theMod) {
+                    // Icon:
+                    $icon = $languageService->moduleLabels['tabs_images'][$theMod . '_tab'];
+                    if ($icon) {
+                        $icon = '../' . PathUtility::stripPathSitePrefix($icon);
+                    }
+                    // Add help text
+                    $helpText = [
+                        'title' => $languageService->moduleLabels['labels'][$theMod . '_tablabel'],
+                        'description' => $languageService->moduleLabels['labels'][$theMod . '_tabdescr']
+                    ];
+
+                    $label = '';
+                    // Add label for main module:
+                    $pp = explode('_', $theMod);
+                    if (count($pp) > 1) {
+                        $label .= $languageService->moduleLabels['tabs'][($pp[0] . '_tab')] . '>';
+                    }
+                    // Add modules own label now:
+                    $label .= $languageService->moduleLabels['tabs'][$theMod . '_tab'];
+
+                    // Item configuration:
+                    $items[] = [$label, $theMod, $icon, $helpText];
+                }
+            }
+        } else {
+            throw new \UnexpectedValueException(
+                'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
+                1439298496
+            );
+        }
+
+        return $items;
+    }
+
+    /**
+     * TCA config "fileFolder" evaluation. Add them to $items
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function addItemsFromFolder(array $result, $fieldName, array $items)
+    {
+        if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
+            || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
+        ) {
+            return $items;
+        }
+
+        $fileFolder = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
+        $fileFolder = GeneralUtility::getFileAbsFileName($fileFolder);
+        $fileFolder = rtrim($fileFolder, '/') . '/';
+
+        if (@is_dir($fileFolder)) {
+            $fileExtensionList = '';
+            if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
+                && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
+            ) {
+                $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
+            }
+            $recursionLevels = isset($fieldValue['config']['fileFolder_recursions'])
+                ? MathUtility::forceIntegerInRange($fieldValue['config']['fileFolder_recursions'], 0, 99)
+                : 99;
+            $fileArray = GeneralUtility::getAllFilesAndFoldersInPath([], $fileFolder, $fileExtensionList, 0, $recursionLevels);
+            $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
+            foreach ($fileArray as $fileReference) {
+                $fileInformation = pathinfo($fileReference);
+                $icon = GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($fileInformation['extension']))
+                    ? '../' . PathUtility::stripPathSitePrefix($fileFolder) . $fileReference
+                    : '';
+                $items[] = [
+                    $fileReference,
+                    $fileReference,
+                    $icon
+                ];
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * TCA config "foreign_table" evaluation. Add them to $items
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
+    {
+        // Guard
+        if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
+            || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
+        ) {
+            return $items;
+        }
+
+        $languageService = $this->getLanguageService();
+        $database = $this->getDatabaseConnection();
+
+        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
+        $foreignTableQueryArray = $this->buildForeignTableQuery($result, $fieldName);
+        $queryResource = $database->exec_SELECT_queryArray($foreignTableQueryArray);
+
+        // Early return on error with flash message
+        $databaseError = $database->sql_error();
+        if (!empty($databaseError)) {
+            $msg = htmlspecialchars($databaseError) . '<br />' . LF;
+            $msg .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch');
+            $msgTitle = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch_title');
+            /** @var $flashMessage FlashMessage */
+            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
+            /** @var $flashMessageService FlashMessageService */
+            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+            /** @var $defaultFlashMessageQueue FlashMessageQueue */
+            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+            $defaultFlashMessageQueue->enqueue($flashMessage);
+            $database->sql_free_result($queryResource);
+            return $items;
+        }
+
+        $labelPrefix = '';
+        if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
+            $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
+            $labelPrefix = $languageService->sL($labelPrefix);
+        }
+        $iconFieldName = '';
+        if (!empty($result['processedTca']['ctrl']['selicon_field'])) {
+            $iconFieldName = $result['processedTca']['ctrl']['selicon_field'];
+        }
+        $iconPath = '';
+        if (!empty($result['processedTca']['ctrl']['selicon_field_path'])) {
+            $iconPath = $result['processedTca']['ctrl']['selicon_field_path'];
+        }
+
+        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+
+        while ($foreignRow = $database->sql_fetch_assoc($queryResource)) {
+            BackendUtility::workspaceOL($foreignTable, $foreignRow);
+            if (is_array($foreignRow)) {
+                // Prepare the icon if available:
+                if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
+                    $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
+                    $icon = '../' . $iconPath . '/' . trim($iParts[0]);
+                } else {
+                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
+                }
+                // Add the item
+                $items[] = [
+                    $labelPrefix . htmlspecialchars(BackendUtility::getRecordTitle($foreignTable, $foreignRow)),
+                    $foreignRow['uid'],
+                    $icon
+                ];
+            }
+        }
+
+        $database->sql_free_result($queryResource);
+
+        return $items;
+    }
+
+    /**
+     * Remove items using "keepItems" pageTsConfig
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
+    {
+        $table = $result['tableName'];
+        if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
+            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
+        ) {
+            return $items;
+        }
+
+        return ArrayUtility::keepItemsInArray(
+            $items,
+            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
+            function ($value) {
+                return $value[1];
+            }
+        );
+    }
+
+    /**
+     * Remove items using "removeItems" pageTsConfig
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
+    {
+        $table = $result['tableName'];
+        if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
+            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
+        ) {
+            return $items;
+        }
+
+        $removeItems = GeneralUtility::trimExplode(
+            ',',
+            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
+            true
+        );
+        foreach ($items as $key => $itemValues) {
+            if (in_array($itemValues[1], $removeItems)) {
+                unset($items[$key]);
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * Remove items user restriction on language field
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
+    {
+        // Guard clause returns if not a language field is handled
+        if (empty($result['processedTca']['ctrl']['languageField'])
+            || $result['processedTca']['ctrl']['languageField'] !== $fieldName
+        ) {
+            return $items;
+        }
+
+        $backendUser = $this->getBackendUser();
+        foreach ($items as $key => $itemValues) {
+            if (!$backendUser->checkLanguageAccess($itemValues[1])) {
+                unset($items[$key]);
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * Remove items by user restriction on authMode items
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
+    {
+        // Guard clause returns early if no authMode field is configured
+        if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
+            || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
+        ) {
+            return $items;
+        }
+
+        $backendUser = $this->getBackendUser();
+        $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
+        foreach ($items as $key => $itemValues) {
+            // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
+            if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
+                unset($items[$key]);
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * Remove items if doktype is handled for non admin users
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @param array $items Incoming items
+     * @return array Modified item array
+     */
+    protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
+    {
+        $table = $result['tableName'];
+        $backendUser = $this->getBackendUser();
+        // Guard clause returns if not correct table and field or if user is admin
+        if ($table !== 'pages' && $table !== 'pages_language_overlay'
+            || $fieldName !== 'doktype' || $backendUser->isAdmin()
+        ) {
+            return $items;
+        }
+
+        $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
+        foreach ($items as $key => $itemValues) {
+            if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
+                unset($items[$key]);
+            }
+        }
+
+        return $items;
+    }
+
+    /**
+     * Returns an array with the exclude fields as defined in TCA and FlexForms
+     * Used for listing the exclude fields in be_groups forms.
+     *
+     * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
+     *               and FlexForms (fieldName, table:extKey;sheetName;fieldName)
+     */
+    protected function getExcludeFields()
+    {
+        $languageService = $this->getLanguageService();
+        $finalExcludeArray = [];
+
+        // Fetch translations for table names
+        $tableToTranslation = [];
+        // All TCA keys
+        foreach ($GLOBALS['TCA'] as $table => $conf) {
+            $tableToTranslation[$table] = $languageService->sl($conf['ctrl']['title']);
+        }
+        // Sort by translations
+        asort($tableToTranslation);
+        foreach ($tableToTranslation as $table => $translatedTable) {
+            $excludeArrayTable = [];
+
+            // All field names configured and not restricted to admins
+            if (is_array($GLOBALS['TCA'][$table]['columns'])
+                && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
+                && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
+            ) {
+                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
+                    if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
+                        // Get human readable names of fields
+                        $translatedField = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
+                        // Add entry
+                        $excludeArrayTable[] = [$translatedTable . ': ' . $translatedField, $table . ':' . $field];
+                    }
+                }
+            }
+            // All FlexForm fields
+            $flexFormArray = $this->getRegisteredFlexForms($table);
+            foreach ($flexFormArray as $tableField => $flexForms) {
+                // Prefix for field label, e.g. "Plugin Options:"
+                $labelPrefix = '';
+                if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
+                    $labelPrefix = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
+                }
+                // Get all sheets and title
+                foreach ($flexForms as $extIdent => $extConf) {
+                    $extTitle = $languageService->sl($extConf['title']);
+                    // Get all fields in sheet
+                    foreach ($extConf['ds']['sheets'] as $sheetName => $sheet) {
+                        if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
+                            continue;
+                        }
+                        foreach ($sheet['ROOT']['el'] as $fieldName => $field) {
+                            // Use only fields that have exclude flag set
+                            if (empty($field['TCEforms']['exclude'])) {
+                                continue;
+                            }
+                            $fieldLabel = !empty($field['TCEforms']['label']) ? $languageService->sl($field['TCEforms']['label']) : $fieldName;
+                            $fieldIdent = $table . ':' . $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $fieldName;
+                            $excludeArrayTable[] = [trim($labelPrefix . ' ' . $extTitle, ': ') . ': ' . $fieldLabel, $fieldIdent];
+                        }
+                    }
+                }
+            }
+            // Sort fields by the translated value
+            if (!empty($excludeArrayTable)) {
+                usort($excludeArrayTable, function (array $array1, array $array2) {
+                    $array1 = reset($array1);
+                    $array2 = reset($array2);
+                    if (is_string($array1) && is_string($array2)) {
+                        return strcasecmp($array1, $array2);
+                    }
+                    return 0;
+                });
+                $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
+            }
+        }
+
+        return $finalExcludeArray;
+    }
+
+    /**
+     * Returns all registered FlexForm definitions with title and fields
+     *
+     * @param string $table Table to handle
+     * @return array Data structures with speaking extension title
+     */
+    protected function getRegisteredFlexForms($table)
+    {
+        if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
+            return [];
+        }
+        $flexForms = [];
+        foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
+            if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] == 'flex') {
+                $flexForms[$tableField] = [];
+                unset($fieldConf['config']['ds']['default']);
+                // Get pointer fields
+                $pointerFields = !empty($fieldConf['config']['ds_pointerField']) ? $fieldConf['config']['ds_pointerField'] : 'list_type,CType';
+                $pointerFields = GeneralUtility::trimExplode(',', $pointerFields);
+                // Get FlexForms
+                foreach ($fieldConf['config']['ds'] as $flexFormKey => $dataStructure) {
+                    // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
+                    $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
+                    $extIdent = $identFields[0];
+                    if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
+                        $extIdent = $identFields[1];
+                    }
+                    // Load external file references
+                    if (!is_array($dataStructure)) {
+                        $file = GeneralUtility::getFileAbsFileName(str_ireplace('FILE:', '', $dataStructure));
+                        if ($file && @is_file($file)) {
+                            $dataStructure = GeneralUtility::getUrl($file);
+                        }
+                        $dataStructure = GeneralUtility::xml2array($dataStructure);
+                        if (!is_array($dataStructure)) {
+                            continue;
+                        }
+                    }
+                    // Get flexform content
+                    $dataStructure = GeneralUtility::resolveAllSheetsInDS($dataStructure);
+                    if (empty($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
+                        continue;
+                    }
+                    // Use DS pointer to get extension title from TCA
+                    // @todo: I don't understand this code ... does it make sense at all?
+                    $title = $extIdent;
+                    $keyFields = GeneralUtility::trimExplode(',', $flexFormKey);
+                    foreach ($pointerFields as $pointerKey => $pointerName) {
+                        if (empty($keyFields[$pointerKey]) || $keyFields[$pointerKey] === '*' || $keyFields[$pointerKey] === 'list') {
+                            continue;
+                        }
+                        if (!empty($GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'])) {
+                            $items = $GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'];
+                            if (!is_array($items)) {
+                                continue;
+                            }
+                            foreach ($items as $itemConf) {
+                                if (!empty($itemConf[0]) && !empty($itemConf[1]) && $itemConf[1] == $keyFields[$pointerKey]) {
+                                    $title = $itemConf[0];
+                                    break 2;
+                                }
+                            }
+                        }
+                    }
+                    $flexForms[$tableField][$extIdent] = [
+                        'title' => $title,
+                        'ds' => $dataStructure
+                    ];
+                }
+            }
+        }
+        return $flexForms;
+    }
+
+    /**
+     * Returns an array with explicit Allow/Deny fields.
+     * Used for listing these field/value pairs in be_groups forms
+     *
+     * @return array Array with information from all of $GLOBALS['TCA']
+     */
+    protected function getExplicitAuthFieldValues()
+    {
+        $languageService = static::getLanguageService();
+        $adLabel = [
+            'ALLOW' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.allow'),
+            'DENY' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.deny')
+        ];
+        $allowDenyOptions = [];
+        foreach ($GLOBALS['TCA'] as $table => $_) {
+            // All field names configured:
+            if (is_array($GLOBALS['TCA'][$table]['columns'])) {
+                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
+                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
+                    if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
+                        // Check for items
+                        if (is_array($fieldConfig['items'])) {
+                            // Get Human Readable names of fields and table:
+                            $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
+                                $languageService->sl($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
+                                . $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
+                            foreach ($fieldConfig['items'] as $iVal) {
+                                // Values '' is not controlled by this setting.
+                                if ((string)$iVal[1] !== '') {
+                                    // Find iMode
+                                    $iMode = '';
+                                    switch ((string)$fieldConfig['authMode']) {
+                                        case 'explicitAllow':
+                                            $iMode = 'ALLOW';
+                                            break;
+                                        case 'explicitDeny':
+                                            $iMode = 'DENY';
+                                            break;
+                                        case 'individual':
+                                            if ($iVal[4] === 'EXPL_ALLOW') {
+                                                $iMode = 'ALLOW';
+                                            } elseif ($iVal[4] === 'EXPL_DENY') {
+                                                $iMode = 'DENY';
+                                            }
+                                            break;
+                                    }
+                                    // Set iMode
+                                    if ($iMode) {
+                                        $allowDenyOptions[$table . ':' . $field]['items'][$iVal[1]] = [
+                                            $iMode,
+                                            $languageService->sl($iVal[0]),
+                                            $adLabel[$iMode]
+                                        ];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return $allowDenyOptions;
+    }
+
+    /**
+     * Build query to fetch foreign records
+     *
+     * @param array $result Result array
+     * @param string $localFieldName Current handle field name
+     * @return array Query array ready to be executed via Database->exec_SELECT_queryArray()
+     * @throws \UnexpectedValueException
+     */
+    protected function buildForeignTableQuery(array $result, $localFieldName)
+    {
+        $backendUser = $this->getBackendUser();
+
+        $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
+
+        if (!is_array($GLOBALS['TCA'][$foreignTableName])) {
+            throw new \UnexpectedValueException(
+                'Field ' . $localFieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
+                . $foreignTableName . ', but this table is not defined in TCA',
+                1439569743
+            );
+        }
+
+        $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
+
+        $queryArray = [];
+        $queryArray['SELECT'] = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
+
+        // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
+        // rootLevel = 0 means that elements are not allowed on root level
+        // rootLevel = 1 means that elements are only on the root level (pid=0)
+        $rootLevel = 0;
+        if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
+            $rootLevel = $GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
+        }
+        $deleteClause = BackendUtility::deleteClause($foreignTableName);
+        if ($rootLevel == 1 || $rootLevel == -1) {
+            $pidWhere = $foreignTableName . '.pid' . (($rootLevel == -1) ? '<>-1' : '=0');
+            $queryArray['FROM'] = $foreignTableName;
+            $queryArray['WHERE'] = $pidWhere . $deleteClause . $foreignTableClauseArray['WHERE'];
+        } else {
+            $pageClause = $backendUser->getPagePermsClause(1);
+            if ($foreignTableName === 'pages') {
+                $queryArray['FROM'] = 'pages';
+                $queryArray['WHERE'] = '1=1' . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
+            } else {
+                $queryArray['FROM'] = $foreignTableName . ', pages';
+                $queryArray['WHERE'] = 'pages.uid=' . $foreignTableName . '.pid AND pages.deleted=0'
+                    . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
+            }
+        }
+
+        $queryArray['GROUPBY'] = $foreignTableClauseArray['GROUPBY'];
+        $queryArray['ORDERBY'] = $foreignTableClauseArray['ORDERBY'];
+        $queryArray['LIMIT'] = $foreignTableClauseArray['LIMIT'];
+
+        return $queryArray;
+    }
+
+    /**
+     * Replace markers in a where clause from TCA foreign_table_where
+     *
+     * ###REC_FIELD_[field name]###
+     * ###THIS_UID### - is current element uid (zero if new).
+     * ###CURRENT_PID### - is the current page id (pid of the record).
+     * ###SITEROOT###
+     * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
+     * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
+     * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
+     *
+     * @param array $result Result array
+     * @param string $foreignTableName Name of foreign table
+     * @param string $localFieldName Current handle field name
+     * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
+     */
+    protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
+    {
+        $database = $this->getDatabaseConnection();
+        $localTable = $result['tableName'];
+
+        $foreignTableClause = '';
+        if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
+            && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
+        ) {
+            $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
+            // Replace possible markers in query
+            if (strstr($foreignTableClause, '###REC_FIELD_')) {
+                // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
+                $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
+                foreach ($whereClauseParts as $key => $value) {
+                    if ($key !== 0) {
+                        // "field1###' AND ..." -> array("field1", "' AND ...")
+                        $whereClauseSubParts = explode('###', $value, 2);
+                        // @todo: Throw exception if there is no value? What happens for NEW records?
+                        $rowFieldValue = $result['databaseRow'][$whereClauseSubParts[0]];
+                        if (is_array($rowFieldValue)) {
+                            // If a select or group field is used here, it may have been processed already and
+                            // is now an array. Use first selected value in this case.
+                            $rowFieldValue = $rowFieldValue[0];
+                        }
+                        if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
+                            $whereClauseParts[$key] = $database->quoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
+                        } else {
+                            $whereClauseParts[$key] = $database->fullQuoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
+                        }
+                    }
+                }
+                $foreignTableClause = implode('', $whereClauseParts);
+            }
+
+            $siteRootUid = 0;
+            foreach ($result['rootline'] as $rootlinePage) {
+                if (!empty($rootlinePage['is_siteroot'])) {
+                    $siteRootUid = (int)$rootlinePage['uid'];
+                    break;
+                }
+            }
+            $pageTsConfigId = 0;
+            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']) {
+                $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
+            }
+            $pageTsConfigIdList = 0;
+            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']) {
+                $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
+                $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
+                $pageTsConfigIdList = [];
+                foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
+                    if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
+                        $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
+                    }
+                }
+                $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
+            }
+            $pageTsConfigString = '';
+            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']) {
+                $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
+                $pageTsConfigString = $database->quoteStr($pageTsConfigString, $foreignTableName);
+            }
+
+            $foreignTableClause = str_replace(
+                [
+                    '###CURRENT_PID###',
+                    '###THIS_UID###',
+                    '###SITEROOT###',
+                    '###PAGE_TSCONFIG_ID###',
+                    '###PAGE_TSCONFIG_IDLIST###',
+                    '###PAGE_TSCONFIG_STR###'
+                ],
+                [
+                    (int)$result['effectivePid'],
+                    (int)$result['databaseRow']['uid'],
+                    $siteRootUid,
+                    $pageTsConfigId,
+                    $pageTsConfigIdList,
+                    $pageTsConfigString
+                ],
+                $foreignTableClause
+            );
+        }
+
+        // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
+        // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
+        $foreignTableClause = ' ' . $foreignTableClause;
+        $foreignTableClauseArray = [
+            'WHERE' => '',
+            'GROUPBY' => '',
+            'ORDERBY' => '',
+            'LIMIT' => '',
+        ];
+        // Find LIMIT
+        $reg = [];
+        if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
+            $foreignTableClauseArray['LIMIT'] = trim($reg[2]);
+            $foreignTableClause = $reg[1];
+        }
+        // Find ORDER BY
+        $reg = [];
+        if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
+            $foreignTableClauseArray['ORDERBY'] = trim($reg[2]);
+            $foreignTableClause = $reg[1];
+        }
+        // Find GROUP BY
+        $reg = [];
+        if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
+            $foreignTableClauseArray['GROUPBY'] = trim($reg[2]);
+            $foreignTableClause = $reg[1];
+        }
+        // Rest is assumed to be "WHERE" clause
+        $foreignTableClauseArray['WHERE'] = $foreignTableClause;
+
+        return $foreignTableClauseArray;
+    }
+
+    /**
+     * Validate and sanitize database row values of the select field with the given name.
+     * Creates an array out of databaseRow[selectField] values.
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result The current result array.
+     * @param string $fieldName Name of the current select field.
+     * @param array $staticValues Array with statically defined items, item value is used as array key.
+     * @return array
+     */
+    protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
+    {
+        $fieldConfig = $result['processedTca']['columns'][$fieldName];
+
+        // For single select fields we just keep the current value because the renderer
+        // will take care of showing the "Invalid value" text.
+        // For maxitems=1 select fields is is also possible to select empty values.
+        // @todo: move handling of invalid values to this data provider.
+        if ($fieldConfig['config']['maxitems'] === 1 && empty($fieldConfig['config']['MM'])) {
+            return [$result['databaseRow'][$fieldName]];
+        }
+
+        $currentDatabaseValues = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : '';
+        // Selecting empty values does not make sense for fields that can contain more than one item
+        // because it is impossible to determine if the empty value or nothing is selected.
+        // This is why empty values will be removed for multi value fields.
+        $currentDatabaseValuesArray = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
+        $newDatabaseValueArray = [];
+
+        // Add all values that were defined by static methods and do not come from the relation
+        // e.g. TCA, TSconfig, itemProcFunc etc.
+        foreach ($currentDatabaseValuesArray as $value) {
+            if (isset($staticValues[$value])) {
+                $newDatabaseValueArray[] = $value;
+            }
+        }
+
+        if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
+            /** @var RelationHandler $relationHandler */
+            $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
+            $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
+            if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
+                // MM relation
+                $relationHandler->start(
+                    $currentDatabaseValues,
+                    $fieldConfig['config']['foreign_table'],
+                    $fieldConfig['config']['MM'],
+                    $result['databaseRow']['uid'],
+                    $result['tableName'],
+                    $fieldConfig['config']
+                );
+            } else {
+                // Non MM relation
+                // If not dealing with MM relations, use default live uid, not versioned uid for record relations
+                $relationHandler->start(
+                    $currentDatabaseValues,
+                    $fieldConfig['config']['foreign_table'],
+                    '',
+                    $this->getLiveUid($result),
+                    $result['tableName'],
+                    $fieldConfig['config']
+                );
+            }
+            $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
+        }
+
+        return array_unique($newDatabaseValueArray);
+    }
+
+    /**
+     * Translate the item labels
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $result Result array
+     * @param array $itemArray Items
+     * @param string $table
+     * @param string $fieldName
+     * @return array
+     */
+    public function translateLabels(array $result, array $itemArray, $table, $fieldName)
+    {
+        $languageService = $this->getLanguageService();
+
+        foreach ($itemArray as $key => $item) {
+            if (!isset($dynamicItems[$key])) {
+                $staticValues[$item[1]] = $item;
+            }
+            if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
+                && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
+            ) {
+                $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
+            } else {
+                $label = $languageService->sL($item[0]);
+            }
+            $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
+            $icon = $item[2] ?: null;
+            $helpText = $item[3] ?: null;
+            $itemArray[$key] = [
+                $label,
+                $value,
+                $icon,
+                $helpText
+            ];
+        }
+
+        return $itemArray;
+    }
+
+    /**
+     * Sanitize incoming item array
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param mixed $itemArray
+     * @param string $tableName
+     * @param string $fieldName
+     * @return array
+     */
+    public function sanitizeItemArray($itemArray, $tableName, $fieldName)
+    {
+        if (!is_array($itemArray)) {
+            $itemArray = [];
+        }
+        foreach ($itemArray as $item) {
+            if (!is_array($item)) {
+                throw new \UnexpectedValueException(
+                    'An item in field ' . $fieldName . ' of table ' . $tableName . ' is not an array as expected',
+                    1439288036
+                );
+            }
+        }
+
+        return $itemArray;
+    }
+
+    /**
+     * Make sure maxitems is always filled with a valid integer value.
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param mixed $maxItems
+     * @return int
+     */
+    public function sanitizeMaxItems($maxItems)
+    {
+        if (
+            !empty($maxItems)
+            && (int)$maxItems > 1
+        ) {
+            $maxItems = (int)$maxItems;
+        } else {
+            $maxItems = 1;
+        }
+
+        return $maxItems;
+    }
+
+    /**
+     * Gets the record uid of the live default record. If already
+     * pointing to the live record, the submitted record uid is returned.
+     *
+     * @param array $result Result array
+     * @return int
+     * @throws \UnexpectedValueException
+     */
+    protected function getLiveUid(array $result)
+    {
+        $table = $result['tableName'];
+        $row = $result['databaseRow'];
+        $uid = $row['uid'];
+        if (!empty($result['processedTca']['ctrl']['versioningWS'])
+            && $result['pid'] === -1
+        ) {
+            if (empty($row['t3ver_oid'])) {
+                throw new \UnexpectedValueException(
+                    'No t3ver_oid found for record ' . $row['uid'] . ' on table ' . $table,
+                    1440066481
+                );
+            }
+            $uid = $row['t3ver_oid'];
+        }
+        return $uid;
+    }
+
+    /**
+     * Determine the static values in the item array
+     *
+     * Used by TcaSelectItems and TcaSelectTreeItems data providers
+     *
+     * @param array $itemArray All item records for the select field
+     * @param array $dynamicItemArray Item records from dynamic sources
+     * @return array
+     */
+    public function getStaticValues($itemArray, $dynamicItemArray)
+    {
+        $staticValues = [];
+        foreach ($itemArray as $key => $item) {
+            if (!isset($dynamicItemArray[$key])) {
+                $staticValues[$item[1]] = $item;
+            }
+        }
+        return $staticValues;
+    }
+
     /**
      * @return LanguageService
      */
@@ -127,4 +1238,20 @@ abstract class AbstractItemProvider
     {
         return $GLOBALS['LANG'];
     }
+
+    /**
+     * @return DatabaseConnection
+     */
+    protected function getDatabaseConnection()
+    {
+        return $GLOBALS['TYPO3_DB'];
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/ReturnUrl.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/ReturnUrl.php
index 51c94df19748fda5c338e238c4123c855eb49d23..857f5e74f31e4a81cb326fd1277d2ae5ab6d1ac6 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/ReturnUrl.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/ReturnUrl.php
@@ -20,7 +20,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 /**
  * Resolve return Url if not set otherwise.
  */
-class ReturnUrl extends AbstractItemProvider implements FormDataProviderInterface
+class ReturnUrl implements FormDataProviderInterface
 {
     /**
      * Add return unl
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexFetch.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexFetch.php
index 3f605bc24449146f17d7ccd7e1c556e1b2f442d3..64bfe0b016d0b2b48676601cbbefcc2fe6b7d3a1 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexFetch.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexFetch.php
@@ -23,7 +23,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *
  * This is the first data provider in the chain of flex form related providers.
  */
-class TcaFlexFetch extends AbstractItemProvider implements FormDataProviderInterface
+class TcaFlexFetch implements FormDataProviderInterface
 {
     /**
      * Resolve ds pointer stuff and parse both ds and dv
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php
index c138b8ae37f7a6f3c724f041f007a6e5fadfbeea..6a7385846b0360de92422c4087fa3c439aae14f5 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexPrepare.php
@@ -23,7 +23,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *
  * This data provider is typically executed directly after TcaFlexFetch
  */
-class TcaFlexPrepare extends AbstractItemProvider implements FormDataProviderInterface
+class TcaFlexPrepare implements FormDataProviderInterface
 {
     /**
      * Resolve flex data structures and prepare flex data values.
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php
index 62f403ee097e4a4f150cb85e04dc2c20d6ad16f7..fb37f3f7a3d3066a38edb2e9a4a2f7cf00168112 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlexProcess.php
@@ -25,7 +25,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *
  * This is typically the last provider, executed after TcaFlexPrepare
  */
-class TcaFlexProcess extends AbstractItemProvider implements FormDataProviderInterface
+class TcaFlexProcess implements FormDataProviderInterface
 {
     /**
      * Determine possible pageTsConfig overrides and apply them to ds.
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineConfiguration.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineConfiguration.php
index 90ab878e5b2960d2f28037b438866b4189b4aad7..ffb3021dddd2ec6c8b3936b0281a10034ff8170a 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineConfiguration.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineConfiguration.php
@@ -21,7 +21,7 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 /**
  * Set or initialize configuration for inline fields in TCA
  */
-class TcaInlineConfiguration extends AbstractItemProvider implements FormDataProviderInterface
+class TcaInlineConfiguration implements FormDataProviderInterface
 {
     /**
      * Find all inline fields and force proper configuration
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineExpandCollapseState.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineExpandCollapseState.php
index 9789d5c6c784429c0f347d9404fb015e001864d4..8fc55a97bce0de4c86d2825d314f3d6d85070da9 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineExpandCollapseState.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineExpandCollapseState.php
@@ -21,7 +21,7 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
  * Fetch information of user specific inline record expanded / collapsed state
  * from user->uc and put it into $result['inlineExpandCollapseStateArray']
  */
-class TcaInlineExpandCollapseState extends AbstractItemProvider implements FormDataProviderInterface
+class TcaInlineExpandCollapseState implements FormDataProviderInterface
 {
     /**
      * Add inline expand / collapse state
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInputPlaceholders.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInputPlaceholders.php
index 0832b1f892c5f6c0381a7770b224c98a58b3c0d5..4e437249ed2e97a8adb9b19df63f95a57e18f199 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInputPlaceholders.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInputPlaceholders.php
@@ -20,13 +20,14 @@ use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
+use TYPO3\CMS\Lang\LanguageService;
 
 /**
  * Resolve placeholders for fields of type input or text. The placeholder value
  * in the processedTca section of the result will be replaced with the resolved
  * value.
  */
-class TcaInputPlaceholders extends AbstractItemProvider implements FormDataProviderInterface
+class TcaInputPlaceholders implements FormDataProviderInterface
 {
     /**
      * Resolve placeholders for input/text fields. Placeholders that are simple
@@ -208,4 +209,12 @@ class TcaInputPlaceholders extends AbstractItemProvider implements FormDataProvi
 
         return $allowedTable;
     }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
 }
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php
index b25f308d29f480e3b5f744776916f8dc5e5cc0dc..1a29d46ca4788c8c97113041473ba443fa8d76d5 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php
@@ -14,22 +14,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
-use TYPO3\CMS\Backend\Module\ModuleLoader;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Database\DatabaseConnection;
-use TYPO3\CMS\Core\Database\RelationHandler;
-use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
-use TYPO3\CMS\Core\Messaging\FlashMessageService;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\MathUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Lang\LanguageService;
 
 /**
  * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
@@ -45,8 +30,6 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
      */
     public function addData(array $result)
     {
-        $languageService = $this->getLanguageService();
-
         $table = $result['tableName'];
 
         foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
@@ -54,29 +37,13 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
                 continue;
             }
 
-            // Sanitize incoming item array
-            if (!is_array($fieldConfig['config']['items'])) {
-                $fieldConfig['config']['items'] = [];
-            }
-
-            // Make sure maxitems is always filled with a valid integer value.
-            if (
-                !empty($fieldConfig['config']['maxitems'])
-                && (int)$fieldConfig['config']['maxitems'] > 1
-            ) {
-                $fieldConfig['config']['maxitems'] = (int)$fieldConfig['config']['maxitems'];
-            } else {
-                $fieldConfig['config']['maxitems'] = 1;
+            // Make sure we are only processing supported renderTypes
+            if (!in_array($fieldConfig['config']['renderType'], ['selectSingle', 'selectSingleBox', 'selectCheckBox', 'selectMultipleSideBySide'])) {
+                continue;
             }
 
-            foreach ($fieldConfig['config']['items'] as $item) {
-                if (!is_array($item)) {
-                    throw new \UnexpectedValueException(
-                        'An item in field ' . $fieldName . ' of table ' . $table . ' is not an array as expected',
-                        1439288036
-                    );
-                }
-            }
+            $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName);
+            $fieldConfig['config']['maxitems'] = $this->sanitizeMaxItems($fieldConfig['config']['maxitems']);
 
             $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
             $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']);
@@ -100,28 +67,10 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
             }
 
             // Translate labels
-            $staticValues = [];
-            foreach ($fieldConfig['config']['items'] as $key => $item) {
-                if (!isset($dynamicItems[$key])) {
-                    $staticValues[$item[1]] = $item;
-                }
-                if (isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
-                    && !empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]])
-                ) {
-                    $label = $languageService->sL($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['altLabels.'][$item[1]]);
-                } else {
-                    $label = $languageService->sL($item[0]);
-                }
-                $value = strlen((string)$item[1]) > 0 ? $item[1] : '';
-                $icon = $item[2] ?: null;
-                $helpText = $item[3] ?: null;
-                $fieldConfig['config']['items'][$key] = [
-                    $label,
-                    $value,
-                    $icon,
-                    $helpText
-                ];
-            }
+            $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
+
+            $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems);
+
             // Keys may contain table names, so a numeric array is created
             $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']);
 
@@ -131,995 +80,4 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
 
         return $result;
     }
-
-    /**
-     * TCA config "special" evaluation. Add them to $items
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     * @throws \UnexpectedValueException
-     */
-    protected function addItemsFromSpecial(array $result, $fieldName, array $items)
-    {
-        // Guard
-        if (empty($result['processedTca']['columns'][$fieldName]['config']['special'])
-            || !is_string($result['processedTca']['columns'][$fieldName]['config']['special'])
-        ) {
-            return $items;
-        }
-
-        $languageService = $this->getLanguageService();
-        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-
-        $special = $result['processedTca']['columns'][$fieldName]['config']['special'];
-        if ($special === 'tables') {
-            foreach ($GLOBALS['TCA'] as $currentTable => $_) {
-                if (!empty($GLOBALS['TCA'][$currentTable]['ctrl']['adminOnly'])) {
-                    // Hide "admin only" tables
-                    continue;
-                }
-                $label = !empty($GLOBALS['TCA'][$currentTable]['ctrl']['title']) ? $GLOBALS['TCA'][$currentTable]['ctrl']['title'] : '';
-                $icon = $iconFactory->mapRecordTypeToIconIdentifier($currentTable, array());
-                $helpText = array();
-                $languageService->loadSingleTableDescription($currentTable);
-                // @todo: check if this actually works, currently help texts are missing
-                $helpTextArray = $GLOBALS['TCA_DESCR'][$currentTable]['columns'][''];
-                if (!empty($helpTextArray['description'])) {
-                    $helpText['description'] = $helpTextArray['description'];
-                }
-                $items[] = array($label, $currentTable, $icon, $helpText);
-            }
-        } elseif ($special === 'pagetypes') {
-            if (isset($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
-                && is_array($GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'])
-            ) {
-                $specialItems = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
-                foreach ($specialItems as $specialItem) {
-                    if (!is_array($specialItem) || $specialItem[1] === '--div--') {
-                        // Skip non arrays and divider items
-                        continue;
-                    }
-                    $label = $specialItem[0];
-                    $value = $specialItem[1];
-                    $icon = $iconFactory->mapRecordTypeToIconIdentifier('pages', array('doktype' => $specialItem[1]));
-                    $items[] = array($label, $value, $icon);
-                }
-            }
-        } elseif ($special === 'exclude') {
-            $excludeArrays = $this->getExcludeFields();
-            foreach ($excludeArrays as $excludeArray) {
-                list($theTable, $theFullField) = explode(':', $excludeArray[1]);
-                // If the field comes from a FlexForm, the syntax is more complex
-                $theFieldParts = explode(';', $theFullField);
-                $theField = array_pop($theFieldParts);
-                // Add header if not yet set for table:
-                if (!array_key_exists($theTable, $items)) {
-                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($theTable, array());
-                    $items[$theTable] = array(
-                        $GLOBALS['TCA'][$theTable]['ctrl']['title'],
-                        '--div--',
-                        $icon
-                    );
-                }
-                // Add help text
-                $helpText = array();
-                $languageService->loadSingleTableDescription($theTable);
-                $helpTextArray = $GLOBALS['TCA_DESCR'][$theTable]['columns'][$theFullField];
-                if (!empty($helpTextArray['description'])) {
-                    $helpText['description'] = $helpTextArray['description'];
-                }
-                // Item configuration:
-                // @todo: the title calculation does not work well for flex form fields, see unit tests
-                $items[] = array(
-                    rtrim($languageService->sL($GLOBALS['TCA'][$theTable]['columns'][$theField]['label']), ':') . ' (' . $theField . ')',
-                    $excludeArray[1],
-                    'empty-empty',
-                    $helpText
-                );
-            }
-        } elseif ($special === 'explicitValues') {
-            $theTypes = $this->getExplicitAuthFieldValues();
-            $icons = array(
-                'ALLOW' => 'status-status-permission-granted',
-                'DENY' => 'status-status-permission-denied'
-            );
-            // Traverse types:
-            foreach ($theTypes as $tableFieldKey => $theTypeArrays) {
-                if (is_array($theTypeArrays['items'])) {
-                    // Add header:
-                    $items[] = array(
-                        $theTypeArrays['tableFieldLabel'],
-                        '--div--',
-                    );
-                    // Traverse options for this field:
-                    foreach ($theTypeArrays['items'] as $itemValue => $itemContent) {
-                        // Add item to be selected:
-                        $items[] = array(
-                            '[' . $itemContent[2] . '] ' . $itemContent[1],
-                            $tableFieldKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $itemContent[0],
-                            $icons[$itemContent[0]]
-                        );
-                    }
-                }
-            }
-        } elseif ($special === 'languages') {
-            // @todo: This should probably use the data provided by DatabaseSystemLanguageRows sitting in $result['systemLanguageRows']
-            /** @var TranslationConfigurationProvider $translationConfigurationProvider */
-            $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
-            $languages = $translationConfigurationProvider->getSystemLanguages();
-            foreach ($languages as $language) {
-                if ($language['uid'] !== -1) {
-                    $items[] = array(
-                        0 => $language['title'] . ' [' . $language['uid'] . ']',
-                        1 => $language['uid'],
-                        2 => $language['flagIcon']
-                    );
-                }
-            }
-        } elseif ($special === 'custom') {
-            $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'];
-            if (is_array($customOptions)) {
-                foreach ($customOptions as $coKey => $coValue) {
-                    if (is_array($coValue['items'])) {
-                        // Add header:
-                        $items[] = array(
-                            $languageService->sL($coValue['header']),
-                            '--div--'
-                        );
-                        // Traverse items:
-                        foreach ($coValue['items'] as $itemKey => $itemCfg) {
-                            $icon = 'empty-empty';
-                            $helpText = array();
-                            if (!empty($itemCfg[2])) {
-                                $helpText['description'] = $languageService->sL($itemCfg[2]);
-                            }
-                            $items[] = array(
-                                $languageService->sL($itemCfg[0]),
-                                $coKey . ':' . preg_replace('/[:|,]/', '', $itemKey),
-                                $icon,
-                                $helpText
-                            );
-                        }
-                    }
-                }
-            }
-        } elseif ($special === 'modListGroup' || $special === 'modListUser') {
-            $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
-            $loadModules->load($GLOBALS['TBE_MODULES']);
-            $modList = $special === 'modListUser' ? $loadModules->modListUser : $loadModules->modListGroup;
-            if (is_array($modList)) {
-                foreach ($modList as $theMod) {
-                    // Icon:
-                    $icon = $languageService->moduleLabels['tabs_images'][$theMod . '_tab'];
-                    if ($icon) {
-                        $icon = '../' . PathUtility::stripPathSitePrefix($icon);
-                    }
-                    // Add help text
-                    $helpText = array(
-                        'title' => $languageService->moduleLabels['labels'][$theMod . '_tablabel'],
-                        'description' => $languageService->moduleLabels['labels'][$theMod . '_tabdescr']
-                    );
-
-                    $label = '';
-                    // Add label for main module:
-                    $pp = explode('_', $theMod);
-                    if (count($pp) > 1) {
-                        $label .= $languageService->moduleLabels['tabs'][($pp[0] . '_tab')] . '>';
-                    }
-                    // Add modules own label now:
-                    $label .= $languageService->moduleLabels['tabs'][$theMod . '_tab'];
-
-                    // Item configuration:
-                    $items[] = array($label, $theMod, $icon, $helpText);
-                }
-            }
-        } else {
-            throw new \UnexpectedValueException(
-                'Unknown special value ' . $special . ' for field ' . $fieldName . ' of table ' . $result['tableName'],
-                1439298496
-            );
-        }
-
-        return $items;
-    }
-
-    /**
-     * TCA config "fileFolder" evaluation. Add them to $items
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function addItemsFromFolder(array $result, $fieldName, array $items)
-    {
-        if (empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
-            || !is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder'])
-        ) {
-            return $items;
-        }
-
-        $fileFolder = $result['processedTca']['columns'][$fieldName]['config']['fileFolder'];
-        $fileFolder = GeneralUtility::getFileAbsFileName($fileFolder);
-        $fileFolder = rtrim($fileFolder, '/') . '/';
-
-        if (@is_dir($fileFolder)) {
-            $fileExtensionList = '';
-            if (!empty($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
-                && is_string($result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'])
-            ) {
-                $fileExtensionList = $result['processedTca']['columns'][$fieldName]['config']['fileFolder_extList'];
-            }
-            $recursionLevels = isset($fieldValue['config']['fileFolder_recursions'])
-                ? MathUtility::forceIntegerInRange($fieldValue['config']['fileFolder_recursions'], 0, 99)
-                : 99;
-            $fileArray = GeneralUtility::getAllFilesAndFoldersInPath(array(), $fileFolder, $fileExtensionList, 0, $recursionLevels);
-            $fileArray = GeneralUtility::removePrefixPathFromList($fileArray, $fileFolder);
-            foreach ($fileArray as $fileReference) {
-                $fileInformation = pathinfo($fileReference);
-                $icon = GeneralUtility::inList('gif,png,jpeg,jpg', strtolower($fileInformation['extension']))
-                    ? '../' . PathUtility::stripPathSitePrefix($fileFolder) . $fileReference
-                    : '';
-                $items[] = array(
-                    $fileReference,
-                    $fileReference,
-                    $icon
-                );
-            }
-        }
-
-        return $items;
-    }
-
-    /**
-     * TCA config "foreign_table" evaluation. Add them to $items
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function addItemsFromForeignTable(array $result, $fieldName, array $items)
-    {
-        // Guard
-        if (empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
-            || !is_string($result['processedTca']['columns'][$fieldName]['config']['foreign_table'])
-        ) {
-            return $items;
-        }
-
-        $languageService = $this->getLanguageService();
-        $database = $this->getDatabaseConnection();
-
-        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
-        $foreignTableQueryArray = $this->buildForeignTableQuery($result, $fieldName);
-        $queryResource = $database->exec_SELECT_queryArray($foreignTableQueryArray);
-
-        // Early return on error with flash message
-        $databaseError = $database->sql_error();
-        if (!empty($databaseError)) {
-            $msg = htmlspecialchars($databaseError) . '<br />' . LF;
-            $msg .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch');
-            $msgTitle = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:error.database_schema_mismatch_title');
-            /** @var $flashMessage FlashMessage */
-            $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $msgTitle, FlashMessage::ERROR, true);
-            /** @var $flashMessageService FlashMessageService */
-            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
-            /** @var $defaultFlashMessageQueue FlashMessageQueue */
-            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
-            $defaultFlashMessageQueue->enqueue($flashMessage);
-            $database->sql_free_result($queryResource);
-            return $items;
-        }
-
-        $labelPrefix = '';
-        if (!empty($result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'])) {
-            $labelPrefix = $result['processedTca']['columns'][$fieldName]['config']['foreign_table_prefix'];
-            $labelPrefix = $languageService->sL($labelPrefix);
-        }
-        $iconFieldName = '';
-        if (!empty($result['processedTca']['ctrl']['selicon_field'])) {
-            $iconFieldName = $result['processedTca']['ctrl']['selicon_field'];
-        }
-        $iconPath = '';
-        if (!empty($result['processedTca']['ctrl']['selicon_field_path'])) {
-            $iconPath = $result['processedTca']['ctrl']['selicon_field_path'];
-        }
-
-        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-
-        while ($foreignRow = $database->sql_fetch_assoc($queryResource)) {
-            BackendUtility::workspaceOL($foreignTable, $foreignRow);
-            if (is_array($foreignRow)) {
-                // Prepare the icon if available:
-                if ($iconFieldName && $iconPath && $foreignRow[$iconFieldName]) {
-                    $iParts = GeneralUtility::trimExplode(',', $foreignRow[$iconFieldName], true);
-                    $icon = '../' . $iconPath . '/' . trim($iParts[0]);
-                } else {
-                    $icon = $iconFactory->mapRecordTypeToIconIdentifier($foreignTable, $foreignRow);
-                }
-                // Add the item
-                $items[] = array(
-                    $labelPrefix . htmlspecialchars(BackendUtility::getRecordTitle($foreignTable, $foreignRow)),
-                    $foreignRow['uid'],
-                    $icon
-                );
-            }
-        }
-
-        $database->sql_free_result($queryResource);
-
-        return $items;
-    }
-
-    /**
-     * Remove items using "keepItems" pageTsConfig
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
-    {
-        $table = $result['tableName'];
-        if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
-            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'])
-        ) {
-            return $items;
-        }
-
-        return ArrayUtility::keepItemsInArray(
-            $items,
-            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['keepItems'],
-            function ($value) {
-                return $value[1];
-            }
-        );
-    }
-
-    /**
-     * Remove items using "removeItems" pageTsConfig
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
-    {
-        $table = $result['tableName'];
-        if (empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
-            || !is_string($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'])
-        ) {
-            return $items;
-        }
-
-        $removeItems = GeneralUtility::trimExplode(
-            ',',
-            $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['removeItems'],
-            true
-        );
-        foreach ($items as $key => $itemValues) {
-            if (in_array($itemValues[1], $removeItems)) {
-                unset($items[$key]);
-            }
-        }
-
-        return $items;
-    }
-
-    /**
-     * Remove items user restriction on language field
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
-    {
-        // Guard clause returns if not a language field is handled
-        if (empty($result['processedTca']['ctrl']['languageField'])
-            || $result['processedTca']['ctrl']['languageField'] !== $fieldName
-        ) {
-            return $items;
-        }
-
-        $backendUser = $this->getBackendUser();
-        foreach ($items as $key => $itemValues) {
-            if (!$backendUser->checkLanguageAccess($itemValues[1])) {
-                unset($items[$key]);
-            }
-        }
-
-        return $items;
-    }
-
-    /**
-     * Remove items by user restriction on authMode items
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function removeItemsByUserAuthMode(array $result, $fieldName, array $items)
-    {
-        // Guard clause returns early if no authMode field is configured
-        if (!isset($result['processedTca']['columns'][$fieldName]['config']['authMode'])
-            || !is_string($result['processedTca']['columns'][$fieldName]['config']['authMode'])
-        ) {
-            return $items;
-        }
-
-        $backendUser = $this->getBackendUser();
-        $authMode = $result['processedTca']['columns'][$fieldName]['config']['authMode'];
-        foreach ($items as $key => $itemValues) {
-            // @todo: checkAuthMode() uses $GLOBAL access for "individual" authMode - get rid of this
-            if (!$backendUser->checkAuthMode($result['tableName'], $fieldName, $itemValues[1], $authMode)) {
-                unset($items[$key]);
-            }
-        }
-
-        return $items;
-    }
-
-    /**
-     * Remove items if doktype is handled for non admin users
-     *
-     * @param array $result Result array
-     * @param string $fieldName Current handle field name
-     * @param array $items Incoming items
-     * @return array Modified item array
-     */
-    protected function removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
-    {
-        $table = $result['tableName'];
-        $backendUser = $this->getBackendUser();
-        // Guard clause returns if not correct table and field or if user is admin
-        if ($table !== 'pages' && $table !== 'pages_language_overlay'
-            || $fieldName !== 'doktype' || $backendUser->isAdmin()
-        ) {
-            return $items;
-        }
-
-        $allowedPageTypes = $backendUser->groupData['pagetypes_select'];
-        foreach ($items as $key => $itemValues) {
-            if (!GeneralUtility::inList($allowedPageTypes, $itemValues[1])) {
-                unset($items[$key]);
-            }
-        }
-
-        return $items;
-    }
-
-    /**
-     * Returns an array with the exclude fields as defined in TCA and FlexForms
-     * Used for listing the exclude fields in be_groups forms.
-     *
-     * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
-     *               and FlexForms (fieldName, table:extKey;sheetName;fieldName)
-     */
-    protected function getExcludeFields()
-    {
-        $languageService = $this->getLanguageService();
-        $finalExcludeArray = array();
-
-        // Fetch translations for table names
-        $tableToTranslation = array();
-        // All TCA keys
-        foreach ($GLOBALS['TCA'] as $table => $conf) {
-            $tableToTranslation[$table] = $languageService->sl($conf['ctrl']['title']);
-        }
-        // Sort by translations
-        asort($tableToTranslation);
-        foreach ($tableToTranslation as $table => $translatedTable) {
-            $excludeArrayTable = array();
-
-            // All field names configured and not restricted to admins
-            if (is_array($GLOBALS['TCA'][$table]['columns'])
-                && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly'])
-                && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction']))
-            ) {
-                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
-                    if ($GLOBALS['TCA'][$table]['columns'][$field]['exclude']) {
-                        // Get human readable names of fields
-                        $translatedField = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
-                        // Add entry
-                        $excludeArrayTable[] = array($translatedTable . ': ' . $translatedField, $table . ':' . $field);
-                    }
-                }
-            }
-            // All FlexForm fields
-            $flexFormArray = $this->getRegisteredFlexForms($table);
-            foreach ($flexFormArray as $tableField => $flexForms) {
-                // Prefix for field label, e.g. "Plugin Options:"
-                $labelPrefix = '';
-                if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) {
-                    $labelPrefix = $languageService->sl($GLOBALS['TCA'][$table]['columns'][$tableField]['label']);
-                }
-                // Get all sheets and title
-                foreach ($flexForms as $extIdent => $extConf) {
-                    $extTitle = $languageService->sl($extConf['title']);
-                    // Get all fields in sheet
-                    foreach ($extConf['ds']['sheets'] as $sheetName => $sheet) {
-                        if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
-                            continue;
-                        }
-                        foreach ($sheet['ROOT']['el'] as $fieldName => $field) {
-                            // Use only fields that have exclude flag set
-                            if (empty($field['TCEforms']['exclude'])) {
-                                continue;
-                            }
-                            $fieldLabel = !empty($field['TCEforms']['label']) ? $languageService->sl($field['TCEforms']['label']) : $fieldName;
-                            $fieldIdent = $table . ':' . $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $fieldName;
-                            $excludeArrayTable[] = array(trim($labelPrefix . ' ' . $extTitle, ': ') . ': ' . $fieldLabel, $fieldIdent);
-                        }
-                    }
-                }
-            }
-            // Sort fields by the translated value
-            if (!empty($excludeArrayTable)) {
-                usort($excludeArrayTable, function (array $array1, array $array2) {
-                    $array1 = reset($array1);
-                    $array2 = reset($array2);
-                    if (is_string($array1) && is_string($array2)) {
-                        return strcasecmp($array1, $array2);
-                    }
-                    return 0;
-                });
-                $finalExcludeArray = array_merge($finalExcludeArray, $excludeArrayTable);
-            }
-        }
-
-        return $finalExcludeArray;
-    }
-
-    /**
-     * Returns all registered FlexForm definitions with title and fields
-     *
-     * @param string $table Table to handle
-     * @return array Data structures with speaking extension title
-     */
-    protected function getRegisteredFlexForms($table)
-    {
-        if (empty($table) || empty($GLOBALS['TCA'][$table]['columns'])) {
-            return array();
-        }
-        $flexForms = array();
-        foreach ($GLOBALS['TCA'][$table]['columns'] as $tableField => $fieldConf) {
-            if (!empty($fieldConf['config']['type']) && !empty($fieldConf['config']['ds']) && $fieldConf['config']['type'] == 'flex') {
-                $flexForms[$tableField] = array();
-                unset($fieldConf['config']['ds']['default']);
-                // Get pointer fields
-                $pointerFields = !empty($fieldConf['config']['ds_pointerField']) ? $fieldConf['config']['ds_pointerField'] : 'list_type,CType';
-                $pointerFields = GeneralUtility::trimExplode(',', $pointerFields);
-                // Get FlexForms
-                foreach ($fieldConf['config']['ds'] as $flexFormKey => $dataStructure) {
-                    // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one)
-                    $identFields = GeneralUtility::trimExplode(',', $flexFormKey);
-                    $extIdent = $identFields[0];
-                    if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') {
-                        $extIdent = $identFields[1];
-                    }
-                    // Load external file references
-                    if (!is_array($dataStructure)) {
-                        $file = GeneralUtility::getFileAbsFileName(str_ireplace('FILE:', '', $dataStructure));
-                        if ($file && @is_file($file)) {
-                            $dataStructure = GeneralUtility::getUrl($file);
-                        }
-                        $dataStructure = GeneralUtility::xml2array($dataStructure);
-                        if (!is_array($dataStructure)) {
-                            continue;
-                        }
-                    }
-                    // Get flexform content
-                    $dataStructure = GeneralUtility::resolveAllSheetsInDS($dataStructure);
-                    if (empty($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
-                        continue;
-                    }
-                    // Use DS pointer to get extension title from TCA
-                    // @todo: I don't understand this code ... does it make sense at all?
-                    $title = $extIdent;
-                    $keyFields = GeneralUtility::trimExplode(',', $flexFormKey);
-                    foreach ($pointerFields as $pointerKey => $pointerName) {
-                        if (empty($keyFields[$pointerKey]) || $keyFields[$pointerKey] === '*' || $keyFields[$pointerKey] === 'list') {
-                            continue;
-                        }
-                        if (!empty($GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'])) {
-                            $items = $GLOBALS['TCA'][$table]['columns'][$pointerName]['config']['items'];
-                            if (!is_array($items)) {
-                                continue;
-                            }
-                            foreach ($items as $itemConf) {
-                                if (!empty($itemConf[0]) && !empty($itemConf[1]) && $itemConf[1] == $keyFields[$pointerKey]) {
-                                    $title = $itemConf[0];
-                                    break 2;
-                                }
-                            }
-                        }
-                    }
-                    $flexForms[$tableField][$extIdent] = array(
-                        'title' => $title,
-                        'ds' => $dataStructure
-                    );
-                }
-            }
-        }
-        return $flexForms;
-    }
-
-    /**
-     * Returns an array with explicit Allow/Deny fields.
-     * Used for listing these field/value pairs in be_groups forms
-     *
-     * @return array Array with information from all of $GLOBALS['TCA']
-     */
-    protected function getExplicitAuthFieldValues()
-    {
-        $languageService = static::getLanguageService();
-        $adLabel = array(
-            'ALLOW' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.allow'),
-            'DENY' => $languageService->sl('LLL:EXT:lang/locallang_core.xlf:labels.deny')
-        );
-        $allowDenyOptions = array();
-        foreach ($GLOBALS['TCA'] as $table => $_) {
-            // All field names configured:
-            if (is_array($GLOBALS['TCA'][$table]['columns'])) {
-                foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $_) {
-                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
-                    if ($fieldConfig['type'] === 'select' && $fieldConfig['authMode']) {
-                        // Check for items
-                        if (is_array($fieldConfig['items'])) {
-                            // Get Human Readable names of fields and table:
-                            $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] =
-                                $languageService->sl($GLOBALS['TCA'][$table]['ctrl']['title']) . ': '
-                                . $languageService->sl($GLOBALS['TCA'][$table]['columns'][$field]['label']);
-                            foreach ($fieldConfig['items'] as $iVal) {
-                                // Values '' is not controlled by this setting.
-                                if ((string)$iVal[1] !== '') {
-                                    // Find iMode
-                                    $iMode = '';
-                                    switch ((string)$fieldConfig['authMode']) {
-                                        case 'explicitAllow':
-                                            $iMode = 'ALLOW';
-                                            break;
-                                        case 'explicitDeny':
-                                            $iMode = 'DENY';
-                                            break;
-                                        case 'individual':
-                                            if ($iVal[4] === 'EXPL_ALLOW') {
-                                                $iMode = 'ALLOW';
-                                            } elseif ($iVal[4] === 'EXPL_DENY') {
-                                                $iMode = 'DENY';
-                                            }
-                                            break;
-                                    }
-                                    // Set iMode
-                                    if ($iMode) {
-                                        $allowDenyOptions[$table . ':' . $field]['items'][$iVal[1]] = array($iMode, $languageService->sl($iVal[0]), $adLabel[$iMode]);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return $allowDenyOptions;
-    }
-
-    /**
-     * Build query to fetch foreign records
-     *
-     * @param array $result Result array
-     * @param string $localFieldName Current handle field name
-     * @return array Query array ready to be executed via Database->exec_SELECT_queryArray()
-     * @throws \UnexpectedValueException
-     */
-    protected function buildForeignTableQuery(array $result, $localFieldName)
-    {
-        $backendUser = $this->getBackendUser();
-
-        $foreignTableName = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table'];
-
-        if (!is_array($GLOBALS['TCA'][$foreignTableName])) {
-            throw new \UnexpectedValueException(
-                'Field ' . $localFieldName . ' of table ' . $result['tableName'] . ' reference to foreign table '
-                . $foreignTableName . ', but this table is not defined in TCA',
-                1439569743
-            );
-        }
-
-        $foreignTableClauseArray = $this->processForeignTableClause($result, $foreignTableName, $localFieldName);
-
-        $queryArray = array();
-        $queryArray['SELECT'] = BackendUtility::getCommonSelectFields($foreignTableName, $foreignTableName . '.');
-
-        // rootLevel = -1 means that elements can be on the rootlevel OR on any page (pid!=-1)
-        // rootLevel = 0 means that elements are not allowed on root level
-        // rootLevel = 1 means that elements are only on the root level (pid=0)
-        $rootLevel = 0;
-        if (isset($GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'])) {
-            $rootLevel = $GLOBALS['TCA'][$foreignTableName]['ctrl']['rootLevel'];
-        }
-        $deleteClause = BackendUtility::deleteClause($foreignTableName);
-        if ($rootLevel == 1 || $rootLevel == -1) {
-            $pidWhere = $foreignTableName . '.pid' . (($rootLevel == -1) ? '<>-1' : '=0');
-            $queryArray['FROM'] = $foreignTableName;
-            $queryArray['WHERE'] = $pidWhere . $deleteClause . $foreignTableClauseArray['WHERE'];
-        } else {
-            $pageClause = $backendUser->getPagePermsClause(1);
-            if ($foreignTableName === 'pages') {
-                $queryArray['FROM'] = 'pages';
-                $queryArray['WHERE'] = '1=1' . $deleteClause . ' AND' . $pageClause . $foreignTableClauseArray['WHERE'];
-            } else {
-                $queryArray['FROM'] = $foreignTableName . ', pages';
-                $queryArray['WHERE'] = 'pages.uid=' . $foreignTableName . '.pid AND pages.deleted=0'
-                    . $deleteClause . ' AND' . $pageClause .  $foreignTableClauseArray['WHERE'];
-            }
-        }
-
-        $queryArray['GROUPBY'] = $foreignTableClauseArray['GROUPBY'];
-        $queryArray['ORDERBY'] = $foreignTableClauseArray['ORDERBY'];
-        $queryArray['LIMIT'] = $foreignTableClauseArray['LIMIT'];
-
-        return $queryArray;
-    }
-
-    /**
-     * Replace markers in a where clause from TCA foreign_table_where
-     *
-     * ###REC_FIELD_[field name]###
-     * ###THIS_UID### - is current element uid (zero if new).
-     * ###CURRENT_PID### - is the current page id (pid of the record).
-     * ###SITEROOT###
-     * ###PAGE_TSCONFIG_ID### - a value you can set from Page TSconfig dynamically.
-     * ###PAGE_TSCONFIG_IDLIST### - a value you can set from Page TSconfig dynamically.
-     * ###PAGE_TSCONFIG_STR### - a value you can set from Page TSconfig dynamically.
-     *
-     * @param array $result Result array
-     * @param string $foreignTableName Name of foreign table
-     * @param string $localFieldName Current handle field name
-     * @return array Query parts with keys WHERE, ORDERBY, GROUPBY, LIMIT
-     */
-    protected function processForeignTableClause(array $result, $foreignTableName, $localFieldName)
-    {
-        $database = $this->getDatabaseConnection();
-        $localTable = $result['tableName'];
-
-        $foreignTableClause = '';
-        if (!empty($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
-            && is_string($result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'])
-        ) {
-            $foreignTableClause = $result['processedTca']['columns'][$localFieldName]['config']['foreign_table_where'];
-            // Replace possible markers in query
-            if (strstr($foreignTableClause, '###REC_FIELD_')) {
-                // " AND table.field='###REC_FIELD_field1###' AND ..." -> array(" AND table.field='", "field1###' AND ...")
-                $whereClauseParts = explode('###REC_FIELD_', $foreignTableClause);
-                foreach ($whereClauseParts as $key => $value) {
-                    if ($key !== 0) {
-                        // "field1###' AND ..." -> array("field1", "' AND ...")
-                        $whereClauseSubParts = explode('###', $value, 2);
-                        // @todo: Throw exception if there is no value? What happens for NEW records?
-                        $rowFieldValue = $result['databaseRow'][$whereClauseSubParts[0]];
-                        if (is_array($rowFieldValue)) {
-                            // If a select or group field is used here, it may have been processed already and
-                            // is now an array. Use first selected value in this case.
-                            $rowFieldValue = $rowFieldValue[0];
-                        }
-                        if (substr($whereClauseParts[0], -1) === '\'' && $whereClauseSubParts[1][0] === '\'') {
-                            $whereClauseParts[$key] = $database->quoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
-                        } else {
-                            $whereClauseParts[$key] = $database->fullQuoteStr($rowFieldValue, $foreignTableName) . $whereClauseSubParts[1];
-                        }
-                    }
-                }
-                $foreignTableClause = implode('', $whereClauseParts);
-            }
-
-            $siteRootUid = 0;
-            foreach ($result['rootline'] as $rootlinePage) {
-                if (!empty($rootlinePage['is_siteroot'])) {
-                    $siteRootUid = (int)$rootlinePage['uid'];
-                    break;
-                }
-            }
-            $pageTsConfigId = 0;
-            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID']) {
-                $pageTsConfigId = (int)$result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_ID'];
-            }
-            $pageTsConfigIdList = 0;
-            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST']) {
-                $pageTsConfigIdList = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_IDLIST'];
-                $pageTsConfigIdListArray = GeneralUtility::trimExplode(',', $pageTsConfigIdList, true);
-                $pageTsConfigIdList = array();
-                foreach ($pageTsConfigIdListArray as $pageTsConfigIdListElement) {
-                    if (MathUtility::canBeInterpretedAsInteger($pageTsConfigIdListElement)) {
-                        $pageTsConfigIdList[] = (int)$pageTsConfigIdListElement;
-                    }
-                }
-                $pageTsConfigIdList = implode(',', $pageTsConfigIdList);
-            }
-            $pageTsConfigString = '';
-            if ($result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR']) {
-                $pageTsConfigString = $result['pageTsConfig']['TCEFORM.'][$localTable . '.'][$localFieldName . '.']['PAGE_TSCONFIG_STR'];
-                $pageTsConfigString = $database->quoteStr($pageTsConfigString, $foreignTableName);
-            }
-
-            $foreignTableClause = str_replace(
-                array(
-                    '###CURRENT_PID###',
-                    '###THIS_UID###',
-                    '###SITEROOT###',
-                    '###PAGE_TSCONFIG_ID###',
-                    '###PAGE_TSCONFIG_IDLIST###',
-                    '###PAGE_TSCONFIG_STR###'
-                ),
-                array(
-                    (int)$result['effectivePid'],
-                    (int)$result['databaseRow']['uid'],
-                    $siteRootUid,
-                    $pageTsConfigId,
-                    $pageTsConfigIdList,
-                    $pageTsConfigString
-                ),
-                $foreignTableClause
-            );
-        }
-
-        // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
-        // Prepend a space to make sure "[[:space:]]+" will find a space there for the first element.
-        $foreignTableClause = ' ' . $foreignTableClause;
-        $foreignTableClauseArray = array(
-            'WHERE' => '',
-            'GROUPBY' => '',
-            'ORDERBY' => '',
-            'LIMIT' => '',
-        );
-        // Find LIMIT
-        $reg = array();
-        if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
-            $foreignTableClauseArray['LIMIT'] = trim($reg[2]);
-            $foreignTableClause = $reg[1];
-        }
-        // Find ORDER BY
-        $reg = array();
-        if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
-            $foreignTableClauseArray['ORDERBY'] = trim($reg[2]);
-            $foreignTableClause = $reg[1];
-        }
-        // Find GROUP BY
-        $reg = array();
-        if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $foreignTableClause, $reg)) {
-            $foreignTableClauseArray['GROUPBY'] = trim($reg[2]);
-            $foreignTableClause = $reg[1];
-        }
-        // Rest is assumed to be "WHERE" clause
-        $foreignTableClauseArray['WHERE'] = $foreignTableClause;
-
-        return $foreignTableClauseArray;
-    }
-
-    /**
-     * Validate and sanitize database row values of the select field with the given name.
-     * Creates an array out of databaseRow[selectField] values.
-     *
-     * @param array $result The current result array.
-     * @param string $fieldName Name of the current select field.
-     * @param array $staticValues Array with statically defined items, item value is used as array key.
-     * @return array
-     */
-    protected function processSelectFieldValue(array $result, $fieldName, array $staticValues)
-    {
-        $fieldConfig = $result['processedTca']['columns'][$fieldName];
-
-        // For single select fields we just keep the current value because the renderer
-        // will take care of showing the "Invalid value" text.
-        // For maxitems=1 select fields is is also possible to select empty values.
-        // @todo: move handling of invalid values to this data provider.
-        if ($fieldConfig['config']['maxitems'] === 1 && empty($fieldConfig['config']['MM'])) {
-            return array($result['databaseRow'][$fieldName]);
-        }
-
-        $currentDatabaseValues = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : '';
-        // Selecting empty values does not make sense for fields that can contain more than one item
-        // because it is impossible to determine if the empty value or nothing is selected.
-        // This is why empty values will be removed for multi value fields.
-        $currentDatabaseValuesArray = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
-        $newDatabaseValueArray = [];
-
-        // Add all values that were defined by static methods and do not come from the relation
-        // e.g. TCA, TSconfig, itemProcFunc etc.
-        foreach ($currentDatabaseValuesArray as $value) {
-            if (isset($staticValues[$value])) {
-                $newDatabaseValueArray[] = $value;
-            }
-        }
-
-        if (isset($fieldConfig['config']['foreign_table']) && !empty($fieldConfig['config']['foreign_table'])) {
-            /** @var RelationHandler $relationHandler */
-            $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
-            $relationHandler->registerNonTableValues = !empty($fieldConfig['config']['allowNonIdValues']);
-            if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
-                // MM relation
-                $relationHandler->start(
-                    $currentDatabaseValues,
-                    $fieldConfig['config']['foreign_table'],
-                    $fieldConfig['config']['MM'],
-                    $result['databaseRow']['uid'],
-                    $result['tableName'],
-                    $fieldConfig['config']
-                );
-            } else {
-                // Non MM relation
-                // If not dealing with MM relations, use default live uid, not versioned uid for record relations
-                $relationHandler->start(
-                    $currentDatabaseValues,
-                    $fieldConfig['config']['foreign_table'],
-                    '',
-                    $this->getLiveUid($result),
-                    $result['tableName'],
-                    $fieldConfig['config']
-                );
-            }
-            $newDatabaseValueArray = array_merge($newDatabaseValueArray, $relationHandler->getValueArray());
-        }
-
-        return array_unique($newDatabaseValueArray);
-    }
-
-    /**
-     * Gets the record uid of the live default record. If already
-     * pointing to the live record, the submitted record uid is returned.
-     *
-     * @param array $result Result array
-     * @return int
-     * @throws \UnexpectedValueException
-     */
-    protected function getLiveUid(array $result)
-    {
-        $table = $result['tableName'];
-        $row = $result['databaseRow'];
-        $uid = $row['uid'];
-        if (!empty($result['processedTca']['ctrl']['versioningWS'])
-            && $result['pid'] === -1
-        ) {
-            if (empty($row['t3ver_oid'])) {
-                throw new \UnexpectedValueException(
-                    'No t3ver_oid found for record ' . $row['uid'] . ' on table ' . $table,
-                    1440066481
-                );
-            }
-            $uid = $row['t3ver_oid'];
-        }
-        return $uid;
-    }
-
-    /**
-     * @return LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-
-    /**
-     * @return DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
-     * @return BackendUserAuthentication
-     */
-    protected function getBackendUser()
-    {
-        return $GLOBALS['BE_USER'];
-    }
 }
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca1af2ee793f49916036e45c3ccade996df4b161
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php
@@ -0,0 +1,189 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\FormDataProvider;
+
+/*
+ * 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 TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Tree\TableConfiguration\ExtJsArrayTreeRenderer;
+use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
+use TYPO3\CMS\Core\Tree\TableConfiguration\TreeDataProviderFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
+ */
+class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProviderInterface
+{
+    /**
+     * Resolve select items
+     *
+     * @param array $result
+     * @return array
+     * @throws \UnexpectedValueException
+     */
+    public function addData(array $result)
+    {
+        $table = $result['tableName'];
+
+        foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
+            if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') {
+                continue;
+            }
+
+            // Make sure we are only processing supported renderTypes
+            if ($fieldConfig['config']['renderType'] !== 'selectTree') {
+                continue;
+            }
+
+            $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'], $table, $fieldName);
+            $fieldConfig['config']['maxitems'] = $this->sanitizeMaxItems($fieldConfig['config']['maxitems']);
+
+            $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
+            $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']);
+            $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']);
+            $staticItems = $fieldConfig['config']['items'];
+
+            $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'] = $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->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;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Renders the Ext JS tree.
+     *
+     * @param array $result The current result array.
+     * @param array $fieldConfig The configuration of the current field.
+     * @param string $fieldName The name of the current field.
+     * @param array $staticItems The static items from the field config.
+     * @return array The tree data configuration
+     */
+    protected function renderTree(array $result, array $fieldConfig, $fieldName, array $staticItems)
+    {
+        $allowedUids = [];
+        foreach ($fieldConfig['config']['items'] as $item) {
+            if ((int)$item[1] > 0) {
+                $allowedUids[] = $item[1];
+            }
+        }
+
+        $treeDataProvider = TreeDataProviderFactory::getDataProvider(
+            $fieldConfig['config'],
+            $result['tableName'],
+            $fieldName,
+            $result['databaseRow']
+        );
+        $treeDataProvider->setSelectedList(implode(',', $result['databaseRow'][$fieldName]));
+        $treeDataProvider->setItemWhiteList($allowedUids);
+        $treeDataProvider->initializeTreeData();
+
+        /** @var ExtJsArrayTreeRenderer $treeRenderer */
+        $treeRenderer = GeneralUtility::makeInstance(ExtJsArrayTreeRenderer::class);
+
+        /** @var TableConfigurationTree $tree */
+        $tree = GeneralUtility::makeInstance(TableConfigurationTree::class);
+        $tree->setDataProvider($treeDataProvider);
+        $tree->setNodeRenderer($treeRenderer);
+
+        $treeItems = $this->prepareAdditionalItems($staticItems, $result['databaseRow'][$fieldName]);
+        $treeItems[] = $tree->render();
+
+        $treeConfig = [
+            'items' => $treeItems,
+            'selectedNodes' => $this->prepareSelectedNodes($fieldConfig['config']['items'], $result['databaseRow'][$fieldName])
+        ];
+
+        return $treeConfig;
+    }
+
+    /**
+     * Prepare the additional items that get prepended to the tree as leaves
+     *
+     * @param array $itemArray
+     * @param array $selectedNodes
+     * @return array
+     */
+    protected function prepareAdditionalItems(array $itemArray, array $selectedNodes)
+    {
+        $additionalItems = [];
+
+        foreach ($itemArray as $item) {
+            if ($item[1] === '--div--') {
+                continue;
+            }
+
+            $additionalItems[] = [
+                'uid' => $item[1],
+                'text' => $item[0],
+                'selectable' => true,
+                'leaf' => true,
+                'checked' => in_array($item[1], $selectedNodes),
+                'icon' => $item[3]
+            ];
+        }
+
+        return $additionalItems;
+    }
+
+    /**
+     * Re-create the old pipe based syntax of selected nodes for the ExtJS rendering part
+     *
+     * @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 . '|' . rawurlencode($possibleSelectBoxItem[0]);
+                    }
+                }
+            }
+        }
+
+        return $selectedNodes;
+    }
+}
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
index 54cc42d982c05cfc4aaf5455bb4f074d0e42dab2..d3404629e0ec3daab22136547f87932e0574be7c 100644
--- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
@@ -110,6 +110,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => 'foo',
                             ],
@@ -138,6 +139,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'aLabel',
@@ -181,6 +183,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'aLabel',
@@ -219,6 +222,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'anUnknownValue',
                         ],
                     ],
@@ -246,6 +250,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'tables',
                             'maxitems' => 1,
                         ],
@@ -305,6 +310,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'pagetypes',
                             'items' => [],
                             'maxitems' => 1,
@@ -481,6 +487,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'exclude',
                         ],
                     ],
@@ -512,6 +519,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'exclude',
                         ],
                     ],
@@ -600,6 +608,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'explicitValues',
                         ],
                     ],
@@ -617,6 +626,7 @@ class TcaSelectItemsTest extends UnitTestCase
                         'label' => 'aFieldTitle',
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'authMode' => 'explicitAllow',
                             'items' => [
                                 0 => [
@@ -668,6 +678,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'explicitValues',
                         ],
                     ],
@@ -685,6 +696,7 @@ class TcaSelectItemsTest extends UnitTestCase
                         'label' => 'aFieldTitle',
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'authMode' => 'explicitDeny',
                             'items' => [
                                 0 => [
@@ -736,6 +748,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'explicitValues',
                         ],
                     ],
@@ -753,6 +766,7 @@ class TcaSelectItemsTest extends UnitTestCase
                         'label' => 'aFieldTitle',
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'authMode' => 'individual',
                             'items' => [
                                 0 => [
@@ -825,6 +839,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'explicitValues',
                         ],
                     ],
@@ -842,6 +857,7 @@ class TcaSelectItemsTest extends UnitTestCase
                         'label' => 'aFieldTitle',
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'authMode' => 'individual',
                             'items' => [
                                 0 => [
@@ -914,6 +930,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'languages',
                         ],
                     ],
@@ -964,6 +981,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'custom',
                         ],
                     ],
@@ -1019,6 +1037,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'special' => 'modListGroup',
                         ],
                     ],
@@ -1083,6 +1102,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'fileFolder' => $directory,
                             'fileFolder_extList' => 'gif',
                             'fileFolder_recursions' => 1,
@@ -1265,6 +1285,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'fTable',
                             'foreign_table_where' => $foreignTableWhere,
                         ],
@@ -1343,6 +1364,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'fTable',
                         ],
                     ],
@@ -1367,6 +1389,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'fTable',
                             'foreign_table_where' => 'AND ftable.uid=1 GROUP BY groupField ORDER BY orderField LIMIT 1,2',
                         ],
@@ -1423,6 +1446,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'fTable',
                             'items' => [
                                 0 => [
@@ -1493,6 +1517,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'fTable',
                             'foreign_table_prefix' => 'aPrefix',
                             'items' => [],
@@ -1571,6 +1596,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'keepMe',
@@ -1626,6 +1652,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'keepMe',
@@ -1684,6 +1711,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'keepMe',
@@ -1736,6 +1764,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'authMode' => 'explicitAllow',
                             'items' => [
                                 0 => [
@@ -1789,6 +1818,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'doktype' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'keepMe',
@@ -1835,6 +1865,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'doktype' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'keepMe',
@@ -1889,6 +1920,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [],
                             'itemsProcFunc' => function (array $parameters, $pObj) {
                                 $parameters['items'] = [
@@ -1915,6 +1947,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $expected['databaseRow']['aField'] = ['aValue'];
         $expected['processedTca']['columns']['aField']['config'] = [
             'type' => 'select',
+            'renderType' => 'selectSingle',
             'items' => [
                 0 => [
                     0 => 'aLabel',
@@ -1955,6 +1988,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'aKey' => 'aValue',
                             'items' => [
                                 0 => [
@@ -2024,6 +2058,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'aKey' => 'aValue',
                             'items' => [
                                 0 => [
@@ -2072,6 +2107,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'items' => [
                                 0 => [
                                     0 => 'aLabel',
@@ -2140,6 +2176,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 999,
                             'foreign_table' => 'foreignTable',
                             'MM' => 'aTable_foreignTable_mm',
@@ -2195,6 +2232,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 999,
                             'foreign_table' => 'foreignTable',
                             'items' => [],
@@ -2256,6 +2294,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'foreign_table' => 'foreignTable',
                             'maxitems' => 999,
                             'items' => [
@@ -2292,6 +2331,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 999,
                             'items' => [
                                 ['foo', 'foo', null, null],
@@ -2331,6 +2371,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 1,
                             'items' => [],
                         ],
@@ -2366,6 +2407,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 999,
                             'items' => [
                                 ['a', '', null, null],
@@ -2411,6 +2453,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 999,
                             'items' => [
                                 ['foo', 'foo', null, null],
@@ -2451,6 +2494,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
+                            'renderType' => 'selectSingle',
                             'maxitems' => 1,
                             'items' => [
                                 ['foo', 'foo', null, null],
@@ -2489,6 +2533,7 @@ class TcaSelectItemsTest extends UnitTestCase
                             'aField' => [
                                 'config' => [
                                     'type' => 'select',
+                                    'renderType' => 'selectSingle',
                                     'maxitems' => 999,
                                     'MM' => 'mm_aTable_foreignTable',
                                     'foreign_table' => 'foreignTable',
@@ -2517,6 +2562,7 @@ class TcaSelectItemsTest extends UnitTestCase
                             'aField' => [
                                 'config' => [
                                     'type' => 'select',
+                                    'renderType' => 'selectSingle',
                                     'maxitems' => 999,
                                     'MM' => 'mm_aTable_foreignTable',
                                     'foreign_table' => 'foreignTable',
@@ -2542,6 +2588,7 @@ class TcaSelectItemsTest extends UnitTestCase
                             'aField' => [
                                 'config' => [
                                     'type' => 'select',
+                                    'renderType' => 'selectSingle',
                                     'maxitems' => 1,
                                     'MM' => 'mm_aTable_foreignTable',
                                     'foreign_table' => 'foreignTable',
@@ -2569,6 +2616,7 @@ class TcaSelectItemsTest extends UnitTestCase
                             'aField' => [
                                 'config' => [
                                     'type' => 'select',
+                                    'renderType' => 'selectSingle',
                                     'maxitems' => 1,
                                     'MM' => 'mm_aTable_foreignTable',
                                     'foreign_table' => 'foreignTable',
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..577cf6442cc220436e8986b1b0b12b8402e52ad8
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php
@@ -0,0 +1,119 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
+
+/*
+ * 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 Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
+use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeDataProvider;
+use TYPO3\CMS\Core\Tree\TableConfiguration\TableConfigurationTree;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Tests for the TcaSelectTreeItems provider.
+ *
+ * This test only covers the renderTree() method. All other methods are covered TcaSelecItemsTest
+ *
+ * @see TcaSelecItemsTest
+ */
+class TcaSelectTreeItemsTest extends UnitTestCase
+{
+    /**
+     * @var TcaSelectTreeItems
+     */
+    protected $subject;
+
+    /**
+     * @var array A backup of registered singleton instances
+     */
+    protected $singletonInstances = [];
+
+    /**
+     * Initializes the mock object.
+     */
+    public function setUp()
+    {
+        $this->singletonInstances = GeneralUtility::getSingletonInstances();
+        $this->subject = new TcaSelectTreeItems();
+    }
+
+    protected function tearDown()
+    {
+        GeneralUtility::purgeInstances();
+        GeneralUtility::resetSingletonInstances($this->singletonInstances);
+        parent::tearDown();
+    }
+
+    /**
+     * @test
+     */
+    public function addDataAddsTreeConfigurationForExtJs()
+    {
+        $GLOBALS['TCA']['foreignTable'] = [];
+
+        /** @var DatabaseConnection|ObjectProphecy $database */
+        $database = $this->prophesize(DatabaseConnection::class);
+        $GLOBALS['TYPO3_DB'] = $database->reveal();
+
+        /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
+        $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
+        $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
+
+        /** @var  DatabaseTreeDataProvider|ObjectProphecy $treeDataProviderProphecy */
+        $treeDataProviderProphecy = $this->prophesize(DatabaseTreeDataProvider::class);
+        GeneralUtility::addInstance(DatabaseTreeDataProvider::class, $treeDataProviderProphecy->reveal());
+
+        /** @var  TableConfigurationTree|ObjectProphecy $treeDataProviderProphecy */
+        $tableConfigurationTreeProphecy = $this->prophesize(TableConfigurationTree::class);
+        GeneralUtility::addInstance(TableConfigurationTree::class, $tableConfigurationTreeProphecy->reveal());
+        $tableConfigurationTreeProphecy->setDataProvider(Argument::cetera())->shouldBeCalled();
+        $tableConfigurationTreeProphecy->setNodeRenderer(Argument::cetera())->shouldBeCalled();
+        $tableConfigurationTreeProphecy->render()->shouldBeCalled()->willReturn(['fake', 'tree', 'data']);
+
+        $input = [
+            'tableName' => 'aTable',
+            'databaseRow' => [
+                'aField' => '1'
+            ],
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'select',
+                            'renderType' => 'selectTree',
+                            'treeConfig' => [
+                                'childrenField' => 'childrenField'
+                            ],
+                            'foreign_table' => 'foreignTable',
+                            'items' => [],
+                            'maxitems' => 1
+                        ],
+                    ],
+                ],
+            ],
+        ];
+
+        $expected = $input;
+        $expected['databaseRow']['aField'] = ['1'];
+        $expected['processedTca']['columns']['aField']['config']['treeData'] = [
+            'items' => [['fake', 'tree', 'data']],
+            'selectedNodes' => []
+        ];
+        $this->assertEquals($expected, $this->subject->addData($input));
+    }
+}
diff --git a/typo3/sysext/compatibility6/Classes/Form/FormDataProvider/TcaFlexProcess.php b/typo3/sysext/compatibility6/Classes/Form/FormDataProvider/TcaFlexProcess.php
index 521b90c464c49c0267775615e17ccdb1873ce827..3d462a11783f84c809b3e5d3b6d3cba99daf64ae 100644
--- a/typo3/sysext/compatibility6/Classes/Form/FormDataProvider/TcaFlexProcess.php
+++ b/typo3/sysext/compatibility6/Classes/Form/FormDataProvider/TcaFlexProcess.php
@@ -26,7 +26,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *
  * This is typically the last provider, executed after TcaFlexPrepare
  */
-class TcaFlexProcess extends AbstractItemProvider implements FormDataProviderInterface
+class TcaFlexProcess implements FormDataProviderInterface
 {
     /**
      * Determine possible pageTsConfig overrides and apply them to ds.
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index 12bd13929c26649174f7a842678985c987e259cd..2b6a6b7385d5fdf268f86c6eb7e431df86010c1f 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -488,11 +488,16 @@ return array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaFlexFetch::class,
                         ),
                     ),
-                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class,
                         ),
                     ),
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                        'depends' => array(
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class,
+                        ),
+                    ),
                     \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class,
@@ -541,11 +546,16 @@ return array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class,
                         ),
                     ),
-                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class,
                         ),
                     ),
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                        'depends' => array(
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class,
+                        ),
+                    ),
                     \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class,
@@ -608,11 +618,16 @@ return array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaCheckboxItems::class,
                         ),
                     ),
-                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class,
                         ),
                     ),
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => array(
+                        'depends' => array(
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectTreeItems::class,
+                        ),
+                    ),
                     \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class => array(
                         'depends' => array(
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class,