[TASK] FormEngine: The factory 17/39517/6
authorChristian Kuhn <lolli@schwarzbu.ch>
Tue, 12 May 2015 21:41:46 +0000 (23:41 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Mon, 18 May 2015 10:20:20 +0000 (12:20 +0200)
Creation of container and elements instances in the FormEngine is
hard coded and hard to overwrite or adapt.
The patch extends the existing NodeFactory with resolver code to
find an appropriate class for a given requested type. All FormEngine
internal container and element requests are now routed through
NodeFactory. This allows to loosen the strict dependency between
TCA config "type" to an implementing class by moving the resolving
code into the factory. This is done for SelectElement which is now
split into multiple smaller classes - one for each display type. The
NodeFactory is covered by unit tests since the resolving code will
become more complex and fine grained in the future.
As a side effect the patch resolves a hack in the FormDataTraverser
which no longer calls internal stuff of the select element.
The NodeFactory is prepared to be extended with an API for extensions
to steer and overwrite default implementations. This will be added
with a next patch.

Change-Id: I2253a0fe3240366d0d271a3cd82119ce3dc52012
Resolves: #67006
Releases: master
Reviewed-on: http://review.typo3.org/39517
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
36 files changed:
typo3/sysext/backend/Classes/Exception.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/AbstractNode.php
typo3/sysext/backend/Classes/Form/Container/FlexFormContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormContainerContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormElementContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormLanguageContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormNoTabsContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormSectionContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormTabsContainer.php
typo3/sysext/backend/Classes/Form/Container/FullRecordContainer.php
typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
typo3/sysext/backend/Classes/Form/Container/InlineRecordContainer.php
typo3/sysext/backend/Classes/Form/Container/ListOfFieldsContainer.php
typo3/sysext/backend/Classes/Form/Container/NoTabsContainer.php
typo3/sysext/backend/Classes/Form/Container/PaletteAndSingleContainer.php
typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php
typo3/sysext/backend/Classes/Form/Container/SoloFieldContainer.php
typo3/sysext/backend/Classes/Form/Container/TabsContainer.php
typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
typo3/sysext/backend/Classes/Form/Element/InputElement.php
typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Element/SelectElement.php [deleted file]
typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Element/TextElement.php
typo3/sysext/backend/Classes/Form/Element/TreeElement.php [deleted file]
typo3/sysext/backend/Classes/Form/Exception.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataTraverser.php
typo3/sysext/backend/Classes/Form/FormEngine.php
typo3/sysext/backend/Classes/Form/NodeFactory.php
typo3/sysext/backend/Classes/Form/NodeInterface.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Utility/FormEngineUtility.php
typo3/sysext/backend/Tests/Unit/Form/NodeFatoryTest.php [new file with mode: 0644]

diff --git a/typo3/sysext/backend/Classes/Exception.php b/typo3/sysext/backend/Classes/Exception.php
new file mode 100644 (file)
index 0000000..500ef52
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace TYPO3\CMS\Backend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Generic backend exception
+ */
+class Exception extends \Exception {
+
+}
\ No newline at end of file
index ce376fc..0928db5 100644 (file)
@@ -20,7 +20,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 /**
  * Base class for container and single elements - their abstracts extend from here.
  */
-abstract class AbstractNode {
+abstract class AbstractNode implements NodeInterface {
 
        /**
         * A list of global options given from parent to child elements
index a865017..c3a6321 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Backend\Form\FlexFormsHelper;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Entry container to a flex form element. This container is created by
@@ -76,9 +77,10 @@ class FlexFormContainer extends AbstractContainer {
                $options = $this->globalOptions;
                $options['flexFormDataStructureArray'] = $flexFormDataStructureArray;
                $options['flexFormRowData'] = $flexFormRowData;
-               /** @var FlexFormLanguageContainer $flexFormLanguageContainer */
-               $flexFormLanguageContainer = GeneralUtility::makeInstance(FlexFormLanguageContainer::class);
-               return $flexFormLanguageContainer->setGlobalOptions($options)->render();
+               $options['type'] = 'flexFormLanguageContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               return $nodeFactory->create($options)->render();
        }
 
        /**
index 11f841c..a816ebc 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Flex form container implementation
@@ -94,9 +95,10 @@ class FlexFormContainerContainer extends AbstractContainer {
                $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
                // Append container specific stuff to field prefix
                $options['flexFormFormPrefix'] =  $flexFormFormPrefix . '[' . $flexFormContainerCounter . '][' .  $this->globalOptions['flexFormContainerName'] . '][el]';
-               /** @var FlexFormElementContainer $containerContent */
-               $containerContent = GeneralUtility::makeInstance(FlexFormElementContainer::class);
-               $containerContentResult = $containerContent->setGlobalOptions($options)->render();
+               $options['type'] = 'flexFormElementContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               $containerContentResult = $nodeFactory->create($options)->render();
 
                $html = array();
                $html[] = '<div id="' . $flexFormFieldIdentifierPrefix . '" class="t3-form-field-container-flexsections t3-flex-section">';
index c736dd2..c5e6117 100644 (file)
@@ -78,9 +78,10 @@ class FlexFormElementContainer extends AbstractContainer {
                                $options['flexFormRowData'] = is_array($flexFormRowData[$flexFormFieldName]['el']) ? $flexFormRowData[$flexFormFieldName]['el'] : array();
                                $options['flexFormSectionType'] = $flexFormFieldName;
                                $options['flexFormSectionTitle'] = $sectionTitle;
-                               /** @var FlexFormSectionContainer $sectionContainer */
-                               $sectionContainer = GeneralUtility::makeInstance(FlexFormSectionContainer::class);
-                               $sectionContainerResult = $sectionContainer->setGlobalOptions($options)->render();
+                               $options['type'] = 'flexFormSectionContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $sectionContainerResult = $nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $sectionContainerResult);
                        } else {
                                // Single element
@@ -146,10 +147,10 @@ class FlexFormElementContainer extends AbstractContainer {
                                $options = $this->globalOptions;
                                $options['parameterArray'] = $fakeParameterArray;
                                $options['elementBaseName'] = $this->globalOptions['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $vDEFkey . ']';
+                               $options['type'] = $flexFormFieldArray['TCEforms']['config']['type'];
                                /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
-                               $child = $nodeFactory->create($flexFormFieldArray['TCEforms']['config']['type']);
-                               $childResult = $child->setGlobalOptions($options)->render();
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $childResult = $nodeFactory->create($options)->render();
 
                                $theTitle = htmlspecialchars($fakeParameterArray['fieldConf']['label']);
                                $defInfo = array();
index 41af724..115a240 100644 (file)
@@ -20,6 +20,7 @@ use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle flex form language overlays.
@@ -98,14 +99,16 @@ class FlexFormLanguageContainer extends AbstractContainer {
                        $options['flexFormCurrentLanguage'] = $flexFormCurrentLanguage;
                        $options['flexFormNoEditDefaultLanguage'] = $flexFormNoEditDefaultLanguage;
                        if (!$hasTabs) {
-                               /** @var FlexFormNoTabsContainer $flexFormNoTabsContainer */
-                               $flexFormNoTabsContainer = GeneralUtility::makeInstance(FlexFormNoTabsContainer::class);
-                               $flexFormNoTabsResult = $flexFormNoTabsContainer->setGlobalOptions($options)->render();
+                               $options['type'] = 'flexFormNoTabsContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $flexFormNoTabsResult = $nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormNoTabsResult);
                        } else {
-                               /** @var FlexFormTabsContainer $flexFormTabsContainer */
-                               $flexFormTabsContainer = GeneralUtility::makeInstance(FlexFormTabsContainer::class);
-                               $flexFormTabsContainerResult = $flexFormTabsContainer->setGlobalOptions($options)->render();
+                               $options['type'] = 'flexFormTabsContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $flexFormTabsContainerResult = $nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormTabsContainerResult);
                        }
                }
index b293440..1246007 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
  */
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle a flex form that has no tabs.
@@ -77,9 +78,10 @@ class FlexFormNoTabsContainer extends AbstractContainer {
                $options['flexFormFormPrefix'] = '[data][' . $flexFormSheetNameInRowData . '][' . $flexFormCurrentLanguage . ']';
                $options['parameterArray'] = $parameterArray;
 
-               /** @var FlexFormElementContainer $flexFormElementContainer */
-               $flexFormElementContainer = GeneralUtility::makeInstance(FlexFormElementContainer::class);
-               return $flexFormElementContainer->setGlobalOptions($options)->render();
+               $options['type'] = 'flexFormElementContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               return $nodeFactory->create($options)->render();
        }
 
 }
index a7c7ce1..aa2399b 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle flex form sections.
@@ -75,9 +76,10 @@ class FlexFormSectionContainer extends AbstractContainer {
                                        $options['flexFormContainerCounter'] = $flexFormContainerCounter;
                                        $options['flexFormContainerTitle'] = $sectionTitle;
                                        $options['flexFormContainerElementCollapsed'] = (bool)$existingSectionContainerData['el']['_TOGGLE'];
-                                       /** @var FlexFormContainerContainer $flexFormContainerContainer */
-                                       $flexFormContainerContainer = GeneralUtility::makeInstance(FlexFormContainerContainer::class);
-                                       $flexFormContainerContainerResult = $flexFormContainerContainer->setGlobalOptions($options)->render();
+                                       $options['type'] = 'flexFormContainerContainer';
+                                       /** @var NodeFactory $nodeFactory */
+                                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                                       $flexFormContainerContainerResult = $nodeFactory->create($options)->render();
                                        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerResult);
                                }
                        }
@@ -103,9 +105,10 @@ class FlexFormSectionContainer extends AbstractContainer {
                        $options['flexFormContainerCounter'] = $flexFormFieldIdentifierPrefix . '-form';
                        $options['flexFormContainerTitle'] = $sectionTitle;
                        $options['flexFormContainerElementCollapsed'] = FALSE;
-                       /** @var FlexFormContainerContainer $flexFormContainerContainer */
-                       $flexFormContainerContainerTemplate = GeneralUtility::makeInstance(FlexFormContainerContainer::class);
-                       $flexFormContainerContainerTemplateResult = $flexFormContainerContainerTemplate->setGlobalOptions($options)->render();
+                       $options['type'] = 'flexFormContainerContainer';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $flexFormContainerContainerTemplateResult = $nodeFactory->create($options)->render();
 
                        $uniqueId = str_replace('.', '', uniqid('idvar', TRUE));
                        $identifierPrefixJs = 'replace(/' . $flexFormFieldIdentifierPrefix . '-/g,"' . $flexFormFieldIdentifierPrefix . '-"+' . $uniqueId . '+"-")';
index 79117a6..d84f485 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle flex forms that have tabs (multiple "sheets").
@@ -91,9 +92,10 @@ class FlexFormTabsContainer extends AbstractContainer {
                                'tab',
                                $tabIdString . '-' . $tabCounter,
                        );
-                       /** @var FlexFormElementContainer $flexFormElementContainer */
-                       $flexFormElementContainer = GeneralUtility::makeInstance(FlexFormElementContainer::class);
-                       $childReturn = $flexFormElementContainer->setGlobalOptions($options)->render();
+                       $options['type'] = 'flexFormElementContainer';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $childReturn = $nodeFactory->create($options)->render();
 
                        $tabsContent[] = array(
                                'label' => !empty($sheetDataStructure['ROOT']['TCEforms']['sheetTitle']) ? $languageService->sL($sheetDataStructure['ROOT']['TCEforms']['sheetTitle']) : $sheetName,
index d747840..37db657 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
 
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * A container rendering a "full record". This is an entry container used as first
@@ -103,15 +104,15 @@ class FullRecordContainer extends AbstractContainer {
                $options['additionalPreviewLanguageData'] = $this->additionalPreviewLanguageData;
 
                if ($hasTabs) {
-                       /** @var TabsContainer $TabsContainer */
-                       $container = GeneralUtility::makeInstance(TabsContainer::class);
-                       $container->setGlobalOptions($options);
-                       $resultArray = $container->render();
+                       $options['type'] = 'tabsContainer';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $resultArray = $nodeFactory->create($options)->render();
                } else {
-                       /** @var NoTabsContainer $NoTabsContainer */
-                       $container = GeneralUtility::makeInstance(NoTabsContainer::class);
-                       $container->setGlobalOptions($options);
-                       $resultArray = $container->render();
+                       $options['type'] = 'noTabsContainer';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $resultArray = $nodeFactory->create($options)->render();
                }
 
                return $resultArray;
index 277cfad..4327e38 100644 (file)
@@ -26,6 +26,7 @@ use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Inline element entry container.
@@ -222,9 +223,10 @@ class InlineControlContainer extends AbstractContainer {
                                $options['inlineRelatedRecordConfig'] = $config;
                                $options['inlineData'] = $this->inlineData;
                                $options['inlineStructure'] = $inlineStackProcessor->getStructure();
-                               /** @var InlineRecordContainer $inlineRecordContainer */
-                               $inlineRecordContainer = GeneralUtility::makeInstance(InlineRecordContainer::class);
-                               $childArray = $inlineRecordContainer->setGlobalOptions($options)->render();
+                               $options['type'] = 'inlineRecordContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $childArray = $nodeFactory->create($options)->render();
                                $html .= $childArray['html'];
                                $childArray['html'] = '';
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
@@ -654,4 +656,4 @@ class InlineControlContainer extends AbstractContainer {
                return $GLOBALS['LANG'];
        }
 
-}
\ No newline at end of file
+}
index fda4645..cbfa6df 100644 (file)
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Render a single inline record relation.
@@ -216,9 +217,10 @@ class InlineRecordContainer extends AbstractContainer {
                        $domObjectId . '-' . $table . '-' . $row['uid'],
                );
                $options['overruleTypesArray'] = $overruleTypesArray;
-               /** @var FullRecordContainer $entryContainer */
-               $entryContainer = GeneralUtility::makeInstance(FullRecordContainer::class);
-               return $entryContainer->setGlobalOptions($options)->render();
+               $options['type'] = 'fullRecordContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               return $nodeFactory->create($options)->render();
        }
 
        /**
index 76672f5..5ec2b22 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Render a given list of field of a TCA table.
@@ -71,10 +72,10 @@ class ListOfFieldsContainer extends AbstractContainer {
 
                $options = $this->globalOptions;
                $options['fieldsArray'] = $finalFieldsConfiguration;
-               /** @var PaletteAndSingleContainer $paletteAndSingleContainer */
-               $paletteAndSingleContainer = GeneralUtility::makeInstance(PaletteAndSingleContainer::class);
-               $paletteAndSingleContainer->setGlobalOptions($options);
-               return $paletteAndSingleContainer->render();
+               $options['type'] = 'paletteAndSingleContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               return $nodeFactory->create($options)->render();
        }
 
        /**
index bc62976..e97ca56 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
  */
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle a record that has no tabs.
@@ -30,10 +31,11 @@ class NoTabsContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               /** @var PaletteAndSingleContainer $paletteAndSingleContainer */
-               $paletteAndSingleContainer = GeneralUtility::makeInstance(PaletteAndSingleContainer::class);
-               $paletteAndSingleContainer->setGlobalOptions($this->globalOptions);
-               $resultArray = $paletteAndSingleContainer->render();
+               $options = $this->globalOptions;
+               $options['type'] = 'paletteAndSingleContainer';
+               /** @var NodeFactory $nodeFactory */
+               $nodeFactory = $this->globalOptions['nodeFactory'];
+               $resultArray = $nodeFactory->create($options)->render();
                $resultArray['html'] = '<div class="tab-content">' . $resultArray['html'] . '</div>';
                return $resultArray;
        }
index 07a7e2e..157c779 100644 (file)
@@ -19,6 +19,7 @@ use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle palettes and single fields.
@@ -136,10 +137,10 @@ class PaletteAndSingleContainer extends AbstractContainer {
                                $options['fieldName'] = $fieldName;
                                $options['fieldExtra'] = $fieldConfiguration['fieldExtra'];
 
-                               /** @var SingleFieldContainer $singleFieldContainer */
-                               $singleFieldContainer = GeneralUtility::makeInstance(SingleFieldContainer::class);
-                               $singleFieldContainer->setGlobalOptions($options);
-                               $childResultArray = $singleFieldContainer->render();
+                               $options['type'] = 'singleFieldContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $childResultArray = $nodeFactory->create($options)->render();
 
                                if (!empty($childResultArray['html'])) {
                                        $mainStructureCounter ++;
@@ -260,10 +261,10 @@ class PaletteAndSingleContainer extends AbstractContainer {
                                $options['fieldName'] = $fieldName;
                                $options['fieldExtra'] = $fieldArray['fieldExtra'];
 
-                               /** @var SingleFieldContainer $singleFieldContainer */
-                               $singleFieldContainer = GeneralUtility::makeInstance(SingleFieldContainer::class);
-                               $singleFieldContainer->setGlobalOptions($options);
-                               $singleFieldContentArray = $singleFieldContainer->render();
+                               $options['type'] = 'singleFieldContainer';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $singleFieldContentArray = $nodeFactory->create($options)->render();
 
                                if (!empty($singleFieldContentArray['html'])) {
                                        $foundRealElement = TRUE;
index a7ae43d..284f209 100644 (file)
@@ -25,7 +25,6 @@ use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Utility\DiffUtility;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Core\Database\RelationHandler;
-use TYPO3\CMS\Backend\Form\Element\NoneElement;
 
 /**
  * Container around a "single field".
@@ -160,10 +159,10 @@ class SingleFieldContainer extends AbstractContainer {
                        $options = $this->globalOptions;
                        $options['parameterArray'] = $parameterArray;
                        $options['elementBaseName'] = $newElementBaseName;
-                       /** @var NodeFactory $childFactory */
-                       $childFactory = GeneralUtility::makeInstance(NodeFactory::class);
-                       $childElement = $childFactory->create($parameterArray['fieldConf']['config']['type']);
-                       $resultArray = $childElement->setGlobalOptions($options)->render();
+                       $options['type'] = $parameterArray['fieldConf']['config']['type'];
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $resultArray = $nodeFactory->create($options)->render();
                        $html = $resultArray['html'];
 
                        // @todo: the language handling, the null and the placeholder stuff should be embedded in the single
@@ -253,9 +252,10 @@ class SingleFieldContainer extends AbstractContainer {
                                $options['table'] = '';
                                $options['parameterArray'] = $parameterArray;
                                $options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
-                               /** @var NoneElement $noneElement */
-                               $noneElement = GeneralUtility::makeInstance(NoneElement::class);
-                               $noneElementResult = $noneElement->setGlobalOptions($options)->render();
+                               $options['type'] = 'none';
+                               /** @var NodeFactory $nodeFactory */
+                               $nodeFactory = $this->globalOptions['nodeFactory'];
+                               $noneElementResult = $nodeFactory->create($options)->render();
                                $noneElementHtml = $noneElementResult['html'];
 
                                $placeholderWrap = array();
index 9ae7138..685c18b 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Form\Container;
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * An entry container to render just a single field.
@@ -69,10 +70,10 @@ class SoloFieldContainer extends AbstractContainer {
                                                        $options['fieldName'] = $fieldName;
                                                        $options['fieldExtra'] = $fieldConfiguration['fieldExtra'];
 
-                                                       /** @var SingleFieldContainer $singleFieldContainer */
-                                                       $singleFieldContainer = GeneralUtility::makeInstance(SingleFieldContainer::class);
-                                                       $singleFieldContainer->setGlobalOptions($options);
-                                                       $resultArray = $singleFieldContainer->render();
+                                                       $options['type'] = 'singleFieldContainer';
+                                                       /** @var NodeFactory $nodeFactory */
+                                                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                                                       $resultArray = $nodeFactory->create($options)->render();
                                                }
                                        }
                                }
index c51f237..a903abc 100644 (file)
@@ -15,8 +15,8 @@ namespace TYPO3\CMS\Backend\Form\Container;
  */
 
 use TYPO3\CMS\Lang\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Render all tabs of a record that has tabs.
@@ -86,11 +86,11 @@ class TabsContainer extends AbstractContainer {
                        foreach ($elements as $element) {
                                $options['fieldsArray'][] = implode(';', $element);
                        }
-                       /** @var PaletteAndSingleContainer $paletteAndSingleContainer */
-                       $paletteAndSingleContainer = GeneralUtility::makeInstance(PaletteAndSingleContainer::class);
-                       $paletteAndSingleContainer->setGlobalOptions($options);
+                       $options['type'] = 'paletteAndSingleContainer';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       $childArray = $nodeFactory->create($options)->render();
 
-                       $childArray = $paletteAndSingleContainer->render();
                        $tabsContent[] = array(
                                'label' => $tabWithLabelAndElements['label'],
                                'content' => $childArray['html'],
index 8934c8a..cee05e7 100644 (file)
@@ -30,6 +30,7 @@ use TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface;
 use TYPO3\CMS\Backend\Clipboard\Clipboard;
 use TYPO3\CMS\Backend\Form\AbstractNode;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Base class for form elements of FormEngine. Contains several helper methods used by single elements.
@@ -429,16 +430,17 @@ abstract class AbstractFormElement extends AbstractNode {
                                // Setting the item to a hidden-field.
                                $item = $itemKinds[1];
                                if (is_array($wizardConfiguration['hideParent'])) {
-                                       /** @var NoneElement $noneElement */
-                                       $noneElement = GeneralUtility::makeInstance(NoneElement::class);
-                                       $noneElementOptions = $this->globalOptions;
-                                       $noneElementOptions['parameterArray'] = array(
+                                       $options = $this->globalOptions;
+                                       $options['parameterArray'] = array(
                                                'fieldConf' => array(
                                                        'config' => $wizardConfiguration['hideParent'],
                                                ),
                                                'itemFormElValue' => $PA['itemFormElValue'],
                                        );
-                                       $noneElementResult = $noneElement->setGlobalOptions($noneElementOptions)->render();
+                                       $options['type'] = 'none';
+                                       /** @var NodeFactory $nodeFactory */
+                                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                                       $noneElementResult = $nodeFactory->create($options)->render();
                                        $item .= $noneElementResult['html'];
                                }
                        }
index ebd6015..b66d7d7 100644 (file)
@@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\Utility\ArrayUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Generation of image manipulation TCEform element
@@ -68,15 +69,17 @@ class ImageManipulationElement extends AbstractFormElement {
                }
 
                if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $formEngineDummy = new FormEngine();
-                       $noneElement = GeneralUtility::makeInstance(NoneElement::class, $formEngineDummy);
-                       $elementConfiguration = array(
+                       $options = array();
+                       $options['parameterArray'] = array(
                                'fieldConf' => array(
                                        'config' => $config,
                                ),
                                'itemFormElValue' => $parameterArray['itemFormElValue'],
                        );
-                       return $noneElement->render('', '', '', $elementConfiguration);
+                       $options['type'] = 'none';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       return $nodeFactory->create($options)->render();
                }
 
                $file = $this->getFile($row, $config['file_field']);
index 4b5af9b..6e2cf3f 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Generation of TCEform elements of the type "input"
@@ -79,16 +80,17 @@ class InputElement extends AbstractFormElement {
                        if (in_array('password', $evalList)) {
                                $itemFormElValue = $itemFormElValue ? '*********' : '';
                        }
-                       /** @var NoneElement $noneElement */
-                       $noneElement = GeneralUtility::makeInstance(NoneElement::class);
-                       $noneElementOptions = $this->globalOptions;
-                       $noneElementOptions['parameterArray'] = array(
+                       $options = $this->globalOptions;
+                       $options['parameterArray'] = array(
                                'fieldConf' => array(
                                        'config' => $config,
                                ),
                                'itemFormElValue' => $itemFormElValue,
                        );
-                       return $noneElement->setGlobalOptions($noneElementOptions)->render();
+                       $options['type'] = 'none';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       return $nodeFactory->create($options)->render();
                }
 
                if (in_array('datetime', $evalList, TRUE)
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
new file mode 100644 (file)
index 0000000..03bf7b1
--- /dev/null
@@ -0,0 +1,274 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+
+/**
+ * Creates a widget with check box elements.
+ *
+ * This is rendered for config type=select, renderMode=checkbox, maxitems > 1
+ */
+class SelectCheckBoxElement extends AbstractFormElement {
+
+       /**
+        * @var array Result array given returned by render() - This property is a helper until class is properly refactored
+        */
+       protected $resultArray = array();
+
+       /**
+        * Render check boxes
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $table = $this->globalOptions['table'];
+               $field = $this->globalOptions['fieldName'];
+               $row = $this->globalOptions['databaseRow'];
+               $parameterArray = $this->globalOptions['parameterArray'];
+               // Field configuration from TCA:
+               $config = $parameterArray['fieldConf']['config'];
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+               $this->resultArray = $this->initializeResultArray();
+               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
+               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
+               $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
+
+               // Creating the label for the "No Matching Value" entry.
+               $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
+
+               $html = $this->getSingleField_typeSelect_checkbox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
+
+               // Wizards:
+               if (!$disabled) {
+                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
+               }
+               $this->resultArray['html'] = $html;
+               return $this->resultArray;
+       }
+
+       /**
+        * Creates a checkbox list (renderMode = "checkbox")
+        *
+        * @param string $table See getSingleField_typeSelect()
+        * @param string $field See getSingleField_typeSelect()
+        * @param array $row See getSingleField_typeSelect()
+        * @param array $parameterArray See getSingleField_typeSelect()
+        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @param array $selItems Items available for selection
+        * @param string $noMatchingLabel Label for no-matching-value
+        * @return string The HTML code for the item
+        */
+       protected function getSingleField_typeSelect_checkbox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) {
+               if (empty($selItems)) {
+                       return '';
+               }
+               // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
+               $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($parameterArray['itemFormElValue']));
+               $output = '';
+
+               // Disabled
+               $disabled = 0;
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = 1;
+               }
+               // Traverse the Array of selector box items:
+               $groups = array();
+               $currentGroup = 0;
+               $c = 0;
+               $sOnChange = '';
+               if (!$disabled) {
+                       $sOnChange = implode('', $parameterArray['fieldChangeFunc']);
+                       // Used to accumulate the JS needed to restore the original selection.
+                       foreach ($selItems as $p) {
+                               // Non-selectable element:
+                               if ($p[1] === '--div--') {
+                                       $selIcon = '';
+                                       if (isset($p[2]) && $p[2] != 'empty-emtpy') {
+                                               $selIcon = FormEngineUtility::getIconHtml($p[2]);
+                                       }
+                                       $currentGroup++;
+                                       $groups[$currentGroup]['header'] = array(
+                                               'icon' => $selIcon,
+                                               'title' => htmlspecialchars($p[0])
+                                       );
+                               } else {
+
+                                       // Check if some help text is available
+                                       // Since TYPO3 4.5 help text is expected to be an associative array
+                                       // with two key, "title" and "description"
+                                       // For the sake of backwards compatibility, we test if the help text
+                                       // is a string and use it as a description (this could happen if items
+                                       // are modified with an itemProcFunc)
+                                       $hasHelp = FALSE;
+                                       $help = '';
+                                       $helpArray = array();
+                                       if (is_array($p[3]) && count($p[3]) > 0 || !empty($p[3])) {
+                                               $hasHelp = TRUE;
+                                               if (is_array($p[3])) {
+                                                       $helpArray = $p[3];
+                                               } else {
+                                                       $helpArray['description'] = $p[3];
+                                               }
+                                       }
+                                       if ($hasHelp) {
+                                               $help = BackendUtility::wrapInHelp('', '', '', $helpArray);
+                                       }
+
+                                       // Selected or not by default:
+                                       $checked = 0;
+                                       if (isset($itemArray[$p[1]])) {
+                                               $checked = 1;
+                                               unset($itemArray[$p[1]]);
+                                       }
+
+                                       // Build item array
+                                       $groups[$currentGroup]['items'][] = array(
+                                               'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
+                                               'name' => $parameterArray['itemFormElName'] . '[' . $c . ']',
+                                               'value' => $p[1],
+                                               'checked' => $checked,
+                                               'disabled' => $disabled,
+                                               'class' => '',
+                                               'icon' => (!empty($p[2]) ? FormEngineUtility::getIconHtml($p[2]) : IconUtility::getSpriteIcon('empty-empty')),
+                                               'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE),
+                                               'help' => $help
+                                       );
+                                       $c++;
+                               }
+                       }
+               }
+               // Remaining values (invalid):
+               if (count($itemArray) && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
+                       $currentGroup++;
+                       foreach ($itemArray as $theNoMatchValue => $temp) {
+                               // Build item array
+                               $groups[$currentGroup]['items'][] = array(
+                                       'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
+                                       'name' => $parameterArray['itemFormElName'] . '[' . $c . ']',
+                                       'value' => $theNoMatchValue,
+                                       'checked' => 1,
+                                       'disabled' => $disabled,
+                                       'class' => 'danger',
+                                       'icon' => '',
+                                       'title' => htmlspecialchars(@sprintf($noMatchingLabel, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE),
+                                       'help' => ''
+                               );
+                               $c++;
+                       }
+               }
+               // Add an empty hidden field which will send a blank value if all items are unselected.
+               $output .= '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="" />';
+
+               // Building the checkboxes
+               foreach($groups as $groupKey => $group){
+                       $groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey;
+                       $output .= '<div class="panel panel-default">';
+                       if(is_array($group['header'])){
+                               $output .= '
+                                       <div class="panel-heading">
+                                               <a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="true" aria-controls="' . $groupId . '">
+                                                       ' . $group['header']['icon'] . '
+                                                       ' . $group['header']['title'] . '
+                                               </a>
+                                       </div>
+                                       ';
+                       }
+                       if(is_array($group['items']) && count($group['items']) >= 1){
+                               $tableRows = '';
+                               $checkGroup = array();
+                               $uncheckGroup = array();
+                               $resetGroup = array();
+
+                               // Render rows
+                               foreach($group['items'] as $item){
+                                       $tableRows .= '
+                                               <tr class="' . $item['class'] . '">
+                                                       <td class="col-checkbox">
+                                                               <input type="checkbox"
+                                                                       id="' . $item['id'] . '"
+                                                                       name="' . htmlspecialchars($item['name']) . '"
+                                                                       value="' . htmlspecialchars($item['value']) . '"
+                                                                       onclick="' . htmlspecialchars($sOnChange) . '"
+                                                                       ' . ($item['checked'] ? ' checked=checked' : '') . '
+                                                                       ' . ($item['disabled'] ? ' disabled=disabled' : '') . '
+                                                                       ' . $parameterArray['onFocus'] . ' />
+                                                       </td>
+                                                       <td class="col-icon">
+                                                               <label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>
+                                                       </td>
+                                                       <td class="col-title">
+                                                               <label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label>
+                                                       </td>
+                                                       <td>' . $item['help'] . '</td>
+                                               </tr>
+                                               ';
+                                       $checkGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=1;';
+                                       $uncheckGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=0;';
+                                       $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked='.$item['checked'] . ';';
+                               }
+
+                               // Build toggle group checkbox
+                               $toggleGroupCheckbox = '';
+                               if(count($resetGroup)){
+                                       $toggleGroupCheckbox = '
+                                               <input type="checkbox" class="checkbox" onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}">
+                                               ';
+                               }
+
+                               // Build reset group button
+                               $resetGroupBtn = '';
+                               if(count($resetGroup)){
+                                       $resetGroupBtn = '
+                                               <a href="#" class="btn btn-default" onclick="' . implode('', $resetGroup) . ' return false;' . '">
+                                                       ' . IconUtility::getSpriteIcon('actions-edit-undo', array('title' => htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')))) . '
+                                                       ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . '
+                                               </a>
+                                               ';
+                               }
+
+                               $output .= '
+                                       <div id="' . $groupId . '" class="panel-collapse collapse in" role="tabpanel">
+                                               <div class="table-fit">
+                                                       <table class="table table-transparent table-hover">
+                                                               <thead>
+                                                                       <tr>
+                                                                               <th class="col-checkbox">' . $toggleGroupCheckbox . '</th>
+                                                                               <th class="col-icon"></th>
+                                                                               <th class="text-right" colspan="2">' . $resetGroupBtn . '</th>
+                                                                       </tr>
+                                                               </thead>
+                                                               <tbody>' . $tableRows . '</tbody>
+                                                       </table>
+                                               </div>
+                                       </div>
+                                       ';
+                       }
+                       $output .= '</div>';
+               }
+
+               return $output;
+       }
+
+}
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectElement.php
deleted file mode 100644 (file)
index 2f35ee7..0000000
+++ /dev/null
@@ -1,1070 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\Form\Element;
-
-/*
- * 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\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\MathUtility;
-use TYPO3\CMS\Backend\Utility\IconUtility;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Backend\Form\DataPreprocessor;
-use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
-use TYPO3\CMS\Backend\Form\InlineStackProcessor;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-
-/**
- * Generation of TCEform elements of the type "select"
- *
- * @todo: This class does way to much and is also misused by FormDataTraverser. It should be split / refactored.
- */
-class SelectElement extends AbstractFormElement {
-
-       /**
-        * If this value is set during traversal and the traversal chain can
-        * not be walked to the end this value will be returned instead.
-        *
-        * @var string
-        */
-       protected $alternativeFieldValue;
-
-       /**
-        * If this is TRUE the alternative field value will be used even if
-        * the detected field value is not empty.
-        *
-        * @var bool
-        */
-       protected $forceAlternativeFieldValueUse = FALSE;
-
-       /**
-        * The row data of the record that is currently traversed.
-        *
-        * @var array
-        */
-       protected $currentRow;
-
-       /**
-        * Name of the table that is currently traversed.
-        *
-        * @var string
-        */
-       protected $currentTable;
-
-       /**
-        * @var array Result array given returned by render() - This property is a helper until class is properly refactored
-        */
-       protected $resultArray = array();
-
-       /**
-        * This will render a selector box element, or possibly a special construction with two selector boxes.
-        *
-        * @return array As defined in initializeResultArray() of AbstractNode
-        * @todo: This method is more like a container and not a single element ...
-        */
-       public function render() {
-               $table = $this->globalOptions['table'];
-               $field = $this->globalOptions['fieldName'];
-               $row = $this->globalOptions['databaseRow'];
-               $parameterArray = $this->globalOptions['parameterArray'];
-               // Field configuration from TCA:
-               $config = $parameterArray['fieldConf']['config'];
-               $disabled = '';
-               if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $disabled = ' disabled="disabled"';
-               }
-               $this->resultArray = $this->initializeResultArray();
-               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
-               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
-               $selItems = $this->getSelectItems($table, $field, $row, $parameterArray);
-
-               // Creating the label for the "No Matching Value" entry.
-               $nMV_label = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                       ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                       : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
-               // Prepare some values:
-               $maxitems = (int)$config['maxitems'];
-               // If a SINGLE selector box...
-               if ($maxitems <= 1 && $config['renderMode'] !== 'tree') {
-                       $html = $this->getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selItems, $nMV_label);
-               } elseif ($config['renderMode'] === 'checkbox') {
-                       // Checkbox renderMode
-                       $html = $this->getSingleField_typeSelect_checkbox($table, $field, $row, $parameterArray, $config, $selItems, $nMV_label);
-               } elseif ($config['renderMode'] === 'singlebox') {
-                       // Single selector box renderMode
-                       $html = $this->getSingleField_typeSelect_singlebox($table, $field, $row, $parameterArray, $config, $selItems, $nMV_label);
-               } elseif ($config['renderMode'] === 'tree') {
-                       // Tree renderMode
-                       $treeClass = GeneralUtility::makeInstance(TreeElement::class);
-                       $html = $treeClass->renderField($table, $field, $row, $parameterArray, $config, $selItems);
-                       // Register the required number of elements
-                       $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
-                       $this->resultArray['requiredElements'][$parameterArray['itemFormElName']] = array(
-                               $minitems,
-                               $maxitems,
-                               'imgName' => $table . '_' . $row['uid'] . '_' . $field
-                       );
-                       $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
-                       if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
-                               array_shift($match);
-                               $this->resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
-                                       'parts' => $match,
-                                       'level' => $tabAndInlineStack,
-                               );
-                       }
-               } else {
-                       // Traditional multiple selector box:
-                       $html = $this->getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $nMV_label);
-               }
-               // Wizards:
-               if (!$disabled) {
-                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
-                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
-               }
-               $this->resultArray['html'] = $html;
-               return $this->resultArray;
-       }
-
-       /**
-        * Creates a multiple-selector box (two boxes, side-by-side)
-        * (Render function for getSingleField_typeSelect())
-        *
-        * @param string $table See getSingleField_typeSelect()
-        * @param string $field See getSingleField_typeSelect()
-        * @param array $row See getSingleField_typeSelect()
-        * @param array $PA See getSingleField_typeSelect()
-        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
-        * @param array $selItems Items available for selection
-        * @param string $nMV_label Label for no-matching-value
-        * @return string The HTML code for the item
-        * @see getSingleField_typeSelect()
-        */
-       public function getSingleField_typeSelect_multiple($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
-               $languageService = $this->getLanguageService();
-               $item = '';
-               $disabled = '';
-               if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $disabled = ' disabled="disabled"';
-               }
-               // Setting this hidden field (as a flag that JavaScript can read out)
-               if (!$disabled) {
-                       $item .= '<input type="hidden" name="' . $PA['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />';
-               }
-               // Set max and min items:
-               $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
-               if (!$maxitems) {
-                       $maxitems = 100000;
-               }
-               $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
-               // Register the required number of elements:
-               $this->resultArray['requiredElements'][$PA['itemFormElName']] = array(
-                       $minitems,
-                       $maxitems,
-                       'imgName' => $table . '_' . $row['uid'] . '_' . $field
-               );
-               $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
-               if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $PA['itemFormElName'], $match)) {
-                       array_shift($match);
-                       $this->resultArray['requiredNested'][$PA['itemFormElName']] = array(
-                               'parts' => $match,
-                               'level' => $tabAndInlineStack,
-                       );
-               }
-               // Get "removeItems":
-               $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
-               // Get the array with selected items:
-               $itemArray = GeneralUtility::trimExplode(',', $PA['itemFormElValue'], TRUE);
-
-               // Possibly filter some items:
-               $itemArray = ArrayUtility::keepItemsInArray(
-                       $itemArray,
-                       $PA['fieldTSConfig']['keepItems'],
-                       function ($value) {
-                               $parts = explode('|', $value, 2);
-                               return rawurldecode($parts[0]);
-                       }
-               );
-
-               // Perform modification of the selected items array:
-               foreach ($itemArray as $tk => $tv) {
-                       $tvP = explode('|', $tv, 2);
-                       $evalValue = $tvP[0];
-                       $isRemoved = in_array($evalValue, $removeItems)
-                               || $config['type'] == 'select' && $config['authMode']
-                               && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']);
-                       if ($isRemoved && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                               $tvP[1] = rawurlencode(@sprintf($nMV_label, $evalValue));
-                       } else {
-                               if (isset($PA['fieldTSConfig']['altLabels.'][$evalValue])) {
-                                       $tvP[1] = rawurlencode($languageService->sL($PA['fieldTSConfig']['altLabels.'][$evalValue]));
-                               }
-                               if (isset($PA['fieldTSConfig']['altIcons.'][$evalValue])) {
-                                       $tvP[2] = $PA['fieldTSConfig']['altIcons.'][$evalValue];
-                               }
-                       }
-                       if ($tvP[1] == '') {
-                               // Case: flexform, default values supplied, no label provided (bug #9795)
-                               foreach ($selItems as $selItem) {
-                                       if ($selItem[1] == $tvP[0]) {
-                                               $tvP[1] = html_entity_decode($selItem[0]);
-                                               break;
-                                       }
-                               }
-                       }
-                       $itemArray[$tk] = implode('|', $tvP);
-               }
-               $itemsToSelect = '';
-               $filterTextfield = '';
-               $filterSelectbox = '';
-               $size = 0;
-               if (!$disabled) {
-                       // Create option tags:
-                       $opt = array();
-                       $styleAttrValue = '';
-                       foreach ($selItems as $p) {
-                               if ($config['iconsInOptionTags']) {
-                                       $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
-                               }
-                               $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"'
-                                       . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '')
-                                       . ' title="' . $p[0] . '">' . $p[0] . '</option>';
-                       }
-                       // Put together the selector box:
-                       $selector_itemListStyle = isset($config['itemListStyle'])
-                               ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
-                               : '';
-                       $size = (int)$config['size'];
-                       $size = $config['autoSizeMax']
-                               ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
-                               : $size;
-                       $sOnChange = implode('', $PA['fieldChangeFunc']);
-
-                       $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE));
-                       $itemsToSelect = '
-                               <select data-relatedfieldname="' . htmlspecialchars($PA['itemFormElName']) . '" data-exclusivevalues="'
-                               . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . htmlspecialchars($PA['itemFormElName']) . '_sel" '
-                               . ' class="form-control t3js-formengine-select-itemstoselect" '
-                               . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"'
-                               . $PA['onFocus'] . $selector_itemListStyle . '>
-                                       ' . implode('
-                                       ', $opt) . '
-                               </select>';
-
-                       // enable filter functionality via a text field
-                       if ($config['enableMultiSelectFilterTextfield']) {
-                               $filterTextfield = '
-                                       <span class="input-group input-group-sm">
-                                               <span class="input-group-addon">
-                                                       <span class="fa fa-filter"></span>
-                                               </span>
-                                               <input class="t3js-formengine-multiselect-filter-textfield form-control" value="" />
-                                       </span>';
-                       }
-
-                       // enable filter functionality via a select
-                       if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
-                               $filterDropDownOptions = array();
-                               foreach ($config['multiSelectFilterItems'] as $optionElement) {
-                                       $optionValue = $languageService->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1]
-                                               : $optionElement[0]);
-                                       $filterDropDownOptions[] = '<option value="' . htmlspecialchars($languageService->sL($optionElement[0])) . '">'
-                                               . htmlspecialchars($optionValue) . '</option>';
-                               }
-                               $filterSelectbox = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">
-                                               ' . implode('
-                                               ', $filterDropDownOptions) . '
-                                       </select>';
-                       }
-               }
-
-               if (!empty(trim($filterSelectbox)) && !empty(trim($filterTextfield))) {
-                       $filterSelectbox = '<div class="form-multigroup-item form-multigroup-element">' . $filterSelectbox . '</div>';
-                       $filterTextfield = '<div class="form-multigroup-item form-multigroup-element">' . $filterTextfield . '</div>';
-                       $selectBoxFilterContents = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">' . $filterSelectbox . $filterTextfield . '</div>';
-               } else {
-                       $selectBoxFilterContents = trim($filterSelectbox . ' ' . $filterTextfield);
-               }
-
-               // Pass to "dbFileIcons" function:
-               $params = array(
-                       'size' => $size,
-                       'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
-                       'style' => isset($config['selectedListStyle'])
-                               ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
-                               : '',
-                       'dontShowMoveIcons' => $maxitems <= 1,
-                       'maxitems' => $maxitems,
-                       'info' => '',
-                       'headers' => array(
-                               'selector' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected'),
-                               'items' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.items'),
-                               'selectorbox' => $selectBoxFilterContents,
-                       ),
-                       'noBrowser' => 1,
-                       'rightbox' => $itemsToSelect,
-                       'readOnly' => $disabled
-               );
-               $item .= $this->dbFileIcons($PA['itemFormElName'], '', '', $itemArray, '', $params, $PA['onFocus']);
-               return $item;
-       }
-
-       /**
-        * Collects the items for a select field by reading the configured
-        * select items from the configuration and / or by collecting them
-        * from a foreign table.
-        *
-        * @param string $table The table name of the record
-        * @param string $fieldName The select field name
-        * @param array $row The record data array where the value(s) for the field can be found
-        * @param array $PA An array with additional configuration options.
-        * @return array
-        */
-       protected function getSelectItems($table, $fieldName, array $row, array $PA) {
-               $config = $PA['fieldConf']['config'];
-
-               // Getting the selector box items from the system
-               $selectItems = FormEngineUtility::addSelectOptionsToItemArray(
-                       FormEngineUtility::initItemArray($PA['fieldConf']),
-                       $PA['fieldConf'],
-                       FormEngineUtility::getTSconfigForTableRow($table, $row),
-                       $fieldName
-               );
-
-               // Possibly filter some items:
-               $selectItems = ArrayUtility::keepItemsInArray(
-                       $selectItems,
-                       $PA['fieldTSConfig']['keepItems'],
-                       function ($value) {
-                               return $value[1];
-                       }
-               );
-
-               // Possibly add some items:
-               $selectItems = FormEngineUtility::addItems($selectItems, $PA['fieldTSConfig']['addItems.']);
-
-               // Process items by a user function:
-               if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
-                       $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
-                       $selectItems = $dataPreprocessor->procItems($selectItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $fieldName);
-               }
-
-               // Possibly remove some items:
-               $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
-               foreach ($selectItems as $selectItemIndex => $selectItem) {
-
-                       // Checking languages and authMode:
-                       $languageDeny = FALSE;
-                       $beUserAuth = $this->getBackendUserAuthentication();
-                       if (
-                               !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])
-                               && $GLOBALS['TCA'][$table]['ctrl']['languageField'] === $fieldName
-                               && !$beUserAuth->checkLanguageAccess($selectItem[1])
-                       ) {
-                               $languageDeny = TRUE;
-                       }
-
-                       $authModeDeny = FALSE;
-                       if (
-                               ($config['type'] === 'select')
-                               && $config['authMode']
-                               && !$beUserAuth->checkAuthMode($table, $fieldName, $selectItem[1], $config['authMode'])
-                       ) {
-                               $authModeDeny = TRUE;
-                       }
-
-                       if (in_array($selectItem[1], $removeItems) || $languageDeny || $authModeDeny) {
-                               unset($selectItems[$selectItemIndex]);
-                       } elseif (isset($PA['fieldTSConfig']['altLabels.'][$selectItem[1]])) {
-                               $selectItems[$selectItemIndex][0] = htmlspecialchars($this->getLanguageService()->sL($PA['fieldTSConfig']['altLabels.'][$selectItem[1]]));
-                       }
-
-                       // Removing doktypes with no access:
-                       if (($table === 'pages' || $table === 'pages_language_overlay') && $fieldName === 'doktype') {
-                               if (!($beUserAuth->isAdmin() || GeneralUtility::inList($beUserAuth->groupData['pagetypes_select'], $selectItem[1]))) {
-                                       unset($selectItems[$selectItemIndex]);
-                               }
-                       }
-               }
-
-               return $selectItems;
-       }
-
-       /**
-        * Creates a single-selector box
-        * (Render function for getSingleField_typeSelect())
-        *
-        * @param string $table See getSingleField_typeSelect()
-        * @param string $field See getSingleField_typeSelect()
-        * @param array $row See getSingleField_typeSelect()
-        * @param array $PA See getSingleField_typeSelect()
-        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
-        * @param array $selectItems Items available for selection
-        * @param string $noMatchingLabel Label for no-matching-value
-        * @return string The HTML code for the item
-        * @see getSingleField_typeSelect()
-        * @todo: Needs refactoring - somewhere else - own element and make select itself a container?
-        */
-       public function getSingleField_typeSelect_single($table, $field, $row, &$PA, $config, $selectItems, $noMatchingLabel) {
-
-               // Check against inline uniqueness
-               /** @var InlineStackProcessor $inlineStackProcessor */
-               $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
-               $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
-               $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
-               $uniqueIds = NULL;
-               if (is_array($inlineParent) && $inlineParent['uid']) {
-                       $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
-                       $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
-                       if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
-                               $uniqueIds = $this->globalOptions['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
-                               $PA['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,\'' . $inlineObjectName
-                                       . '-' . $table . '\',\'' . $inlineFormName
-                                       . '\',\'' . $row['uid'] . '\');';
-                       }
-                       // hide uid of parent record for symmetric relations
-                       if (
-                               $inlineParent['config']['foreign_table'] == $table
-                               && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
-                       ) {
-                               $uniqueIds[] = $inlineParent['uid'];
-                       }
-               }
-
-               // Initialization:
-               $selectId = str_replace('.', '', uniqid('tceforms-select-', TRUE));
-               $selectedIndex = 0;
-               $selectedIcon = '';
-               $noMatchingValue = 1;
-               $onlySelectedIconShown = 0;
-               $size = (int)$config['size'];
-
-               // Style set on <select/>
-               $out = '';
-               $options = '';
-               $disabled = FALSE;
-               if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $disabled = TRUE;
-                       $onlySelectedIconShown = 1;
-               }
-               // Register as required if minitems is greater than zero
-               if (($minItems = MathUtility::forceIntegerInRange($config['minitems'], 0)) > 0) {
-                       $this->resultArray['requiredFields'][$table . '_' . $row['uid'] . '_' . $field] = $PA['itemFormElName'];
-                       $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
-                       if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $PA['itemFormElName'], $match)) {
-                               array_shift($match);
-                               $this->resultArray['requiredNested'][$PA['itemFormElName']] = array(
-                                       'parts' => $match,
-                                       'level' => $tabAndInlineStack,
-                               );
-                       }
-               }
-
-               // Icon configuration:
-               if ($config['suppress_icons'] == 'IF_VALUE_FALSE') {
-                       $suppressIcons = !$PA['itemFormElValue'] ? 1 : 0;
-               } elseif ($config['suppress_icons'] == 'ONLY_SELECTED') {
-                       $suppressIcons = 0;
-                       $onlySelectedIconShown = 1;
-               } elseif ($config['suppress_icons']) {
-                       $suppressIcons = 1;
-               } else {
-                       $suppressIcons = 0;
-               }
-
-               // Prepare groups
-               $selectItemCounter = 0;
-               $selectItemGroupCount = 0;
-               $selectItemGroups = array();
-               $selectIcons = array();
-               $selectedValue = '';
-               foreach ($selectItems as $item) {
-                       if ($item[1] === '--div--') {
-                               // IS OPTGROUP
-                               if ($selectItemCounter !== 0) {
-                                       $selectItemGroupCount++;
-                               }
-                               $selectItemGroups[$selectItemGroupCount]['header'] = array(
-                                       'title' => $item[0],
-                                       'icon' => (!empty($item[2]) ? FormEngineUtility::getIconHtml($item[2]) : ''),
-                               );
-                       } else {
-                               // IS ITEM
-                               $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', FALSE);
-                               $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
-                               $selected = ((string)$PA['itemFormElValue'] === (string)$item[1] ? 1 : 0);
-                               if ($selected) {
-                                       $selectedIndex = $selectItemCounter;
-                                       $selectedValue = $item[1];
-                                       $selectedIcon = $icon;
-                                       $noMatchingValue = 0;
-                               }
-                               $selectItemGroups[$selectItemGroupCount]['items'][] = array(
-                                       'title' => $title,
-                                       'value' => $item[1],
-                                       'icon' => $icon,
-                                       'selected' => $selected,
-                                       'index' => $selectItemCounter
-                               );
-                               // ICON
-                               if ($icon && !$suppressIcons && (!$onlySelectedIconShown || $selected)) {
-                                       $onClick = 'document.editform[' . GeneralUtility::quoteJSvalue($PA['itemFormElName']) . '].selectedIndex=' . $selectItemCounter . ';';
-                                       if ($config['iconsInOptionTags']) {
-                                               $onClick .= 'document.getElementById(\'' . $selectId . '_icon\').innerHTML = '
-                                                       . 'document.editform[' . GeneralUtility::quoteJSvalue($PA['itemFormElName']) . ']'
-                                                       . '.options[' . $selectItemCounter . '].getAttribute(\'data-icon\'); ';
-                                       }
-                                       $onClick .= implode('', $PA['fieldChangeFunc']);
-                                       $onClick .= 'this.blur();return false;';
-                                       $selectIcons[] = array(
-                                               'title' => $title,
-                                               'icon' => $icon,
-                                               'index' => $selectItemCounter,
-                                               'onClick' => $onClick
-                                       );
-                               }
-                               $selectItemCounter++;
-                       }
-
-               }
-
-               // No-matching-value:
-               if ($PA['itemFormElValue'] && $noMatchingValue && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                       $noMatchingLabel = @sprintf($noMatchingLabel, $PA['itemFormElValue']);
-                       $options = '<option value="' . htmlspecialchars($PA['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
-               } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
-                       $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
-               }
-
-               // Process groups
-               foreach ($selectItemGroups as $selectItemGroup) {
-                       $optionGroup = is_array($selectItemGroup['header']);
-                       $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', FALSE) . '">' : '');
-                       if (is_array($selectItemGroup['items'])) {
-                               foreach ($selectItemGroup['items'] as $item) {
-                                       $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
-                                               htmlspecialchars($item['icon']) . '"'
-                                               . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>';
-                               }
-                       }
-                       $options .= ($optionGroup ? '</optgroup>' : '');
-               }
-
-               // Create item form fields:
-               $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $selectedIndex . ';} ';
-               if ($config['iconsInOptionTags']) {
-                       $sOnChange .= 'document.getElementById(\'' . $selectId . '_icon\').innerHTML = this.options[this.selectedIndex].getAttribute(\'data-icon\'); ';
-               }
-               $sOnChange .= implode('', $PA['fieldChangeFunc']);
-
-               // Add icons in option tags
-               $prepend = '';
-               $append = '';
-               if ($config['iconsInOptionTags']) {
-                       $prepend = '<div class="input-group"><div id="' . $selectId . '_icon" class="input-group-addon input-group-icon t3js-formengine-select-prepend">' . $selectedIcon . '</div>';
-                       $append = '</div>';
-               }
-
-               // Build the element
-               $out .= '
-                       <div class="form-control-wrap">
-                               ' . $prepend . '
-                               <select'
-                                       . ' id="' . $selectId . '"'
-                                       . ' name="' . htmlspecialchars($PA['itemFormElName']) . '"'
-                                       . ' class="form-control form-control-adapt"'
-                                       . ($size ? ' size="' . $size . '"' : '')
-                                       . ' onchange="' . htmlspecialchars($sOnChange) . '"'
-                                       . $PA['onFocus']
-                                       . ($disabled ? ' disabled="disabled"' : '')
-                                       . '>
-                                       ' . $options . '
-                               </select>
-                               ' . $append . '
-                       </div>';
-
-               // Create icon table:
-               if (count($selectIcons) && !$config['noIconsBelowSelect']) {
-                       $selectIconColumns = (int)$config['selicon_cols'];
-                       if (!$selectIconColumns) {
-                               $selectIconColumns = count($selectIcons);
-                       }
-                       $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
-                       $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
-                       $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
-                       $out .= '<div class="table-fit table-fit-inline-block"><table class="table table-condensed table-white table-center"><tbody><tr>';
-                       for ($selectIconCount = 0; $selectIconCount < count($selectIcons); $selectIconCount++) {
-                               if ($selectIconCount % $selectIconColumns === 0 && $selectIconCount !== 0) {
-                                       $out .= '</tr><tr>';
-                               }
-                               $out .= '<td>';
-                               if (is_array($selectIcons[$selectIconCount])) {
-                                       $out .= (!$onlySelectedIconShown ? '<a href="#" title="' . $selectIcons[$selectIconCount]['title'] . '" onClick="' . htmlspecialchars($selectIcons[$selectIconCount]['onClick']) . '">' : '');
-                                       $out .= $selectIcons[$selectIconCount]['icon'];
-                                       $out .= (!$onlySelectedIconShown ? '</a>' : '');
-                               }
-                               $out .= '</td>';
-                       }
-                       $out .= '</tr></tbody></table></div>';
-               }
-
-               return $out;
-       }
-
-       /**
-        * Creates a checkbox list (renderMode = "checkbox")
-        * (Render function for getSingleField_typeSelect())
-        *
-        * @param string $table See getSingleField_typeSelect()
-        * @param string $field See getSingleField_typeSelect()
-        * @param array $row See getSingleField_typeSelect()
-        * @param array $PA See getSingleField_typeSelect()
-        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
-        * @param array $selItems Items available for selection
-        * @param string $nMV_label Label for no-matching-value
-        * @return string The HTML code for the item
-        * @see getSingleField_typeSelect()
-        * @todo: Needs refactoring - somewhere else - own element and make select itself a container?
-        */
-       public function getSingleField_typeSelect_checkbox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
-               if (empty($selItems)) {
-                       return '';
-               }
-               // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
-               $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
-               $output = '';
-
-               // Disabled
-               $disabled = 0;
-               if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $disabled = 1;
-               }
-               // Traverse the Array of selector box items:
-               $groups = array();
-               $currentGroup = 0;
-               $c = 0;
-               $sOnChange = '';
-               if (!$disabled) {
-                       $sOnChange = implode('', $PA['fieldChangeFunc']);
-                       // Used to accumulate the JS needed to restore the original selection.
-                       foreach ($selItems as $p) {
-                               // Non-selectable element:
-                               if ($p[1] === '--div--') {
-                                       $selIcon = '';
-                                       if (isset($p[2]) && $p[2] != 'empty-emtpy') {
-                                               $selIcon = FormEngineUtility::getIconHtml($p[2]);
-                                       }
-                                       $currentGroup++;
-                                       $groups[$currentGroup]['header'] = array(
-                                               'icon' => $selIcon,
-                                               'title' => htmlspecialchars($p[0])
-                                       );
-                               } else {
-
-                                       // Check if some help text is available
-                                       // Since TYPO3 4.5 help text is expected to be an associative array
-                                       // with two key, "title" and "description"
-                                       // For the sake of backwards compatibility, we test if the help text
-                                       // is a string and use it as a description (this could happen if items
-                                       // are modified with an itemProcFunc)
-                                       $hasHelp = FALSE;
-                                       $help = '';
-                                       $helpArray = array();
-                                       if (is_array($p[3]) && count($p[3]) > 0 || !empty($p[3])) {
-                                               $hasHelp = TRUE;
-                                               if (is_array($p[3])) {
-                                                       $helpArray = $p[3];
-                                               } else {
-                                                       $helpArray['description'] = $p[3];
-                                               }
-                                       }
-                                       if ($hasHelp) {
-                                               $help = BackendUtility::wrapInHelp('', '', '', $helpArray);
-                                       }
-
-                                       // Selected or not by default:
-                                       $checked = 0;
-                                       if (isset($itemArray[$p[1]])) {
-                                               $checked = 1;
-                                               unset($itemArray[$p[1]]);
-                                       }
-
-                                       // Build item array
-                                       $groups[$currentGroup]['items'][] = array(
-                                               'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
-                                               'name' => $PA['itemFormElName'] . '[' . $c . ']',
-                                               'value' => $p[1],
-                                               'checked' => $checked,
-                                               'disabled' => $disabled,
-                                               'class' => '',
-                                               'icon' => (!empty($p[2]) ? FormEngineUtility::getIconHtml($p[2]) : IconUtility::getSpriteIcon('empty-empty')),
-                                               'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE),
-                                               'help' => $help
-                                       );
-                                       $c++;
-                               }
-                       }
-               }
-               // Remaining values (invalid):
-               if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                       $currentGroup++;
-                       foreach ($itemArray as $theNoMatchValue => $temp) {
-                               // Build item array
-                               $groups[$currentGroup]['items'][] = array(
-                                       'id' => str_replace('.', '', uniqid('select_checkbox_row_', TRUE)),
-                                       'name' => $PA['itemFormElName'] . '[' . $c . ']',
-                                       'value' => $theNoMatchValue,
-                                       'checked' => 1,
-                                       'disabled' => $disabled,
-                                       'class' => 'danger',
-                                       'icon' => '',
-                                       'title' => htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE),
-                                       'help' => ''
-                               );
-                               $c++;
-                       }
-               }
-               // Add an empty hidden field which will send a blank value if all items are unselected.
-               $output .= '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
-
-               // Building the checkboxes
-               foreach($groups as $groupKey => $group){
-                       $groupId = htmlspecialchars($PA['itemFormElID']) . '-group-' . $groupKey;
-                       $output .= '<div class="panel panel-default">';
-                       if(is_array($group['header'])){
-                               $output .= '
-                                       <div class="panel-heading">
-                                               <a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="true" aria-controls="' . $groupId . '">
-                                                       ' . $group['header']['icon'] . '
-                                                       ' . $group['header']['title'] . '
-                                               </a>
-                                       </div>
-                                       ';
-                       }
-                       if(is_array($group['items']) && count($group['items']) >= 1){
-                               $tableRows = '';
-                               $checkGroup = array();
-                               $uncheckGroup = array();
-                               $resetGroup = array();
-
-                               // Render rows
-                               foreach($group['items'] as $item){
-                                       $tableRows .= '
-                                               <tr class="' . $item['class'] . '">
-                                                       <td class="col-checkbox">
-                                                               <input type="checkbox"
-                                                                       id="' . $item['id'] . '"
-                                                                       name="' . htmlspecialchars($item['name']) . '"
-                                                                       value="' . htmlspecialchars($item['value']) . '"
-                                                                       onclick="' . htmlspecialchars($sOnChange) . '"
-                                                                       ' . ($item['checked'] ? ' checked=checked' : '') . '
-                                                                       ' . ($item['disabled'] ? ' disabled=disabled' : '') . '
-                                                                       ' . $PA['onFocus'] . ' />
-                                                       </td>
-                                                       <td class="col-icon">
-                                                               <label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>
-                                                       </td>
-                                                       <td class="col-title">
-                                                               <label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label>
-                                                       </td>
-                                                       <td>' . $item['help'] . '</td>
-                                               </tr>
-                                               ';
-                                       $checkGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=1;';
-                                       $uncheckGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=0;';
-                                       $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked='.$item['checked'] . ';';
-                               }
-
-                               // Build toggle group checkbox
-                               $toggleGroupCheckbox = '';
-                               if(count($resetGroup)){
-                                       $toggleGroupCheckbox = '
-                                               <input type="checkbox" class="checkbox" onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}">
-                                               ';
-                               }
-
-                               // Build reset group button
-                               $resetGroupBtn = '';
-                               if(count($resetGroup)){
-                                       $resetGroupBtn = '
-                                               <a href="#" class="btn btn-default" onclick="' . implode('', $resetGroup) . ' return false;' . '">
-                                                       ' . IconUtility::getSpriteIcon('actions-edit-undo', array('title' => htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')))) . '
-                                                       ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . '
-                                               </a>
-                                               ';
-                               }
-
-                               $output .= '
-                                       <div id="' . $groupId . '" class="panel-collapse collapse in" role="tabpanel">
-                                               <div class="table-fit">
-                                                       <table class="table table-transparent table-hover">
-                                                               <thead>
-                                                                       <tr>
-                                                                               <th class="col-checkbox">' . $toggleGroupCheckbox . '</th>
-                                                                               <th class="col-icon"></th>
-                                                                               <th class="text-right" colspan="2">' . $resetGroupBtn . '</th>
-                                                                       </tr>
-                                                               </thead>
-                                                               <tbody>' . $tableRows . '</tbody>
-                                                       </table>
-                                               </div>
-                                       </div>
-                                       ';
-                       }
-                       $output .= '</div>';
-               }
-
-               return $output;
-       }
-
-       /**
-        * Creates a selectorbox list (renderMode = "singlebox")
-        * (Render function for getSingleField_typeSelect())
-        *
-        * @param string $table See getSingleField_typeSelect()
-        * @param string $field See getSingleField_typeSelect()
-        * @param array $row See getSingleField_typeSelect()
-        * @param array $PA See getSingleField_typeSelect()
-        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
-        * @param array $selItems Items available for selection
-        * @param string $nMV_label Label for no-matching-value
-        * @return string The HTML code for the item
-        * @see getSingleField_typeSelect()
-        * @todo: Needs refactoring - somewhere else - own element and make select itself a container?
-        */
-       public function getSingleField_typeSelect_singlebox($table, $field, $row, &$PA, $config, $selItems, $nMV_label) {
-               $languageService = $this->getLanguageService();
-               // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
-               $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($PA['itemFormElValue']));
-               $item = '';
-               $disabled = '';
-               if ($this->isGlobalReadonly() || $config['readOnly']) {
-                       $disabled = ' disabled="disabled"';
-               }
-               // Traverse the Array of selector box items:
-               $opt = array();
-               // Used to accumulate the JS needed to restore the original selection.
-               $restoreCmd = array();
-               $c = 0;
-               foreach ($selItems as $p) {
-                       // Selected or not by default:
-                       $sM = '';
-                       if (isset($itemArray[$p[1]])) {
-                               $sM = ' selected="selected"';
-                               $restoreCmd[] = 'document.editform[' . GeneralUtility::quoteJSvalue($PA['itemFormElName'] . '[]') . '].options[' . $c . '].selected=1;';
-                               unset($itemArray[$p[1]]);
-                       }
-                       // Non-selectable element:
-                       $nonSel = '';
-                       if ((string)$p[1] === '--div--') {
-                               $nonSel = ' onclick="this.selected=0;" class="formcontrol-select-divider"';
-                       }
-                       // Icon style for option tag:
-                       $styleAttrValue = '';
-                       if ($config['iconsInOptionTags']) {
-                               $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
-                       }
-                       // Compile <option> tag:
-                       $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM . $nonSel
-                               . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
-                               . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>';
-                       $c++;
-               }
-               // Remaining values:
-               if (count($itemArray) && !$PA['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                       foreach ($itemArray as $theNoMatchValue => $temp) {
-                               // Compile <option> tag:
-                               array_unshift($opt, '<option value="' . htmlspecialchars($theNoMatchValue) . '" selected="selected">'
-                                       . htmlspecialchars(@sprintf($nMV_label, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</option>');
-                       }
-               }
-               // Compile selector box:
-               $sOnChange = implode('', $PA['fieldChangeFunc']);
-               $selector_itemListStyle = isset($config['itemListStyle'])
-                       ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
-                       : '';
-               $size = (int)$config['size'];
-               $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
-               $size = $config['autoSizeMax']
-                       ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
-                       : $size;
-               $selectBox = '<select id="' . str_replace('.', '', uniqid($cssPrefix, TRUE)) . '" name="' . htmlspecialchars($PA['itemFormElName']) . '[]" '
-                       . 'class="form-control ' . $cssPrefix . '"' . ($size ? ' size="' . $size . '" ' : '')
-                       . ' multiple="multiple" onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus']
-                       . ' ' . $selector_itemListStyle . $disabled . '>
-                                               ' . implode('
-                                               ', $opt) . '
-                                       </select>';
-               // Add an empty hidden field which will send a blank value if all items are unselected.
-               if (!$disabled) {
-                       $item .= '<input type="hidden" name="' . htmlspecialchars($PA['itemFormElName']) . '" value="" />';
-               }
-               // Put it all into a table:
-               $onClick = htmlspecialchars('document.editform[' . GeneralUtility::quoteJSvalue($PA['itemFormElName'] . '[]') . '].selectedIndex=-1;' . implode('', $restoreCmd) . ' return false;');
-               $width = $this->formMaxWidth($this->defaultInputWidth);
-               $item .= '
-                       <div class="form-control-wrap" ' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>
-                               <div class="form-wizards-wrap form-wizards-aside">
-                                       <div class="form-wizards-element">
-                                               ' . $selectBox . '
-                                       </div>
-                                       <div class="form-wizards-items">
-                                               <a href="#" class="btn btn-default" onclick="' . $onClick . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')) . '">'
-                                                       . IconUtility::getSpriteIcon('actions-edit-undo') . '</a>
-                                       </div>
-                               </div>
-                       </div>
-                       <p>
-                               <em>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.holdDownCTRL')) . '</em>
-                       </p>
-                       ';
-               return $item;
-       }
-
-       /**
-        * If the select field is build by a foreign_table the related UIDs
-        * will be returned.
-        *
-        * Otherwise the label of the currently selected value will be written
-        * to the alternativeFieldValue class property.
-        *
-        * @param array $fieldConfig The "config" section of the TCA for the current select field.
-        * @param string $fieldName The name of the select field.
-        * @param string $value The current value in the local record, usually a comma separated list of selected values.
-        * @return array Array of related UIDs.
-        * @todo: Needs refactoring and probably out of this class
-        */
-       public function getRelatedSelectFieldUids(array $fieldConfig, $fieldName, $value) {
-               $relatedUids = array();
-
-               $isTraversable = FALSE;
-               if (isset($fieldConfig['foreign_table'])) {
-                       $isTraversable = TRUE;
-                       // if a foreign_table is used we pre-filter the records for performance
-                       $fieldConfig['foreign_table_where'] .= ' AND ' . $fieldConfig['foreign_table'] . '.uid IN (' . $value . ')';
-               }
-
-               $PA = array();
-               $PA['fieldConf']['config'] = $fieldConfig;
-               $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($this->currentTable, $this->currentRow, $fieldName);
-               $PA['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($PA['fieldConf']['config'], $PA['fieldTSConfig']);
-               $selectItemArray = $this->getSelectItems($this->currentTable, $fieldName, $this->currentRow, $PA);
-
-               if ($isTraversable && count($selectItemArray)) {
-                       $this->currentTable = $fieldConfig['foreign_table'];
-                       $relatedUids = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value);
-               } else {
-                       $selectedLabels = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value, 1, TRUE);
-                       if (count($selectedLabels) === 1) {
-                               $this->alternativeFieldValue = $selectedLabels[0];
-                               $this->forceAlternativeFieldValueUse = TRUE;
-                       }
-               }
-
-               return $relatedUids;
-       }
-
-       /**
-        * Extracts the selected values from a given array of select items.
-        *
-        * @param array $selectItemArray The select item array generated by \TYPO3\CMS\Backend\Form\FormEngine->getSelectItems.
-        * @param string $value The currently selected value(s) as comma separated list.
-        * @param int|NULL $maxItems Optional value, if set processing is skipped and an empty array will be returned when the number of selected values is larger than the provided value.
-        * @param bool $returnLabels If TRUE the select labels will be returned instead of the values.
-        * @return array
-        */
-       protected function getSelectedValuesFromSelectItemArray(array $selectItemArray, $value, $maxItems = NULL, $returnLabels = FALSE) {
-               $values = GeneralUtility::trimExplode(',', $value);
-               $selectedValues = array();
-
-               if ($maxItems !== NULL && (count($values) > (int)$maxItems)) {
-                       return $selectedValues;
-               }
-
-               foreach ($selectItemArray as $selectItem) {
-                       $selectItemValue = $selectItem[1];
-                       if (in_array($selectItemValue, $values)) {
-                               if ($returnLabels) {
-                                       $selectedValues[] = $selectItem[0];
-                               } else {
-                                       $selectedValues[] = $selectItemValue;
-                               }
-                       }
-               }
-
-               return $selectedValues;
-       }
-
-       /**
-        * @param string $alternativeFieldValue
-        */
-       public function setAlternativeFieldValue($alternativeFieldValue) {
-               $this->alternativeFieldValue = $alternativeFieldValue;
-       }
-
-       /**
-        * @param array $currentRow
-        */
-       public function setCurrentRow($currentRow) {
-               $this->currentRow = $currentRow;
-       }
-
-       /**
-        * @param string $currentTable
-        */
-       public function setCurrentTable($currentTable) {
-               $this->currentTable = $currentTable;
-       }
-
-       /**
-        * @param bool $forceAlternativeFieldValueUse
-        */
-       public function setForceAlternativeFieldValueUse($forceAlternativeFieldValueUse) {
-               $this->forceAlternativeFieldValueUse = $forceAlternativeFieldValueUse;
-       }
-
-       /**
-        * @return string
-        */
-       public function getAlternativeFieldValue() {
-               return $this->alternativeFieldValue;
-       }
-
-       /**
-        * @return array
-        */
-       public function getCurrentRow() {
-               return $this->currentRow;
-       }
-
-       /**
-        * @return string
-        */
-       public function getCurrentTable() {
-               return $this->currentTable;
-       }
-
-       /**
-        * @return boolean
-        */
-       public function isForceAlternativeFieldValueUse() {
-               return $this->forceAlternativeFieldValueUse;
-       }
-
-       /**
-        * @return BackendUserAuthentication
-        */
-       protected function getBackendUserAuthentication() {
-               return $GLOBALS['BE_USER'];
-       }
-
-}
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
new file mode 100644 (file)
index 0000000..01c6186
--- /dev/null
@@ -0,0 +1,261 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+
+/**
+ * Render a widget with two boxes side by side.
+ *
+ * This is rendered for config type=select, maxitems > 1, no other renderMode set
+ */
+class SelectMultipleSideBySideElement extends AbstractFormElement {
+
+       /**
+        * @var array Result array given returned by render() - This property is a helper until class is properly refactored
+        */
+       protected $resultArray = array();
+
+       /**
+        * Render side by side element.
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $table = $this->globalOptions['table'];
+               $field = $this->globalOptions['fieldName'];
+               $row = $this->globalOptions['databaseRow'];
+               $parameterArray = $this->globalOptions['parameterArray'];
+               // Field configuration from TCA:
+               $config = $parameterArray['fieldConf']['config'];
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+               $this->resultArray = $this->initializeResultArray();
+               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
+               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
+               $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
+
+               // Creating the label for the "No Matching Value" entry.
+               $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
+
+               $html = $this->getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
+
+               // Wizards:
+               if (!$disabled) {
+                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
+               }
+               $this->resultArray['html'] = $html;
+               return $this->resultArray;
+       }
+
+       /**
+        * Creates a multiple-selector box (two boxes, side-by-side)
+        *
+        * @param string $table See getSingleField_typeSelect()
+        * @param string $field See getSingleField_typeSelect()
+        * @param array $row See getSingleField_typeSelect()
+        * @param array $parameterArray See getSingleField_typeSelect()
+        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @param array $selItems Items available for selection
+        * @param string $noMatchingLabel Label for no-matching-value
+        * @return string The HTML code for the item
+        */
+       protected function getSingleField_typeSelect_multiple($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) {
+               $languageService = $this->getLanguageService();
+               $item = '';
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+               // Setting this hidden field (as a flag that JavaScript can read out)
+               if (!$disabled) {
+                       $item .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '_mul" value="' . ($config['multiple'] ? 1 : 0) . '" />';
+               }
+               // Set max and min items:
+               $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
+               if (!$maxitems) {
+                       $maxitems = 100000;
+               }
+               $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
+               // Register the required number of elements:
+               $this->resultArray['requiredElements'][$parameterArray['itemFormElName']] = array(
+                       $minitems,
+                       $maxitems,
+                       'imgName' => $table . '_' . $row['uid'] . '_' . $field
+               );
+               $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
+               if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
+                       array_shift($match);
+                       $this->resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
+                               'parts' => $match,
+                               'level' => $tabAndInlineStack,
+                       );
+               }
+               // Get "removeItems":
+               $removeItems = GeneralUtility::trimExplode(',', $parameterArray['fieldTSConfig']['removeItems'], TRUE);
+               // Get the array with selected items:
+               $itemArray = GeneralUtility::trimExplode(',', $parameterArray['itemFormElValue'], TRUE);
+
+               // Possibly filter some items:
+               $itemArray = ArrayUtility::keepItemsInArray(
+                       $itemArray,
+                       $parameterArray['fieldTSConfig']['keepItems'],
+                       function ($value) {
+                               $parts = explode('|', $value, 2);
+                               return rawurldecode($parts[0]);
+                       }
+               );
+
+               // Perform modification of the selected items array:
+               foreach ($itemArray as $tk => $tv) {
+                       $tvP = explode('|', $tv, 2);
+                       $evalValue = $tvP[0];
+                       $isRemoved = in_array($evalValue, $removeItems)
+                               || $config['type'] == 'select' && $config['authMode']
+                               && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $evalValue, $config['authMode']);
+                       if ($isRemoved && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
+                               $tvP[1] = rawurlencode(@sprintf($noMatchingLabel, $evalValue));
+                       } else {
+                               if (isset($parameterArray['fieldTSConfig']['altLabels.'][$evalValue])) {
+                                       $tvP[1] = rawurlencode($languageService->sL($parameterArray['fieldTSConfig']['altLabels.'][$evalValue]));
+                               }
+                               if (isset($parameterArray['fieldTSConfig']['altIcons.'][$evalValue])) {
+                                       $tvP[2] = $parameterArray['fieldTSConfig']['altIcons.'][$evalValue];
+                               }
+                       }
+                       if ($tvP[1] == '') {
+                               // Case: flexform, default values supplied, no label provided (bug #9795)
+                               foreach ($selItems as $selItem) {
+                                       if ($selItem[1] == $tvP[0]) {
+                                               $tvP[1] = html_entity_decode($selItem[0]);
+                                               break;
+                                       }
+                               }
+                       }
+                       $itemArray[$tk] = implode('|', $tvP);
+               }
+               $itemsToSelect = '';
+               $filterTextfield = '';
+               $filterSelectbox = '';
+               $size = 0;
+               if (!$disabled) {
+                       // Create option tags:
+                       $opt = array();
+                       $styleAttrValue = '';
+                       foreach ($selItems as $p) {
+                               if ($config['iconsInOptionTags']) {
+                                       $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
+                               }
+                               $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"'
+                                       . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '')
+                                       . ' title="' . $p[0] . '">' . $p[0] . '</option>';
+                       }
+                       // Put together the selector box:
+                       $selector_itemListStyle = isset($config['itemListStyle'])
+                               ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
+                               : '';
+                       $size = (int)$config['size'];
+                       $size = $config['autoSizeMax']
+                               ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
+                               : $size;
+                       $sOnChange = implode('', $parameterArray['fieldChangeFunc']);
+
+                       $multiSelectId = str_replace('.', '', uniqid('tceforms-multiselect-', TRUE));
+                       $itemsToSelect = '
+                               <select data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '" data-exclusivevalues="'
+                               . htmlspecialchars($config['exclusiveKeys']) . '" id="' . $multiSelectId . '" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '_sel" '
+                               . ' class="form-control t3js-formengine-select-itemstoselect" '
+                               . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"'
+                               . $parameterArray['onFocus'] . $selector_itemListStyle . '>
+                                       ' . implode('
+                                       ', $opt) . '
+                               </select>';
+
+                       // enable filter functionality via a text field
+                       if ($config['enableMultiSelectFilterTextfield']) {
+                               $filterTextfield = '
+                                       <span class="input-group input-group-sm">
+                                               <span class="input-group-addon">
+                                                       <span class="fa fa-filter"></span>
+                                               </span>
+                                               <input class="t3js-formengine-multiselect-filter-textfield form-control" value="" />
+                                       </span>';
+                       }
+
+                       // enable filter functionality via a select
+                       if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) {
+                               $filterDropDownOptions = array();
+                               foreach ($config['multiSelectFilterItems'] as $optionElement) {
+                                       $optionValue = $languageService->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1]
+                                               : $optionElement[0]);
+                                       $filterDropDownOptions[] = '<option value="' . htmlspecialchars($languageService->sL($optionElement[0])) . '">'
+                                               . htmlspecialchars($optionValue) . '</option>';
+                               }
+                               $filterSelectbox = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">
+                                               ' . implode('
+                                               ', $filterDropDownOptions) . '
+                                       </select>';
+                       }
+               }
+
+               if (!empty(trim($filterSelectbox)) && !empty(trim($filterTextfield))) {
+                       $filterSelectbox = '<div class="form-multigroup-item form-multigroup-element">' . $filterSelectbox . '</div>';
+                       $filterTextfield = '<div class="form-multigroup-item form-multigroup-element">' . $filterTextfield . '</div>';
+                       $selectBoxFilterContents = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">' . $filterSelectbox . $filterTextfield . '</div>';
+               } else {
+                       $selectBoxFilterContents = trim($filterSelectbox . ' ' . $filterTextfield);
+               }
+
+               // Pass to "dbFileIcons" function:
+               $params = array(
+                       'size' => $size,
+                       'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0),
+                       'style' => isset($config['selectedListStyle'])
+                               ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"'
+                               : '',
+                       'dontShowMoveIcons' => $maxitems <= 1,
+                       'maxitems' => $maxitems,
+                       'info' => '',
+                       'headers' => array(
+                               'selector' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected'),
+                               'items' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.items'),
+                               'selectorbox' => $selectBoxFilterContents,
+                       ),
+                       'noBrowser' => 1,
+                       'rightbox' => $itemsToSelect,
+                       'readOnly' => $disabled
+               );
+               $item .= $this->dbFileIcons($parameterArray['itemFormElName'], '', '', $itemArray, '', $params, $parameterArray['onFocus']);
+               return $item;
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUserAuthentication() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php
new file mode 100644 (file)
index 0000000..87fc6f1
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+
+/**
+ * Create a widget with a select box where multiple items can be selected
+ *
+ * This is rendered for config type=select, maxitems > 1, renderMode=singlebox
+ */
+class SelectSingleBoxElement extends AbstractFormElement {
+
+       /**
+        * @var array Result array given returned by render() - This property is a helper until class is properly refactored
+        */
+       protected $resultArray = array();
+
+       /**
+        * This will render a selector box element, or possibly a special construction with two selector boxes.
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $table = $this->globalOptions['table'];
+               $field = $this->globalOptions['fieldName'];
+               $row = $this->globalOptions['databaseRow'];
+               $parameterArray = $this->globalOptions['parameterArray'];
+               // Field configuration from TCA:
+               $config = $parameterArray['fieldConf']['config'];
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+               $this->resultArray = $this->initializeResultArray();
+               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
+               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
+               $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
+
+               // Creating the label for the "No Matching Value" entry.
+               $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
+
+               $html = $this->getSingleField_typeSelect_singlebox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
+
+               // Wizards:
+               if (!$disabled) {
+                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
+               }
+               $this->resultArray['html'] = $html;
+               return $this->resultArray;
+       }
+
+       /**
+        * Creates a selectorbox list (renderMode = "singlebox")
+        *
+        * @param string $table See getSingleField_typeSelect()
+        * @param string $field See getSingleField_typeSelect()
+        * @param array $row See getSingleField_typeSelect()
+        * @param array $parameterArray See getSingleField_typeSelect()
+        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @param array $selItems Items available for selection
+        * @param string $noMatchingLabel Label for no-matching-value
+        * @return string The HTML code for the item
+        */
+       protected function getSingleField_typeSelect_singlebox($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel) {
+               $languageService = $this->getLanguageService();
+               // Get values in an array (and make unique, which is fine because there can be no duplicates anyway):
+               $itemArray = array_flip(FormEngineUtility::extractValuesOnlyFromValueLabelList($parameterArray['itemFormElValue']));
+               $item = '';
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+               // Traverse the Array of selector box items:
+               $opt = array();
+               // Used to accumulate the JS needed to restore the original selection.
+               $restoreCmd = array();
+               $c = 0;
+               foreach ($selItems as $p) {
+                       // Selected or not by default:
+                       $sM = '';
+                       if (isset($itemArray[$p[1]])) {
+                               $sM = ' selected="selected"';
+                               $restoreCmd[] = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'] . '[]') . '].options[' . $c . '].selected=1;';
+                               unset($itemArray[$p[1]]);
+                       }
+                       // Non-selectable element:
+                       $nonSel = '';
+                       if ((string)$p[1] === '--div--') {
+                               $nonSel = ' onclick="this.selected=0;" class="formcontrol-select-divider"';
+                       }
+                       // Icon style for option tag:
+                       $styleAttrValue = '';
+                       if ($config['iconsInOptionTags']) {
+                               $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
+                       }
+                       // Compile <option> tag:
+                       $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . $sM . $nonSel
+                               . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>'
+                               . htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', FALSE) . '</option>';
+                       $c++;
+               }
+               // Remaining values:
+               if (count($itemArray) && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
+                       foreach ($itemArray as $theNoMatchValue => $temp) {
+                               // Compile <option> tag:
+                               array_unshift($opt, '<option value="' . htmlspecialchars($theNoMatchValue) . '" selected="selected">'
+                                       . htmlspecialchars(@sprintf($noMatchingLabel, $theNoMatchValue), ENT_COMPAT, 'UTF-8', FALSE) . '</option>');
+                       }
+               }
+               // Compile selector box:
+               $sOnChange = implode('', $parameterArray['fieldChangeFunc']);
+               $selector_itemListStyle = isset($config['itemListStyle'])
+                       ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"'
+                       : '';
+               $size = (int)$config['size'];
+               $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect';
+               $size = $config['autoSizeMax']
+                       ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax'])
+                       : $size;
+               $selectBox = '<select id="' . str_replace('.', '', uniqid($cssPrefix, TRUE)) . '" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '[]" '
+                       . 'class="form-control ' . $cssPrefix . '"' . ($size ? ' size="' . $size . '" ' : '')
+                       . ' multiple="multiple" onchange="' . htmlspecialchars($sOnChange) . '"' . $parameterArray['onFocus']
+                       . ' ' . $selector_itemListStyle . $disabled . '>
+                                               ' . implode('
+                                               ', $opt) . '
+                                       </select>';
+               // Add an empty hidden field which will send a blank value if all items are unselected.
+               if (!$disabled) {
+                       $item .= '<input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="" />';
+               }
+               // Put it all into a table:
+               $onClick = htmlspecialchars('document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName'] . '[]') . '].selectedIndex=-1;' . implode('', $restoreCmd) . ' return false;');
+               $width = $this->formMaxWidth($this->defaultInputWidth);
+               $item .= '
+                       <div class="form-control-wrap" ' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>
+                               <div class="form-wizards-wrap form-wizards-aside">
+                                       <div class="form-wizards-element">
+                                               ' . $selectBox . '
+                                       </div>
+                                       <div class="form-wizards-items">
+                                               <a href="#" class="btn btn-default" onclick="' . $onClick . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection')) . '">'
+                                                       . IconUtility::getSpriteIcon('actions-edit-undo') . '</a>
+                                       </div>
+                               </div>
+                       </div>
+                       <p>
+                               <em>' . htmlspecialchars($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.holdDownCTRL')) . '</em>
+                       </p>
+                       ';
+               return $item;
+       }
+
+}
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php
new file mode 100644 (file)
index 0000000..a5ad9b3
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+use TYPO3\CMS\Backend\Form\InlineStackProcessor;
+
+/**
+ * Creates a widget where only one item can be selected.
+ * This is either a select drop-down if no size config is given or set to 1, or a select box.
+ *
+ * This is rendered for type=select, maxitems=1
+ */
+class SelectSingleElement extends AbstractFormElement {
+
+       /**
+        * @var array Result array given returned by render() - This property is a helper until class is properly refactored
+        */
+       protected $resultArray = array();
+
+       /**
+        * Render single element
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $table = $this->globalOptions['table'];
+               $field = $this->globalOptions['fieldName'];
+               $row = $this->globalOptions['databaseRow'];
+               $parameterArray = $this->globalOptions['parameterArray'];
+               $config = $parameterArray['fieldConf']['config'];
+
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+
+               $this->resultArray = $this->initializeResultArray();
+
+               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
+               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
+               $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
+
+               // Creating the label for the "No Matching Value" entry.
+               $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
+                       : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
+
+               $html = $this->getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
+
+               // Wizards:
+               if (!$disabled) {
+                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
+               }
+               $this->resultArray['html'] = $html;
+               return $this->resultArray;
+       }
+
+       /**
+        * Creates a single-selector box
+        *
+        * @param string $table See getSingleField_typeSelect()
+        * @param string $field See getSingleField_typeSelect()
+        * @param array $row See getSingleField_typeSelect()
+        * @param array $parameterArray See getSingleField_typeSelect()
+        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @param array $selectItems Items available for selection
+        * @param string $noMatchingLabel Label for no-matching-value
+        * @return string The HTML code for the item
+        */
+       protected function getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selectItems, $noMatchingLabel) {
+               // Check against inline uniqueness
+               /** @var InlineStackProcessor $inlineStackProcessor */
+               $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+               $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
+               $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
+               $uniqueIds = NULL;
+               if (is_array($inlineParent) && $inlineParent['uid']) {
+                       $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+                       $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
+                       if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
+                               $uniqueIds = $this->globalOptions['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
+                               $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,\'' . $inlineObjectName
+                                       . '-' . $table . '\',\'' . $inlineFormName
+                                       . '\',\'' . $row['uid'] . '\');';
+                       }
+                       // hide uid of parent record for symmetric relations
+                       if (
+                               $inlineParent['config']['foreign_table'] == $table
+                               && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
+                       ) {
+                               $uniqueIds[] = $inlineParent['uid'];
+                       }
+               }
+
+               // Initialization:
+               $selectId = str_replace('.', '', uniqid('tceforms-select-', TRUE));
+               $selectedIndex = 0;
+               $selectedIcon = '';
+               $noMatchingValue = 1;
+               $onlySelectedIconShown = 0;
+               $size = (int)$config['size'];
+
+               // Style set on <select/>
+               $out = '';
+               $options = '';
+               $disabled = FALSE;
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = TRUE;
+                       $onlySelectedIconShown = 1;
+               }
+               // Register as required if minitems is greater than zero
+               if (($minItems = MathUtility::forceIntegerInRange($config['minitems'], 0)) > 0) {
+                       $this->resultArray['requiredFields'][$table . '_' . $row['uid'] . '_' . $field] = $parameterArray['itemFormElName'];
+                       $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
+                       if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
+                               array_shift($match);
+                               $this->resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
+                                       'parts' => $match,
+                                       'level' => $tabAndInlineStack,
+                               );
+                       }
+               }
+
+               // Icon configuration:
+               if ($config['suppress_icons'] == 'IF_VALUE_FALSE') {
+                       $suppressIcons = !$parameterArray['itemFormElValue'] ? 1 : 0;
+               } elseif ($config['suppress_icons'] == 'ONLY_SELECTED') {
+                       $suppressIcons = 0;
+                       $onlySelectedIconShown = 1;
+               } elseif ($config['suppress_icons']) {
+                       $suppressIcons = 1;
+               } else {
+                       $suppressIcons = 0;
+               }
+
+               // Prepare groups
+               $selectItemCounter = 0;
+               $selectItemGroupCount = 0;
+               $selectItemGroups = array();
+               $selectIcons = array();
+               foreach ($selectItems as $item) {
+                       if ($item[1] === '--div--') {
+                               // IS OPTGROUP
+                               if ($selectItemCounter !== 0) {
+                                       $selectItemGroupCount++;
+                               }
+                               $selectItemGroups[$selectItemGroupCount]['header'] = array(
+                                       'title' => $item[0],
+                                       'icon' => (!empty($item[2]) ? FormEngineUtility::getIconHtml($item[2]) : ''),
+                               );
+                       } else {
+                               // IS ITEM
+                               $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', FALSE);
+                               $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
+                               $selected = ((string)$parameterArray['itemFormElValue'] === (string)$item[1] ? 1 : 0);
+                               if ($selected) {
+                                       $selectedIndex = $selectItemCounter;
+                                       $selectedIcon = $icon;
+                                       $noMatchingValue = 0;
+                               }
+                               $selectItemGroups[$selectItemGroupCount]['items'][] = array(
+                                       'title' => $title,
+                                       'value' => $item[1],
+                                       'icon' => $icon,
+                                       'selected' => $selected,
+                                       'index' => $selectItemCounter
+                               );
+                               // ICON
+                               if ($icon && !$suppressIcons && (!$onlySelectedIconShown || $selected)) {
+                                       $onClick = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . '].selectedIndex=' . $selectItemCounter . ';';
+                                       if ($config['iconsInOptionTags']) {
+                                               $onClick .= 'document.getElementById(\'' . $selectId . '_icon\').innerHTML = '
+                                                       . 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ']'
+                                                       . '.options[' . $selectItemCounter . '].getAttribute(\'data-icon\'); ';
+                                       }
+                                       $onClick .= implode('', $parameterArray['fieldChangeFunc']);
+                                       $onClick .= 'this.blur();return false;';
+                                       $selectIcons[] = array(
+                                               'title' => $title,
+                                               'icon' => $icon,
+                                               'index' => $selectItemCounter,
+                                               'onClick' => $onClick
+                                       );
+                               }
+                               $selectItemCounter++;
+                       }
+
+               }
+
+               // No-matching-value:
+               if ($parameterArray['itemFormElValue'] && $noMatchingValue && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
+                       $noMatchingLabel = @sprintf($noMatchingLabel, $parameterArray['itemFormElValue']);
+                       $options = '<option value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
+               } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
+                       $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
+               }
+
+               // Process groups
+               foreach ($selectItemGroups as $selectItemGroup) {
+                       $optionGroup = is_array($selectItemGroup['header']);
+                       $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', FALSE) . '">' : '');
+                       if (is_array($selectItemGroup['items'])) {
+                               foreach ($selectItemGroup['items'] as $item) {
+                                       $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
+                                               htmlspecialchars($item['icon']) . '"'
+                                               . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>';
+                               }
+                       }
+                       $options .= ($optionGroup ? '</optgroup>' : '');
+               }
+
+               // Create item form fields:
+               $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $selectedIndex . ';} ';
+               if ($config['iconsInOptionTags']) {
+                       $sOnChange .= 'document.getElementById(\'' . $selectId . '_icon\').innerHTML = this.options[this.selectedIndex].getAttribute(\'data-icon\'); ';
+               }
+               $sOnChange .= implode('', $parameterArray['fieldChangeFunc']);
+
+               // Add icons in option tags
+               $prepend = '';
+               $append = '';
+               if ($config['iconsInOptionTags']) {
+                       $prepend = '<div class="input-group"><div id="' . $selectId . '_icon" class="input-group-addon input-group-icon t3js-formengine-select-prepend">' . $selectedIcon . '</div>';
+                       $append = '</div>';
+               }
+
+               // Build the element
+               $out .= '
+                       <div class="form-control-wrap">
+                               ' . $prepend . '
+                               <select'
+                                       . ' id="' . $selectId . '"'
+                                       . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
+                                       . ' class="form-control form-control-adapt"'
+                                       . ($size ? ' size="' . $size . '"' : '')
+                                       . ' onchange="' . htmlspecialchars($sOnChange) . '"'
+                                       . $parameterArray['onFocus']
+                                       . ($disabled ? ' disabled="disabled"' : '')
+                                       . '>
+                                       ' . $options . '
+                               </select>
+                               ' . $append . '
+                       </div>';
+
+               // Create icon table:
+               if (count($selectIcons) && !$config['noIconsBelowSelect']) {
+                       $selectIconColumns = (int)$config['selicon_cols'];
+                       if (!$selectIconColumns) {
+                               $selectIconColumns = count($selectIcons);
+                       }
+                       $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
+                       $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
+                       $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
+                       $out .= '<div class="table-fit table-fit-inline-block"><table class="table table-condensed table-white table-center"><tbody><tr>';
+                       for ($selectIconCount = 0; $selectIconCount < count($selectIcons); $selectIconCount++) {
+                               if ($selectIconCount % $selectIconColumns === 0 && $selectIconCount !== 0) {
+                                       $out .= '</tr><tr>';
+                               }
+                               $out .= '<td>';
+                               if (is_array($selectIcons[$selectIconCount])) {
+                                       $out .= (!$onlySelectedIconShown ? '<a href="#" title="' . $selectIcons[$selectIconCount]['title'] . '" onClick="' . htmlspecialchars($selectIcons[$selectIconCount]['onClick']) . '">' : '');
+                                       $out .= $selectIcons[$selectIconCount]['icon'];
+                                       $out .= (!$onlySelectedIconShown ? '</a>' : '');
+                               }
+                               $out .= '</td>';
+                       }
+                       $out .= '</tr></tbody></table></div>';
+               }
+
+               return $out;
+       }
+
+}
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
new file mode 100644 (file)
index 0000000..8f7b6c0
--- /dev/null
@@ -0,0 +1,276 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+use TYPO3\CMS\Lang\LanguageService;
+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\ExtensionManagementUtility;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+
+/**
+ * Render data as a tree.
+ *
+ * Typically rendered for config [type=select, renderMode=tree
+ */
+class SelectTreeElement extends AbstractFormElement {
+
+       /**
+        * Render tree widget
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $table = $this->globalOptions['table'];
+               $field = $this->globalOptions['fieldName'];
+               $row = $this->globalOptions['databaseRow'];
+               $parameterArray = $this->globalOptions['parameterArray'];
+
+               // Field configuration from TCA:
+               $config = $parameterArray['fieldConf']['config'];
+               $disabled = '';
+               if ($this->isGlobalReadonly() || $config['readOnly']) {
+                       $disabled = ' disabled="disabled"';
+               }
+
+               $resultArray = $this->initializeResultArray();
+
+               // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
+               $specConf = BackendUtility::getSpecConfParts($parameterArray['extra'], $parameterArray['fieldConf']['defaultExtras']);
+               $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
+
+               $maxitems = (int)$config['maxitems'];
+
+               $html = $this->renderField($table, $field, $row, $parameterArray, $config, $selItems);
+
+               // Register the required number of elements
+               $minitems = MathUtility::forceIntegerInRange($config['minitems'], 0);
+               $resultArray['requiredElements'][$parameterArray['itemFormElName']] = array(
+                       $minitems,
+                       $maxitems,
+                       'imgName' => $table . '_' . $row['uid'] . '_' . $field
+               );
+               $tabAndInlineStack = $this->globalOptions['tabAndInlineStack'];
+               if (!empty($tabAndInlineStack) && preg_match('/^(.+\\])\\[(\\w+)\\]$/', $parameterArray['itemFormElName'], $match)) {
+                       array_shift($match);
+                       $resultArray['requiredNested'][$parameterArray['itemFormElName']] = array(
+                               'parts' => $match,
+                               'level' => $tabAndInlineStack,
+                       );
+               }
+
+               // Wizards:
+               if (!$disabled) {
+                       $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
+               }
+               $resultArray['html'] = $html;
+               return $resultArray;
+       }
+
+       /**
+        * Renders the tree
+        *
+        * @param string $table The table name of the record
+        * @param string $field The field name which this element is supposed to edit
+        * @param array $row The record data array where the value(s) for the field can be found
+        * @param array $PA An array with additional configuration options.
+        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @param array $possibleSelectboxItems Items available for selection
+        * @return string The HTML code for the TCEform field
+        */
+       protected function renderField($table, $field, $row, &$PA, $config, $possibleSelectboxItems) {
+               $backendUserAuthentication = $this->getBackendUserAuthentication();
+               $valueArray = array();
+               $selectedNodes = array();
+               if (!empty($PA['itemFormElValue'])) {
+                       $valueArray = explode(',', $PA['itemFormElValue']);
+               }
+               if (count($valueArray)) {
+                       foreach ($valueArray as $selectedValue) {
+                               $temp = explode('|', $selectedValue);
+                               $selectedNodes[] = $temp[0];
+                       }
+               }
+               $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();
+               if (is_array($PA['fieldConf']['config']['items'])) {
+                       foreach ($PA['fieldConf']['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;
+                               }
+                       }
+               }
+               $itemArray[] = $treeData;
+               $treeData = json_encode($itemArray);
+               $id = md5($PA['itemFormElName']);
+               if (isset($PA['fieldConf']['config']['size']) && (int)$PA['fieldConf']['config']['size'] > 0) {
+                       $height = (int)$PA['fieldConf']['config']['size'] * 20;
+               } else {
+                       $height = 280;
+               }
+               $autoSizeMax = NULL;
+               if (isset($PA['fieldConf']['config']['autoSizeMax']) && (int)$PA['fieldConf']['config']['autoSizeMax'] > 0) {
+                       $autoSizeMax = (int)$PA['fieldConf']['config']['autoSizeMax'] * 20;
+               }
+               $header = FALSE;
+               $expanded = FALSE;
+               $width = 280;
+               $appearance = $PA['fieldConf']['config']['treeConfig']['appearance'];
+               if (is_array($appearance)) {
+                       $header = $appearance['showHeader'] ? TRUE : FALSE;
+                       $expanded = $appearance['expandAll'] === TRUE;
+                       if (isset($appearance['width'])) {
+                               $width = (int)$appearance['width'];
+                       }
+               }
+               $onChange = '';
+               if ($PA['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) {
+                       $onChange = $PA['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
+               }
+               // 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 (
+                       !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
+                       && $field === $GLOBALS['TCA'][$table]['ctrl']['type']
+                       || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
+                       && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $field)
+               ) {
+                       if ($backendUserAuthentication->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
+                               $onChange .= 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && ' . 'TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
+                       } else {
+                               $onChange .= 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
+                       }
+               }
+               /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
+               $pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
+               $pageRenderer->loadExtJs();
+               $pageRenderer->addJsFile('sysext/backend/Resources/Public/JavaScript/tree.js');
+               $pageRenderer->addInlineLanguageLabelFile(ExtensionManagementUtility::extPath('lang') . 'locallang_csh_corebe.xlf', 'tcatree');
+               $pageRenderer->addExtOnReadyCode('
+                       TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . $treeData . ';
+                       var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
+                               id: "' . $id . '",
+                               showHeader: ' . (int)$header . ',
+                               onChange: "' . $onChange . '",
+                               countSelectedNodes: ' . count($selectedNodes) . ',
+                               width: ' . $width . ',
+                               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") {
+                                                       top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
+                                               }
+                                       },
+                                       expandnode: function(node) {
+                                               if (node.id !== "root") {
+                                                       top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
+                                               }
+                                       },
+                                       beforerender: function(treeCmp) {
+                                               // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
+                                               if (Ext.fly(treeCmp.getId())) {
+                                                       return false;
+                                               }
+                                       }' . ($expanded ? ',
+                                       afterrender: function(treeCmp) {
+                                               treeCmp.expandAll();
+                                       }' : '') . '
+                               },
+                               tcaMaxItems: ' . ($PA['fieldConf']['config']['maxitems'] ? (int)$PA['fieldConf']['config']['maxitems'] : 99999) . ',
+                               tcaSelectRecursiveAllowed: ' . ($appearance['allowRecursiveMode'] ? 'true' : 'false') . ',
+                               tcaSelectRecursive: false,
+                               tcaExclusiveKeys: "' . ($PA['fieldConf']['config']['exclusiveKeys'] ? $PA['fieldConf']['config']['exclusiveKeys'] : '') . '",
+                               ucId: "' . md5(($table . '|' . $field)) . '",
+                               selModel: TYPO3.Components.Tree.EmptySelectionModel,
+                               disabled: ' . ($PA['fieldConf']['config']['readOnly'] || $this->isGlobalReadonly() ? 'true' : 'false') . '
+                       });' . LF .
+                       ($autoSizeMax
+                               ? 'tree' . $id . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";'
+                               : 'tree' . $id . '.height = ' . $height . ';'
+                       ) . LF .
+                       '(function() {
+                                       tree' . $id . '.render("tree_' . $id . '");
+                               }).defer(20);
+               ');
+               $formField = '
+                       <div class="typo3-tceforms-tree">
+                               <input class="treeRecord" type="hidden" name="' . htmlspecialchars($PA['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars($PA['itemFormElValue']) . '" />
+                       </div>
+                       <div id="tree_' . $id . '">
+
+                       </div>';
+               return $formField;
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUserAuthentication() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
index d7e36ef..c944b4b 100644 (file)
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Backend\Form\FormEngine;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Generation of TCEform elements of the type "text"
@@ -77,16 +78,17 @@ class TextElement extends AbstractFormElement {
                if ($this->isGlobalReadonly() || $config['readOnly']) {
                        $config['cols'] = $cols;
                        $config['rows'] = $rows;
-                       /** @var NoneElement $noneElement */
-                       $noneElement = GeneralUtility::makeInstance(NoneElement::class);
-                       $noneElementOptions = $this->globalOptions;
-                       $noneElementOptions['parameterArray'] = array(
+                       $options = $this->globalOptions;
+                       $options['parameterArray'] = array(
                                'fieldConf' => array(
                                        'config' => $config,
                                ),
                                'itemFormElValue' => $parameterArray['itemFormElValue'],
                        );
-                       return $noneElement->setGlobalOptions($noneElementOptions)->render();
+                       $options['type'] = 'none';
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = $this->globalOptions['nodeFactory'];
+                       return $nodeFactory->create($options)->render();
                }
 
                $evalList = GeneralUtility::trimExplode(',', $config['eval'], TRUE);
diff --git a/typo3/sysext/backend/Classes/Form/Element/TreeElement.php b/typo3/sysext/backend/Classes/Form/Element/TreeElement.php
deleted file mode 100644 (file)
index a724a68..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\Form\Element;
-
-/*
- * 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\Utility\IconUtility;
-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\ExtensionManagementUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Lang\LanguageService;
-
-/**
- * TCEforms wizard for rendering an AJAX selector for records
- *
- * @todo: Refactor - This class is not a "usual" element but just called as a sub class of SelectElement
- */
-class TreeElement extends AbstractFormElement {
-
-       /**
-        * Renders the tree as replacement for the selector
-        *
-        * @param string $table The table name of the record
-        * @param string $field The field name which this element is supposed to edit
-        * @param array $row The record data array where the value(s) for the field can be found
-        * @param array $PA An array with additional configuration options.
-        * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
-        * @param array $possibleSelectboxItems Items available for selection
-        * @return string The HTML code for the TCEform field
-        */
-       public function renderField($table, $field, $row, &$PA, $config, $possibleSelectboxItems) {
-               $backendUserAuthentication = $this->getBackendUserAuthentication();
-               $valueArray = array();
-               $selectedNodes = array();
-               if (!empty($PA['itemFormElValue'])) {
-                       $valueArray = explode(',', $PA['itemFormElValue']);
-               }
-               if (count($valueArray)) {
-                       foreach ($valueArray as $selectedValue) {
-                               $temp = explode('|', $selectedValue);
-                               $selectedNodes[] = $temp[0];
-                       }
-               }
-               $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();
-               if (is_array($PA['fieldConf']['config']['items'])) {
-                       foreach ($PA['fieldConf']['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;
-                               }
-                       }
-               }
-               $itemArray[] = $treeData;
-               $treeData = json_encode($itemArray);
-               $id = md5($PA['itemFormElName']);
-               if (isset($PA['fieldConf']['config']['size']) && (int)$PA['fieldConf']['config']['size'] > 0) {
-                       $height = (int)$PA['fieldConf']['config']['size'] * 20;
-               } else {
-                       $height = 280;
-               }
-               $autoSizeMax = NULL;
-               if (isset($PA['fieldConf']['config']['autoSizeMax']) && (int)$PA['fieldConf']['config']['autoSizeMax'] > 0) {
-                       $autoSizeMax = (int)$PA['fieldConf']['config']['autoSizeMax'] * 20;
-               }
-               $header = FALSE;
-               $expanded = FALSE;
-               $width = 280;
-               $appearance = $PA['fieldConf']['config']['treeConfig']['appearance'];
-               if (is_array($appearance)) {
-                       $header = $appearance['showHeader'] ? TRUE : FALSE;
-                       $expanded = $appearance['expandAll'] === TRUE;
-                       if (isset($appearance['width'])) {
-                               $width = (int)$appearance['width'];
-                       }
-               }
-               $onChange = '';
-               if ($PA['fieldChangeFunc']['TBE_EDITOR_fieldChanged']) {
-                       $onChange = $PA['fieldChangeFunc']['TBE_EDITOR_fieldChanged'];
-               }
-               // 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 (
-                       !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
-                       && $field === $GLOBALS['TCA'][$table]['ctrl']['type']
-                       || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
-                       && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $field)
-               ) {
-                       if ($backendUserAuthentication->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
-                               $onChange .= 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && ' . 'TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
-                       } else {
-                               $onChange .= 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
-                       }
-               }
-               /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
-               $pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
-               $pageRenderer->loadExtJs();
-               $pageRenderer->addJsFile('sysext/backend/Resources/Public/JavaScript/tree.js');
-               $pageRenderer->addInlineLanguageLabelFile(ExtensionManagementUtility::extPath('lang') . 'locallang_csh_corebe.xlf', 'tcatree');
-               $pageRenderer->addExtOnReadyCode('
-                       TYPO3.Components.Tree.StandardTreeItemData["' . $id . '"] = ' . $treeData . ';
-                       var tree' . $id . ' = new TYPO3.Components.Tree.StandardTree({
-                               id: "' . $id . '",
-                               showHeader: ' . (int)$header . ',
-                               onChange: "' . $onChange . '",
-                               countSelectedNodes: ' . count($selectedNodes) . ',
-                               width: ' . $width . ',
-                               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") {
-                                                       top.TYPO3.Storage.Persistent.removeFromList("tcaTrees." + this.ucId, node.attributes.uid);
-                                               }
-                                       },
-                                       expandnode: function(node) {
-                                               if (node.id !== "root") {
-                                                       top.TYPO3.Storage.Persistent.addToList("tcaTrees." + this.ucId, node.attributes.uid);
-                                               }
-                                       },
-                                       beforerender: function(treeCmp) {
-                                               // Check if that tree element is already rendered. It is appended on the first tceforms_inline call.
-                                               if (Ext.fly(treeCmp.getId())) {
-                                                       return false;
-                                               }
-                                       }' . ($expanded ? ',
-                                       afterrender: function(treeCmp) {
-                                               treeCmp.expandAll();
-                                       }' : '') . '
-                               },
-                               tcaMaxItems: ' . ($PA['fieldConf']['config']['maxitems'] ? (int)$PA['fieldConf']['config']['maxitems'] : 99999) . ',
-                               tcaSelectRecursiveAllowed: ' . ($appearance['allowRecursiveMode'] ? 'true' : 'false') . ',
-                               tcaSelectRecursive: false,
-                               tcaExclusiveKeys: "' . ($PA['fieldConf']['config']['exclusiveKeys'] ? $PA['fieldConf']['config']['exclusiveKeys'] : '') . '",
-                               ucId: "' . md5(($table . '|' . $field)) . '",
-                               selModel: TYPO3.Components.Tree.EmptySelectionModel,
-                               disabled: ' . ($PA['fieldConf']['config']['readOnly'] || $this->isGlobalReadonly() ? 'true' : 'false') . '
-                       });' . LF .
-                               ($autoSizeMax
-                                       ? 'tree' . $id . '.bodyStyle = "max-height: ' . $autoSizeMax . 'px;min-height: ' . $height . 'px;";'
-                                       : 'tree' . $id . '.height = ' . $height . ';'
-                               ) . LF .
-                               '(function() {
-                                       tree' . $id . '.render("tree_' . $id . '");
-                               }).defer(20);
-               ');
-               $formField = '
-                       <div class="typo3-tceforms-tree">
-                               <input class="treeRecord" type="hidden" name="' . htmlspecialchars($PA['itemFormElName']) . '" id="treeinput' . $id . '" value="' . htmlspecialchars($PA['itemFormElValue']) . '" />
-                       </div>
-                       <div id="tree_' . $id . '">
-
-                       </div>';
-               return $formField;
-       }
-
-       /**
-        * Dummy method at the moment ...
-        *
-        * @throws \RuntimeException
-        * @return array As defined in initializeResultArray() of AbstractNode
-        */
-       public function render() {
-               throw new \RuntimeException(
-                       'This method is not supposed to be called',
-                       1427114105
-               );
-       }
-
-       /**
-        * @return LanguageService
-        */
-       protected function getLanguageService() {
-               return $GLOBALS['LANG'];
-       }
-
-       /**
-        * @return BackendUserAuthentication
-        */
-       protected function getBackendUserAuthentication() {
-               return $GLOBALS['BE_USER'];
-       }
-
-}
diff --git a/typo3/sysext/backend/Classes/Form/Exception.php b/typo3/sysext/backend/Classes/Form/Exception.php
new file mode 100644 (file)
index 0000000..61b876c
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace TYPO3\CMS\Backend\Form;
+
+/*
+ * 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!
+ */
+
+/**
+ * Generic backend form exception
+ */
+class Exception extends \TYPO3\CMS\Backend\Exception {
+
+}
\ No newline at end of file
index a59a3c5..d3e9323 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Backend\Form;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Form\DataPreprocessor;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -160,16 +162,7 @@ class FormDataTraverser {
                                $possibleUids = $this->getRelatedGroupFieldUids($fieldConfig, $value);
                                break;
                        case 'select':
-                               // @todo: This misuses SelectElement and should be solved differently
-                               /** @var $selectObject \TYPO3\CMS\Backend\Form\Element\SelectElement */
-                               $selectObject = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\Element\SelectElement::class);
-                               $selectObject->setCurrentRow($this->currentRow);
-                               $selectObject->setCurrentTable($this->currentTable);
-                               $selectObject->setAlternativeFieldValue($this->alternativeFieldValue);
-                               $selectObject->setForceAlternativeFieldValueUse($this->forceAlternativeFieldValueUse);
-                               $possibleUids = $selectObject->getRelatedSelectFieldUids($fieldConfig, $fieldName, $value);
-                               $this->alternativeFieldValue = $selectObject->getAlternativeFieldValue();
-                               $this->forceAlternativeFieldValueUse = $selectObject->isForceAlternativeFieldValueUse();
+                               $possibleUids = $this->getRelatedSelectFieldUids($fieldConfig, $fieldName, $value);
                                break;
                        case 'inline':
                                $possibleUids = $this->getRelatedInlineFieldUids($fieldConfig, $fieldName, $value);
@@ -275,8 +268,8 @@ class FormDataTraverser {
         * @return array|boolean FALSE if the record can not be accessed, otherwise the data of the requested record.
         */
        protected function getRecordRow($uid) {
-               /** @var \TYPO3\CMS\Backend\Form\DataPreprocessor $dataPreprocessor */
-               $dataPreprocessor = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\DataPreprocessor::class);
+               /** @var DataPreprocessor $dataPreprocessor */
+               $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
                $dataPreprocessor->fetchRecord($this->currentTable, $uid, '');
                return reset($dataPreprocessor->regTableItems_data);
        }
@@ -322,4 +315,77 @@ class FormDataTraverser {
                return $relatedRow;
        }
 
+       /**
+        * If the select field is build by a foreign_table the related UIDs
+        * will be returned.
+        *
+        * Otherwise the label of the currently selected value will be written
+        * to the alternativeFieldValue class property.
+        *
+        * @param array $fieldConfig The "config" section of the TCA for the current select field.
+        * @param string $fieldName The name of the select field.
+        * @param string $value The current value in the local record, usually a comma separated list of selected values.
+        * @return array Array of related UIDs.
+        */
+       protected function getRelatedSelectFieldUids(array $fieldConfig, $fieldName, $value) {
+               $relatedUids = array();
+
+               $isTraversable = FALSE;
+               if (isset($fieldConfig['foreign_table'])) {
+                       $isTraversable = TRUE;
+                       // if a foreign_table is used we pre-filter the records for performance
+                       $fieldConfig['foreign_table_where'] .= ' AND ' . $fieldConfig['foreign_table'] . '.uid IN (' . $value . ')';
+               }
+
+               $PA = array();
+               $PA['fieldConf']['config'] = $fieldConfig;
+               $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($this->currentTable, $this->currentRow, $fieldName);
+               $PA['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($PA['fieldConf']['config'], $PA['fieldTSConfig']);
+               $selectItemArray = FormEngineUtility::getSelectItems($this->currentTable, $fieldName, $this->currentRow, $PA);
+
+               if ($isTraversable && count($selectItemArray)) {
+                       $this->currentTable = $fieldConfig['foreign_table'];
+                       $relatedUids = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value);
+               } else {
+                       $selectedLabels = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value, 1, TRUE);
+                       if (count($selectedLabels) === 1) {
+                               $this->alternativeFieldValue = $selectedLabels[0];
+                               $this->forceAlternativeFieldValueUse = TRUE;
+                       }
+               }
+
+               return $relatedUids;
+       }
+
+       /**
+        * Extracts the selected values from a given array of select items.
+        *
+        * @param array $selectItemArray The select item array generated by \TYPO3\CMS\Backend\Form\FormEngine->getSelectItems.
+        * @param string $value The currently selected value(s) as comma separated list.
+        * @param int|NULL $maxItems Optional value, if set processing is skipped and an empty array will be returned when the number of selected values is larger than the provided value.
+        * @param bool $returnLabels If TRUE the select labels will be returned instead of the values.
+        * @return array
+        */
+       protected function getSelectedValuesFromSelectItemArray(array $selectItemArray, $value, $maxItems = NULL, $returnLabels = FALSE) {
+               $values = GeneralUtility::trimExplode(',', $value);
+               $selectedValues = array();
+
+               if ($maxItems !== NULL && (count($values) > (int)$maxItems)) {
+                       return $selectedValues;
+               }
+
+               foreach ($selectItemArray as $selectItem) {
+                       $selectItemValue = $selectItem[1];
+                       if (in_array($selectItemValue, $values)) {
+                               if ($returnLabels) {
+                                       $selectedValues[] = $selectItem[0];
+                               } else {
+                                       $selectedValues[] = $selectItemValue;
+                               }
+                       }
+               }
+
+               return $selectedValues;
+       }
+
 }
index 072b222..9f844ad 100644 (file)
@@ -241,6 +241,11 @@ class FormEngine {
        protected $databaseRow = array();
 
        /**
+        * @var NodeFactory Factory taking care of creating appropriate sub container and elements
+        */
+       protected $nodeFactory;
+
+       /**
         * Constructor function, setting internal variables, loading the styles used.
         *
         */
@@ -258,6 +263,7 @@ class FormEngine {
                $template = GeneralUtility::getUrl(PATH_typo3 . $this->templateFile);
                // Wrapping all table rows for a particular record being edited:
                $this->totalWrap = HtmlParser::getSubpart($template, '###TOTALWRAP###');
+               $this->nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
        }
 
        /**
@@ -296,10 +302,10 @@ class FormEngine {
                        }
                }
 
-               /** @var FullRecordContainer $entryContainer */
-               $entryContainer = GeneralUtility::makeInstance(FullRecordContainer::class);
-               $entryContainer->setGlobalOptions($this->getConfigurationOptionsForChildElements());
-               $resultArray = $entryContainer->render();
+               $options = $this->getConfigurationOptionsForChildElements();
+               $options['type'] = 'fullRecordContainer';
+               $resultArray = $this->nodeFactory->create($options)->render();
+
                $content = $resultArray['html'];
                $this->requiredElements = $resultArray['requiredElements'];
                $this->requiredFields = $resultArray['requiredFields'];
@@ -339,10 +345,8 @@ class FormEngine {
 
                $options = $this->getConfigurationOptionsForChildElements();
                $options['singleFieldToRender'] = $theFieldToReturn;
-               /** @var SoloFieldContainer $soloFieldContainer */
-               $soloFieldContainer = GeneralUtility::makeInstance(SoloFieldContainer::class);
-               $soloFieldContainer->setGlobalOptions($options);
-               $resultArray = $soloFieldContainer->render();
+               $options['type'] = 'soloFieldContainer';
+               $resultArray = $this->nodeFactory->create($options)->render();
                $html = $resultArray['html'];
 
                $this->requiredElements = $resultArray['requiredElements'];
@@ -379,10 +383,8 @@ class FormEngine {
 
                $options = $this->getConfigurationOptionsForChildElements();
                $options['fieldListToRender'] = $list;
-               /** @var ListOfFieldsContainer $listOfFieldsContainer */
-               $listOfFieldsContainer = GeneralUtility::makeInstance(ListOfFieldsContainer::class);
-               $listOfFieldsContainer->setGlobalOptions($options);
-               $resultArray = $listOfFieldsContainer->render();
+               $options['type'] = 'listOfFieldsContainer';
+               $resultArray = $this->nodeFactory->create($options)->render();
                $html = $resultArray['html'];
 
                foreach ($resultArray['requiredElements'] as $element) {
@@ -440,6 +442,7 @@ class FormEngine {
                        'hiddenFieldListArray' => $this->hiddenFieldListArr,
                        'isAjaxContext' => FALSE,
                        'flexFormFieldIdentifierPrefix' => 'ID',
+                       'nodeFactory' => $this->nodeFactory,
                );
        }
 
@@ -542,9 +545,8 @@ class FormEngine {
                $options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
                $options['isAjaxContext'] = TRUE;
 
-               /** @var InlineRecordContainer $inlineRecordContainer */
-               $inlineRecordContainer = GeneralUtility::makeInstance(InlineRecordContainer::class);
-               $childArray = $inlineRecordContainer->setGlobalOptions($options)->render();
+               $options['type'] = 'inlineRecordContainer';
+               $childArray = $this->nodeFactory->create($options)->render();
 
                if ($childArray === FALSE) {
                        return $this->getErrorMessageForAJAX('Access denied');
@@ -675,9 +677,8 @@ class FormEngine {
                $options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
                $options['isAjaxContext'] = TRUE;
 
-               /** @var InlineRecordContainer $inlineRecordContainer */
-               $inlineRecordContainer = GeneralUtility::makeInstance(InlineRecordContainer::class);
-               $childArray = $inlineRecordContainer->setGlobalOptions($options)->render();
+               $options['type'] = 'inlineRecordContainer';
+               $childArray = $this->nodeFactory->create($options)->render();
 
                if ($childArray === FALSE) {
                        return $this->getErrorMessageForAJAX('Access denied');
@@ -785,9 +786,8 @@ class FormEngine {
                                $options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
                                $options['isAjaxContext'] = TRUE;
 
-                               /** @var InlineRecordContainer $inlineRecordContainer */
-                               $inlineRecordContainer = GeneralUtility::makeInstance(InlineRecordContainer::class);
-                               $childArray = $inlineRecordContainer->setGlobalOptions($options)->render();
+                               $options['type'] = 'inlineRecordContainer';
+                               $childArray = $this->nodeFactory->create($options)->render();
                                $html .= $childArray['html'];
                                $childArray['html'] = '';
 
index 96997fc..bbb5e49 100644 (file)
@@ -15,8 +15,8 @@ namespace TYPO3\CMS\Backend\Form;
  */
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Backend\Form\Container\FlexFormContainer;
-use TYPO3\CMS\Backend\Form\Container\InlineControlContainer;
+use TYPO3\CMS\Backend\Form\Container;
+use TYPO3\CMS\Backend\Form\Element;
 
 /**
  * Create an element object depending on type.
@@ -29,36 +29,97 @@ use TYPO3\CMS\Backend\Form\Container\InlineControlContainer;
 class NodeFactory {
 
        /**
+        * Default registry of node-type to handling class
+        *
+        * @var array
+        */
+       protected $nodeTypes = array(
+               'flex' => Container\FlexFormContainer::class,
+               'flexFormContainerContainer' => Container\FlexFormContainerContainer::class,
+               'flexFormElementContainer' => Container\FlexFormElementContainer::class,
+               'flexFormLanguageContainer' => Container\FlexFormLanguageContainer::class,
+               'flexFormNoTabsContainer' => Container\FlexFormNoTabsContainer::class,
+               'flexFormSectionContainer' => Container\FlexFormSectionContainer::class,
+               'flexFormTabsContainer' => Container\FlexFormTabsContainer::class,
+               'fullRecordContainer' => Container\FullRecordContainer::class,
+               'inline' => Container\InlineControlContainer::class,
+               'inlineRecordContainer' => Container\InlineRecordContainer::class,
+               'listOfFieldsContainer' => Container\ListOfFieldsContainer::class,
+               'noTabsContainer' => Container\NoTabsContainer::class,
+               'paletteAndSingleContainer' => Container\PaletteAndSingleContainer::class,
+               'singleFieldContainer' => Container\SingleFieldContainer::class,
+               'soloFieldContainer' => Container\SoloFieldContainer::class,
+               'tabsContainer' => Container\TabsContainer::class,
+
+               'check' => Element\CheckboxElement::class,
+               'group' => Element\GroupElement::class,
+               'input' => Element\InputElement::class,
+               'imageManipulation' => Element\ImageManipulationElement::class,
+               'none' => Element\NoneElement::class,
+               'radio' => Element\RadioElement::class,
+               'selectCheckBox' => Element\SelectCheckBoxElement::class,
+               'selectMultipleSideBySide' => Element\SelectMultipleSideBySideElement::class,
+               'selectTree' => Element\SelectTreeElement::class,
+               'selectSingle' => Element\SelectSingleElement::class,
+               'selectSingleBox' => Element\SelectSingleBoxElement::class,
+               'text' => Element\TextElement::class,
+               'unknown' => Element\UnknownElement::class,
+               'user' => Element\UserElement::class,
+       );
+
+       /**
+        * Set up factory
+        */
+       public function __construct() {
+               // @todo: Add additional base types and override existing types
+       }
+
+       /**
         * Create an element depending on type
         *
-        * @param string $type Type identifier
+        * @param array $globalOptions All information to decide which class should be instantiated and given down to sub nodes
         * @return AbstractNode
+        * @throws Exception
         */
-       public function create($type) {
-               if ($type === 'flex') {
-                       /** @var FlexFormContainer $flexContainer */
-                       $resultObject = GeneralUtility::makeInstance(FlexFormContainer::class);
-               } elseif ($type === 'inline') {
-                       $resultObject = GeneralUtility::makeInstance(InlineControlContainer::class);
-               } else {
-                       $typeClassNameMapping = array(
-                               'check' => 'CheckboxElement',
-                               'group' => 'GroupElement',
-                               'imageManipulation' => 'ImageManipulationElement',
-                               'input' => 'InputElement',
-                               'none' => 'NoneElement',
-                               'radio' => 'RadioElement',
-                               'select' => 'SelectElement',
-                               'text' => 'TextElement',
-                               'unknown' => 'UnknownElement',
-                               'user' => 'UserElement',
-                       );
-                       if (!isset($typeClassNameMapping[$type])) {
-                               $type = 'unknown';
+       public function create(array $globalOptions) {
+               if (!is_string($globalOptions['type'])) {
+                       throw new Exception('No type definition found', 1431452406);
+               }
+               $type = $globalOptions['type'];
+
+               if ($type === 'select') {
+                       $config = $globalOptions['parameterArray']['fieldConf']['config'];
+                       $maxitems = (int)$config['maxitems'];
+                       if (isset($config['renderMode']) && $config['renderMode'] === 'tree') {
+                               $type = 'selectTree';
+                       } elseif ($maxitems <= 1) {
+                               $type = 'selectSingle';
+                       } elseif (isset($config['renderMode']) && $config['renderMode'] === 'singlebox') {
+                               $type = 'selectSingleBox';
+                       } elseif (isset($config['renderMode']) && $config['renderMode'] === 'checkbox') {
+                               $type = 'selectCheckBox';
+                       } else {
+                               $type = 'selectMultipleSideBySide';
                        }
-                       $resultObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Form\\Element\\' . $typeClassNameMapping[$type]);
                }
-               return $resultObject;
+
+               $className = isset($this->nodeTypes[$type]) ? $this->nodeTypes[$type] : $this->nodeTypes['unknown'];
+               /** @var AbstractNode $nodeInstance */
+               $nodeInstance = $this->instantiate($className);
+               if (!$nodeInstance instanceof NodeInterface) {
+                       throw new Exception('Node of type ' . get_class($nodeInstance) . ' must implement NodeInterface', 1431872546);
+               }
+               return $nodeInstance->setGlobalOptions($globalOptions);
+       }
+
+       /**
+        * Instantiate given class name
+        *
+        * @param string $className Given class name
+        * @return object
+        */
+       protected function instantiate($className) {
+               return GeneralUtility::makeInstance($className);
        }
 
 }
diff --git a/typo3/sysext/backend/Classes/Form/NodeInterface.php b/typo3/sysext/backend/Classes/Form/NodeInterface.php
new file mode 100644 (file)
index 0000000..45f3f68
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace TYPO3\CMS\Backend\Form;
+
+/*
+ * 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!
+ */
+
+/**
+ * Interface must be implemented by all container and widget classes
+ */
+interface NodeInterface {
+
+       /**
+        * Set global options from parent instance
+        *
+        * @param array $globalOptions Global options like 'readonly' for all elements
+        * @return $this
+        */
+       public function setGlobalOptions(array $globalOptions);
+
+       /**
+        * Main render method
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render();
+
+}
index 9e1b956..5834578 100644 (file)
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Backend\Form\DataPreprocessor;
 
 /**
  * This is a static, internal and intermediate helper class for various
@@ -315,6 +316,87 @@ class FormEngineUtility {
        }
 
        /**
+        * Collects the items for a select field by reading the configured
+        * select items from the configuration and / or by collecting them
+        * from a foreign table.
+        *
+        * @param string $table The table name of the record
+        * @param string $fieldName The select field name
+        * @param array $row The record data array where the value(s) for the field can be found
+        * @param array $PA An array with additional configuration options.
+        * @return array
+        */
+       static public function getSelectItems($table, $fieldName, array $row, array $PA) {
+               $config = $PA['fieldConf']['config'];
+
+               // Getting the selector box items from the system
+               $selectItems = FormEngineUtility::addSelectOptionsToItemArray(
+                       FormEngineUtility::initItemArray($PA['fieldConf']),
+                       $PA['fieldConf'],
+                       FormEngineUtility::getTSconfigForTableRow($table, $row),
+                       $fieldName
+               );
+
+               // Possibly filter some items:
+               $selectItems = ArrayUtility::keepItemsInArray(
+                       $selectItems,
+                       $PA['fieldTSConfig']['keepItems'],
+                       function ($value) {
+                               return $value[1];
+                       }
+               );
+
+               // Possibly add some items:
+               $selectItems = FormEngineUtility::addItems($selectItems, $PA['fieldTSConfig']['addItems.']);
+
+               // Process items by a user function:
+               if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
+                       $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
+                       $selectItems = $dataPreprocessor->procItems($selectItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $fieldName);
+               }
+
+               // Possibly remove some items:
+               $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
+               foreach ($selectItems as $selectItemIndex => $selectItem) {
+
+                       // Checking languages and authMode:
+                       $languageDeny = FALSE;
+                       $beUserAuth = static::getBackendUserAuthentication();
+                       if (
+                               !empty($GLOBALS['TCA'][$table]['ctrl']['languageField'])
+                               && $GLOBALS['TCA'][$table]['ctrl']['languageField'] === $fieldName
+                               && !$beUserAuth->checkLanguageAccess($selectItem[1])
+                       ) {
+                               $languageDeny = TRUE;
+                       }
+
+                       $authModeDeny = FALSE;
+                       if (
+                               ($config['type'] === 'select')
+                               && $config['authMode']
+                               && !$beUserAuth->checkAuthMode($table, $fieldName, $selectItem[1], $config['authMode'])
+                       ) {
+                               $authModeDeny = TRUE;
+                       }
+
+                       if (in_array($selectItem[1], $removeItems) || $languageDeny || $authModeDeny) {
+                               unset($selectItems[$selectItemIndex]);
+                       } elseif (isset($PA['fieldTSConfig']['altLabels.'][$selectItem[1]])) {
+                               $selectItems[$selectItemIndex][0] = htmlspecialchars(static::getLanguageService()->sL($PA['fieldTSConfig']['altLabels.'][$selectItem[1]]));
+                       }
+
+                       // Removing doktypes with no access:
+                       if (($table === 'pages' || $table === 'pages_language_overlay') && $fieldName === 'doktype') {
+                               if (!($beUserAuth->isAdmin() || GeneralUtility::inList($beUserAuth->groupData['pagetypes_select'], $selectItem[1]))) {
+                                       unset($selectItems[$selectItemIndex]);
+                               }
+                       }
+               }
+
+               return $selectItems;
+       }
+
+       /**
         * Add selector box items of more exotic kinds.
         *
         * @param array $items The array of items (label,value,icon)
@@ -391,6 +473,7 @@ class FormEngineUtility {
                                        $theTypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
                                        foreach ($theTypes as $theTypeArrays) {
                                                // Icon:
+                                               // @todo: typo here
                                                $icon = 'empty-emtpy';
                                                if ($theTypeArrays[1] != '--div--') {
                                                        $icon = IconUtility::mapRecordTypeToSpriteIconName('pages', array('doktype' => $theTypeArrays[1]));
diff --git a/typo3/sysext/backend/Tests/Unit/Form/NodeFatoryTest.php b/typo3/sysext/backend/Tests/Unit/Form/NodeFatoryTest.php
new file mode 100644 (file)
index 0000000..83f1096
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Unit\Form;
+
+/*
+ * 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\Element;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Backend\Form\NodeInterface;
+
+/**
+ * Test case
+ */
+class NodeFactoryTest extends UnitTestCase {
+
+       /**
+        * @test
+        * @expectedException \TYPO3\CMS\Backend\Form\Exception
+        */
+       public function createThrowsExceptionIfTypeIsNotGiven() {
+               $subject = new NodeFactory();
+               $subject->create(array());
+       }
+
+       /**
+        * @test
+        * @expectedException \TYPO3\CMS\Backend\Form\Exception
+        */
+       public function createThrowsExceptionIfNodeDoesNotImplementNodeInterface() {
+               $mockNode = new \stdClass();
+               /** @var NodeInterface|\PHPUnit_Framework_MockObject_MockObject $mockSubject */
+               $mockSubject = $this->getMock(NodeFactory::class, array('instantiate'), array(), '', FALSE);
+               $mockSubject->expects($this->once())->method('instantiate')->will($this->returnValue($mockNode));
+               $mockSubject->create(array('type' => 'foo'));
+       }
+
+       /**
+        * @test
+        */
+       public function createSetsGlobalOptionsInInstantiatedObject() {
+               $globalOptions = array('type' => 'foo');
+               $mockNode = $this->getMock(NodeInterface::class, array(), array(), '', FALSE);
+               $mockNode->expects($this->once())->method('setGlobalOptions')->with($globalOptions);
+               /** @var NodeInterface|\PHPUnit_Framework_MockObject_MockObject $mockSubject */
+               $mockSubject = $this->getMock(NodeFactory::class, array('instantiate'), array(), '', FALSE);
+               $mockSubject->expects($this->once())->method('instantiate')->will($this->returnValue($mockNode));
+               $mockSubject->create($globalOptions);
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfUnknownElementIfTypeIsNotRegistered() {
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\UnknownElement::class, $subject->create(array('type' => 'foo')));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectTreeElementIfNeeded() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'renderMode' => 'tree',
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectTreeElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectSingleElementIfNeeded() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'maxitems' => 1,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectSingleElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectSingleElementIfSelectboxIsConfiguredButMaxitemsIsOne() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'renderMode' => 'singlebox',
+                                               'maxitems' => 1,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectSingleElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectSingleElementIfCheckboxIsConfiguredButMaxitemsIsOne() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'renderMode' => 'checkbox',
+                                               'maxitems' => 1,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectSingleElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectSingleBoxElementIfNeeded() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'renderMode' => 'singlebox',
+                                               'maxitems' => 2,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectSingleBoxElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectCheckBoxElementIfNeeded() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'renderMode' => 'checkbox',
+                                               'maxitems' => 2,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectCheckBoxElement::class, $subject->create($globalOptions));
+       }
+
+       /**
+        * @test
+        */
+       public function createReturnsInstanceOfSelectMultipleSideBySideElementIfNeeded() {
+               $globalOptions = array(
+                       'type' => 'select',
+                       'parameterArray' => array(
+                               'fieldConf' => array(
+                                       'config' => array(
+                                               'maxitems' => 2,
+                                       ),
+                               ),
+                       ),
+               );
+               $subject = new NodeFactory();
+               $this->assertInstanceOf(Element\SelectMultipleSideBySideElement::class, $subject->create($globalOptions));
+       }
+
+}