[!!!][TASK] The FormEngine is dead, long live the FormEngine! 33/41933/101
authorChristian Kuhn <lolli@schwarzbu.ch>
Thu, 23 Jul 2015 19:07:46 +0000 (21:07 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 8 Sep 2015 17:19:37 +0000 (19:19 +0200)
tl;dr: This patch makes FormEngine insanely flexible, extensions
however should not rely on structures for now, since class names
and array content will change.

The patch applies a separation of concerns to the FormEngine
class structure by extracting the data processing from rendering.

As a main goal the render part consisting of container and element
classes routed through the flexible NodeFactory only works on data
created by the new FormDataCompiler class construct. This makes the
FormEngine much more flexible and opens ways to not only use the render
part in the context of database driven data, but on anything that is
fed to it.

This patch creates the main structure for this. The FormDataCompiler
class returns a defined array container and elements can work on it.
Data is added by single FormDataProvider, which are combined in
FormDataGroups. FormDataProvider may depend on each other and a
FormDataGroup "knows" its providers and calls them in a dedicated order.

For instance, the "FullDatabaseRecord" FormDataGroup first calls a
provider that fetches the record defined by uid and table name and
a later called provider determines the given record type this record
is assigned to, so another provider can then work on TCA to determine
the list of record fields to be shown. The FormDataProvider used
for the main FormDataGroup are defined in TYPO3_CONF_VARS, so
extensions can add and remove their own providers to add or change
certain data if needed. This is highly flexible and extensions are
able to hook in at a specific position within the provider chain for
the main data groups.

This construct obsoletes the DataPreprocessor as well as several
other side classes.

With this patch the main architecture is created and lots of data
preparation is transfered already, supported by a high unit test
coverage.

The FormEngine class itself is removed: The inline ajax entry point
is moved to an own controller class, the getMainFields() and friends
methods are substituted with FormDataCompiler / NodeFactory combinations
and the data gathering is for now parked in a FormResultCompiler class.

However, this process is not yet finished and lots of @todo
statements are added to the code base to document open ends and to
further separate the data handling from the render engine. Especially
the IRRE data handling is currently still located within the render
engine and makes the whole thing much more complicated than it should
be. Lots of detail patches need to follow to bring this code
to a level where it belongs to be.

Warning: While this patch is already insanely huge touching more than
22 thousands lines of code, lots of loose ends need to be tackled and
the API is not final yet. The arrays will be reduced and sharpened
during the next weeks, class names may change and structures will
change.

Change-Id: Ief1769f478373cc26d1bf6c49114258f0dae8355
Resolves: #69568
Releases: master
Reviewed-on: http://review.typo3.org/41933
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Florian Peters <fpeters1392@googlemail.com>
Reviewed-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Tested-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Tested-by: Alexander Opitz <opitz.alexander@googlemail.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
168 files changed:
typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php
typo3/sysext/backend/Classes/Controller/ContentElement/ElementInformationController.php
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/File/CreateFolderController.php
typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Controller/PageLayoutController.php
typo3/sysext/backend/Classes/Controller/Wizard/AddController.php
typo3/sysext/backend/Classes/Controller/Wizard/RteController.php
typo3/sysext/backend/Classes/Form/AbstractNode.php
typo3/sysext/backend/Classes/Form/Container/AbstractContainer.php
typo3/sysext/backend/Classes/Form/Container/FlexFormContainer.php [deleted file]
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/OuterWrapContainer.php [new file with mode: 0644]
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/DataPreprocessor.php [deleted file]
typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
typo3/sysext/backend/Classes/Form/Element/CheckboxElement.php
typo3/sysext/backend/Classes/Form/Element/GroupElement.php
typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
typo3/sysext/backend/Classes/Form/Element/InputElement.php
typo3/sysext/backend/Classes/Form/Element/NoneElement.php
typo3/sysext/backend/Classes/Form/Element/RadioElement.php
typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php
typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php
typo3/sysext/backend/Classes/Form/Element/SelectTreeElement.php
typo3/sysext/backend/Classes/Form/Element/TextElement.php
typo3/sysext/backend/Classes/Form/Element/UnknownElement.php
typo3/sysext/backend/Classes/Form/Element/UserElement.php
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedContentEditException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedEditInternalsException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedException.php
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedHookException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedPageEditException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedPageNewException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedRootNodeException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/AccessDeniedTableModifyException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/DatabaseDefaultLanguageException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FlexFormsHelper.php [deleted file]
typo3/sysext/backend/Classes/Form/FormDataCompiler.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataGroup/FlexFormSegment.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataGroup/OnTheFly.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataGroup/TcaDatabaseRecord.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataGroupInterface.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractDatabaseRecordProvider.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseEditRow.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseEffectivePid.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabasePageLanguageOverlayRows.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabasePageRootline.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseParentPageRow.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDateTimeFields.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowDefaultValues.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowGroupRelations.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRowInitializeNew.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseSystemLanguageRows.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseUniqueUidNewRow.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseUserPermissionCheck.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/InitializeProcessedTca.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/PageTsConfig.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/PageTsConfigMerged.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/ParentPageTca.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/ReturnUrl.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TableTca.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaCheckboxItems.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaColumnsOverrides.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaFlex.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaRadioItems.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectValues.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaTypesShowitem.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProvider/UserTsConfig.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataProviderInterface.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/FormDataTraverser.php
typo3/sysext/backend/Classes/Form/FormEngine.php [deleted file]
typo3/sysext/backend/Classes/Form/FormResultCompiler.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Form/InlineRelatedRecordResolver.php
typo3/sysext/backend/Classes/Form/NodeFactory.php
typo3/sysext/backend/Classes/Form/NodeInterface.php
typo3/sysext/backend/Classes/Form/NodeResolverInterface.php
typo3/sysext/backend/Classes/Form/Utility/FormEngineUtility.php
typo3/sysext/backend/Classes/Form/Wizard/SuggestWizard.php
typo3/sysext/backend/Classes/Form/Wizard/ValueSliderWizard.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Classes/View/BackendLayoutView.php
typo3/sysext/backend/Resources/Private/Templates/FormEngine.html [deleted file]
typo3/sysext/backend/Resources/Private/Templates/alt_doc.html
typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.inline.js
typo3/sysext/backend/Tests/Functional/Fixtures/Extensions/inlinetest/Configuration/TCA/tx_inlinetest_inline.php [deleted file]
typo3/sysext/backend/Tests/Functional/Fixtures/Extensions/inlinetest/Configuration/TCA/tx_inlinetest_record.php [deleted file]
typo3/sysext/backend/Tests/Functional/Fixtures/Extensions/inlinetest/ext_emconf.php [deleted file]
typo3/sysext/backend/Tests/Functional/Fixtures/Extensions/inlinetest/ext_tables.sql [deleted file]
typo3/sysext/backend/Tests/Functional/Fixtures/inlinetest.xml [deleted file]
typo3/sysext/backend/Tests/Functional/Form/FormEngineInlineTest.php [deleted file]
typo3/sysext/backend/Tests/Unit/Controller/FormInlineAjaxControllerTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataComplierTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/FlexFormSegmentTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/OnTheFlyTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/TcaDatabaseRecordTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEffectivePidTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabasePageLanguageOverlayRowsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseParentPageRowTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDefaultValuesTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowGroupRelationsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowInitializeNewTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseSystemLanguageRowsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseUniqueUidNewRowTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseUserPermissionCheckTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/PageTsConfigMergedTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/ParentPageTcaTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TableTcaTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaCheckboxItemsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsOverridesTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRadioItemsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectValuesTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesShowitemTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/UserTsConfigTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormEngineTest.php [deleted file]
typo3/sysext/backend/Tests/Unit/Form/NodeFactoryTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/NodeFatoryTest.php [deleted file]
typo3/sysext/backend/Tests/Unit/Utility/BackendUtilityTest.php
typo3/sysext/backend/Tests/Unit/Utility/Fixtures/ExcludeFieldsReturnsCorrectFieldListFixture.php [deleted file]
typo3/sysext/backend/Tests/Unit/Utility/Fixtures/ExcludeFieldsReturnsCorrectListWithFlexFormFieldsFixture.php [deleted file]
typo3/sysext/compatibility6/Migrations/Code/ClassAliasMap.php
typo3/sysext/compatibility6/Migrations/Code/LegacyClassesForIde.php
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Classes/DataHandling/ItemProcessingService.php
typo3/sysext/core/Classes/Database/DatabaseConnection.php
typo3/sysext/core/Classes/Database/RelationHandler.php
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/7.1/Feature-61711-SignalAtVeryEndOfDataPreprocessorFetchRecord.rst [deleted file]
typo3/sysext/core/Documentation/Changelog/7.3/Breaking-63846-FormEngineRefactoring.rst
typo3/sysext/core/Documentation/Changelog/master/Breaking-69568-FormEngine.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Deprecation-69562-DeprecateHelperMethodsForRedundantCSRFProtection.rst
typo3/sysext/core/Documentation/Changelog/master/Deprecation-69568-VariousFormEngineRelatedMethods.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-69568-FormEngineDataProcessing.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Service/DependencyOrderingServiceTest.php
typo3/sysext/install/Tests/Unit/Updates/TableFlexFormToTtContentFieldsUpdateTest.php
typo3/sysext/rsaauth/Classes/Form/Element/RsaInputElement.php
typo3/sysext/rtehtmlarea/Classes/Form/Element/RichTextElement.php
typo3/sysext/rtehtmlarea/Classes/Form/Resolver/RichTextNodeResolver.php
typo3/sysext/t3editor/Classes/Form/Element/T3editorElement.php

index 2b9903a..b0f13ac 100644 (file)
@@ -74,6 +74,7 @@ class TranslationConfigurationProvider {
                $languageRecords = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'sys_language', '');
                foreach ($languageRecords as $languageRecord) {
                        $languages[$languageRecord['uid']] = $languageRecord;
+                       // @todo: this should probably resolve language_isocode too and throw a deprecation if not filled
                        if ($languageRecord['static_lang_isocode'] && ExtensionManagementUtility::isLoaded('static_info_tables')) {
                                $staticLangRow = BackendUtility::getRecord('static_languages', $languageRecord['static_lang_isocode'], 'lg_iso_2');
                                if ($staticLangRow['lg_iso_2']) {
index 3fd5287..a6df5f4 100644 (file)
@@ -163,9 +163,6 @@ class ElementInformationController implements \TYPO3\CMS\Core\Http\ControllerInt
                                        $this->access = is_array($this->pageInfo) ? 1 : 0;
                                }
                        }
-                       /** @var $treatData \TYPO3\CMS\Backend\Form\DataPreprocessor */
-                       $treatData = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\DataPreprocessor::class);
-                       $treatData->renderRecord($this->table, $this->uid, 0, $this->row);
                }
        }
 
index 7452313..851c360 100644 (file)
@@ -15,8 +15,8 @@ namespace TYPO3\CMS\Backend\Controller;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Backend\Form\DataPreprocessor;
-use TYPO3\CMS\Backend\Form\FormEngine;
+use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
+use TYPO3\CMS\Backend\Form\FormResultCompiler;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
@@ -30,10 +30,12 @@ use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
-use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
 use TYPO3\CMS\Frontend\Page\PageRepository;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
 
 /**
  * Script Class: Drawing the editing form for editing records in TYPO3.
@@ -208,13 +210,6 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
        public $returnEditConf;
 
        /**
-        * localization mode for TCEforms (eg. "text")
-        *
-        * @var string
-        */
-       public $localizationMode;
-
-       /**
         * Workspace used for the editing action.
         *
         * @var NULL|integer
@@ -291,7 +286,7 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
         *
         * @var string
         */
-       public $storeTitle;
+       public $storeTitle = '';
 
        /**
         * Contains an array with key/value pairs of GET parameters needed to reach the
@@ -383,25 +378,16 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
        public $modTSconfig;
 
        /**
-        * instance of TCEforms class
-        *
-        * @var \TYPO3\CMS\Backend\Form\FormEngine
+        * @var FormResultCompiler
         */
-       public $tceforms;
-
-       /**
-        * Contains the root-line path of the currently edited record(s) - for display.
-        *
-        * @var string
-        */
-       public $generalPathOfForm;
+       protected $formResultCompiler;
 
        /**
         * Used internally to disable the storage of the document reference (eg. new records)
         *
         * @var bool
         */
-       public $dontStoreDocumentRef;
+       public $dontStoreDocumentRef = 0;
 
        /**
         * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
@@ -485,9 +471,6 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $this->R_URL_getvars['edit'] = $this->editconf;
                // MAKE url for storing
                $this->compileStoreDat();
-               // Initialize more variables.
-               $this->dontStoreDocumentRef = 0;
-               $this->storeTitle = '';
                // Get session data for the module:
                $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
                $this->docHandler = $this->docDat[0];
@@ -861,12 +844,9 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $body = '';
                // Begin edit:
                if (is_array($this->editconf)) {
-                       // Initialize TCEforms (rendering the forms)
-                       $this->tceforms = GeneralUtility::makeInstance(FormEngine::class);
-                       $this->tceforms->doSaveFieldName = 'doSave';
-                       $this->tceforms->localizationMode = GeneralUtility::inList('text,media', $this->localizationMode) ? $this->localizationMode : '';
-                       // text,media is keywords defined in TYPO3 Core API..., see "l10n_cat"
-                       $this->tceforms->returnUrl = $this->R_URI;
+                       /** @var FormResultCompiler formResultCompiler */
+                       $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
+
                        if ($this->editRegularContentFromId) {
                                $this->editRegularContentFromId();
                        }
@@ -882,9 +862,9 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                                }
                                // Module configuration
                                $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig($this->viewId, 'mod.xMOD_alt_doc') : array();
-                               $body = $this->tceforms->printNeededJSFunctions_top();
+                               $body = $this->formResultCompiler->JStop();
                                $body .= $this->compileForm($editForm);
-                               $body .= $this->tceforms->printNeededJSFunctions();
+                               $body .= $this->formResultCompiler->printNeededJSFunctions();
                        }
                }
                // Access check...
@@ -894,7 +874,6 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $docHeaderButtons = $this->getButtons();
                $markers = array(
                        'LANGSELECTOR' => $this->langSelector(),
-                       'EXTRAHEADER' => $this->extraFormHeaders(),
                        'CSH' => $docHeaderButtons['csh'],
                        'CONTENT' => $body
                );
@@ -922,7 +901,7 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
         *
         ***************************/
        /**
-        * Creates the editing form with TCEforms, based on the input from GPvars.
+        * Creates the editing form with FormEnigne, based on the input from GPvars.
         *
         * @return string HTML form elements wrapped in tables
         */
@@ -931,7 +910,6 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $this->elementsData = array();
                $this->errorC = 0;
                $this->newC = 0;
-               $thePrevUid = '';
                $editForm = '';
                $trData = NULL;
                $beUser = $this->getBackendUser();
@@ -940,147 +918,71 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                foreach ($this->editconf as $table => $conf) {
                        if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
                                // Traverse the keys/comments of each table (keys can be a commalist of uids)
-                               foreach ($conf as $cKey => $cmd) {
-                                       if ($cmd == 'edit' || $cmd == 'new') {
+                               foreach ($conf as $cKey => $command) {
+                                       if ($command == 'edit' || $command == 'new') {
                                                // Get the ids:
                                                $ids = GeneralUtility::trimExplode(',', $cKey, TRUE);
                                                // Traverse the ids:
                                                foreach ($ids as $theUid) {
-                                                       // Checking if the user has permissions? (Only working as a precaution,
-                                                       // because the final permission check is always down in TCE. But it's
-                                                       // good to notify the user on beforehand...)
-                                                       // First, resetting flags.
-                                                       $hasAccess = 1;
-                                                       $deniedAccessReason = '';
-                                                       $deleteAccess = 0;
-                                                       $this->viewId = 0;
-                                                       // If the command is to create a NEW record...:
-                                                       if ($cmd == 'new') {
-                                                               // NOTICE: the id values in this case points to the page uid onto which the
-                                                               // record should be create OR (if the id is negativ) to a record from the
-                                                               // same table AFTER which to create the record.
-                                                               if ((int)$theUid) {
-                                                                       // Find parent page on which the new record reside
-                                                                       // Less than zero - find parent page
-                                                                       if ($theUid < 0) {
-                                                                               $calcPRec = BackendUtility::getRecord($table, abs($theUid));
-                                                                               $calcPRec = BackendUtility::getRecord('pages', $calcPRec['pid']);
-                                                                       } else {
-                                                                               // always a page
-                                                                               $calcPRec = BackendUtility::getRecord('pages', abs($theUid));
-                                                                       }
-                                                                       // Now, calculate whether the user has access to creating new records on this position:
-                                                                       if (is_array($calcPRec)) {
-                                                                               // Permissions for the parent page
-                                                                               $CALC_PERMS = $beUser->calcPerms($calcPRec);
-                                                                               if ($table == 'pages') {
-                                                                                       // If pages:
-                                                                                       $hasAccess = $CALC_PERMS & Permission::PAGE_NEW ? 1 : 0;
-                                                                                       $this->viewId = 0;
-                                                                               } else {
-                                                                                       $hasAccess = $CALC_PERMS & Permission::CONTENT_EDIT ? 1 : 0;
-                                                                                       $this->viewId = $calcPRec['uid'];
-                                                                               }
-                                                                       }
-                                                               }
-                                                               // Don't save this document title in the document selector if the document is new.
+
+                                                       // Don't save this document title in the document selector if the document is new.
+                                                       if ($command === 'new') {
                                                                $this->dontStoreDocumentRef = 1;
-                                                       } else {
-                                                               // Edit:
-                                                               $calcPRec = BackendUtility::getRecord($table, $theUid);
-                                                               BackendUtility::fixVersioningPid($table, $calcPRec);
-                                                               if (is_array($calcPRec)) {
-                                                                       if ($table == 'pages') { // If pages:
-                                                                               $CALC_PERMS = $beUser->calcPerms($calcPRec);
-                                                                               $hasAccess = $CALC_PERMS & Permission::PAGE_EDIT ? 1 : 0;
-                                                                               $deleteAccess = $CALC_PERMS & Permission::PAGE_DELETE ? 1 : 0;
-                                                                               $this->viewId = $calcPRec['uid'];
-                                                                       } else {
-                                                                               // Fetching pid-record first
-                                                                               $CALC_PERMS = $beUser->calcPerms(BackendUtility::getRecord('pages', $calcPRec['pid']));
-                                                                               $hasAccess = $CALC_PERMS & Permission::CONTENT_EDIT ? 1 : 0;
-                                                                               $deleteAccess = $CALC_PERMS & Permission::CONTENT_EDIT ? 1 : 0;
-                                                                               $this->viewId = $calcPRec['pid'];
+                                                       }
+
+                                                       /** @var TcaDatabaseRecord $formDataGroup */
+                                                       $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+                                                       /** @var FormDataCompiler $formDataCompiler */
+                                                       $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+                                                       /** @var NodeFactory $nodeFactory */
+                                                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+
+                                                       try {
+                                                               // Reset viewId - it should hold data of last entry only
+                                                               $this->viewId = 0;
+                                                               $this->viewId_addParams = '';
+
+                                                               $formDataCompilerInput = [
+                                                                       'tableName' => $table,
+                                                                       'vanillaUid' => (int)$theUid,
+                                                                       'command' => $command,
+                                                                       'returnUrl' => $this->R_URI,
+                                                               ];
+                                                               $formData = $formDataCompiler->compile($formDataCompilerInput);
+
+                                                               // Set this->viewId if possible
+                                                               if ($command === 'new' && $table !== 'pages' && !empty($formData['parentPageRow']['uid'])) {
+                                                                       $this->viewId = $formData['parentPageRow']['uid'];
+                                                               } else {
+                                                                       if ($table == 'pages') {
+                                                                               $this->viewId = $formData['databaseRow']['uid'];
+                                                                       } elseif (!empty($formData['parentPageRow']['uid'])) {
+                                                                               $this->viewId = $formData['parentPageRow']['uid'];
                                                                                // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
-                                                                               if ($GLOBALS['TCA'][$table]['ctrl']['languageField'] && $calcPRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) {
-                                                                                       $this->viewId_addParams = '&L=' . $calcPRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
+                                                                               if (!empty($formData['vanillaTableTca']['ctrl']['languageField'])
+                                                                                       && is_array($formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']])
+                                                                                       && $formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']][0] > 0
+                                                                               ) {
+                                                                                       $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['vanillaTableTca']['ctrl']['languageField']][0];
                                                                                }
                                                                        }
-                                                                       // Check internals regarding access:
-                                                                       $isRootLevelRestrictionIgnored = BackendUtility::isRootLevelRestrictionIgnored($table);
-                                                                       if ($hasAccess || (int)$calcPRec['pid'] === 0 && $isRootLevelRestrictionIgnored) {
-                                                                               $hasAccess = $beUser->recordEditAccessInternals($table, $calcPRec);
-                                                                               $deniedAccessReason = $beUser->errorMsg;
-                                                                       }
-                                                               } else {
-                                                                       $hasAccess = 0;
                                                                }
-                                                       }
-                                                       if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'])) {
-                                                               foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] as $_funcRef) {
-                                                                       $_params = array(
-                                                                               'table' => $table,
-                                                                               'uid' => $theUid,
-                                                                               'cmd' => $cmd,
-                                                                               'hasAccess' => $hasAccess
-                                                                       );
-                                                                       $hasAccess = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
-                                                               }
-                                                       }
-                                                       // AT THIS POINT we have checked the access status of the editing/creation of
-                                                       // records and we can now proceed with creating the form elements:
-                                                       if ($hasAccess) {
-                                                               /** @var DataPreprocessor $trData */
-                                                               $prevPageID = is_object($trData) ? $trData->prevPageID : '';
-                                                               $trData = GeneralUtility::makeInstance(DataPreprocessor::class);
-                                                               $trData->addRawData = TRUE;
-                                                               $trData->defVals = $this->defVals;
-                                                               $trData->lockRecords = 1;
-                                                               $trData->prevPageID = $prevPageID;
-                                                               // 'new'
-                                                               $trData->fetchRecord($table, $theUid, $cmd == 'new' ? 'new' : '');
-                                                               $rec = reset($trData->regTableItems_data);
-                                                               $rec['uid'] = $cmd == 'new' ? uniqid('NEW', TRUE) : $theUid;
-                                                               if ($cmd == 'new') {
-                                                                       $rec['pid'] = $theUid == 'prev' ? $thePrevUid : $theUid;
-                                                               }
-                                                               $this->elementsData[] = array(
-                                                                       'table' => $table,
-                                                                       'uid' => $rec['uid'],
-                                                                       'pid' => $rec['pid'],
-                                                                       'cmd' => $cmd,
-                                                                       'deleteAccess' => $deleteAccess
-                                                               );
-                                                               // Now, render the form:
-                                                               if (is_array($rec)) {
-                                                                       // Setting visual path / title of form:
-                                                                       $this->generalPathOfForm = $this->tceforms->getRecordPath($table, $rec);
-                                                                       if (!$this->storeTitle) {
-                                                                               $this->storeTitle = $this->recTitle ? htmlspecialchars($this->recTitle) : BackendUtility::getRecordTitle($table, $rec, TRUE);
-                                                                       }
-                                                                       // Setting variables in TCEforms object:
-                                                                       if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
-                                                                               $this->tceforms->hiddenFieldListArr = array_keys($this->overrideVals[$table]);
-                                                                       }
-                                                                       // Create form for the record (either specific list of fields or the whole record):
-                                                                       $panel = '';
-                                                                       if ($this->columnsOnly) {
-                                                                               if (is_array($this->columnsOnly)) {
-                                                                                       $panel .= $this->tceforms->getListedFields($table, $rec, $this->columnsOnly[$table]);
-                                                                               } else {
-                                                                                       $panel .= $this->tceforms->getListedFields($table, $rec, $this->columnsOnly);
-                                                                               }
+
+                                                               // Determine if delete button can be shown
+                                                               $deleteAccess = FALSE;
+                                                               if ($command === 'edit') {
+                                                                       $permission = $formData['userPermissionOnPage'];
+                                                                       if ($formData['tableName'] === 'pages') {
+                                                                               $deleteAccess = $permission & Permission::PAGE_DELETE ? TRUE : FALSE;
                                                                        } else {
-                                                                               $panel .= $this->tceforms->getMainFields($table, $rec);
-                                                                       }
-                                                                       $panel = $this->tceforms->wrapTotal($panel, $rec, $table);
-                                                                       // Setting the pid value for new records:
-                                                                       if ($cmd == 'new') {
-                                                                               $panel .= '<input type="hidden" name="data[' . $table . '][' . $rec['uid'] . '][pid]" value="' . $rec['pid'] . '" />';
-                                                                               $this->newC++;
+                                                                               $deleteAccess = $permission & Permission::CONTENT_EDIT ? TRUE : FALSE;
                                                                        }
-                                                                       // Display "is-locked" message:
-                                                                       if ($lockInfo = BackendUtility::isRecordLocked($table, $rec['uid'])) {
+                                                               }
+
+                                                               // Display "is-locked" message:
+                                                               if ($command === 'edit') {
+                                                                       $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
+                                                                       if ($lockInfo) {
                                                                                /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
                                                                                $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING);
                                                                                /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
@@ -1089,15 +991,81 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
                                                                                $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
                                                                                $defaultFlashMessageQueue->enqueue($flashMessage);
                                                                        }
-                                                                       // Combine it all:
-                                                                       $editForm .= $panel;
                                                                }
-                                                               $thePrevUid = $rec['uid'];
-                                                       } else {
+
+                                                               // Record title
+                                                               if (!$this->storeTitle) {
+                                                                       $this->storeTitle = $this->recTitle
+                                                                               ? htmlspecialchars($this->recTitle)
+                                                                               : BackendUtility::getRecordTitle($table, $formData['databaseRow'], TRUE);
+                                                               }
+
+                                                               $this->elementsData[] = array(
+                                                                       'table' => $table,
+                                                                       'uid' => $formData['databaseRow']['uid'],
+                                                                       'pid' => $formData['databaseRow']['pid'],
+                                                                       'cmd' => $command,
+                                                                       'deleteAccess' => $deleteAccess
+                                                               );
+
+                                                               // Set additional FormData
+                                                               // @todo: This is a hack and should be done differently
+                                                               if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
+                                                                       $formData['hiddenFieldListArray'] = array_keys($this->overrideVals[$table]);
+                                                               }
+
+                                                               if ($command !== 'new') {
+                                                                       BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
+                                                               }
+
+                                                               // Set list if only specific fields should be rendered. This will trigger
+                                                               // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
+                                                               if ($this->columnsOnly) {
+                                                                       if (is_array($this->columnsOnly)) {
+                                                                               $formData['fieldListToRender'] = $this->columnsOnly[$table];
+                                                                       } else {
+                                                                               $formData['fieldListToRender'] = $this->columnsOnly;
+                                                                       }
+                                                               }
+
+                                                               $formData['renderType'] = 'outerWrapContainer';
+                                                               $formResult = $nodeFactory->create($formData)->render();
+
+                                                               $html = $formResult['html'];
+
+                                                               $formResult['html'] = '';
+                                                               $formResult['doSaveFieldName'] = 'doSave';
+
+                                                               // @todo: Put all the stuff into FormEngine as final "compiler" class
+                                                               // @todo: This is done here for now to not rewrite JStop()
+                                                               // @todo: and printNeededJSFunctions() now
+                                                               $this->formResultCompiler->mergeResult($formResult);
+
+                                                               // Seems the pid is set as hidden field (again) at end?!
+                                                               if ($command == 'new') {
+                                                                       // @todo: looks ugly
+                                                                       $html .= LF
+                                                                               . '<input type="hidden"'
+                                                                               . ' name="data[' . $table . '][' . $formData['databaseRow']['uid'] . '][pid]"'
+                                                                               . ' value="' . $formData['databaseRow']['pid'] . '" />';
+                                                                       $this->newC++;
+                                                               }
+
+                                                               $editForm .= $html;
+
+                                                       } catch (AccessDeniedException $e) {
                                                                $this->errorC++;
-                                                               $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', TRUE) . '<br /><br />' . ($deniedAccessReason ? 'Reason: ' . htmlspecialchars($deniedAccessReason) . '<br /><br />' : '');
+                                                               // Try to fetch error message from "recordInternals" be user object
+                                                               // @todo: This construct should be logged and localized and de-uglified
+                                                               $message = $beUser->errorMsg;
+                                                               if (empty($message)) {
+                                                                       // Create message from exception.
+                                                                       $message = $e->getMessage() . ' ' . $e->getCode();
+                                                               }
+                                                               $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', TRUE)
+                                                                       . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
                                                        }
-                                               }
+                                               } // End of for each uid
                                        }
                                }
                        }
@@ -1239,22 +1207,6 @@ class EditDocumentController implements \TYPO3\CMS\Core\Http\ControllerInterface
        }
 
        /**
-        * Compiles the extra form headers if the tceforms
-        *
-        * @return string The HTML
-        */
-       public function extraFormHeaders() {
-               /** @var MarkerBasedTemplateService $templateService */
-               $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
-               $extraTemplate = '';
-               if (is_array($this->tceforms->extraFormHeaders)) {
-                       $extraTemplate = $templateService->getSubpart($this->doc->moduleTemplate, '###DOCHEADER_EXTRAHEADER###');
-                       $extraTemplate = $templateService->substituteMarker($extraTemplate, '###EXTRAHEADER###', implode(LF, $this->tceforms->extraFormHeaders));
-               }
-               return $extraTemplate;
-       }
-
-       /**
         * Put together the various elements (buttons, selectors, form) into a table
         *
         * @param string $editForm HTML form.
index 0e520b5..62a47fa 100644 (file)
@@ -145,7 +145,6 @@ class CreateFolderController implements \TYPO3\CMS\Core\Http\ControllerInterface
                $this->content .= $this->doc->startPage($lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.pagetitle'));
                // Make page header:
                $pageContent = $this->doc->header($lang->sL('LLL:EXT:lang/locallang_core.xlf:file_newfolder.php.pagetitle'));
-
                if ($this->folderObject->checkActionPermission('add')) {
                        $code = '<form role="form" action="' . htmlspecialchars(BackendUtility::getModuleUrl('tce_file')) . '" method="post" name="editform">';
                        // Making the selector box for the number of concurrent folder-creations
diff --git a/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php
new file mode 100644 (file)
index 0000000..45d00a3
--- /dev/null
@@ -0,0 +1,589 @@
+<?php
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
+use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
+use TYPO3\CMS\Backend\Form\InlineStackProcessor;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Http\AjaxRequestHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * Handle FormEngine inline ajax calls
+ */
+class FormInlineAjaxController {
+
+       /**
+        * @var InlineStackProcessor
+        */
+       protected $inlineStackProcessor;
+
+       /**
+        * General processor for AJAX requests concerning IRRE.
+        *
+        * @param array $_ Additional parameters (not used here)
+        * @param AjaxRequestHandler $ajaxObj The AjaxRequestHandler object of this request
+        * @throws \RuntimeException
+        * @return void
+        */
+       public function processInlineAjaxRequest($_, AjaxRequestHandler $ajaxObj) {
+               $this->inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+               $ajaxArguments = GeneralUtility::_GP('ajax');
+               $ajaxIdParts = explode('::', $GLOBALS['ajaxID'], 2);
+               if (isset($ajaxArguments) && is_array($ajaxArguments) && !empty($ajaxArguments)) {
+                       $ajaxMethod = $ajaxIdParts[1];
+                       $ajaxObj->setContentFormat('jsonbody');
+                       // @todo: ajaxArguments[2] is "returnUrl" in the first 3 calls - still needed?
+                       switch ($ajaxMethod) {
+                               case 'synchronizeLocalizeRecords':
+                                       $domObjectId = $ajaxArguments[0];
+                                       $type = $ajaxArguments[1];
+                                       // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
+                                       $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
+                                       $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
+                                       $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
+                                       $ajaxObj->setContent($this->renderInlineSynchronizeLocalizeRecords($type, $inlineFirstPid));
+                                       break;
+                               case 'createNewRecord':
+                                       $domObjectId = $ajaxArguments[0];
+                                       $createAfterUid = 0;
+                                       if (isset($ajaxArguments[1])) {
+                                               $createAfterUid = $ajaxArguments[1];
+                                       }
+                                       // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
+                                       $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
+                                       $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
+                                       $ajaxObj->setContent($this->renderInlineNewChildRecord($domObjectId, $createAfterUid));
+                                       break;
+                               case 'getRecordDetails':
+                                       $domObjectId = $ajaxArguments[0];
+                                       // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
+                                       $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
+                                       $this->inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
+                                       $ajaxObj->setContent($this->renderInlineChildRecord($domObjectId));
+                                       break;
+                               case 'setExpandedCollapsedState':
+                                       $domObjectId = $ajaxArguments[0];
+                                       // Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
+                                       $this->inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId, FALSE);
+                                       $expand = $ajaxArguments[1];
+                                       $collapse = $ajaxArguments[2];
+                                       $this->setInlineExpandedCollapsedState($expand, $collapse);
+                                       break;
+                               default:
+                                       throw new \RuntimeException('Not a valid ajax identifier', 1428227862);
+                       }
+               }
+       }
+
+       /**
+        * Handle AJAX calls to dynamically load the form fields of a given inline record.
+        *
+        * @param string $domObjectId The calling object in hierarchy, that requested a new record.
+        * @return array An array to be used for JSON
+        */
+       protected function renderInlineChildRecord($domObjectId) {
+               // The current table - for this table we should add/import records
+               $current = $this->inlineStackProcessor->getUnstableStructure();
+               // The parent table - this table embeds the current table
+               $parent = $this->inlineStackProcessor->getStructureLevel(-1);
+               $config = $parent['config'];
+
+               if (empty($config['foreign_table']) || !is_array($GLOBALS['TCA'][$config['foreign_table']])) {
+                       return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
+               }
+
+               $config = FormEngineUtility::mergeInlineConfiguration($config);
+
+               // Set flag in config so that only the fields are rendered
+               $config['renderFieldsOnly'] = TRUE;
+               $collapseAll = isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll'];
+               $expandSingle = isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle'];
+
+               $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
+               $record = $inlineRelatedRecordResolver->getRecord($current['table'], $current['uid']);
+
+               $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
+               // The HTML-object-id's prefix of the dynamically created record
+               $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $current['table'];
+               $objectId = $objectPrefix . '-' . $record['uid'];
+
+               $formDataInput = [];
+               $formDataInput['vanillaUid'] = (int)$parent['uid'];
+               $formDataInput['command'] = 'edit';
+               $formDataInput['tableName'] = $parent['table'];
+               $formDataInput['inlineFirstPid'] = $inlineFirstPid;
+               $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
+
+               /** @var TcaDatabaseRecord $formDataGroup */
+               $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+               /** @var FormDataCompiler $formDataCompiler */
+               $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+
+               $formData = $formDataCompiler->compile($formDataInput);
+               $formData['renderType'] = 'inlineRecordContainer';
+               $formData['inlineRelatedRecordToRender'] = $record;
+               $formData['inlineRelatedRecordConfig'] = $config;
+
+               try {
+                       // Access to this record may be denied, create an according error message in this case
+                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+                       $childArray = $nodeFactory->create($formData)->render();
+               } catch (AccessDeniedException $e) {
+                       return $this->getErrorMessageForAJAX('Access denied');
+               }
+
+               $jsonArray = [
+                       'data' => '',
+                       'stylesheetFiles' => [],
+                       'scriptCall' => [],
+               ];
+               $jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
+               if ($config['foreign_unique']) {
+                       $jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
+               }
+               if (!empty($childArray['inlineData'])) {
+                       $jsonArray['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childArray['inlineData']) . ');';
+               }
+               $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
+               if ($config['appearance']['useSortable']) {
+                       $inlineObjectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+                       $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
+               }
+               if (!$collapseAll && $expandSingle) {
+                       $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
+               }
+
+               return $jsonArray;
+       }
+
+       /**
+        * Handle AJAX calls to show a new inline-record of the given table.
+        *
+        * @param string $domObjectId The calling object in hierarchy, that requested a new record.
+        * @param string|int $foreignUid If set, the new record should be inserted after that one.
+        * @return array An array to be used for JSON
+        */
+       protected function renderInlineNewChildRecord($domObjectId, $foreignUid) {
+               // The current table - for this table we should add/import records
+               $current = $this->inlineStackProcessor->getUnstableStructure();
+               // The parent table - this table embeds the current table
+               $parent = $this->inlineStackProcessor->getStructureLevel(-1);
+               $config = $parent['config'];
+
+               if (empty($config['foreign_table']) || !is_array($GLOBALS['TCA'][$config['foreign_table']])) {
+                       return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
+               }
+
+               /** @var InlineRelatedRecordResolver $inlineRelatedRecordResolver */
+               $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
+
+               $config = FormEngineUtility::mergeInlineConfiguration($config);
+
+               $collapseAll = isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll'];
+               $expandSingle = isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle'];
+
+               $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId);
+
+               // Dynamically create a new record
+               if (!$foreignUid || !MathUtility::canBeInterpretedAsInteger($foreignUid) || $config['foreign_selector']) {
+                       $record = $inlineRelatedRecordResolver->getNewRecord($inlineFirstPid, $current['table']);
+                       // Set default values for new created records
+                       if (isset($config['foreign_record_defaults']) && is_array($config['foreign_record_defaults'])) {
+                               $foreignTableConfig = $GLOBALS['TCA'][$current['table']];
+                               // The following system relevant fields can't be set by foreign_record_defaults
+                               $notSettableFields = array(
+                                       'uid', 'pid', 't3ver_oid', 't3ver_id', 't3ver_label', 't3ver_wsid', 't3ver_state', 't3ver_stage',
+                                       't3ver_count', 't3ver_tstamp', 't3ver_move_id'
+                               );
+                               $configurationKeysForNotSettableFields = array(
+                                       'crdate', 'cruser_id', 'delete', 'origUid', 'transOrigDiffSourceField', 'transOrigPointerField',
+                                       'tstamp'
+                               );
+                               foreach ($configurationKeysForNotSettableFields as $configurationKey) {
+                                       if (isset($foreignTableConfig['ctrl'][$configurationKey])) {
+                                               $notSettableFields[] = $foreignTableConfig['ctrl'][$configurationKey];
+                                       }
+                               }
+                               foreach ($config['foreign_record_defaults'] as $fieldName => $defaultValue) {
+                                       if (isset($foreignTableConfig['columns'][$fieldName]) && !in_array($fieldName, $notSettableFields)) {
+                                               $record[$fieldName] = $defaultValue;
+                                       }
+                               }
+                       }
+                       // Set language of new child record to the language of the parent record:
+                       if ($parent['localizationMode'] === 'select') {
+                               $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']);
+                               $parentLanguageField = $GLOBALS['TCA'][$parent['table']]['ctrl']['languageField'];
+                               $childLanguageField = $GLOBALS['TCA'][$current['table']]['ctrl']['languageField'];
+                               if ($parentRecord[$parentLanguageField] > 0) {
+                                       $record[$childLanguageField] = $parentRecord[$parentLanguageField];
+                               }
+                       }
+               } else {
+                       // @todo: Check this: Else also hits if $foreignUid = 0?
+                       $record = $inlineRelatedRecordResolver->getRecord($current['table'], $foreignUid);
+               }
+               // Now there is a foreign_selector, so there is a new record on the intermediate table, but
+               // this intermediate table holds a field, which is responsible for the foreign_selector, so
+               // we have to set this field to the uid we get - or if none, to a new uid
+               if ($config['foreign_selector'] && $foreignUid) {
+                       $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_selector']);
+                       // For a selector of type group/db, prepend the tablename (<tablename>_<uid>):
+                       $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'] . '_';
+                       $record[$config['foreign_selector']] .= $foreignUid;
+                       if ($selConfig['table'] === 'sys_file') {
+                               $fileRecord = $inlineRelatedRecordResolver->getRecord($selConfig['table'], $foreignUid);
+                               if ($fileRecord !== FALSE && !$this->checkInlineFileTypeAccessForField($selConfig, $fileRecord)) {
+                                       return $this->getErrorMessageForAJAX('File extension ' . $fileRecord['extension'] . ' is not allowed here!');
+                               }
+                       }
+               }
+               // The HTML-object-id's prefix of the dynamically created record
+               $objectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+               $objectPrefix = $objectName . '-' . $current['table'];
+               $objectId = $objectPrefix . '-' . $record['uid'];
+
+               $formDataInput = [];
+               $formDataInput['vanillaUid'] = (int)$parent['uid'];
+               $formDataInput['command'] = 'edit';
+               $formDataInput['tableName'] = $parent['table'];
+               $formDataInput['inlineFirstPid'] = $inlineFirstPid;
+               $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
+
+               if (!MathUtility::canBeInterpretedAsInteger($parent['uid']) && (int)$formDataInput['inlineFirstPid'] > 0) {
+                       $formDataInput['command'] = 'new';
+                       $formDataInput['vanillaUid'] = (int)$formDataInput['inlineFirstPid'];
+               }
+
+               /** @var TcaDatabaseRecord $formDataGroup */
+               $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+               /** @var FormDataCompiler $formDataCompiler */
+               $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+
+               $formData = $formDataCompiler->compile($formDataInput);
+               $formData['renderType'] = 'inlineRecordContainer';
+               $formData['inlineRelatedRecordToRender'] = $record;
+               $formData['inlineRelatedRecordConfig'] = $config;
+
+               try {
+                       // Access to this record may be denied, create an according error message in this case
+                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+                       $childArray = $nodeFactory->create($formData)->render();
+               } catch (AccessDeniedException $e) {
+                       return $this->getErrorMessageForAJAX('Access denied');
+               }
+
+               $jsonArray = [
+                       'data' => '',
+                       'stylesheetFiles' => [],
+                       'scriptCall' => [],
+               ];
+               if (!$current['uid']) {
+                       $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
+                       $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',null,' . GeneralUtility::quoteJSvalue($foreignUid) . ');';
+               } else {
+                       $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
+                       $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',' . GeneralUtility::quoteJSvalue($current['uid']) . ',' . GeneralUtility::quoteJSvalue($foreignUid) . ');';
+               }
+               $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
+               if ($config['appearance']['useSortable']) {
+                       $inlineObjectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+                       $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
+               }
+               if (!$collapseAll && $expandSingle) {
+                       $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ');';
+               }
+               // Fade out and fade in the new record in the browser view to catch the user's eye
+               $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
+
+               return $jsonArray;
+       }
+
+       /**
+        * Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent.
+        *
+        * @param string $type Defines the type 'localize' or 'synchronize' (string) or a single uid to be localized (int)
+        * @param int $inlineFirstPid Inline first pid
+        * @return array An array to be used for JSON
+        */
+       protected function renderInlineSynchronizeLocalizeRecords($type, $inlineFirstPid) {
+               $jsonArray = FALSE;
+               if (GeneralUtility::inList('localize,synchronize', $type) || MathUtility::canBeInterpretedAsInteger($type)) {
+                       $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
+                       // The parent level:
+                       $parent = $this->inlineStackProcessor->getStructureLevel(-1);
+                       $current = $this->inlineStackProcessor->getUnstableStructure();
+                       $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']);
+
+                       $cmd = array();
+                       $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type;
+                       /** @var $tce DataHandler */
+                       $tce = GeneralUtility::makeInstance(DataHandler::class);
+                       $tce->stripslashes_values = FALSE;
+                       $tce->start(array(), $cmd);
+                       $tce->process_cmdmap();
+
+                       $oldItemList = $parentRecord[$parent['field']];
+                       $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parent['field']];
+
+                       $jsonArray = array(
+                               'data' => '',
+                               'stylesheetFiles' => [],
+                               'scriptCall' => [],
+                       );
+                       $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+                       $nameObjectForeignTable = $nameObject . '-' . $current['table'];
+                       // Get the name of the field pointing to the original record:
+                       $transOrigPointerField = $GLOBALS['TCA'][$current['table']]['ctrl']['transOrigPointerField'];
+                       // Get the name of the field used as foreign selector (if any):
+                       $foreignSelector = isset($parent['config']['foreign_selector']) && $parent['config']['foreign_selector'] ? $parent['config']['foreign_selector'] : FALSE;
+                       // Convert lists to array with uids of child records:
+                       $oldItems = FormEngineUtility::getInlineRelatedRecordsUidArray($oldItemList);
+                       $newItems = FormEngineUtility::getInlineRelatedRecordsUidArray($newItemList);
+                       // Determine the items that were localized or localized:
+                       $removedItems = array_diff($oldItems, $newItems);
+                       $localizedItems = array_diff($newItems, $oldItems);
+                       // Set the items that should be removed in the forms view:
+                       foreach ($removedItems as $item) {
+                               $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $item) . ', {forceDirectRemoval: true});';
+                       }
+                       foreach ($localizedItems as $item) {
+                               $row = $inlineRelatedRecordResolver->getRecord($current['table'], $item);
+                               $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($row[$foreignSelector]) : 'null';
+
+                               $formDataInput = [];
+                               $formDataInput['vanillaUid'] = (int)$parent['uid'];
+                               $formDataInput['command'] = 'edit';
+                               $formDataInput['tableName'] = $parent['table'];
+                               $formDataInput['inlineFirstPid'] = $inlineFirstPid;
+                               $formDataInput['inlineStructure'] = $this->inlineStackProcessor->getStructure();
+
+                               /** @var TcaDatabaseRecord $formDataGroup */
+                               $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+                               /** @var FormDataCompiler $formDataCompiler */
+                               $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+
+                               $formData = $formDataCompiler->compile($formDataInput);
+                               $formData['renderType'] = 'inlineRecordContainer';
+                               $formData['inlineRelatedRecordToRender'] = $row;
+                               $formData['inlineRelatedRecordConfig'] = $parent['config'];
+
+                               try {
+                                       // Access to this record may be denied, create an according error message in this case
+                                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+                                       $childArray = $nodeFactory->create($formData)->render();
+                               } catch (AccessDeniedException $e) {
+                                       return $this->getErrorMessageForAJAX('Access denied');
+                               }
+
+                               $jsonArray['html'] .= $childArray['html'];
+                               $jsonArray = [
+                                       'data' => '',
+                                       'stylesheetFiles' => [],
+                                       'scriptCall' => [],
+                               ];
+                               $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childArray);
+                               $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($item) . ', null, ' . $selectedValue . ');';
+                               // Remove possible virtual records in the form which showed that a child records could be localized:
+                               if (isset($row[$transOrigPointerField]) && $row[$transOrigPointerField]) {
+                                       $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $row[$transOrigPointerField] . '_div') . ');';
+                               }
+                       }
+                       if (!empty($jsonArray['data'])) {
+                               array_unshift($jsonArray['scriptCall'], 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records') . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', json.data);');
+                       }
+               }
+               return $jsonArray;
+       }
+
+       /**
+        * Merge stuff from child array into json array.
+        * This method is needed since ajax handling methods currently need to put scriptCalls before and after child code.
+        *
+        * @param array $jsonResult Given json result
+        * @param array $childResult Given child result
+        * @return array Merged json array
+        */
+       protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult) {
+               $jsonResult['data'] = $childResult['html'];
+               $jsonResult['stylesheetFiles'] = $childResult['stylesheetFiles'];
+               if (!empty($childResult['inlineData'])) {
+                       $jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
+               }
+               if (!empty($childResult['additionalJavaScriptSubmit'])) {
+                       $additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
+                       $additionalJavaScriptSubmit = str_replace(array(CR, LF), '', $additionalJavaScriptSubmit);
+                       $jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
+               }
+               foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
+                       $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
+               }
+               $jsonResult['scriptCall'][] = $childResult['extJSCODE'];
+               foreach ($childResult['requireJsModules'] as $moduleName => $callbacks) {
+                       if (!is_array($callbacks)) {
+                               $callbacks = array($callbacks);
+                       }
+                       foreach ($callbacks as $callback) {
+                               $inlineCodeKey = $moduleName;
+                               $javaScriptCode = 'require(["' . $moduleName . '"]';
+                               if ($callback !== NULL) {
+                                       $inlineCodeKey .= sha1($callback);
+                                       $javaScriptCode .= ', ' . $callback;
+                               }
+                               $javaScriptCode .= ');';
+                               $jsonResult['scriptCall'][] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
+                       }
+               }
+               return $jsonResult;
+       }
+
+       /**
+        * Save the expanded/collapsed state of a child record in the BE_USER->uc.
+        *
+        * @param string $expand Whether this record is expanded.
+        * @param string $collapse Whether this record is collapsed.
+        * @return void
+        */
+       protected function setInlineExpandedCollapsedState($expand, $collapse) {
+               $backendUser = $this->getBackendUserAuthentication();
+               // The current table - for this table we should add/import records
+               $currentTable = $this->inlineStackProcessor->getUnstableStructure();
+               $currentTable = $currentTable['table'];
+               // The top parent table - this table embeds the current table
+               $top = $this->inlineStackProcessor->getStructureLevel(0);
+               $topTable = $top['table'];
+               $topUid = $top['uid'];
+               $inlineView = $this->getInlineExpandCollapseStateArray();
+               // Only do some action if the top record and the current record were saved before
+               if (MathUtility::canBeInterpretedAsInteger($topUid)) {
+                       $expandUids = GeneralUtility::trimExplode(',', $expand);
+                       $collapseUids = GeneralUtility::trimExplode(',', $collapse);
+                       // Set records to be expanded
+                       foreach ($expandUids as $uid) {
+                               $inlineView[$topTable][$topUid][$currentTable][] = $uid;
+                       }
+                       // Set records to be collapsed
+                       foreach ($collapseUids as $uid) {
+                               $inlineView[$topTable][$topUid][$currentTable] = $this->removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
+                       }
+                       // Save states back to database
+                       if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
+                               $inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
+                               $backendUser->uc['inlineView'] = serialize($inlineView);
+                               $backendUser->writeUC();
+                       }
+               }
+       }
+
+       /**
+        * Checks if a record selector may select a certain file type
+        *
+        * @param array $selectorConfiguration
+        * @param array $fileRecord
+        * @return bool
+        */
+       protected function checkInlineFileTypeAccessForField(array $selectorConfiguration, array $fileRecord) {
+               if (!empty($selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'])) {
+                       $allowedFileExtensions = GeneralUtility::trimExplode(
+                               ',',
+                               $selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'],
+                               TRUE
+                       );
+                       if (!in_array(strtolower($fileRecord['extension']), $allowedFileExtensions, TRUE)) {
+                               return FALSE;
+                       }
+               }
+               return TRUE;
+       }
+
+       /**
+        * Return expand / collapse state array for a given table / uid combination
+        *
+        * @param string $table Handled table
+        * @param int $uid Handled uid
+        * @return array
+        */
+       protected function getInlineExpandCollapseStateArrayForTableUid($table, $uid) {
+               $inlineView = $this->getInlineExpandCollapseStateArray();
+               $result = array();
+               if (MathUtility::canBeInterpretedAsInteger($uid)) {
+                       if (!empty($inlineView[$table][$uid])) {
+                               $result = $inlineView[$table][$uid];
+                       }
+               }
+               return $result;
+       }
+
+       /**
+        * Get expand / collapse state of inline items
+        *
+        * @return array
+        */
+       protected function getInlineExpandCollapseStateArray() {
+               $backendUser = $this->getBackendUserAuthentication();
+               $inlineView = unserialize($backendUser->uc['inlineView']);
+               if (!is_array($inlineView)) {
+                       $inlineView = array();
+               }
+               return $inlineView;
+       }
+
+       /**
+        * Remove an element from an array.
+        *
+        * @param mixed $needle The element to be removed.
+        * @param array $haystack The array the element should be removed from.
+        * @param mixed $strict Search elements strictly.
+        * @return array The array $haystack without the $needle
+        */
+       protected function removeFromArray($needle, $haystack, $strict = NULL) {
+               $pos = array_search($needle, $haystack, $strict);
+               if ($pos !== FALSE) {
+                       unset($haystack[$pos]);
+               }
+               return $haystack;
+       }
+
+       /**
+        * Generates an error message that transferred as JSON for AJAX calls
+        *
+        * @param string $message The error message to be shown
+        * @return array The error message in a JSON array
+        */
+       protected function getErrorMessageForAJAX($message) {
+               return [
+                       'data' => $message,
+                       'scriptCall' => [
+                               'alert("' . $message . '");'
+                       ],
+               ];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUserAuthentication() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
index 80b2f88..abee3ce 100755 (executable)
@@ -14,6 +14,11 @@ namespace TYPO3\CMS\Backend\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
+use TYPO3\CMS\Backend\Form\FormResultCompiler;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -21,7 +26,6 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -33,8 +37,6 @@ use TYPO3\CMS\Frontend\Page\PageRepository;
 use TYPO3\CMS\Backend\Module\ModuleLoader;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\View\BackendLayoutView;
-use TYPO3\CMS\Backend\Form\DataPreprocessor;
-use TYPO3\CMS\Backend\Form\FormEngine;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Backend\View\PageLayoutView;
 use TYPO3\CMS\Backend\Tree\View\ContentLayoutPagePositionMap;
@@ -816,11 +818,12 @@ class PageLayoutController {
                }
                // Splitting the edit-record cmd value into table/uid:
                $this->eRParts = explode(':', $edit_record);
+               $tableName = $this->eRParts[0];
                // Delete-button flag?
-               $this->deleteButton = MathUtility::canBeInterpretedAsInteger($this->eRParts[1]) && $edit_record && ($this->eRParts[0] != 'pages' && $this->EDIT_CONTENT || $this->eRParts[0] == 'pages' && $this->CALC_PERMS & Permission::PAGE_DELETE);
+               $this->deleteButton = MathUtility::canBeInterpretedAsInteger($this->eRParts[1]) && $edit_record && ($tableName !== 'pages' && $this->EDIT_CONTENT || $tableName === 'pages' && $this->CALC_PERMS & Permission::PAGE_DELETE);
                // If undo-button should be rendered (depends on available items in sys_history)
                $this->undoButton = FALSE;
-               $undoRes = $databaseConnection->exec_SELECTquery('tstamp', 'sys_history', 'tablename=' . $databaseConnection->fullQuoteStr($this->eRParts[0], 'sys_history') . ' AND recuid=' . (int)$this->eRParts[1], '', 'tstamp DESC', '1');
+               $undoRes = $databaseConnection->exec_SELECTquery('tstamp', 'sys_history', 'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_history') . ' AND recuid=' . (int)$this->eRParts[1], '', 'tstamp DESC', '1');
                if ($this->undoButtonR = $databaseConnection->sql_fetch_assoc($undoRes)) {
                        $this->undoButton = TRUE;
                }
@@ -843,58 +846,77 @@ class PageLayoutController {
                $this->editSelect = '<select name="edit_record" onchange="' . htmlspecialchars('jumpToUrl(' . GeneralUtility::quoteJSvalue(
                        BackendUtility::getModuleUrl('web_layout') . '&id=' . $this->id . '&edit_record='
                ) . '+escape(this.options[this.selectedIndex].value)' . $retUrlStr . ',this);') . '">' . implode('', $opt) . '</select>';
-               $content = '';
+
                // Creating editing form:
-               if ($beUser->check('tables_modify', $this->eRParts[0]) && $edit_record && ($this->eRParts[0] !== 'pages' && $this->EDIT_CONTENT || $this->eRParts[0] === 'pages' && $this->CALC_PERMS & Permission::PAGE_SHOW)) {
+               $content = '';
+
+               if ($edit_record) {
                        // Splitting uid parts for special features, if new:
-                       list($uidVal, $ex_pid, $ex_colPos) = explode('/', $this->eRParts[1]);
-                       // Convert $uidVal to workspace version if any:
-                       if ($uidVal != 'new') {
-                               if ($draftRecord = BackendUtility::getWorkspaceVersionOfRecord($beUser->workspace, $this->eRParts[0], $uidVal, 'uid')) {
-                                       $uidVal = $draftRecord['uid'];
+                       list($uidVal, $neighborRecordUid, $ex_colPos) = explode('/', $this->eRParts[1]);
+
+                       if ($uidVal === 'new') {
+                               $command = 'new';
+                               // Page id of this new record
+                               $theUid = $this->id;
+                               if ($neighborRecordUid) {
+                                       $theUid = $neighborRecordUid;
+                               }
+                       } else {
+                               $command = 'edit';
+                               $theUid = $uidVal;
+                               // Convert $uidVal to workspace version if any:
+                               $draftRecord = BackendUtility::getWorkspaceVersionOfRecord($beUser->workspace, $tableName, $theUid, 'uid');
+                               if ($draftRecord) {
+                                       $theUid = $draftRecord['uid'];
                                }
                        }
-                       // Initializing transfer-data object:
-                       $trData = GeneralUtility::makeInstance(DataPreprocessor::class);
-                       $trData->addRawData = TRUE;
-                       $trData->defVals[$this->eRParts[0]] = array(
+
+                       // @todo: Hack because DatabaseInitializeNewRow reads from _GP directly
+                       $GLOBALS['_GET']['defVals'][$tableName] = array(
                                'colPos' => (int)$ex_colPos,
                                'sys_language_uid' => (int)$this->current_sys_language
                        );
-                       $trData->lockRecords = 1;
-                       // 'new'
-                       $trData->fetchRecord($this->eRParts[0], $uidVal == 'new' ? $this->id : $uidVal, $uidVal);
-                       $new_unique_uid = '';
-                       // Getting/Making the record:
-                       reset($trData->regTableItems_data);
-                       $rec = current($trData->regTableItems_data);
-                       if ($uidVal == 'new') {
-                               $new_unique_uid = uniqid('NEW', TRUE);
-                               $rec['uid'] = $new_unique_uid;
-                               $rec['pid'] = (int)$ex_pid ?: $this->id;
-                               $recordAccess = TRUE;
-                       } else {
-                               $rec['uid'] = $uidVal;
-                               // Checking internals access:
-                               $recordAccess = $beUser->recordEditAccessInternals($this->eRParts[0], $uidVal);
-                       }
-                       if (!$recordAccess) {
-                               // If no edit access, print error message:
-                               $content = $this->doc->section($lang->getLL('noAccess'), $lang->getLL('noAccess_msg') . '<br /><br />' . ($beUser->errorMsg ? 'Reason: ' . $beUser->errorMsg . '<br /><br />' : ''), 0, 1);
-                       } elseif (is_array($rec)) {
-                               // If the record is an array (which it will always be... :-)
-                               // Create instance of TCEforms, setting defaults:
-                               $tceForms = GeneralUtility::makeInstance(FormEngine::class);
-                               // Render form, wrap it:
-                               $panel = '';
-                               $panel .= $tceForms->getMainFields($this->eRParts[0], $rec);
-                               $panel = $tceForms->wrapTotal($panel, $rec, $this->eRParts[0]);
+
+                       /** @var TcaDatabaseRecord $formDataGroup */
+                       $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+                       /** @var FormDataCompiler $formDataCompiler */
+                       $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+
+                       try {
+                               $formDataCompilerInput = [
+                                       'tableName' => $tableName,
+                                       'vanillaUid' => (int)$theUid,
+                                       'command' => $command,
+                               ];
+                               $formData = $formDataCompiler->compile($formDataCompilerInput);
+
+                               if ($command !== 'new') {
+                                       BackendUtility::lockRecords($tableName, $formData['databaseRow']['uid'], $tableName === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
+                               }
+
+                               $formData['renderType'] = 'outerWrapContainer';
+                               $formResult = $nodeFactory->create($formData)->render();
+
+                               $panel = $formResult['html'];
+                               $formResult['html'] = '';
+
+                               /** @var FormResultCompiler $formResultCompiler */
+                               $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
+                               $formResultCompiler->mergeResult($formResult);
+
+                               $row = $formData['databaseRow'];
+                               $new_unique_uid = '';
+                               if ($command === 'new') {
+                                       $new_unique_uid = $row['uid'];
+                               }
+
                                // Add hidden fields:
-                               $theCode = $panel;
                                if ($uidVal == 'new') {
-                                       $theCode .= '<input type="hidden" name="data[' . $this->eRParts[0] . '][' . $rec['uid'] . '][pid]" value="' . $rec['pid'] . '" />';
+                                       $panel .= '<input type="hidden" name="data[' . $tableName . '][' . $row['uid'] . '][pid]" value="' . $row['pid'] . '" />';
                                }
-                               $theCode .= '
+                               $panel .= '
                                        <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
                                        <input type="hidden" name="edit_record" value="' . $edit_record . '" />
                                        <input type="hidden" name="redirect" value="' . htmlspecialchars(($uidVal == 'new' ? BackendUtility::getModuleUrl(
@@ -907,24 +929,34 @@ class PageLayoutController {
                                        ) : $this->R_URI)) . '" />
                                        ';
                                // Add JavaScript as needed around the form:
-                               $theCode = $tceForms->printNeededJSFunctions_top() . $theCode . $tceForms->printNeededJSFunctions();
-                               // Add warning sign if record was "locked":
-                               if ($lockInfo = BackendUtility::isRecordLocked($this->eRParts[0], $rec['uid'])) {
-                                       /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
-                                       $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING);
-                                       /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-                                       $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
-                                       /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
-                                       $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
-                                       $defaultFlashMessageQueue->enqueue($flashMessage);
+                               $panel = $formResultCompiler->JStop() . $panel . $formResultCompiler->printNeededJSFunctions();
+                               $content = $this->doc->section('', $panel);
+
+                               // Display "is-locked" message:
+                               if ($command === 'edit') {
+                                       $lockInfo = BackendUtility::isRecordLocked($tableName, $formData['databaseRow']['uid']);
+                                       if ($lockInfo) {
+                                               /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
+                                               $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING);
+                                               /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
+                                               $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                                               /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
+                                               $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                                               $defaultFlashMessageQueue->enqueue($flashMessage);
+                                       }
                                }
-                               // Add whole form as a document section:
-                               $content = $this->doc->section('', $theCode);
+                       } catch (AccessDeniedException $e) {
+                               // If no edit access, print error message:
+                               $content = $this->doc->section($lang->getLL('noAccess'), $lang->getLL('noAccess_msg')
+                                       . '<br /><br />'
+                                       . ($beUser->errorMsg ? 'Reason: ' . $beUser->errorMsg . '<br /><br />' : ''), 0, 1
+                               );
                        }
                } else {
                        // If no edit access, print error message:
                        $content = $this->doc->section($lang->getLL('noAccess'), $lang->getLL('noAccess_msg') . '<br /><br />', 0, 1);
                }
+
                // Bottom controls (function menus):
                $q_count = $this->getNumberOfHiddenElements();
                if ($q_count) {
@@ -940,7 +972,7 @@ class PageLayoutController {
                }
 
                // Select element matrix:
-               if ($this->eRParts[0] == 'tt_content' && MathUtility::canBeInterpretedAsInteger($this->eRParts[1])) {
+               if ($tableName === 'tt_content' && MathUtility::canBeInterpretedAsInteger($this->eRParts[1])) {
                        $posMap = GeneralUtility::makeInstance(ContentLayoutPagePositionMap::class);
                        $posMap->cur_sys_language = $this->current_sys_language;
                        $HTMLcode = '';
@@ -951,6 +983,7 @@ class PageLayoutController {
                        $content .= $this->doc->section($lang->getLL('CEonThisPage'), $HTMLcode, 0, 1);
                        $content .= $this->doc->spacer(20);
                }
+
                return $content;
        }
 
index 3dce516..a2fe021 100644 (file)
@@ -16,8 +16,10 @@ namespace TYPO3\CMS\Backend\Controller\Wizard;
 
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Message\ResponseInterface;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
+use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEditRow;
 use TYPO3\CMS\Core\Http\Response;
-use TYPO3\CMS\Backend\Form\DataPreprocessor;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
@@ -27,6 +29,7 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 
 /**
  * Script Class for adding new items to a group/select field. Performs proper redirection as needed.
+ * Script is typically called after new child record was added and then adds the new child to select value of parent.
  */
 class AddController extends AbstractWizardController implements \TYPO3\CMS\Core\Http\ControllerInterface {
 
@@ -164,14 +167,25 @@ class AddController extends AbstractWizardController implements \TYPO3\CMS\Core\
        public function main() {
                if ($this->returnEditConf) {
                        if ($this->processDataFlag) {
-                               // Preparing the data of the parent record...:
-                               /** @var DataPreprocessor $dataPreprocessor */
-                               $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
-                               // 'new'
-                               $dataPreprocessor->fetchRecord($this->P['table'], $this->P['uid'], '');
-                               $current = reset($dataPreprocessor->regTableItems_data);
+
+                               // This data processing is done here to basically just get the current record. It can be discussed
+                               // if this isn't overkill here. In case this construct does not work out well, it would be less
+                               // overhead to just BackendUtility::fetchRecord the current parent here.
+                               /** @var OnTheFly $formDataGroup */
+                               $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
+                               $formDataGroup->setProviderList([ DatabaseEditRow::class ]);
+                               /** @var FormDataCompiler $formDataCompiler */
+                               $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+                               $input = [
+                                       'tableName' => $this->P['table'],
+                                       'vanillaUid' => (int)$this->P['uid'],
+                                       'command' => 'edit',
+                               ];
+                               $result = $formDataCompiler->compile($input);
+                               $currentParentRow = $result['databaseRow'];
+
                                // If that record was found (should absolutely be...), then init DataHandler and set, prepend or append the record
-                               if (is_array($current)) {
+                               if (is_array($currentParentRow)) {
                                        /** @var DataHandler $dataHandler */
                                        $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
                                        $dataHandler->stripslashes_values = FALSE;
@@ -181,7 +195,7 @@ class AddController extends AbstractWizardController implements \TYPO3\CMS\Core\
                                        // If the field is a flexForm field, work with the XML structure instead:
                                        if ($this->P['flexFormPath']) {
                                                // Current value of flexForm path:
-                                               $currentFlexFormData = GeneralUtility::xml2array($current[$this->P['field']]);
+                                               $currentFlexFormData = GeneralUtility::xml2array($currentParentRow[$this->P['field']]);
                                                /** @var FlexFormTools $flexFormTools */
                                                $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                                                $currentFlexFormValue = $flexFormTools->getArrayValueByPath($this->P['flexFormPath'], $currentFlexFormData);
@@ -206,10 +220,10 @@ class AddController extends AbstractWizardController implements \TYPO3\CMS\Core\
                                                                $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $recordId;
                                                                break;
                                                        case 'prepend':
-                                                               $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $current[$this->P['field']] . ',' . $recordId;
+                                                               $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentParentRow[$this->P['field']] . ',' . $recordId;
                                                                break;
                                                        case 'append':
-                                                               $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $recordId . ',' . $current[$this->P['field']];
+                                                               $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $recordId . ',' . $currentParentRow[$this->P['field']];
                                                                break;
                                                }
                                                $data[$this->P['table']][$this->P['uid']][$this->P['field']] = implode(',', GeneralUtility::trimExplode(',', $data[$this->P['table']][$this->P['uid']][$this->P['field']], TRUE));
index d292db6..b5eaf65 100644 (file)
@@ -16,6 +16,10 @@ namespace TYPO3\CMS\Backend\Controller\Wizard;
 
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Message\ResponseInterface;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
+use TYPO3\CMS\Backend\Form\FormResultCompiler;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Backend\Form\DataPreprocessor;
 use TYPO3\CMS\Backend\Form\FormEngine;
@@ -139,66 +143,73 @@ class RteController extends AbstractWizardController implements \TYPO3\CMS\Core\
                }
                // If all parameters are available:
                if ($this->P['table'] && $this->P['field'] && $this->P['uid'] && $this->checkEditAccess($this->P['table'], $this->P['uid'])) {
-                       // Getting the raw record (we need only the pid-value from here...)
-                       $rawRecord = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
-                       BackendUtility::fixVersioningPid($this->P['table'], $rawRecord);
+                       /** @var TcaDatabaseRecord $formDataGroup */
+                       $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+                       /** @var FormDataCompiler $formDataCompiler */
+                       $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+                       /** @var NodeFactory $nodeFactory */
+                       $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+
+                       $formDataCompilerInput = [
+                               'vanillaUid' => (int)$this->P['uid'],
+                               'tableName' => $this->P['table'],
+                               'command' => 'edit',
+                               'disabledWizards' => TRUE,
+                       ];
+
+                       $formData = $formDataCompiler->compile($formDataCompilerInput);
+
+                       $formData['fieldListToRender'] = $this->P['field'];
+                       $formData['renderType'] = 'outerWrapContainer';
+                       $formResult = $nodeFactory->create($formData)->render();
+
+                       /** @var FormResultCompiler $formResultCompiler */
+                       $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
+                       $formResultCompiler->mergeResult($formResult);
 
                        // override the default jumpToUrl
                        $this->doc->JScodeArray['jumpToUrl'] = '
-               function jumpToUrl(URL,formEl) {
-                       if (document.editform) {
-                               if (!TBE_EDITOR.isFormChanged()) {
-                                       window.location.href = URL;
-                               } else if (formEl) {
-                                       if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1;
+                               function jumpToUrl(URL,formEl) {
+                                       if (document.editform) {
+                                               if (!TBE_EDITOR.isFormChanged()) {
+                                                       window.location.href = URL;
+                                               } else if (formEl) {
+                                                       if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1;
+                                               }
+                                       } else {
+                                               window.location.href = URL;
+                                       }
                                }
-                       } else {
-                               window.location.href = URL;
-                       }
-               }
-';
+                       ';
 
                        // Setting JavaScript of the pid value for viewing:
                        if ($this->popView) {
-                               $this->doc->JScode = $this->doc->wrapScriptTags(BackendUtility::viewOnClick($rawRecord['pid'], '', BackendUtility::BEgetRootLine($rawRecord['pid'])));
+                               $this->doc->JScode = $this->doc->wrapScriptTags(
+                                       BackendUtility::viewOnClick($formData['databaseRow']['pid'], '', BackendUtility::BEgetRootLine($formData['databaseRow']['pid']))
+                               );
                        }
-                       // Initialize FormEngine - for rendering the field:
-                       /** @var FormEngine $formEngine */
-                       $formEngine = GeneralUtility::makeInstance(FormEngine::class);
-                       // SPECIAL: Disables all wizards - we are NOT going to need them.
-                       $formEngine->disableWizards = 1;
-                       // Fetching content of record:
-                       /** @var DataPreprocessor $dataPreprocessor */
-                       $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
-                       $dataPreprocessor->lockRecords = 1;
-                       $dataPreprocessor->fetchRecord($this->P['table'], $this->P['uid'], '');
-                       // Getting the processed record content out:
-                       $processedRecord = reset($dataPreprocessor->regTableItems_data);
-                       $processedRecord['uid'] = $this->P['uid'];
-                       $processedRecord['pid'] = $rawRecord['pid'];
-                       // TSconfig, setting width:
-                       $fieldTSConfig = FormEngineUtility::getTSconfigForTableRow($this->P['table'], $processedRecord, $this->P['field']);
-                       if ((string)$fieldTSConfig['RTEfullScreenWidth'] !== '') {
-                               $width = $fieldTSConfig['RTEfullScreenWidth'];
+
+                       $pageTsConfigMerged = $formData['pageTsConfigMerged'];
+                       if ((string)$pageTsConfigMerged['TCEFORM.'][$this->P['table'] . '.'][$this->P['field'] . '.']['RTEfullScreenWidth'] !== '') {
+                               $width = (string)$pageTsConfigMerged['TCEFORM.'][$this->P['table'] . '.'][$this->P['field'] . '.']['RTEfullScreenWidth'];
                        } else {
                                $width = '100%';
                        }
                        // Get the form field and wrap it in the table with the buttons:
-                       $formContent = $formEngine->getSoloField($this->P['table'], $processedRecord, $this->P['field']);
+                       $formContent = $formResult['html'];
                        $formContent = '
-
-                       <!-- RTE wizard: -->
                                <table border="0" cellpadding="0" cellspacing="0" width="' . $width . '" id="typo3-rtewizard">
                                        <tr>
                                                <td width="' . $width . '" colspan="2" id="c-formContent">' . $formContent . '</td>
                                                <td></td>
                                        </tr>
                                </table>';
+
                        // Adding hidden fields:
                        $formContent .= '<input type="hidden" name="redirect" value="' . htmlspecialchars($this->R_URI) . '" />
                                                <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />';
                        // Finally, add the whole setup:
-                       $this->content .= $formEngine->printNeededJSFunctions_top() . $formContent . $formEngine->printNeededJSFunctions();
+                       $this->content .= $formResultCompiler->JStop() . $formContent . $formResultCompiler->printNeededJSFunctions();
                } else {
                        // ERROR:
                        $this->content .= $this->doc->section($this->getLanguageService()->getLL('forms_title'), '<span class="text-danger">' . $this->getLanguageService()->getLL('table_noData', TRUE) . '</span>', 0, 1);
index 98cfd32..00a94fa 100644 (file)
@@ -23,29 +23,34 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 abstract class AbstractNode implements NodeInterface {
 
        /**
-        * A list of global options given from parent to child elements
+        * Main data array to work on, given from parent to child elements
         *
         * @var array
         */
-       protected $globalOptions = array();
+       protected $data = array();
 
        /**
-        * Handler for single nodes
+        * Set data to data array.
         *
-        * @return array As defined in initializeResultArray() of AbstractNode
+        * @todo: Should NOT set the nodeFactory instance, this is done by AbstractContainer only,
+        * @todo: but not done for Element classes: Elements are tree leaves, they MUST
+        * @todo: not create new nodes again.
+        * @todo: Currently, AbstractFormElement still does that, but do not rely on the fact that
+        * @todo: Element classes have an instance of NodeFactory at hand.
+        *
+        * @param NodeFactory $nodeFactory
+        * @param array $data
         */
-       abstract public function render();
+       public function __construct(NodeFactory $nodeFactory, array $data) {
+               $this->data = $data;
+       }
 
        /**
-        * Set global options from parent instance
+        * Handler for single nodes
         *
-        * @param array $globalOptions Global options like 'readonly' for all elements
-        * @return $this
+        * @return array As defined in initializeResultArray() of AbstractNode
         */
-       public function setGlobalOptions(array $globalOptions) {
-               $this->globalOptions = $globalOptions;
-               return $this;
-       }
+       abstract public function render();
 
        /**
         * Initialize the array that is returned to parent after calling. This structure
@@ -59,7 +64,7 @@ abstract class AbstractNode implements NodeInterface {
                        'additionalJavaScriptPost' => array(),
                        'additionalJavaScriptSubmit' => array(),
                        'additionalHiddenFields' => array(),
-                       'additionalHeadTags' => array(),
+                       'stylesheetFiles' => array(),
                        // can hold strings or arrays, string = requireJS module, array = requireJS module + callback e.g. array('TYPO3/Foo/Bar', 'function() {}')
                        'requireJsModules' => array(),
                        'extJSCODE' => '',
@@ -91,8 +96,8 @@ abstract class AbstractNode implements NodeInterface {
                foreach ($childReturn['additionalHiddenFields'] as $value) {
                        $existing['additionalHiddenFields'][] = $value;
                }
-               foreach ($childReturn['additionalHeadTags'] as $value) {
-                       $existing['additionalHeadTags'][] = $value;
+               foreach ($childReturn['stylesheetFiles'] as $value) {
+                       $existing['stylesheetFiles'][] = $value;
                }
                if (!empty($childReturn['requireJsModules'])) {
                        foreach ($childReturn['requireJsModules'] as $module) {
@@ -128,7 +133,7 @@ abstract class AbstractNode implements NodeInterface {
                        /** @var FormDataTraverser $traverser */
                        $traverseFields = GeneralUtility::trimExplode('|', substr($value, 6));
                        $traverser = GeneralUtility::makeInstance(FormDataTraverser::class);
-                       $value = $traverser->getTraversedFieldValue($traverseFields, $table, $row, $this->globalOptions['inlineFirstPid']);
+                       $value = $traverser->getTraversedFieldValue($traverseFields, $table, $row, $this->data['inlineFirstPid']);
                }
 
                return $value;
index 45aa55f..87bb6f6 100644 (file)
@@ -14,15 +14,13 @@ namespace TYPO3\CMS\Backend\Form\Container;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Form\AbstractNode;
 use TYPO3\CMS\Backend\Form\ElementConditionMatcher;
 use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
-use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
-use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Core\Utility\MathUtility;
 
 /**
  * Abstract container has various methods used by the container classes
@@ -30,168 +28,24 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 abstract class AbstractContainer extends AbstractNode {
 
        /**
-        * Array where records in the default language are stored. (processed by transferdata)
+        * Instance of the node factory to create sub elements and container.
         *
-        * @var array
+        * @var NodeFactory
         */
-       protected $defaultLanguageData = array();
+       protected $nodeFactory;
 
        /**
-        * Array where records in the default language are stored (raw without any processing. used for making diff).
-        * This is the unserialized content of configured TCA ['ctrl']['transOrigDiffSourceField'] field, typically l18n_diffsource
+        * Container objects give $nodeFactory down to other containers.
         *
-        * @var array
+        * @param NodeFactory $nodeFactory
+        * @param array $data
         */
-       protected $defaultLanguageDataDiff = array();
-
-       /**
-        * Contains row data of "additional" language overlays
-        * array(
-        *   $table:$uid => array(
-        *     $additionalPreviewLanguageUid => $rowData
-        *   )
-        * )
-        *
-        * @var array
-        */
-       protected $additionalPreviewLanguageData = array();
-
-       /**
-        * Calculate and return the current type value of a record
-        *
-        * @param string $table The table name. MUST be in $GLOBALS['TCA']
-        * @param array $row The row from the table, should contain at least the "type" field, if applicable.
-        * @return string Return the "type" value for this record, ready to pick a "types" configuration from the $GLOBALS['TCA'] array.
-        * @throws \RuntimeException
-        */
-       protected function getRecordTypeValue($table, array $row) {
-               $typeNum = 0;
-               $field = $GLOBALS['TCA'][$table]['ctrl']['type'];
-               if ($field) {
-                       if (strpos($field, ':') !== FALSE) {
-                               list($pointerField, $foreignTypeField) = explode(':', $field);
-                               $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$pointerField]['config'];
-                               $relationType = $fieldConfig['type'];
-                               if ($relationType === 'select') {
-                                       $foreignUid = $row[$pointerField];
-                                       $foreignTable = $fieldConfig['foreign_table'];
-                               } elseif ($relationType === 'group') {
-                                       $values = FormEngineUtility::extractValuesOnlyFromValueLabelList($row[$pointerField]);
-                                       list(, $foreignUid) = GeneralUtility::revExplode('_', $values[0], 2);
-                                       $allowedTables = explode(',', $fieldConfig['allowed']);
-                                       // Always take the first configured table.
-                                       $foreignTable = $allowedTables[0];
-                               } else {
-                                       throw new \RuntimeException('TCA Foreign field pointer fields are only allowed to be used with group or select field types.', 1325861239);
-                               }
-                               if ($foreignUid) {
-                                       $foreignRow = BackendUtility::getRecord($foreignTable, $foreignUid, $foreignTypeField);
-                                       $this->registerDefaultLanguageData($foreignTable, $foreignRow);
-                                       if ($foreignRow[$foreignTypeField]) {
-                                               $foreignTypeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
-                                               $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($foreignTable, $foreignRow, $foreignTypeField, $foreignTypeFieldConfig);
-                                       }
-                               }
-                       } else {
-                               $typeFieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
-                               $typeNum = $this->overrideTypeWithValueFromDefaultLanguageRecord($table, $row, $field, $typeFieldConfig);
-                       }
-               }
-               if (empty($typeNum)) {
-                       // If that value is an empty string, set it to "0" (zero)
-                       $typeNum = 0;
-               }
-               // If current typeNum doesn't exist, set it to 0 (or to 1 for historical reasons, if 0 doesn't exist)
-               if (!$GLOBALS['TCA'][$table]['types'][$typeNum]) {
-                       $typeNum = $GLOBALS['TCA'][$table]['types']['0'] ? 0 : 1;
-               }
-               // Force to string. Necessary for eg '-1' to be recognized as a type value.
-               return (string)$typeNum;
-       }
-
-       /**
-        * Producing an array of field names NOT to display in the form,
-        * based on settings from subtype_value_field, bitmask_excludelist_bits etc.
-        * Notice, this list is in NO way related to the "excludeField" flag
-        *
-        * @param string $table Table name, MUST be in $GLOBALS['TCA']
-        * @param array $row A record from table.
-        * @param string $typeNum A "type" pointer value, probably the one calculated based on the record array.
-        * @return array Array with field names as values. The field names are those which should NOT be displayed "anyways
-        */
-       protected function getExcludeElements($table, $row, $typeNum) {
-               $excludeElements = array();
-               // If a subtype field is defined for the type
-               if ($GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field']) {
-                       $subTypeField = $GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field'];
-                       if (trim($GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_excludelist'][$row[$subTypeField]])) {
-                               $excludeElements = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_excludelist'][$row[$subTypeField]], TRUE);
-                       }
-               }
-               // If a bitmask-value field has been configured, then find possible fields to exclude based on that:
-               if ($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_value_field']) {
-                       $subTypeField = $GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_value_field'];
-                       $sTValue = MathUtility::forceIntegerInRange($row[$subTypeField], 0);
-                       if (is_array($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_excludelist_bits'])) {
-                               foreach ($GLOBALS['TCA'][$table]['types'][$typeNum]['bitmask_excludelist_bits'] as $bitKey => $eList) {
-                                       $bit = substr($bitKey, 1);
-                                       if (MathUtility::canBeInterpretedAsInteger($bit)) {
-                                               $bit = MathUtility::forceIntegerInRange($bit, 0, 30);
-                                               if ($bitKey[0] === '-' && !($sTValue & pow(2, $bit)) || $bitKey[0] === '+' && $sTValue & pow(2, $bit)) {
-                                                       $excludeElements = array_merge($excludeElements, GeneralUtility::trimExplode(',', $eList, TRUE));
-                                               }
-                                       }
-                               }
-                       }
-               }
-               return $excludeElements;
-       }
-
-       /**
-        * The requested field value will be overridden with the data from the default
-        * language if the field is configured accordingly.
-        *
-        * @param string $table Table name of the record being edited
-        * @param array $row Record array of the record being edited in current language
-        * @param string $field Field name represented by $item
-        * @param array $fieldConf Content of $PA['fieldConf']
-        * @return string Unprocessed field value merged with default language data if needed
-        */
-       protected function overrideTypeWithValueFromDefaultLanguageRecord($table, array $row, $field, $fieldConf) {
-               $value = $row[$field];
-               if (is_array($this->defaultLanguageData[$table . ':' . $row['uid']])) {
-                       if (
-                               $fieldConf['l10n_mode'] === 'exclude'
-                               || ($fieldConf['l10n_mode'] === 'mergeIfNotBlank' && trim($row[$field] === ''))
-                       ) {
-                               $value = $this->defaultLanguageData[$table . ':' . $row['uid']][$field];
-                       }
-               }
-               return $value;
+       public function __construct(NodeFactory $nodeFactory, array $data) {
+               parent::__construct($nodeFactory, $data);
+               $this->nodeFactory = $nodeFactory;
        }
 
        /**
-        * Return a list without excluded elements.
-        *
-        * @param array $fieldsArray Typically coming from types show item
-        * @param array $excludeElements Field names to be excluded
-        * @return array $fieldsArray without excluded elements
-        */
-       protected function removeExcludeElementsFromFieldArray(array $fieldsArray, array $excludeElements) {
-               $newFieldArray = array();
-               foreach ($fieldsArray as $fieldString) {
-                       $fieldArray = $this->explodeSingleFieldShowItemConfiguration($fieldString);
-                       $fieldName = $fieldArray['fieldName'];
-                       // It doesn't make sense to exclude palettes and tabs
-                       if (!in_array($fieldName, $excludeElements, TRUE) || $fieldName === '--palette--' || $fieldName === '--div--') {
-                               $newFieldArray[] = $fieldString;
-                       }
-               }
-               return $newFieldArray;
-       }
-
-
-       /**
         * A single field of TCA 'types' 'showitem' can have four semicolon separated configuration options:
         *   fieldName: Name of the field to be found in TCA 'columns' section
         *   fieldLabel: An alternative field label
@@ -215,49 +69,6 @@ abstract class AbstractContainer extends AbstractNode {
        }
 
        /**
-        * Will register data from original language records if the current record is a translation of another.
-        * The original data is shown with the edited record in the form.
-        * The information also includes possibly diff-views of what changed in the original record.
-        * Function called from outside (see alt_doc.php + quick edit) before rendering a form for a record
-        *
-        * @param string $table Table name of the record being edited
-        * @param array $rec Record array of the record being edited
-        * @return void
-        */
-       protected function registerDefaultLanguageData($table, $rec) {
-               // @todo: early return here if the arrays are already filled?
-
-               // Add default language:
-               if (
-                       $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $rec[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
-                       && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
-                       && (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0
-               ) {
-                       $lookUpTable = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
-                               ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
-                               : $table;
-                       // Get data formatted:
-                       $this->defaultLanguageData[$table . ':' . $rec['uid']] = BackendUtility::getRecordWSOL(
-                               $lookUpTable,
-                               (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]
-                       );
-                       // Get data for diff:
-                       if ($GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']) {
-                               $this->defaultLanguageDataDiff[$table . ':' . $rec['uid']] = unserialize($rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']]);
-                       }
-                       // If there are additional preview languages, load information for them also:
-                       foreach ($this->globalOptions['additionalPreviewLanguages'] as $prL) {
-                               /** @var $translationConfigurationProvider TranslationConfigurationProvider */
-                               $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
-                               $translationInfo = $translationConfigurationProvider->translationInfo($lookUpTable, (int)$rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']], $prL['uid']);
-                               if (is_array($translationInfo['translations']) && is_array($translationInfo['translations'][$prL['uid']])) {
-                                       $this->additionalPreviewLanguageData[$table . ':' . $rec['uid']][$prL['uid']] = BackendUtility::getRecordWSOL($table, (int)$translationInfo['translations'][$prL['uid']]['uid']);
-                               }
-                       }
-               }
-       }
-
-       /**
         * Evaluate condition of flex forms
         *
         * @param string $displayCondition The condition to evaluate
@@ -288,7 +99,7 @@ abstract class AbstractContainer extends AbstractNode {
                        case 'EXT':
                                break;
                        case 'REC':
-                               $fakeRow = array('uid' => $this->globalOptions['databaseRow']['uid']);
+                               $fakeRow = array('uid' => $this->data['databaseRow']['uid']);
                                break;
                        default:
                                $skipCondition = TRUE;
diff --git a/typo3/sysext/backend/Classes/Form/Container/FlexFormContainer.php b/typo3/sysext/backend/Classes/Form/Container/FlexFormContainer.php
deleted file mode 100644 (file)
index ca12cbe..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\Form\Container;
-
-/*
- * 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\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
- * SingleFieldContainer if a type='flexform' field is rendered.
- * The container prepares the flex form data and structure and hands
- * over to FlexFormLanguageContainer for further processing.
- */
-class FlexFormContainer extends AbstractContainer {
-
-       /**
-        * Entry method
-        *
-        * @return array As defined in initializeResultArray() of AbstractNode
-        */
-       public function render() {
-               $languageService = $this->getLanguageService();
-
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $field = $this->globalOptions['fieldName'];
-               $parameterArray = $this->globalOptions['parameterArray'];
-
-               // Data Structure
-               $flexFormDataStructureArray = BackendUtility::getFlexFormDS($parameterArray['fieldConf']['config'], $row, $table, $field);
-
-               // Early return if no data structure was found at all
-               if (!is_array($flexFormDataStructureArray)) {
-                       $resultArray = $this->initializeResultArray();
-                       $resultArray['html'] = 'Data Structure ERROR: ' . $flexFormDataStructureArray;
-                       return $resultArray;
-               }
-
-               // Manipulate Flex form DS via TSConfig and group access lists
-               if (is_array($flexFormDataStructureArray)) {
-                       $flexFormHelper = GeneralUtility::makeInstance(FlexFormsHelper::class);
-                       $flexFormDataStructureArray = $flexFormHelper->modifyFlexFormDS($flexFormDataStructureArray, $table, $field, $row, $parameterArray['fieldConf']);
-               }
-
-               // Get data
-               $xmlData = $parameterArray['itemFormElValue'];
-               $xmlHeaderAttributes = GeneralUtility::xmlGetHeaderAttribs($xmlData);
-               $storeInCharset = strtolower($xmlHeaderAttributes['encoding']);
-               if ($storeInCharset) {
-                       $currentCharset = $languageService->charSet;
-                       $xmlData = $languageService->csConvObj->conv($xmlData, $storeInCharset, $currentCharset, 1);
-               }
-               $flexFormRowData = GeneralUtility::xml2array($xmlData);
-
-               // Must be XML parsing error...
-               if (!is_array($flexFormRowData)) {
-                       $flexFormRowData = array();
-               } elseif (!isset($flexFormRowData['meta']) || !is_array($flexFormRowData['meta'])) {
-                       $flexFormRowData['meta'] = array();
-               }
-
-               $options = $this->globalOptions;
-               $options['flexFormDataStructureArray'] = $flexFormDataStructureArray;
-               $options['flexFormRowData'] = $flexFormRowData;
-               $options['renderType'] = 'flexFormLanguageContainer';
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               return $nodeFactory->create($options)->render();
-       }
-
-       /**
-        * @return LanguageService
-        */
-       protected function getLanguageService() {
-               return $GLOBALS['LANG'];
-       }
-
-}
\ No newline at end of file
index eb42517..5e87908 100644 (file)
@@ -19,7 +19,6 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Flex form container implementation
@@ -34,14 +33,14 @@ class FlexFormContainerContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $fieldName = $this->globalOptions['fieldName'];
-               $flexFormFormPrefix = $this->globalOptions['flexFormFormPrefix'];
-               $flexFormContainerElementCollapsed = $this->globalOptions['flexFormContainerElementCollapsed'];
-               $flexFormContainerTitle = $this->globalOptions['flexFormContainerTitle'];
-               $flexFormFieldIdentifierPrefix = $this->globalOptions['flexFormFieldIdentifierPrefix'];
-               $parameterArray = $this->globalOptions['parameterArray'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $fieldName = $this->data['fieldName'];
+               $flexFormFormPrefix = $this->data['flexFormFormPrefix'];
+               $flexFormContainerElementCollapsed = $this->data['flexFormContainerElementCollapsed'];
+               $flexFormContainerTitle = $this->data['flexFormContainerTitle'];
+               $flexFormFieldIdentifierPrefix = $this->data['flexFormFieldIdentifierPrefix'];
+               $parameterArray = $this->data['parameterArray'];
 
                // Every container adds its own part to the id prefix
                $flexFormFieldIdentifierPrefix = $flexFormFieldIdentifierPrefix . '-' . GeneralUtility::shortMd5(uniqid('id', TRUE));
@@ -54,10 +53,10 @@ class FlexFormContainerContainer extends AbstractContainer {
                        . $iconFactory->getIcon('actions-move-right', Icon::SIZE_SMALL)
                        . '</span>';
 
-               $flexFormContainerCounter = $this->globalOptions['flexFormContainerCounter'];
+               $flexFormContainerCounter = $this->data['flexFormContainerCounter'];
                $actionFieldName = '_ACTION_FLEX_FORM'
                        . $parameterArray['itemFormElName']
-                       . $this->globalOptions['flexFormFormPrefix']
+                       . $this->data['flexFormFormPrefix']
                        . '[_ACTION]'
                        . '[' . $flexFormContainerCounter . ']';
                $toggleFieldName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']'
@@ -76,14 +75,12 @@ class FlexFormContainerContainer extends AbstractContainer {
                        $moveAndDeleteContent[] = '</div>';
                }
 
-               $options = $this->globalOptions;
+               $options = $this->data;
                $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
                // Append container specific stuff to field prefix
-               $options['flexFormFormPrefix'] =  $flexFormFormPrefix . '[' . $flexFormContainerCounter . '][' .  $this->globalOptions['flexFormContainerName'] . '][el]';
+               $options['flexFormFormPrefix'] =  $flexFormFormPrefix . '[' . $flexFormContainerCounter . '][' .  $this->data['flexFormContainerName'] . '][el]';
                $options['renderType'] = 'flexFormElementContainer';
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               $containerContentResult = $nodeFactory->create($options)->render();
+               $containerContentResult = $this->nodeFactory->create($options)->render();
 
                $html = array();
                $html[] = '<div id="' . $flexFormFieldIdentifierPrefix . '" class="t3-form-field-container-flexsections t3-flex-section">';
index 4a5e60c..9be93cb 100644 (file)
@@ -20,7 +20,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
-use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 
@@ -40,15 +39,13 @@ class FlexFormElementContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $fieldName = $this->globalOptions['fieldName'];
-               $flexFormDataStructureArray = $this->globalOptions['flexFormDataStructureArray'];
-               $flexFormRowData = $this->globalOptions['flexFormRowData'];
-               $flexFormCurrentLanguage = $this->globalOptions['flexFormCurrentLanguage'];
-               $flexFormNoEditDefaultLanguage = $this->globalOptions['flexFormNoEditDefaultLanguage'];
-               $flexFormFormPrefix = $this->globalOptions['flexFormFormPrefix'];
-               $parameterArray = $this->globalOptions['parameterArray'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $fieldName = $this->data['fieldName'];
+               $flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
+               $flexFormRowData = $this->data['flexFormRowData'];
+               $flexFormFormPrefix = $this->data['flexFormFormPrefix'];
+               $parameterArray = $this->data['parameterArray'];
 
                $languageService = $this->getLanguageService();
                $resultArray = $this->initializeResultArray();
@@ -57,7 +54,7 @@ class FlexFormElementContainer extends AbstractContainer {
                                // No item array found at all
                                !is_array($flexFormFieldArray)
                                // Not a section or container and not a list of single items
-                               || (!isset($flexFormFieldArray['type']) && !is_array($flexFormFieldArray['TCEforms']['config']))
+                               || (!isset($flexFormFieldArray['type']) && !is_array($flexFormFieldArray['config']))
                        ) {
                                continue;
                        }
@@ -74,27 +71,29 @@ class FlexFormElementContainer extends AbstractContainer {
                                        $sectionTitle = $languageService->sL($flexFormFieldArray['title']);
                                }
 
-                               $options = $this->globalOptions;
+                               $options = $this->data;
                                $options['flexFormDataStructureArray'] = $flexFormFieldArray['el'];
                                $options['flexFormRowData'] = is_array($flexFormRowData[$flexFormFieldName]['el']) ? $flexFormRowData[$flexFormFieldName]['el'] : array();
                                $options['flexFormSectionType'] = $flexFormFieldName;
                                $options['flexFormSectionTitle'] = $sectionTitle;
                                $options['renderType'] = 'flexFormSectionContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $sectionContainerResult = $nodeFactory->create($options)->render();
+                               $sectionContainerResult = $this->nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $sectionContainerResult);
                        } else {
                                // Single element
+                               // @todo: There is a bug in here - with langChildren = 1, "single" fields can be localized.
+                               // @todo: This case is not handled correctly here, see for details method #2 of
+                               // @todo: https://docs.typo3.org/typo3cms/TCAReference/Reference/Columns/Flex/Index.html#handling-languages-in-flexforms
+                               // @todo: All data should be properly prepared by flex provider already
                                $vDEFkey = 'vDEF';
 
                                $displayConditionResult = TRUE;
-                               if (!empty($flexFormFieldArray['TCEforms']['displayCond'])) {
+                               if (!empty($flexFormFieldArray['displayCond'])) {
                                        $conditionData = is_array($flexFormRowData) ? $flexFormRowData : array();
                                        $conditionData['parentRec'] = $row;
                                        /** @var $elementConditionMatcher ElementConditionMatcher */
                                        $elementConditionMatcher = GeneralUtility::makeInstance(ElementConditionMatcher::class);
-                                       $displayConditionResult = $elementConditionMatcher->match($flexFormFieldArray['TCEforms']['displayCond'], $conditionData, $vDEFkey);
+                                       $displayConditionResult = $elementConditionMatcher->match($flexFormFieldArray['displayCond'], $conditionData, $vDEFkey);
                                }
                                if (!$displayConditionResult) {
                                        continue;
@@ -102,10 +101,11 @@ class FlexFormElementContainer extends AbstractContainer {
 
                                // On-the-fly migration for flex form "TCA"
                                // @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8. This can be removed *if* no additional TCA migration is added with CMS 8, see class TcaMigration
+                               // @todo: this migration should be done in flex provider?
                                $dummyTca = array(
                                        'dummyTable' => array(
                                                'columns' => array(
-                                                       'dummyField' => $flexFormFieldArray['TCEforms'],
+                                                       'dummyField' => $flexFormFieldArray,
                                                ),
                                        ),
                                );
@@ -118,26 +118,18 @@ class FlexFormElementContainer extends AbstractContainer {
                                        array_unshift($messages, $context);
                                        GeneralUtility::deprecationLog(implode(LF, $messages));
                                }
-                               $flexFormFieldArray['TCEforms'] = $migratedTca['dummyTable']['columns']['dummyField'];
+                               $flexFormFieldArray = $migratedTca['dummyTable']['columns']['dummyField'];
 
                                // Set up options for single element
                                $fakeParameterArray = array(
                                        'fieldConf' => array(
-                                               'label' => $languageService->sL(trim($flexFormFieldArray['TCEforms']['label'])),
-                                               'config' => $flexFormFieldArray['TCEforms']['config'],
-                                               'defaultExtras' => $flexFormFieldArray['TCEforms']['defaultExtras'],
-                                               'onChange' => $flexFormFieldArray['TCEforms']['onChange'],
+                                               'label' => $languageService->sL(trim($flexFormFieldArray['label'])),
+                                               'config' => $flexFormFieldArray['config'],
+                                               'defaultExtras' => $flexFormFieldArray['defaultExtras'],
+                                               'onChange' => $flexFormFieldArray['onChange'],
                                        ),
                                );
 
-                               // Force a none field if default language can not be edited
-                               if ($flexFormNoEditDefaultLanguage && $flexFormCurrentLanguage === 'lDEF') {
-                                       $fakeParameterArray['fieldConf']['config'] = array(
-                                               'type' => 'none',
-                                               'rows' => 2
-                                       );
-                               }
-
                                $alertMsgOnChange = '';
                                if (
                                        $fakeParameterArray['fieldConf']['onChange'] === 'reload'
@@ -165,31 +157,20 @@ class FlexFormElementContainer extends AbstractContainer {
                                        $fakeParameterArray['itemFormElValue'] = $fakeParameterArray['fieldConf']['config']['default'];
                                }
 
-                               $options = $this->globalOptions;
+                               $options = $this->data;
                                $options['parameterArray'] = $fakeParameterArray;
-                               $options['elementBaseName'] = $this->globalOptions['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $vDEFkey . ']';
+                               $options['elementBaseName'] = $this->data['elementBaseName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][' . $vDEFkey . ']';
 
-                               if (!empty($flexFormFieldArray['TCEforms']['config']['renderType'])) {
-                                       $options['renderType'] = $flexFormFieldArray['TCEforms']['config']['renderType'];
+                               if (!empty($flexFormFieldArray['config']['renderType'])) {
+                                       $options['renderType'] = $flexFormFieldArray['config']['renderType'];
                                } else {
                                        // Fallback to type if no renderType is given
-                                       $options['renderType'] = $flexFormFieldArray['TCEforms']['config']['type'];
+                                       $options['renderType'] = $flexFormFieldArray['config']['type'];
                                }
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $childResult = $nodeFactory->create($options)->render();
+                               $childResult = $this->nodeFactory->create($options)->render();
 
                                $theTitle = htmlspecialchars($fakeParameterArray['fieldConf']['label']);
                                $defInfo = array();
-                               if (!$flexFormNoEditDefaultLanguage) {
-                                       $previewLanguages = $this->globalOptions['additionalPreviewLanguages'];
-                                       foreach ($previewLanguages as $previewLanguage) {
-                                               $defInfo[] = '<div class="t3-form-original-language">';
-                                               $defInfo[] =    FormEngineUtility::getLanguageIcon($table, $row, ('v' . $previewLanguage['ISOcode']));
-                                               $defInfo[] =    $this->previewFieldValue($flexFormRowData[$flexFormFieldName][('v' . $previewLanguage['ISOcode'])], $fakeParameterArray['fieldConf'], $fieldName);
-                                               $defInfo[] = '</div>';
-                                       }
-                               }
 
                                $languageIcon = '';
                                if ($vDEFkey !== 'vDEF') {
index 921f598..b424734 100644 (file)
@@ -14,20 +14,16 @@ namespace TYPO3\CMS\Backend\Form\Container;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 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.
  *
- * This container is falled from the entry FlexFormContainer. For each existing language overlay
- * it forks a FlexFormTabsContainer or a FlexFormNoTabsContainer for rendering a full flex form
- * record of the specific language.
+ * Entry container to a flex form element. This container is created by
+ * SingleFieldContainer if a type='flexform' field is rendered.
+ *
+ * For each existing language overlay it forks a FlexFormTabsContainer or a
+ * FlexFormNoTabsContainer for rendering a full flex form record of the specific language.
  */
 class FlexFormLanguageContainer extends AbstractContainer {
 
@@ -35,58 +31,36 @@ class FlexFormLanguageContainer extends AbstractContainer {
         * Entry method
         *
         * @return array As defined in initializeResultArray() of AbstractNode
+        * @todo: Implement langChildren=1 case where each single element is localized and not the whole thing.
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $flexFormDataStructureArray = $this->globalOptions['flexFormDataStructureArray'];
-               $flexFormRowData = $this->globalOptions['flexFormRowData'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $flexFormDataStructureArray = $this->data['parameterArray']['fieldConf']['config']['ds'];
+               $flexFormRowData = $this->data['parameterArray']['itemFormElValue'];
 
                // Determine available languages
+               // @todo: Would be better to have evaluated langChildren & langDisable in meta already, prepared by data provider
                $langChildren = (bool)$flexFormDataStructureArray['meta']['langChildren'];
                $langDisabled = (bool)$flexFormDataStructureArray['meta']['langDisable'];
-               $flexFormRowData['meta']['currentLangId'] = array();
-               // Look up page language overlays
-               $checkPageLanguageOverlay = (bool)$this->getBackendUserAuthentication()->getTSConfigVal('options.checkPageLanguageOverlay');
-               $pageOverlays = array();
-               if ($checkPageLanguageOverlay) {
-                       $whereClause = 'pid=' . (int)$row['pid'] . BackendUtility::deleteClause('pages_language_overlay')
-                               . BackendUtility::versioningPlaceholderClause('pages_language_overlay');
-                       $pageOverlays = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'pages_language_overlay', $whereClause, '', '', '', 'sys_language_uid');
-               }
-               $languages = $this->getAvailableLanguages();
-               foreach ($languages as $langInfo) {
-                       if (
-                               $this->getBackendUserAuthentication()->checkLanguageAccess($langInfo['uid'])
-                               && (!$checkPageLanguageOverlay || $langInfo['uid'] <= 0 || is_array($pageOverlays[$langInfo['uid']]))
-                       ) {
-                               $flexFormRowData['meta']['currentLangId'][] = $langInfo['ISOcode'];
-                       }
-               }
-               if (!is_array($flexFormRowData['meta']['currentLangId']) || empty($flexFormRowData['meta']['currentLangId'])) {
-                       $flexFormRowData['meta']['currentLangId'] = array('DEF');
-               }
-               $flexFormRowData['meta']['currentLangId'] = array_unique($flexFormRowData['meta']['currentLangId']);
-               $flexFormNoEditDefaultLanguage = FALSE;
+
+               $availableLanguageCodes = $flexFormDataStructureArray['meta']['availableLanguageCodes'];
+               // @todo: Would be better to have $languagesOnSheetLevel and $languagesOnElementLevel prepared in meta already
                if ($langChildren || $langDisabled) {
-                       $availableLanguages = array('DEF');
+                       $languagesOnSheetLevel = [ 'DEF' ];
                } else {
-                       if (!in_array('DEF', $flexFormRowData['meta']['currentLangId'])) {
-                               array_unshift($flexFormRowData['meta']['currentLangId'], 'DEF');
-                               $flexFormNoEditDefaultLanguage = TRUE;
-                       }
-                       $availableLanguages = $flexFormRowData['meta']['currentLangId'];
+                       $languagesOnSheetLevel = $availableLanguageCodes;
                }
 
                // Tabs or no tabs - that's the question
                $hasTabs = FALSE;
-               if (is_array($flexFormDataStructureArray['sheets'])) {
+               if (count($flexFormDataStructureArray['sheets']) > 1) {
                        $hasTabs = TRUE;
                }
 
                $resultArray = $this->initializeResultArray();
 
-               foreach ($availableLanguages as $lKey) {
+               foreach ($languagesOnSheetLevel as $lKey) {
                        // Add language as header
                        if (!$langChildren && !$langDisabled) {
                                $resultArray['html'] .= LF . '<strong>' . FormEngineUtility::getLanguageIcon($table, $row, ('v' . $lKey)) . $lKey . ':</strong>';
@@ -95,20 +69,17 @@ class FlexFormLanguageContainer extends AbstractContainer {
                        // Default language "lDEF", other options are "lUK" or whatever country code
                        $flexFormCurrentLanguage = 'l' . $lKey;
 
-                       $options = $this->globalOptions;
+                       $options = $this->data;
                        $options['flexFormCurrentLanguage'] = $flexFormCurrentLanguage;
-                       $options['flexFormNoEditDefaultLanguage'] = $flexFormNoEditDefaultLanguage;
+                       $options['flexFormDataStructureArray'] = $flexFormDataStructureArray;
+                       $options['flexFormRowData'] = $flexFormRowData;
                        if (!$hasTabs) {
                                $options['renderType'] = 'flexFormNoTabsContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $flexFormNoTabsResult = $nodeFactory->create($options)->render();
+                               $flexFormNoTabsResult = $this->nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormNoTabsResult);
                        } else {
                                $options['renderType'] = 'flexFormTabsContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $flexFormTabsContainerResult = $nodeFactory->create($options)->render();
+                               $flexFormTabsContainerResult = $this->nodeFactory->create($options)->render();
                                $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormTabsContainerResult);
                        }
                }
@@ -117,65 +88,4 @@ class FlexFormLanguageContainer extends AbstractContainer {
                return $resultArray;
        }
 
-       /**
-        * Returns an array of available languages (to use for FlexForms)
-        *
-        * @return array
-        */
-       protected function getAvailableLanguages() {
-               $isLoaded = ExtensionManagementUtility::isLoaded('static_info_tables');
-
-               // Find all language records in the system
-               $db = $this->getDatabaseConnection();
-               $res = $db->exec_SELECTquery(
-                       'language_isocode,static_lang_isocode,title,uid',
-                       'sys_language',
-                       'pid=0 AND hidden=0' . BackendUtility::deleteClause('sys_language'),
-                       '',
-                       'title'
-               );
-
-               // Traverse them
-               $output = array(
-                       0 => array(
-                               'uid' => 0,
-                               'title' => 'Default language',
-                               'ISOcode' => 'DEF',
-                       )
-               );
-
-               while ($row = $db->sql_fetch_assoc($res)) {
-                       $output[$row['uid']] = $row;
-                       if (!empty($row['language_isocode'])) {
-                               $output[$row['uid']]['ISOcode'] = $row['language_isocode'];
-                       } elseif ($isLoaded && $row['static_lang_isocode']) {
-                               GeneralUtility::deprecationLog('Usage of the field "static_lang_isocode" is discouraged, and will stop working with CMS 8. Use the built-in language field "language_isocode" in your sys_language records.');
-                               $rr = BackendUtility::getRecord('static_languages', $row['static_lang_isocode'], 'lg_iso_2');
-                               if ($rr['lg_iso_2']) {
-                                       $output[$row['uid']]['ISOcode'] = $rr['lg_iso_2'];
-                               }
-                       }
-                       if (!$output[$row['uid']]['ISOcode']) {
-                               unset($output[$row['uid']]);
-                       }
-               }
-               $db->sql_free_result($res);
-
-               return $output;
-       }
-
-       /**
-        * @return BackendUserAuthentication
-        */
-       protected function getBackendUserAuthentication() {
-               return $GLOBALS['BE_USER'];
-       }
-
-       /**
-        * @return DatabaseConnection
-        */
-       protected function getDatabaseConnection() {
-               return $GLOBALS['TYPO3_DB'];
-       }
-
 }
index da5b419..1b3d554 100644 (file)
@@ -15,7 +15,6 @@ 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.
@@ -32,23 +31,29 @@ class FlexFormNoTabsContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $fieldName = $this->globalOptions['fieldName']; // field name of the flex form field in DB
-               $parameterArray = $this->globalOptions['parameterArray'];
-               $flexFormDataStructureArray = $this->globalOptions['flexFormDataStructureArray'];
-               $flexFormSheetNameInRowData = 'sDEF';
-               $flexFormCurrentLanguage = $this->globalOptions['flexFormCurrentLanguage'];
-               $flexFormRowData = $this->globalOptions['flexFormRowData'];
-               $flexFormRowDataSubPart = $flexFormRowData['data'][$flexFormSheetNameInRowData][$flexFormCurrentLanguage];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $fieldName = $this->data['fieldName']; // field name of the flex form field in DB
+               $parameterArray = $this->data['parameterArray'];
+               $flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
+               $flexFormCurrentLanguage = $this->data['flexFormCurrentLanguage'];
+               $flexFormRowData = $this->data['flexFormRowData'];
                $resultArray = $this->initializeResultArray();
 
+               // Flex ds was normalized in flex provider to always have a sheet.
+               // Determine this single sheet name, most often it ends up with sDEF, except if only one sheet was defined
+               $sheetName = array_pop(array_keys($flexFormDataStructureArray['sheets']));
+               $flexFormRowDataSubPart = $flexFormRowData['data'][$sheetName][$flexFormCurrentLanguage];
+
+
                // That was taken from GeneralUtility::resolveSheetDefInDS - no idea if it is important
                unset($flexFormDataStructureArray['meta']);
 
+
                // Evaluate display condition for this "sheet" if there is one
                $displayConditionResult = TRUE;
-               if (!empty($flexFormDataStructureArray['ROOT']['TCEforms']['displayCond'])) {
+               // @todo: flex provider should remove the TCEforms sub array for display conditions here as well
+               if (!empty($flexFormDataStructureArray['sheets'][$sheetName]['ROOT']['TCEforms']['displayCond'])) {
                        $displayConditionDefinition = $flexFormDataStructureArray['ROOT']['TCEforms']['displayCond'];
                        $displayConditionResult = $this->evaluateFlexFormDisplayCondition(
                                $displayConditionDefinition,
@@ -60,7 +65,7 @@ class FlexFormNoTabsContainer extends AbstractContainer {
                        return $resultArray;
                }
 
-               if (!is_array($flexFormDataStructureArray['ROOT']['el'])) {
+               if (!is_array($flexFormDataStructureArray['sheets'][$sheetName]['ROOT']['el'])) {
                        $resultArray['html'] = 'Data Structure ERROR: No [\'ROOT\'][\'el\'] element found in flex form definition.';
                        return $resultArray;
                }
@@ -75,16 +80,14 @@ class FlexFormNoTabsContainer extends AbstractContainer {
                        }
                }
 
-               $options = $this->globalOptions;
-               $options['flexFormDataStructureArray'] = $flexFormDataStructureArray['ROOT']['el'];
+               $options = $this->data;
+               $options['flexFormDataStructureArray'] = $flexFormDataStructureArray['sheets'][$sheetName]['ROOT']['el'];
                $options['flexFormRowData'] = $flexFormRowDataSubPart;
-               $options['flexFormFormPrefix'] = '[data][' . $flexFormSheetNameInRowData . '][' . $flexFormCurrentLanguage . ']';
+               $options['flexFormFormPrefix'] = '[data][' . $sheetName . '][' . $flexFormCurrentLanguage . ']';
                $options['parameterArray'] = $parameterArray;
 
                $options['renderType'] = 'flexFormElementContainer';
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               return $nodeFactory->create($options)->render();
+               return $this->nodeFactory->create($options)->render();
        }
 
 }
index a477bba..f15cd5e 100644 (file)
@@ -20,7 +20,6 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
 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.
@@ -42,11 +41,11 @@ class FlexFormSectionContainer extends AbstractContainer {
                $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
                $languageService = $this->getLanguageService();
 
-               $flexFormFieldsArray = $this->globalOptions['flexFormDataStructureArray'];
-               $flexFormRowData = $this->globalOptions['flexFormRowData'];
-               $flexFormFieldIdentifierPrefix = $this->globalOptions['flexFormFieldIdentifierPrefix'];
-               $flexFormSectionType = $this->globalOptions['flexFormSectionType'];
-               $flexFormSectionTitle = $this->globalOptions['flexFormSectionTitle'];
+               $flexFormFieldsArray = $this->data['flexFormDataStructureArray'];
+               $flexFormRowData = $this->data['flexFormRowData'];
+               $flexFormFieldIdentifierPrefix = $this->data['flexFormFieldIdentifierPrefix'];
+               $flexFormSectionType = $this->data['flexFormSectionType'];
+               $flexFormSectionTitle = $this->data['flexFormSectionTitle'];
 
                $userHasAccessToDefaultLanguage = $this->getBackendUserAuthentication()->checkLanguageAccess(0);
 
@@ -71,19 +70,17 @@ class FlexFormSectionContainer extends AbstractContainer {
                                                $sectionTitle = $languageService->sL($containerDataStructure['title']);
                                        }
 
-                                       $options = $this->globalOptions;
+                                       $options = $this->data;
                                        $options['flexFormRowData'] = $existingSectionContainerData['el'];
                                        $options['flexFormDataStructureArray'] = $containerDataStructure['el'];
                                        $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
-                                       $options['flexFormFormPrefix'] = $this->globalOptions['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
+                                       $options['flexFormFormPrefix'] = $this->data['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
                                        $options['flexFormContainerName'] = $existingSectionContainerDataStructureType;
                                        $options['flexFormContainerCounter'] = $flexFormContainerCounter;
                                        $options['flexFormContainerTitle'] = $sectionTitle;
                                        $options['flexFormContainerElementCollapsed'] = (bool)$existingSectionContainerData['el']['_TOGGLE'];
                                        $options['renderType'] = 'flexFormContainerContainer';
-                                       /** @var NodeFactory $nodeFactory */
-                                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                                       $flexFormContainerContainerResult = $nodeFactory->create($options)->render();
+                                       $flexFormContainerContainerResult = $this->nodeFactory->create($options)->render();
                                        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerResult);
                                }
                        }
@@ -100,19 +97,18 @@ class FlexFormSectionContainer extends AbstractContainer {
                                $sectionTitle = $languageService->sL($flexFormFieldDefinition['title']);
                        }
 
-                       $options = $this->globalOptions;
+                       $options = $this->data;
+                       // @todo: this should use the prepared templateRow parallel to the single elements to have support of default values!
                        $options['flexFormRowData'] = array();
                        $options['flexFormDataStructureArray'] = $flexFormFieldDefinition['el'];
                        $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix;
-                       $options['flexFormFormPrefix'] = $this->globalOptions['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
+                       $options['flexFormFormPrefix'] = $this->data['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]';
                        $options['flexFormContainerName'] = $flexFormContainerName;
                        $options['flexFormContainerCounter'] = $flexFormFieldIdentifierPrefix . '-form';
                        $options['flexFormContainerTitle'] = $sectionTitle;
                        $options['flexFormContainerElementCollapsed'] = FALSE;
                        $options['renderType'] = 'flexFormContainerContainer';
-                       /** @var NodeFactory $nodeFactory */
-                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                       $flexFormContainerContainerTemplateResult = $nodeFactory->create($options)->render();
+                       $flexFormContainerContainerTemplateResult = $this->nodeFactory->create($options)->render();
 
                        $uniqueId = str_replace('.', '', uniqid('idvar', TRUE));
                        $identifierPrefixJs = 'replace(/' . $flexFormFieldIdentifierPrefix . '-/g,"' . $flexFormFieldIdentifierPrefix . '-"+' . $uniqueId . '+"-")';
index 73ccfbc..4923bad 100644 (file)
@@ -17,7 +17,6 @@ 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").
@@ -36,15 +35,15 @@ class FlexFormTabsContainer extends AbstractContainer {
                $languageService = $this->getLanguageService();
                $docTemplate = $this->getDocumentTemplate();
 
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $fieldName = $this->globalOptions['fieldName']; // field name of the flex form field in DB
-               $parameterArray = $this->globalOptions['parameterArray'];
-               $flexFormDataStructureArray = $this->globalOptions['flexFormDataStructureArray'];
-               $flexFormCurrentLanguage = $this->globalOptions['flexFormCurrentLanguage'];
-               $flexFormRowData = $this->globalOptions['flexFormRowData'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $fieldName = $this->data['fieldName']; // field name of the flex form field in DB
+               $parameterArray = $this->data['parameterArray'];
+               $flexFormDataStructureArray = $this->data['flexFormDataStructureArray'];
+               $flexFormCurrentLanguage = $this->data['flexFormCurrentLanguage'];
+               $flexFormRowData = $this->data['flexFormRowData'];
 
-               $tabId = 'TCEFORMS:flexform:' . $this->globalOptions['parameterArray']['itemFormElName'] . $flexFormCurrentLanguage;
+               $tabId = 'TCEFORMS:flexform:' . $this->data['parameterArray']['itemFormElName'] . $flexFormCurrentLanguage;
                $tabIdString = $docTemplate->getDynTabMenuId($tabId);
                $tabCounter = 0;
 
@@ -55,6 +54,7 @@ class FlexFormTabsContainer extends AbstractContainer {
 
                        // Evaluate display condition for this sheet if there is one
                        $displayConditionResult = TRUE;
+                       // @todo: flex provider should remove the TCEforms sub array for display conditions here as well
                        if (!empty($sheetDataStructure['ROOT']['TCEforms']['displayCond'])) {
                                $displayConditionDefinition = $sheetDataStructure['ROOT']['TCEforms']['displayCond'];
                                $displayConditionResult = $this->evaluateFlexFormDisplayCondition(
@@ -84,7 +84,7 @@ class FlexFormTabsContainer extends AbstractContainer {
                                }
                        }
 
-                       $options = $this->globalOptions;
+                       $options = $this->data;
                        $options['flexFormDataStructureArray'] = $sheetDataStructure['ROOT']['el'];
                        $options['flexFormRowData'] = $flexFormRowSheetDataSubPart;
                        $options['flexFormFormPrefix'] = '[data][' . $sheetName . '][' . $flexFormCurrentLanguage . ']';
@@ -96,9 +96,7 @@ class FlexFormTabsContainer extends AbstractContainer {
                                $tabIdString . '-' . $tabCounter,
                        );
                        $options['renderType'] = 'flexFormElementContainer';
-                       /** @var NodeFactory $nodeFactory */
-                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                       $childReturn = $nodeFactory->create($options)->render();
+                       $childReturn = $this->nodeFactory->create($options)->render();
 
                        $tabsContent[] = array(
                                'label' => !empty($sheetDataStructure['ROOT']['TCEforms']['sheetTitle']) ? $languageService->sL($sheetDataStructure['ROOT']['TCEforms']['sheetTitle']) : $sheetName,
index ed44682..0595c6c 100644 (file)
@@ -16,7 +16,6 @@ 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
@@ -34,42 +33,20 @@ class FullRecordContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-
-               if (!$GLOBALS['TCA'][$table]) {
-                       return $this->initializeResultArray();
-               }
-
-               $languageService = $this->getLanguageService();
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $recordTypeValue = $this->data['recordTypeValue'];
 
                // Load the description content for the table if requested
-               if ($GLOBALS['TCA'][$table]['interface']['always_description']) {
+               if (!empty($this->data['processedTca']['interface']['always_description'])) {
+                       $languageService = $this->getLanguageService();
                        $languageService->loadSingleTableDescription($table);
                }
 
-               // If this is a localized record, stuff data from original record to local registry, will then be given to child elements
-               $this->registerDefaultLanguageData($table, $row);
-
-               // Current type value of the record.
-               $recordTypeValue = $this->getRecordTypeValue($table, $row);
-
                // List of items to be rendered
-               $itemList = '';
-               if ($GLOBALS['TCA'][$table]['types'][$recordTypeValue]) {
-                       $itemList = $GLOBALS['TCA'][$table]['types'][$recordTypeValue]['showitem'];
-                       // Inline may override the type value - setting is given down from InlineRecordContainer if so - used primarily for FAL
-                       $overruleTypesArray = $this->globalOptions['overruleTypesArray'];
-                       if (isset($overruleTypesArray[$recordTypeValue]['showitem'])) {
-                               $itemList = $overruleTypesArray[$recordTypeValue]['showitem'];
-                       }
-               }
+               $itemList = $this->data['processedTca']['types'][$recordTypeValue]['showitem'];
 
                $fieldsArray = GeneralUtility::trimExplode(',', $itemList, TRUE);
-               // Add fields and remove excluded fields
-               $fieldsArray = $this->mergeFieldsWithAddedFields($fieldsArray, $this->getFieldsToAdd($table, $row, $recordTypeValue), $table);
-               $excludeElements = $this->getExcludeElements($table, $row, $recordTypeValue);
-               $fieldsArray = $this->removeExcludeElementsFromFieldArray($fieldsArray, $excludeElements);
 
                // Streamline the fields array
                // First, make sure there is always a --div-- definition for the first element
@@ -95,91 +72,15 @@ class FullRecordContainer extends AbstractContainer {
                        $hasTabs = FALSE;
                }
 
-               $options = $this->globalOptions;
-               $options['fieldsArray'] = $fieldsArray;
-               // Palettes may contain elements that should be excluded, resolved in PaletteContainer
-               $options['excludeElements'] = $excludeElements;
-               $options['recordTypeValue'] = $recordTypeValue;
-               $options['defaultLanguageData'] = $this->defaultLanguageData;
-               $options['defaultLanguageDataDiff'] = $this->defaultLanguageDataDiff;
-               $options['additionalPreviewLanguageData'] = $this->additionalPreviewLanguageData;
-
+               $data = $this->data;
+               $data['fieldsArray'] = $fieldsArray;
                if ($hasTabs) {
-                       $options['renderType'] = 'tabsContainer';
-                       /** @var NodeFactory $nodeFactory */
-                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                       $resultArray = $nodeFactory->create($options)->render();
+                       $data['renderType'] = 'tabsContainer';
                } else {
-                       $options['renderType'] = 'noTabsContainer';
-                       /** @var NodeFactory $nodeFactory */
-                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                       $resultArray = $nodeFactory->create($options)->render();
-               }
-
-               return $resultArray;
-       }
-
-       /**
-        * Finds possible field to add to the form, based on subtype fields.
-        *
-        * @param string $table Table name, MUST be in $GLOBALS['TCA']
-        * @param array $row A record from table.
-        * @param string $typeNum A "type" pointer value, probably the one calculated based on the record array.
-        * @return array An array containing two values: 1) Another array containing field names to add and 2) the subtype value field.
-        */
-       protected function getFieldsToAdd($table, $row, $typeNum) {
-               $addElements = array();
-               $subTypeField = '';
-               if ($GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field']) {
-                       $subTypeField = $GLOBALS['TCA'][$table]['types'][$typeNum]['subtype_value_field'];
-                       if (trim($GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_addlist'][$row[$subTypeField]])) {
-                               $addElements = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['types'][$typeNum]['subtypes_addlist'][$row[$subTypeField]], TRUE);
-                       }
+                       $data['renderType'] = 'noTabsContainer';
                }
-               return array($addElements, $subTypeField);
-       }
 
-       /**
-        * Merges the current [types][showitem] array with the array of fields to add for the current subtype field of the "type" value.
-        *
-        * @param array $fields A [types][showitem] list of fields, exploded by ",
-        * @param array $fieldsToAdd The output from getFieldsToAdd()
-        * @param string $table The table name, if we want to consider its palettes when positioning the new elements
-        * @return array Return the modified $fields array.
-        */
-       protected function mergeFieldsWithAddedFields($fields, $fieldsToAdd, $table = '') {
-               if (!empty($fieldsToAdd[0])) {
-                       $c = 0;
-                       $found = FALSE;
-                       foreach ($fields as $fieldInfo) {
-                               list($fieldName, $label, $paletteName) = GeneralUtility::trimExplode(';', $fieldInfo);
-                               if ($fieldName === $fieldsToAdd[1]) {
-                                       $found = TRUE;
-                               } elseif ($fieldName === '--palette--' && $paletteName && $table !== '') {
-                                       // Look inside the palette
-                                       if (is_array($GLOBALS['TCA'][$table]['palettes'][$paletteName])) {
-                                               $itemList = $GLOBALS['TCA'][$table]['palettes'][$paletteName]['showitem'];
-                                               if ($itemList) {
-                                                       $paletteFields = GeneralUtility::trimExplode(',', $itemList, TRUE);
-                                                       foreach ($paletteFields as $info) {
-                                                               $fieldParts = GeneralUtility::trimExplode(';', $info);
-                                                               $theField = $fieldParts[0];
-                                                               if ($theField === $fieldsToAdd[1]) {
-                                                                       $found = TRUE;
-                                                                       break 1;
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                               if ($found) {
-                                       array_splice($fields, $c + 1, 0, $fieldsToAdd[0]);
-                                       break;
-                               }
-                               $c++;
-                       }
-               }
-               return $fields;
+               return $this->nodeFactory->create($data)->render();
        }
 
        /**
index d48ea72..f224d05 100644 (file)
@@ -14,6 +14,10 @@ namespace TYPO3\CMS\Backend\Form\Container;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
+use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -21,7 +25,6 @@ use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Backend\Form\DataPreprocessor;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
@@ -29,7 +32,6 @@ use TYPO3\CMS\Backend\Utility\IconUtility;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
-use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Inline element entry container.
@@ -62,9 +64,13 @@ class InlineControlContainer extends AbstractContainer {
        protected $iconFactory;
 
        /**
-        * Construct to initialize class variables.
+        * Container objects give $nodeFactory down to other containers.
+        *
+        * @param NodeFactory $nodeFactory
+        * @param array $data
         */
-       public function __construct() {
+       public function __construct(NodeFactory $nodeFactory, array $data) {
+               parent::__construct($nodeFactory, $data);
                $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
        }
 
@@ -76,17 +82,17 @@ class InlineControlContainer extends AbstractContainer {
        public function render() {
                $languageService = $this->getLanguageService();
 
-               $this->inlineData = $this->globalOptions['inlineData'];
+               $this->inlineData = $this->data['inlineData'];
 
                /** @var InlineStackProcessor $inlineStackProcessor */
                $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
                $this->inlineStackProcessor = $inlineStackProcessor;
-               $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
+               $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
 
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $field = $this->globalOptions['fieldName'];
-               $parameterArray = $this->globalOptions['parameterArray'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $field = $this->data['fieldName'];
+               $parameterArray = $this->data['parameterArray'];
 
                $resultArray = $this->initializeResultArray();
                $html = '';
@@ -132,11 +138,12 @@ class InlineControlContainer extends AbstractContainer {
                // e.g. data[<table>][<uid>][<field>]
                $nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
                // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
-               $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
 
                // Get the records related to this inline record
+               /** @var InlineRelatedRecordResolver $inlineRelatedRecordResolver */
                $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
-               $relatedRecords = $inlineRelatedRecordResolver->getRelatedRecords($table, $field, $row, $parameterArray, $config, $this->globalOptions['inlineFirstPid']);
+               $relatedRecords = $inlineRelatedRecordResolver->getRelatedRecords($table, $field, $row, $parameterArray, $config, $this->data['inlineFirstPid']);
 
                // Set the first and last record to the config array
                $relatedRecordsUids = array_keys($relatedRecords['records']);
@@ -162,7 +169,7 @@ class InlineControlContainer extends AbstractContainer {
                                'hmac' => GeneralUtility::hmac(serialize($config)),
                        ),
                );
-               $this->inlineData['nested'][$nameObject] = $this->globalOptions['tabAndInlineStack'];
+               $this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
 
                // If relations are required to be unique, get the uids that have already been used on the foreign side of the relation
                if ($config['foreign_unique']) {
@@ -231,18 +238,16 @@ class InlineControlContainer extends AbstractContainer {
                $relationList = array();
                if (!empty($relatedRecords['records'])) {
                        foreach ($relatedRecords['records'] as $rec) {
-                               $options = $this->globalOptions;
+                               $options = $this->data;
                                $options['inlineRelatedRecordToRender'] = $rec;
                                $options['inlineRelatedRecordConfig'] = $config;
                                $options['inlineData'] = $this->inlineData;
                                $options['inlineStructure'] = $inlineStackProcessor->getStructure();
                                $options['renderType'] = 'inlineRecordContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
                                try {
                                        // This container may raise an access denied exception, to not kill further processing,
                                        // just a simple "empty" return is created here to ignore this field.
-                                       $childArray = $nodeFactory->create($options)->render();
+                                       $childArray = $this->nodeFactory->create($options)->render();
                                } catch (AccessDeniedException $e) {
                                        $childArray = $this->initializeResultArray();
                                }
@@ -330,63 +335,37 @@ class InlineControlContainer extends AbstractContainer {
         * @return mixed Array of possible record items; FALSE if type is "group/db", then everything could be "possible
         */
        protected function getPossibleRecords($table, $field, $row, $conf, $checkForConfField = 'foreign_selector') {
-               $backendUser = $this->getBackendUserAuthentication();
-               $languageService = $this->getLanguageService();
-
-               // ctrl configuration from TCA:
-               $tcaTableCtrl = $GLOBALS['TCA'][$table]['ctrl'];
                // Field configuration from TCA:
                $foreign_check = $conf[$checkForConfField];
                $foreignConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_check);
                $PA = $foreignConfig['PA'];
-               $config = $PA['fieldConf']['config'];
                if ($foreignConfig['type'] == 'select') {
-                       // Getting the selector box items from the system
-                       $selItems = FormEngineUtility::addSelectOptionsToItemArray(
-                               FormEngineUtility::initItemArray($PA['fieldConf']),
-                               $PA['fieldConf'],
-                               FormEngineUtility::getTSconfigForTableRow($table, $row),
-                               $field
-                       );
-
-                       // Possibly filter some items:
-                       $selItems = ArrayUtility::keepItemsInArray(
-                               $selItems,
-                               $PA['fieldTSConfig']['keepItems'],
-                               function ($value) {
-                                       return $value[1];
-                               }
-                       );
-
-                       // Possibly add some items:
-                       $selItems = FormEngineUtility::addItems($selItems, $PA['fieldTSConfig']['addItems.']);
-                       if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
-                               $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
-                               $selItems = $dataPreprocessor->procItems($selItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $field);
-                       }
-                       // Possibly remove some items:
-                       $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
-                       foreach ($selItems as $tk => $p) {
-                               // Checking languages and authMode:
-                               $languageDeny = $tcaTableCtrl['languageField'] && (string)$tcaTableCtrl['languageField'] === $field && !$backendUser->checkLanguageAccess($p[1]);
-                               $authModeDeny = $config['type'] == 'select' && $config['authMode'] && !$backendUser->checkAuthMode($table, $field, $p[1], $config['authMode']);
-                               if (in_array($p[1], $removeItems) || $languageDeny || $authModeDeny) {
-                                       unset($selItems[$tk]);
-                               } else {
-                                       if (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) {
-                                               $selItems[$tk][0] = htmlspecialchars($languageService->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]));
-                                       }
-                                       if (isset($PA['fieldTSConfig']['altIcons.'][$p[1]])) {
-                                               $selItems[$tk][2] = $PA['fieldTSConfig']['altIcons.'][$p[1]];
-                                       }
-                               }
-                               // Removing doktypes with no access:
-                               if (($table === 'pages' || $table === 'pages_language_overlay') && $field === 'doktype') {
-                                       if (!($backendUser->isAdmin() || GeneralUtility::inList($backendUser->groupData['pagetypes_select'], $p[1]))) {
-                                               unset($selItems[$tk]);
-                                       }
-                               }
-                       }
+                       $pageTsConfig['TCEFORM.']['dummyTable.']['dummyField.'] = $PA['fieldTSConfig'];
+                       $selectDataInput = [
+                               'tableName' => 'dummyTable',
+                               'command' => 'edit',
+                               'pageTsConfigMerged' => $pageTsConfig,
+                               'vanillaTableTca' => [
+                                       'ctrl' => [],
+                                       'columns' => [
+                                               'dummyField' => $PA['fieldConf'],
+                                       ],
+                               ],
+                               'processedTca' => [
+                                       'ctrl' => [],
+                                       'columns' => [
+                                               'dummyField' => $PA['fieldConf'],
+                                       ],
+                               ],
+                       ];
+
+                       /** @var OnTheFly $formDataGroup */
+                       $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
+                       $formDataGroup->setProviderList([ TcaSelectItems::class ]);
+                       /** @var FormDataCompiler $formDataCompiler */
+                       $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+                       $compilerResult = $formDataCompiler->compile($selectDataInput);
+                       $selItems = $compilerResult['processedTca']['columns']['dummyField']['config']['items'];
                } else {
                        $selItems = FALSE;
                }
@@ -423,7 +402,7 @@ class InlineControlContainer extends AbstractContainer {
         */
        protected function getLevelInteractionLink($type, $objectPrefix, $conf = array()) {
                $languageService = $this->getLanguageService();
-               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                $attributes = array();
                switch ($type) {
                        case 'newRecord':
@@ -523,8 +502,8 @@ class InlineControlContainer extends AbstractContainer {
                ArrayUtility::mergeRecursiveWithOverrule($config, $conf);
                $foreign_table = $config['foreign_table'];
                $allowed = $config['allowed'];
-               $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']) . '-' . $foreign_table;
-               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $foreign_table;
+               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                $mode = 'db';
                $showUpload = FALSE;
                if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
@@ -571,7 +550,7 @@ class InlineControlContainer extends AbstractContainer {
                                $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
                                $item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
                                        ' . $buttonStyle . '
-                                       data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid'])) . '"
+                                       data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid'])) . '"
                                        data-insert-dropzone-before="1"
                                        data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
                                        data-file-allowed="' . htmlspecialchars($allowed) . '"
@@ -618,7 +597,7 @@ class InlineControlContainer extends AbstractContainer {
                // @todo $disabled is not present - should be read from config?
                $disabled = FALSE;
                if (!$disabled) {
-                       $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);;
+                       $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);;
                        // Create option tags:
                        $opt = array();
                        $styleAttrValue = '';
index ded05fa..9bade8a 100644 (file)
@@ -15,6 +15,10 @@ namespace TYPO3\CMS\Backend\Form\Container;
  */
 
 use TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface;
+use TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
+use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -33,8 +37,6 @@ 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\Exception\AccessDeniedException;
-use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Render a single inline record relation.
@@ -73,9 +75,13 @@ class InlineRecordContainer extends AbstractContainer {
        protected $iconFactory;
 
        /**
-        * Construct
+        * Default constructor
+        *
+        * @param NodeFactory $nodeFactory
+        * @param array $data
         */
-       public function __construct() {
+       public function __construct(NodeFactory $nodeFactory, array $data) {
+               parent::__construct($nodeFactory, $data);
                $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
        }
 
@@ -83,31 +89,31 @@ class InlineRecordContainer extends AbstractContainer {
         * Entry method
         *
         * @return array As defined in initializeResultArray() of AbstractNode
+        * @throws AccessDeniedContentEditException
         */
        public function render() {
-               $this->inlineData = $this->globalOptions['inlineData'];
+               $this->inlineData = $this->data['inlineData'];
 
                /** @var InlineStackProcessor $inlineStackProcessor */
                $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
                $this->inlineStackProcessor = $inlineStackProcessor;
-               $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
+               $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
 
                $this->initHookObjects();
 
-               $row = $this->globalOptions['databaseRow'];
+               $row = $this->data['databaseRow'];
                $parentUid = $row['uid'];
-               $record = $this->globalOptions['inlineRelatedRecordToRender'];
-               $config = $this->globalOptions['inlineRelatedRecordConfig'];
+               $record = $this->data['inlineRelatedRecordToRender'];
+               $config = $this->data['inlineRelatedRecordConfig'];
 
                $foreign_table = $config['foreign_table'];
                $foreign_selector = $config['foreign_selector'];
                $resultArray = $this->initializeResultArray();
-               $html = '';
 
                // Send a mapping information to the browser via JSON:
                // e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField>
                $formPrefix = $inlineStackProcessor->getCurrentStructureFormPrefix();
-               $domObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $domObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                $this->inlineData['map'][$formPrefix] = $domObjectId;
 
                $resultArray['inlineData'] = $this->inlineData;
@@ -118,11 +124,17 @@ class InlineRecordContainer extends AbstractContainer {
                $isVirtualRecord = isset($record['__virtual']) && $record['__virtual'];
                // If there is a selector field, normalize it:
                if ($foreign_selector) {
-                       $record[$foreign_selector] = $this->normalizeUid($record[$foreign_selector]);
+                       $valueToNormalize = $record[$foreign_selector];
+                       if (is_array($record[$foreign_selector])) {
+                               // @todo: this can be kicked again if always prepared rows are handled here
+                               $valueToNormalize = implode(',', $record[$foreign_selector]);
+                       }
+                       $record[$foreign_selector] = $this->normalizeUid($valueToNormalize);
                }
                if (!$this->checkAccess(($isNewRecord ? 'new' : 'edit'), $foreign_table, $record['uid'])) {
                        // This is caught by InlineControlContainer or FormEngine, they need to handle this case differently
-                       throw new AccessDeniedException('Access denied', 1437081986);
+                       // @todo: This is actually not the correct exception, but this code will vanish if inline data stuff is within provider
+                       throw new AccessDeniedContentEditException('Access denied', 1437081986);
                }
                // Get the current naming scheme for DOM name/id attributes:
                $appendFormFieldNames = '[' . $foreign_table . '][' . $record['uid'] . ']';
@@ -222,20 +234,44 @@ class InlineRecordContainer extends AbstractContainer {
         * @return string The rendered form
         */
        protected function renderRecord($table, array $row, array $overruleTypesArray = array()) {
-               $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
-               $options = $this->globalOptions;
-               $options['inlineData'] = $this->inlineData;
-               $options['databaseRow'] = $row;
-               $options['table'] = $table;
+               $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
+
+               $options = $this->data['tabAndInlineStack'];
                $options['tabAndInlineStack'][] = array(
                        'inline',
                        $domObjectId . '-' . $table . '-' . $row['uid'],
                );
-               $options['overruleTypesArray'] = $overruleTypesArray;
+
+               $command = 'edit';
+               $vanillaUid = (int)$row['uid'];
+
+               // If dealing with a new record, take pid as vanillaUid and set command to new
+               if (!MathUtility::canBeInterpretedAsInteger($row['uid'])) {
+                       $command = 'new';
+                       $vanillaUid = (int)$row['pid'];
+               }
+
+               /** @var TcaDatabaseRecord $formDataGroup */
+               $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
+               /** @var FormDataCompiler $formDataCompiler */
+               $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+               $formDataCompilerInput = [
+                       'command' => $command,
+                       'vanillaUid' => $vanillaUid,
+                       'tableName' => $table,
+                       'inlineData' => $this->inlineData,
+                       'tabAndInlineStack' => $options['tabAndInlineStack'],
+                       'overruleTypesArray' => $overruleTypesArray,
+                       'inlineStructure' => $this->data['inlineStructure'],
+               ];
+               $options = $formDataCompiler->compile($formDataCompilerInput);
                $options['renderType'] = 'fullRecordContainer';
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               return $nodeFactory->create($options)->render();
+
+               if (!MathUtility::canBeInterpretedAsInteger($row['uid'])) {
+                       $options['databaseRow']['uid'] = $row['uid'];
+               }
+
+               return $this->nodeFactory->create($options)->render();
        }
 
        /**
@@ -243,7 +279,7 @@ class InlineRecordContainer extends AbstractContainer {
         * so two tables are combined (the intermediate table with attributes and the sub-embedded table).
         * -> This is a direct embedding over two levels!
         *
-        * @param array $record The table record of the child/embedded table (normaly post-processed by \TYPO3\CMS\Backend\Form\DataPreprocessor)
+        * @param array $record The table record of the child/embedded table
         * @param string $appendFormFieldNames The [<table>][<uid>] of the parent record (the intermediate table)
         * @param array $config content of $PA['fieldConf']['config']
         * @return array As defined in initializeResultArray() of AbstractNode
@@ -258,13 +294,13 @@ class InlineRecordContainer extends AbstractContainer {
                if ($foreign_selector && $config['appearance']['useCombination']) {
                        $comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config'];
                        // If record does already exist, load it:
+                       /** @var InlineRelatedRecordResolver $inlineRelatedRecordResolver */
+                       $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
                        if ($record[$foreign_selector] && MathUtility::canBeInterpretedAsInteger($record[$foreign_selector])) {
-                               $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
                                $comboRecord = $inlineRelatedRecordResolver->getRecord($comboConfig['foreign_table'], $record[$foreign_selector]);
                                $isNewRecord = FALSE;
                        } else {
-                               $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
-                               $comboRecord = $inlineRelatedRecordResolver->getNewRecord($this->globalOptions['inlineFirstPid'], $comboConfig['foreign_table']);
+                               $comboRecord = $inlineRelatedRecordResolver->getNewRecord($this->data['inlineFirstPid'], $comboConfig['foreign_table']);
                                $isNewRecord = TRUE;
                        }
 
@@ -314,7 +350,7 @@ class InlineRecordContainer extends AbstractContainer {
         */
        protected function renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config, $isVirtualRecord = FALSE) {
                // Init:
-               $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                $objectId = $domObjectId . '-' . $foreign_table . '-' . $rec['uid'];
                // We need the returnUrl of the main script when loading the fields via AJAX-call (to correct wizard code, so include it as 3rd parameter)
                // Pre-Processing:
@@ -381,7 +417,16 @@ class InlineRecordContainer extends AbstractContainer {
                }
 
                $altText = BackendUtility::getRecordIconAltText($rec, $foreign_table);
-               $iconImg = IconUtility::getSpriteIconForRecord($foreign_table, $rec, array('title' => htmlspecialchars($altText), 'id' => $objectId . '_icon'));
+
+               // @todo: Hack for getSpriteIconForRecord
+               $recordForIconUtility = $rec;
+               if (isset($GLOBALS['TCA'][$foreign_table]['ctrl']['typeicon_column']) && is_array($rec[$GLOBALS['TCA'][$foreign_table]['ctrl']['typeicon_column']])) {
+                       $recordForIconUtility[$GLOBALS['TCA'][$foreign_table]['ctrl']['typeicon_column']] = implode(
+                               ',',
+                               $rec[$GLOBALS['TCA'][$foreign_table]['ctrl']['typeicon_column']]
+                       );
+               }
+               $iconImg = IconUtility::getSpriteIconForRecord($foreign_table, $recordForIconUtility, array('title' => htmlspecialchars($altText), 'id' => $objectId . '_icon'));
                $label = '<span id="' . $objectId . '_label">' . $recTitle . '</span>';
                $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config, $isVirtualRecord);
                $thumbnail = FALSE;
@@ -456,7 +501,7 @@ class InlineRecordContainer extends AbstractContainer {
                $isSysFileReferenceTable = $foreign_table === 'sys_file_reference';
                $isOnSymmetricSide = RelationHandler::isOnSymmetricSide($parentUid, $config, $rec);
                $enableManualSorting = $tcaTableCtrl['sortby'] || $config['MM'] || !$isOnSymmetricSide && $config['foreign_sortby'] || $isOnSymmetricSide && $config['symmetric_sortby'];
-               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+               $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                $nameObjectFt = $nameObject . '-' . $foreign_table;
                $nameObjectFtId = $nameObjectFt . '-' . $rec['uid'];
                $calcPerms = $backendUser->calcPerms(BackendUtility::readPageAccess($rec['pid'], $backendUser->getPagePermsClause(1)));
@@ -532,10 +577,14 @@ class InlineRecordContainer extends AbstractContainer {
                        }
                        // "Edit" link:
                        if (($rec['table_local'] === 'sys_file') && !$isNewItem) {
+                               $sys_language_uid = 0;
+                               if (!empty($rec['sys_language_uid'])) {
+                                       $sys_language_uid = $rec['sys_language_uid'][0];
+                               }
                                $recordInDatabase = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
                                        'uid',
                                        'sys_file_metadata',
-                                       'file = ' . (int)substr($rec['uid_local'], 9) . ' AND sys_language_uid = ' . $rec['sys_language_uid']
+                                       'file = ' . (int)substr($rec['uid_local'], 9) . ' AND sys_language_uid = ' . $sys_language_uid
                                );
                                if ($backendUser->check('tables_modify', 'sys_file_metadata')) {
                                        $url = BackendUtility::getModuleUrl('record_edit', array(
@@ -637,6 +686,7 @@ class InlineRecordContainer extends AbstractContainer {
         * @return bool Returns TRUE is the user has access, or FALSE if not
         */
        protected function checkAccess($cmd, $table, $theUid) {
+               // @todo This should be within data provider
                $backendUser = $this->getBackendUserAuthentication();
                // Checking if the user has permissions? (Only working as a precaution, because the final permission check is always down in TCE. But it's good to notify the user on beforehand...)
                // First, resetting flags.
@@ -648,8 +698,8 @@ class InlineRecordContainer extends AbstractContainer {
                // If the command is to create a NEW record...:
                if ($cmd === 'new') {
                        // If the pid is numerical, check if it's possible to write to this page:
-                       if (MathUtility::canBeInterpretedAsInteger($this->globalOptions['inlineFirstPid'])) {
-                               $calcPRec = BackendUtility::getRecord('pages', $this->globalOptions['inlineFirstPid']);
+                       if (MathUtility::canBeInterpretedAsInteger($this->data['inlineFirstPid'])) {
+                               $calcPRec = BackendUtility::getRecord('pages', $this->data['inlineFirstPid']);
                                if (!is_array($calcPRec)) {
                                        return FALSE;
                                }
@@ -732,7 +782,7 @@ class InlineRecordContainer extends AbstractContainer {
         * @return bool TRUE=expand, FALSE=collapse
         */
        protected function getExpandedCollapsedState($table, $uid) {
-               $inlineView = $this->globalOptions['inlineExpandCollapseStateArray'];
+               $inlineView = $this->data['inlineExpandCollapseStateArray'];
                // @todo Add checking/cleaning for unused tables, records, etc. to save space in uc-field
                if (isset($inlineView[$table]) && is_array($inlineView[$table])) {
                        if (in_array($uid, $inlineView[$table]) !== FALSE) {
index 800ada7..f3e7fb5 100644 (file)
@@ -16,8 +16,6 @@ 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.
@@ -34,49 +32,42 @@ class ListOfFieldsContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $list = $this->globalOptions['fieldListToRender'];
+               $table = $this->data['tableName'];
+               $fieldListToRender = $this->data['fieldListToRender'];
+               $recordTypeValue = $this->data['recordTypeValue'];
 
-               if (!$GLOBALS['TCA'][$table]) {
-                       return $this->initializeResultArray();
-               }
-
-               $languageService = $this->getLanguageService();
                // Load the description content for the table if requested
                if ($GLOBALS['TCA'][$table]['interface']['always_description']) {
+                       $languageService = $this->getLanguageService();
                        $languageService->loadSingleTableDescription($table);
                }
 
-               // If this is a localized record, stuff data from original record to local registry, will then be given to child elements
-               $this->registerDefaultLanguageData($table, $row);
+               $fieldListToRender = array_unique(GeneralUtility::trimExplode(',', $fieldListToRender, TRUE));
+
+               $fieldsByShowitem = $this->data['processedTca']['types'][$recordTypeValue]['showitem'];
+               $fieldsByShowitem = GeneralUtility::trimExplode(',', $fieldsByShowitem, TRUE);
 
-               $list = array_unique(GeneralUtility::trimExplode(',', $list, TRUE));
-               $typesFieldConfig = BackendUtility::getTCAtypes($table, $row, 1);
-               $finalFieldsConfiguration = array();
-               foreach ($list as $singleField) {
-                       if (!is_array($GLOBALS['TCA'][$table]['columns'][$singleField])) {
-                               continue;
+               $finalFieldsList = array();
+               foreach ($fieldListToRender as $fieldName) {
+                       $found = FALSE;
+                       foreach ($fieldsByShowitem as $fieldByShowitem) {
+                               $fieldByShowitemArray = $this->explodeSingleFieldShowItemConfiguration($fieldByShowitem);
+                               if ($fieldByShowitemArray['fieldName'] === $fieldName) {
+                                       $found = TRUE;
+                                       $finalFieldsList[] = implode(';', $fieldByShowitemArray);
+                                       break;
+                               }
                        }
-                       if (isset($typesFieldConfig[$singleField]['origString'])) {
-                               $fieldConfiguration = $this->explodeSingleFieldShowItemConfiguration($typesFieldConfig[$singleField]['origString']);
-                               // Fields of sub palettes should not be rendered
-                               $fieldConfiguration['paletteName'] = '';
-                       } else {
-                               $fieldConfiguration = array(
-                                       'fieldName' => $singleField,
-                               );
+                       if (!$found) {
+                               // @todo: Field is shown even if it is not in type showitem list? Will be shown if field is in a palette.
+                               $finalFieldsList[] = $fieldName;
                        }
-                       $finalFieldsConfiguration[] = implode(';', $fieldConfiguration);
                }
 
-               $options = $this->globalOptions;
-               $options['fieldsArray'] = $finalFieldsConfiguration;
+               $options = $this->data;
+               $options['fieldsArray'] = $finalFieldsList;
                $options['renderType'] = 'paletteAndSingleContainer';
-               $options['recordTypeValue'] = $this->getRecordTypeValue($table, $row);
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               return $nodeFactory->create($options)->render();
+               return $this->nodeFactory->create($options)->render();
        }
 
        /**
index 4606e21..5152a31 100644 (file)
@@ -14,9 +14,6 @@ namespace TYPO3\CMS\Backend\Form\Container;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Backend\Form\NodeFactory;
-
 /**
  * Handle a record that has no tabs.
  *
@@ -31,11 +28,9 @@ class NoTabsContainer extends AbstractContainer {
         * @return array As defined in initializeResultArray() of AbstractNode
         */
        public function render() {
-               $options = $this->globalOptions;
+               $options = $this->data;
                $options['renderType'] = 'paletteAndSingleContainer';
-               /** @var NodeFactory $nodeFactory */
-               $nodeFactory = $this->globalOptions['nodeFactory'];
-               $resultArray = $nodeFactory->create($options)->render();
+               $resultArray = $this->nodeFactory->create($options)->render();
                $resultArray['html'] = '<div class="tab-content">' . $resultArray['html'] . '</div>';
                return $resultArray;
        }
diff --git a/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php b/typo3/sysext/backend/Classes/Form/Container/OuterWrapContainer.php
new file mode 100644 (file)
index 0000000..a6950fd
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Container;
+
+/*
+ * 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\Template\DocumentTemplate;
+use TYPO3\CMS\Backend\Utility\IconUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+
+/**
+ * Render header and footer row.
+ *
+ * This is an entry container called from controllers.
+ * It wraps the title and a footer around the main html.
+ * It either calls a FullRecordContainer or ListOfFieldsContainer to render
+ * a full record or only some fields from a full record.
+ */
+class OuterWrapContainer extends AbstractContainer {
+
+       /**
+        * Entry method
+        *
+        * @return array As defined in initializeResultArray() of AbstractNode
+        */
+       public function render() {
+               $languageService = $this->getLanguageService();
+               $backendUser = $this->getBackendUserAuthentication();
+
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+
+               $options = $this->data;
+               if (empty($this->data['fieldListToRender'])) {
+                       $options['renderType'] = 'fullRecordContainer';
+               } else {
+                       $options['renderType'] = 'listOfFieldsContainer';
+               }
+               $result = $this->nodeFactory->create($options)->render();
+
+               $childHtml = $result['html'];
+
+               $recordPath = '';
+               // @todo: what is this >= 0 check for? wsol cases?!
+               if ($this->data['effectivePid'] >= 0) {
+                       $permissionsClause = $backendUser->getPagePermsClause(1);
+                       $recordPath = BackendUtility::getRecordPath($this->data['effectivePid'], $permissionsClause, 15);
+               }
+
+               // @todo: Hack for getSpriteIconForRecord
+               $recordForIconUtility = $row;
+               if (isset($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) && is_array($row[$GLOBALS['TCA'][$table]['ctrl']['typeicon_column']])) {
+                       $recordForIconUtility[$GLOBALS['TCA'][$table]['ctrl']['typeicon_column']] = implode(
+                               ',',
+                               $row[$GLOBALS['TCA'][$table]['ctrl']['typeicon_column']]
+                       );
+               }
+               $icon = IconUtility::getSpriteIconForRecord($table, $recordForIconUtility, array('title' => $recordPath));
+
+               // @todo: Could this be done in a more clever way? Does it work at all?
+               $tableTitle = $languageService->sL($this->data['processedTca']['ctrl']['title']);
+
+               if ($this->data['command'] === 'new') {
+                       $newOrUid = ' <span class="typo3-TCEforms-newToken">' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', TRUE) . '</span>';
+
+                       // @todo: There is quite some stuff do to for WS overlays ...
+                       $workspacedPageRecord = BackendUtility::getRecordWSOL('pages', $this->data['effectivePid'], 'title');
+                       $pageTitle = BackendUtility::getRecordTitle('pages', $workspacedPageRecord, TRUE, FALSE);
+                       if ($table === 'pages') {
+                               $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.createNewPage', TRUE);
+                               $pageTitle = sprintf($label, $tableTitle);
+                       } else {
+                               $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.createNewRecord', TRUE);
+                               if ($this->data['effectivePid'] === 0) {
+                                       $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.createNewRecordRootLevel', TRUE);
+                               }
+                               $pageTitle = sprintf($label, $tableTitle, $pageTitle);
+                       }
+               } else {
+                       // DocumentTemplate is needed for wrapClickMenuOnIcon(), the method has no state, simply use fresh instance
+                       /** @var DocumentTemplate $documentTemplate */
+                       $documentTemplate = GeneralUtility::makeInstance(DocumentTemplate::class);
+                       $icon = $documentTemplate->wrapClickMenuOnIcon($icon, $table, $row['uid'], 1, '', '+copy,info,edit,view');
+
+                       $newOrUid = ' <span class="typo3-TCEforms-recUid">[' . htmlspecialchars($row['uid']) . ']</span>';
+
+                       $recordLabel = BackendUtility::getRecordTitle($table, $row, TRUE, FALSE);
+                       if ($table === 'pages') {
+                               $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editPage', TRUE);
+                               $pageTitle = sprintf($label, $tableTitle, $recordLabel);
+                       } else {
+                               $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecord', TRUE);
+                               $workspacedPageRecord = BackendUtility::getRecordWSOL('pages', $row['pid'], 'uid,title');
+                               $pageTitle = BackendUtility::getRecordTitle('pages', $workspacedPageRecord, TRUE, FALSE);
+                               if ($recordLabel === BackendUtility::getNoRecordTitle(TRUE)) {
+                                       $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecordNoTitle', TRUE);
+                               }
+                               if ($this->data['effectivePid'] === 0) {
+                                       $label = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.editRecordRootLevel', TRUE);
+                               }
+                               if ($recordLabel !== BackendUtility::getNoRecordTitle(TRUE)) {
+                                       // Use record title and prepend an edit label.
+                                       $pageTitle = sprintf($label, $tableTitle, $recordLabel, $pageTitle);
+                               } else {
+                                       // Leave out the record title since it is not set.
+                                       $pageTitle = sprintf($label, $tableTitle, $pageTitle);
+                               }
+                       }
+               }
+
+               $html = array();
+               $html[] = '<h1>' . $pageTitle . '</h1>';
+               $html[] = '<div class="typo3-TCEforms">';
+               $html[] =       $childHtml;
+               $html[] =       '<div class="help-block text-right">';
+               $html[] =               $icon . ' <strong>' . htmlspecialchars($tableTitle) . '</strong>' . ' ' . $newOrUid;
+               $html[] =       '</div>';
+               $html[] = '</div>';
+
+               $result['html'] = implode(LF, $html);
+               return $result;
+       }
+
+       /**
+        * @return LanguageService
+        */
+       protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @return BackendUserAuthentication
+        */
+       protected function getBackendUserAuthentication() {
+               return $GLOBALS['BE_USER'];
+       }
+
+}
index 43cfa40..9967b63 100644 (file)
@@ -18,7 +18,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Backend\Form\NodeFactory;
 
 /**
  * Handle palettes and single fields.
@@ -45,7 +44,6 @@ class PaletteAndSingleContainer extends AbstractContainer {
         */
        public function render() {
                $languageService = $this->getLanguageService();
-               $table = $this->globalOptions['table'];
 
                /**
                 * The first code block creates a target structure array to later create the final
@@ -110,7 +108,7 @@ class PaletteAndSingleContainer extends AbstractContainer {
                // Create an intermediate structure of rendered sub elements and elements nested in palettes
                $targetStructure = array();
                $mainStructureCounter = -1;
-               $fieldsArray = $this->globalOptions['fieldsArray'];
+               $fieldsArray = $this->data['fieldsArray'];
                $this->resultArray = $this->initializeResultArray();
                foreach ($fieldsArray as $fieldString) {
                        $fieldConfiguration = $this->explodeSingleFieldShowItemConfiguration($fieldString);
@@ -127,21 +125,18 @@ class PaletteAndSingleContainer extends AbstractContainer {
                                        );
                                }
                        } else {
-                               if (!is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                               if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
                                        continue;
                                }
 
-                               $options = $this->globalOptions;
+                               $options = $this->data;
                                $options['fieldName'] = $fieldName;
 
                                $options['renderType'] = 'singleFieldContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $childResultArray = $nodeFactory->create($options)->render();
+                               $childResultArray = $this->nodeFactory->create($options)->render();
 
                                if (!empty($childResultArray['html'])) {
                                        $mainStructureCounter ++;
-
                                        $targetStructure[$mainStructureCounter] = array(
                                                'type' => 'single',
                                                'fieldName' => $fieldConfiguration['fieldName'],
@@ -162,7 +157,7 @@ class PaletteAndSingleContainer extends AbstractContainer {
                                $paletteName = $element['fieldName'];
                                $paletteElementsHtml = $this->renderInnerPaletteContent($element);
 
-                               $isHiddenPalette = !empty($GLOBALS['TCA'][$table]['palettes'][$paletteName]['isHiddenPalette']);
+                               $isHiddenPalette = !empty($this->data['processedTca']['palettes'][$paletteName]['isHiddenPalette']);
 
                                $paletteElementsHtml = '<div class="row">' . $paletteElementsHtml . '</div>';
 
@@ -189,17 +184,14 @@ class PaletteAndSingleContainer extends AbstractContainer {
         * @return array
         */
        protected function createPaletteContentArray($paletteName) {
-               $table = $this->globalOptions['table'];
-               $excludeElements = $this->globalOptions['excludeElements'];
-
                // palette needs a palette name reference, otherwise it does not make sense to try rendering of it
-               if (empty($paletteName) || empty($GLOBALS['TCA'][$table]['palettes'][$paletteName]['showitem'])) {
+               if (empty($paletteName) || empty($this->data['processedTca']['palettes'][$paletteName]['showitem'])) {
                        return array();
                }
 
                $resultStructure = array();
                $foundRealElement = FALSE; // Set to true if not only line breaks were rendered
-               $fieldsArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['palettes'][$paletteName]['showitem'], TRUE);
+               $fieldsArray = GeneralUtility::trimExplode(',', $this->data['processedTca']['palettes'][$paletteName]['showitem'], TRUE);
                foreach ($fieldsArray as $fieldString) {
                        $fieldArray = $this->explodeSingleFieldShowItemConfiguration($fieldString);
                        $fieldName = $fieldArray['fieldName'];
@@ -208,16 +200,14 @@ class PaletteAndSingleContainer extends AbstractContainer {
                                        'type' => 'linebreak',
                                );
                        } else {
-                               if (in_array($fieldName, $excludeElements, TRUE) || !is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                               if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
                                        continue;
                                }
-                               $options = $this->globalOptions;
+                               $options = $this->data;
                                $options['fieldName'] = $fieldName;
 
                                $options['renderType'] = 'singleFieldContainer';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $singleFieldContentArray = $nodeFactory->create($options)->render();
+                               $singleFieldContentArray = $this->nodeFactory->create($options)->render();
 
                                if (!empty($singleFieldContentArray['html'])) {
                                        $foundRealElement = TRUE;
@@ -368,7 +358,7 @@ class PaletteAndSingleContainer extends AbstractContainer {
                        $paletteFieldClasses[] = $class;
                }
 
-               $label = BackendUtility::wrapInHelp($this->globalOptions['table'], $fieldName, htmlspecialchars($element['fieldLabel']));
+               $label = BackendUtility::wrapInHelp($this->data['tableName'], $fieldName, htmlspecialchars($element['fieldLabel']));
 
                $content = array();
                $content[] = '<div class="' . implode(' ', $paletteFieldClasses) . '">';
@@ -390,15 +380,22 @@ class PaletteAndSingleContainer extends AbstractContainer {
         */
        protected function getSingleFieldLabel($fieldName, $labelFromShowItem) {
                $languageService = $this->getLanguageService();
-               $table = $this->globalOptions['table'];
+               $table = $this->data['tableName'];
                $label = $labelFromShowItem;
-               if (!empty($GLOBALS['TCA'][$table]['columns'][$fieldName]['label'])) {
-                       $label = $GLOBALS['TCA'][$table]['columns'][$fieldName]['label'];
+               if (!empty($this->data['processedTca']['columns'][$fieldName]['label'])) {
+                       $label = $this->data['processedTca']['columns'][$fieldName]['label'];
                }
                if (!empty($labelFromShowItem)) {
                        $label = $labelFromShowItem;
                }
-               $fieldTSConfig = FormEngineUtility::getTSconfigForTableRow($table, $this->globalOptions['databaseRow'], $fieldName);
+
+               $fieldTSConfig = [];
+               if (isset($this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'])
+                       && is_array($this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'])
+               ) {
+                       $fieldTSConfig = $this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'];
+               }
+
                if (!empty($fieldTSConfig['label'])) {
                        $label = $fieldTSConfig['label'];
                }
@@ -415,11 +412,10 @@ class PaletteAndSingleContainer extends AbstractContainer {
         * @return bool TRUE if user and noTableWrapping is set
         */
        protected function isUserNoTableWrappingField($element) {
-               $table = $this->globalOptions['table'];
                $fieldName = $element['fieldName'];
                if (
-                       $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['type'] === 'user'
-                       && !empty($GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['noTableWrapping'])
+                       $this->data['processedTca']['columns'][$fieldName]['config']['type'] === 'user'
+                       && !empty($this->data['processedTca']['columns'][$fieldName]['config']['noTableWrapping'])
                ) {
                        return TRUE;
                }
index 1ff7073..ef3e94c 100644 (file)
@@ -32,7 +32,7 @@ use TYPO3\CMS\Core\Database\RelationHandler;
  * Container around a "single field".
  *
  * This container is the last one in the chain before processing is hand over to single element classes.
- * If a single field is of type flex or inline, it however creates FlexFormContainer or InlineControlContainer.
+ * If a single field is of type flex or inline, it however creates FlexFormLanguageContainer or InlineControlContainer.
  *
  * The container does various checks and processing for a given single fields, for example it resolves
  * display conditions and the HTML to compare compare different languages.
@@ -49,37 +49,29 @@ class SingleFieldContainer extends AbstractContainer {
                $languageService = $this->getLanguageService();
                $resultArray = $this->initializeResultArray();
 
-               $table = $this->globalOptions['table'];
-               $row = $this->globalOptions['databaseRow'];
-               $fieldName = $this->globalOptions['fieldName'];
+               $table = $this->data['tableName'];
+               $row = $this->data['databaseRow'];
+               $fieldName = $this->data['fieldName'];
 
-               if (!is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+               // @todo: it should be safe at this point, this array exists ...
+               if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
                        return $resultArray;
                }
 
                $parameterArray = array();
-               $parameterArray['fieldConf'] = $GLOBALS['TCA'][$table]['columns'][$fieldName];
-
-               // Overlay fieldConf with possible defined columnsOverrides of given record type
-               $recordTypeValue = $this->globalOptions['recordTypeValue'];
-               // Hint: 0 is a valid $recordTypeValue, !empty() does not work here
-               if (trim($recordTypeValue) !== '' && is_array($GLOBALS['TCA'][$table]['types'][$recordTypeValue]['columnsOverrides'][$fieldName])) {
-                       // Merge columnsOverrides of this field over existing field configuration
-                       ArrayUtility::mergeRecursiveWithOverrule(
-                               $parameterArray['fieldConf'],
-                               $GLOBALS['TCA'][$table]['types'][$recordTypeValue]['columnsOverrides'][$fieldName]
-                       );
-               }
+               $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName];
 
                // A couple of early returns in case the field should not be rendered
                // Check if this field is configured and editable according to exclude fields and other configuration
                if (
+                       // @todo: another user access rights check!
                        $parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
                        || $parameterArray['fieldConf']['config']['type'] === 'passthrough'
                        // @todo: Drop option "showIfRTE" ?
                        || !$backendUser->isRTE() && $parameterArray['fieldConf']['config']['showIfRTE']
-                       || $GLOBALS['TCA'][$table]['ctrl']['languageField'] && !$parameterArray['fieldConf']['l10n_display'] && $parameterArray['fieldConf']['l10n_mode'] === 'exclude' && ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0)
-                       || $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $this->globalOptions['localizationMode'] && $this->globalOptions['localizationMode'] !== $parameterArray['fieldConf']['l10n_cat']
+                       || $this->data['processedTca']['ctrl']['languageField'] && !$parameterArray['fieldConf']['l10n_display'] && $parameterArray['fieldConf']['l10n_mode'] === 'exclude' && ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0)
+                       // @todo: localizationMode still needs handling!
+                       || $this->data['processedTca']['ctrl']['languageField'] && $this->data['localizationMode'] && $this->data['localizationMode'] !== $parameterArray['fieldConf']['l10n_cat']
                        || $this->inlineFieldShouldBeSkipped()
                ) {
                        return $resultArray;
@@ -93,8 +85,13 @@ class SingleFieldContainer extends AbstractContainer {
                                return $resultArray;
                        }
                }
-               // Fetching the TSconfig for the current table/field. This includes the $row which means that
-               $parameterArray['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($table, $row, $fieldName);
+
+               $parameterArray['fieldTSConfig'] = [];
+               if (isset($this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'])
+                       && is_array($this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'])
+               ) {
+                       $parameterArray['fieldTSConfig'] = $this->data['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'];
+               }
                if ($parameterArray['fieldTSConfig']['disabled']) {
                        return $resultArray;
                }
@@ -103,31 +100,31 @@ class SingleFieldContainer extends AbstractContainer {
                $parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
                $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
                $parameterArray['itemFormElID'] = 'data_' . $table . '_' . $row['uid'] . '_' . $fieldName;
-               $newElementBaseName = $this->globalOptions['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
+               $newElementBaseName = $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
 
                // The value to show in the form field.
                $parameterArray['itemFormElValue'] = $row[$fieldName];
                // Set field to read-only if configured for translated records to show default language content as readonly
                if ($parameterArray['fieldConf']['l10n_display']
                        && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly')
-                       && $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
+                       && $row[$this->data['processedTca']['ctrl']['languageField']] > 0
                ) {
                        $parameterArray['fieldConf']['config']['readOnly'] = TRUE;
-                       $parameterArray['itemFormElValue'] = $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']][$fieldName];
+                       $parameterArray['itemFormElValue'] = $this->data['defaultLanguageData'][$table . ':' . $row['uid']][$fieldName];
                }
 
-               if (strpos($GLOBALS['TCA'][$table]['ctrl']['type'], ':') === FALSE) {
-                       $typeField = $GLOBALS['TCA'][$table]['ctrl']['type'];
+               if (strpos($this->data['processedTca']['ctrl']['type'], ':') === FALSE) {
+                       $typeField = $this->data['processedTca']['ctrl']['type'];
                } else {
-                       $typeField = substr($GLOBALS['TCA'][$table]['ctrl']['type'], 0, strpos($GLOBALS['TCA'][$table]['ctrl']['type'], ':'));
+                       $typeField = substr($this->data['processedTca']['ctrl']['type'], 0, strpos($this->data['processedTca']['ctrl']['type'], ':'));
                }
                // 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'])
+                       !empty($this->data['processedTca']['ctrl']['type'])
                        && $fieldName === $typeField
-                       || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
-                       && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $fieldName)
+                       || !empty($this->data['processedTca']['ctrl']['requestUpdate'])
+                       && GeneralUtility::inList(str_replace(' ', '', $this->data['processedTca']['ctrl']['requestUpdate']), $fieldName)
                ) {
                        if ($backendUser->jsConfirmation(1)) {
                                $alertMsgOnChange = 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
@@ -138,13 +135,16 @@ class SingleFieldContainer extends AbstractContainer {
                        $alertMsgOnChange = '';
                }
 
-
-               if (in_array($fieldName, $this->globalOptions['hiddenFieldListArray'], TRUE)) {
+               if (in_array($fieldName, $this->data['hiddenFieldListArray'], TRUE)) {
                        // Render as a hidden field if this field had a forced value in overrideVals
                        // @todo: This is an ugly concept ... search for overrideVals and defVals for a full picture of this madness
                        $resultArray = $this->initializeResultArray();
                        // This hidden field can not just be returned as casual html since upper containers will then render a label and wrapping stuff - this is not wanted here
-                       $resultArray['additionalHiddenFields'][] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
+                       $value = $parameterArray['itemFormElValue'];
+                       if (is_array($value)) {
+                               $value = array_shift($value);
+                       }
+                       $resultArray['additionalHiddenFields'][] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($value) . '" />';
                } else {
                        // JavaScript code for event handlers:
                        $parameterArray['fieldChangeFunc'] = array();
@@ -155,8 +155,8 @@ class SingleFieldContainer extends AbstractContainer {
                        if ($this->isInlineChildAndLabelField($table, $fieldName)) {
                                /** @var InlineStackProcessor $inlineStackProcessor */
                                $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
-                               $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
-                               $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
+                               $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
+                               $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
                                $inlineObjectId = implode(
                                        '-',
                                        array(
@@ -169,7 +169,7 @@ class SingleFieldContainer extends AbstractContainer {
                        }
 
                        // Based on the type of the item, call a render function on a child element
-                       $options = $this->globalOptions;
+                       $options = $this->data;
                        $options['parameterArray'] = $parameterArray;
                        $options['elementBaseName'] = $newElementBaseName;
                        if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
@@ -178,9 +178,7 @@ class SingleFieldContainer extends AbstractContainer {
                                // Fallback to type if no renderType is given
                                $options['renderType'] = $parameterArray['fieldConf']['config']['type'];
                        }
-                       /** @var NodeFactory $nodeFactory */
-                       $nodeFactory = $this->globalOptions['nodeFactory'];
-                       $resultArray = $nodeFactory->create($options)->render();
+                       $resultArray = $this->nodeFactory->create($options)->render();
                        $html = $resultArray['html'];
 
                        // @todo: the language handling, the null and the placeholder stuff should be embedded in the single
@@ -220,7 +218,7 @@ class SingleFieldContainer extends AbstractContainer {
                                // If the value of the field *is* NULL at the moment, an additional class is set
                                // @todo: This does not work well at the moment, but is kept for now. see input_14 of ext:styleguide as example
                                $checked = ' checked="checked"';
-                               if ($this->globalOptions['databaseRow'][$fieldName] === NULL) {
+                               if ($this->data['databaseRow'][$fieldName] === NULL) {
                                        $fieldItemClasses[] = 'disabled';
                                        $checked = '';
                                }
@@ -271,9 +269,7 @@ class SingleFieldContainer extends AbstractContainer {
                                $options['parameterArray'] = $parameterArray;
                                $options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
                                $options['renderType'] = 'none';
-                               /** @var NodeFactory $nodeFactory */
-                               $nodeFactory = $this->globalOptions['nodeFactory'];
-                               $noneElementResult = $nodeFactory->create($options)->render();
+                               $noneElementResult = $this->nodeFactory->create($options)->render();
                                $noneElementHtml = $noneElementResult['html'];
 
                                $placeholderWrap = array();
@@ -313,8 +309,7 @@ class SingleFieldContainer extends AbstractContainer {
 
        /**
         * Renders the display of default language record content around current field.
-        * Will render content if any is found in the internal array, $this->defaultLanguageData,
-        * depending on registerDefaultLanguageData() being called prior to this.
+        * Will render content if any is found in the internal array.
         *
         * @param string $table Table name of the record being edited
         * @param string $field Field name represented by $item
@@ -323,17 +318,17 @@ class SingleFieldContainer extends AbstractContainer {
         * @return string Item string returned again, possibly with the original value added to.
         */
        protected function renderDefaultLanguageContent($table, $field, $row, $item) {
-               if (is_array($this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']])) {
+               if (is_array($this->data['defaultLanguageData'][$table . ':' . $row['uid']])) {
                        $defaultLanguageValue = BackendUtility::getProcessedValue(
                                $table,
                                $field,
-                               $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']][$field],
+                               $this->data['defaultLanguageData'][$table . ':' . $row['uid']][$field],
                                0,
                                1,
                                FALSE,
-                               $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']]['uid']
+                               $this->data['defaultLanguageData'][$table . ':' . $row['uid']]['uid']
                        );
-                       $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
+                       $fieldConfig = $this->data['processedTca']['columns'][$field];
                        // Don't show content if it's for IRRE child records:
                        if ($fieldConfig['config']['type'] !== 'inline') {
                                if ($defaultLanguageValue !== '') {
@@ -342,12 +337,12 @@ class SingleFieldContainer extends AbstractContainer {
                                                . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode'])
                                                . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>';
                                }
-                               $additionalPreviewLanguages = $this->globalOptions['additionalPreviewLanguages'];
+                               $additionalPreviewLanguages = $this->data['additionalPreviewLanguages'];
                                foreach ($additionalPreviewLanguages as $previewLanguage) {
                                        $defaultLanguageValue = BackendUtility::getProcessedValue(
                                                $table,
                                                $field,
-                                               $this->globalOptions['additionalPreviewLanguageData'][$table . ':' . $row['uid']][$previewLanguage['uid']][$field],
+                                               $this->data['additionalPreviewLanguageData'][$table . ':' . $row['uid']][$previewLanguage['uid']][$field],
                                                0,
                                                1
                                        );
@@ -384,8