[BUGFIX] FormEngine inline foreign_selector and foreign_unique 26/44126/6
authorChristian Kuhn <lolli@schwarzbu.ch>
Sat, 17 Oct 2015 12:00:02 +0000 (14:00 +0200)
committerWouter Wolters <typo3@wouterwolters.nl>
Sat, 17 Oct 2015 13:44:22 +0000 (15:44 +0200)
Resolves: #70434
Resolves: #70245
Releases: master
Change-Id: I14e187532b7f5eafa2e73c54ab8056a8033d0822
Reviewed-on: http://review.typo3.org/44126
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php
typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
typo3/sysext/backend/Classes/Form/Container/InlineRecordContainer.php
typo3/sysext/backend/Classes/Form/FormDataProvider/InlineOverrideChildTca.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInlineConfiguration.php
typo3/sysext/backend/Classes/Form/Utility/FormEngineUtility.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InlineOverrrideChildTcaTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInlineConfigurationTest.php

index 838f379..60cf8ae 100644 (file)
@@ -370,8 +370,8 @@ class FormInlineAjaxController
             $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
             $nameObjectForeignTable = $nameObject . '-' . $child['table'];
 
-            $oldItems = FormEngineUtility::getInlineRelatedRecordsUidArray($oldItemList);
-            $newItems = FormEngineUtility::getInlineRelatedRecordsUidArray($newItemList);
+            $oldItems = $this->getInlineRelatedRecordsUidArray($oldItemList);
+            $newItems = $this->getInlineRelatedRecordsUidArray($newItemList);
 
             // Set the items that should be removed in the forms view:
             $removedItems = array_diff($oldItems, $newItems);
@@ -517,7 +517,6 @@ class FormInlineAjaxController
      * @param array $intermediate Full data array of "mm" record
      * @param array $parentConfig TCA configuration of "parent"
      * @return array Full data array of child
-     * @todo: probably foreign_selector_fieldTcaOverride should be merged over here before
      */
     protected function compileCombinationChild(array $intermediate, array $parentConfig)
     {
@@ -583,6 +582,26 @@ class FormInlineAjaxController
     }
 
     /**
+     * Gets an array with the uids of related records out of a list of items.
+     * This list could contain more information than required. This methods just
+     * extracts the uids.
+     *
+     * @param string $itemList The list of related child records
+     * @return array An array with uids
+     */
+    protected function getInlineRelatedRecordsUidArray($itemList)
+    {
+        $itemArray = GeneralUtility::trimExplode(',', $itemList, true);
+        // Perform modification of the selected items array:
+        foreach ($itemArray as &$value) {
+            $parts = explode('|', $value, 2);
+            $value = $parts[0];
+        }
+        unset($value);
+        return $itemArray;
+    }
+
+    /**
      * Checks if a record selector may select a certain file type
      *
      * @param array $selectorConfiguration
index 8ce6b10..ee38400 100644 (file)
@@ -167,27 +167,48 @@ class InlineControlContainer extends AbstractContainer
         );
         $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
         $uniqueMax = 0;
-        $possibleRecords = [];
         $uniqueIds = [];
+
         if ($config['foreign_unique']) {
-            // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one:
-            $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_unique']);
-            // Get the used unique ids:
-            $uniqueIds = $this->getUniqueIds($this->data['parameterArray']['fieldConf']['children'], $config, $selConfig['type'] == 'groupdb');
-            $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config, 'foreign_unique');
-            $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === false ? -1 : count($possibleRecords);
+            // Add inlineData['unique'] with JS unique configuration
+            $type = $config['selectorOrUniqueConfiguration']['config']['type'] === 'select' ? 'select' : 'groupdb';
+            foreach ($parameterArray['fieldConf']['children'] as $child) {
+                // Determine used unique ids, skip not localized records
+                if (!$child['inlineIsDefaultLanguage']) {
+                    $value = $child['databaseRow'][$config['foreign_unique']];
+                    // We're assuming there is only one connected value here for both select and group
+                    if ($type === 'select') {
+                        // A resolved select field is an array - take first value
+                        $value = $value['0'];
+                    } else {
+                        // A group field is still a list with pipe separated uid|tableName
+                        $valueParts = GeneralUtility::trimExplode('|', $value);
+                        $itemParts = explode('_', $valueParts[0]);
+                        $value = array(
+                            'uid' => array_pop($itemParts),
+                            'table' => implode('_', $itemParts)
+                        );
+                    }
+                    // @todo: This is weird, $value has different structure for group and select fields?
+                    $uniqueIds[$child['databaseRow']['uid']] = $value;
+                }
+            }
+            $possibleRecords = $config['selectorOrUniquePossibleRecords'];
+            $possibleRecordsUidToTitle = [];
+            foreach ($possibleRecords as $possibleRecord) {
+                $possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
+            }
+            $uniqueMax = $config['appearance']['useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
             $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = array(
                 'max' => $uniqueMax,
                 'used' => $uniqueIds,
-                'type' => $selConfig['type'],
-                'table' => $config['foreign_table'],
-                'elTable' => $selConfig['table'],
-                // element/record table (one step down in hierarchy)
+                'type' => $type,
+                'table' => $foreign_table,
+                'elTable' => $config['selectorOrUniqueConfiguration']['foreignTable'],
                 'field' => $config['foreign_unique'],
-                'selector' => $selConfig['selector'],
-                'possible' => $this->getPossibleRecordsFlat($possibleRecords)
+                'selector' => $config['selectorOrUniqueConfiguration']['isSelector'] ? $type : false,
+                'possible' => $possibleRecordsUidToTitle,
             );
         }
 
@@ -230,12 +251,11 @@ class InlineControlContainer extends AbstractContainer
 
         // If it's required to select from possible child records (reusable children), add a selector box
         if ($config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== false) {
-            // If not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array
-            if (!$config['foreign_unique']) {
-                $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
-                $uniqueIds = array();
+            if ($config['selectorOrUniqueConfiguration']['config']['type'] === 'select') {
+                $selectorBox = $this->renderPossibleRecordsSelectorTypeSelect($config, $uniqueIds);
+            } else {
+                $selectorBox = $this->renderPossibleRecordsSelectorTypeGroupDB($config);
             }
-            $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords, $config, $uniqueIds);
             $html .= $selectorBox . $localizationLinks;
         }
 
@@ -301,105 +321,6 @@ class InlineControlContainer extends AbstractContainer
     }
 
     /**
-     * Gets the uids of a select/selector that should be unique and have already been used.
-     *
-     * @param array $children All inline records on this level
-     * @param array $conf The TCA field configuration of the inline field to be rendered
-     * @param bool $splitValue For usage with group/db, values come like "tx_table_123|Title%20abc", but we need "tx_table" and "123
-     * @return array The uids, that have been used already and should be used unique
-     */
-    protected function getUniqueIds($children, $conf = array(), $splitValue = false)
-    {
-        $uniqueIds = array();
-        if (isset($conf['foreign_unique']) && $conf['foreign_unique'] && !empty($children)) {
-            foreach ($children as $child) {
-                // Skip virtual records (e.g. shown in localization mode):
-                if (!$child['inlineIsDefaultLanguage']) {
-                    $value = $child[$conf['foreign_unique']];
-                    if (is_array($value)) {
-                        $value = $value['0'];
-                    }
-                    // Split the value and extract the table and uid:
-                    if ($splitValue) {
-                        $valueParts = GeneralUtility::trimExplode('|', $value);
-                        $itemParts = explode('_', $valueParts[0]);
-                        $value = array(
-                            'uid' => array_pop($itemParts),
-                            'table' => implode('_', $itemParts)
-                        );
-                    }
-                    $uniqueIds[$child['uid']] = $value;
-                }
-            }
-        }
-        return $uniqueIds;
-    }
-
-    /**
-     * Get possible records.
-     * Copied from FormEngine and modified.
-     *
-     * @param string $table The table name of the record
-     * @param string $field The field name which this element is supposed to edit
-     * @param array $row The record data array where the value(s) for the field can be found
-     * @param array $conf An array with additional configuration options.
-     * @param string $checkForConfField For which field in the foreign_table the possible records should be fetched
-     * @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')
-    {
-        // Field configuration from TCA:
-        $foreign_check = $conf[$checkForConfField];
-        $foreignConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_check);
-        $PA = $foreignConfig['PA'];
-        if ($foreignConfig['type'] == 'select') {
-            $pageTsConfig['TCEFORM.']['dummyTable.']['dummyField.'] = $PA['fieldTSConfig'];
-            $selectDataInput = [
-                'tableName' => 'dummyTable',
-                'command' => 'edit',
-                'pageTsConfig' => $pageTsConfig,
-                '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;
-        }
-        return $selItems;
-    }
-
-    /**
-     * Makes a flat array from the $possibleRecords array.
-     * The key of the flat array is the value of the record,
-     * the value of the flat array is the label of the record.
-     *
-     * @param array $possibleRecords The possibleRecords array (for select fields)
-     * @return mixed A flat array with key=uid, value=label; if $possibleRecords isn't an array, FALSE is returned.
-     */
-    protected function getPossibleRecordsFlat($possibleRecords)
-    {
-        $flat = false;
-        if (is_array($possibleRecords)) {
-            $flat = array();
-            foreach ($possibleRecords as $record) {
-                $flat[$record[1]] = $record[0];
-            }
-        }
-        return $flat;
-    }
-
-    /**
      * Creates the HTML code of a general link to be used on a level of inline children.
      * The possible keys for the parameter $type are 'newRecord', 'localize' and 'synchronize'.
      *
@@ -477,73 +398,50 @@ class InlineControlContainer extends AbstractContainer
     }
 
     /**
-     * Get a selector as used for the select type, to select from all available
-     * records and to create a relation to the embedding record (e.g. like MM).
-     *
-     * @param array $selItems Array of all possible records
-     * @param array $conf TCA configuration of the parent(!) field
-     * @param array $uniqueIds The uids that have already been used and should be unique
-     * @return string A HTML <select> box with all possible records
-     */
-    protected function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds = array())
-    {
-        $foreign_selector = $conf['foreign_selector'];
-        $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_selector);
-        $item  = '';
-        if ($selConfig['type'] === 'select') {
-            $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds);
-        } elseif ($selConfig['type'] === 'groupdb') {
-            $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']);
-        }
-        return $item;
-    }
-
-    /**
      * Generate a link that opens an element browser in a new window.
      * For group/db there is no way to use a "selector" like a <select>|</select>-box.
      *
-     * @param array $conf TCA configuration of the parent(!) field
-     * @param array $PA An array with additional configuration options
+     * @param array $inlineConfiguration TCA inline configuration of the parent(!) field
      * @return string A HTML link that opens an element browser in a new window
      */
-    protected function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA)
+    protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration)
     {
         $backendUser = $this->getBackendUserAuthentication();
         $languageService = $this->getLanguageService();
 
-        $config = $PA['fieldConf']['config'];
-        ArrayUtility::mergeRecursiveWithOverrule($config, $conf);
-        $foreign_table = $config['foreign_table'];
-        $allowed = $config['allowed'];
+        $groupFieldConfiguration = $inlineConfiguration['selectorOrUniqueConfiguration']['config'];
+
+        $foreign_table = $inlineConfiguration['foreign_table'];
+        $allowed = $groupFieldConfiguration['allowed'];
         $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'])) {
-            $createNewRelationText = $languageService->sL($config['appearance']['createNewRelationLinkTitle'], true);
+        if (!empty($inlineConfiguration['appearance']['createNewRelationLinkTitle'])) {
+            $createNewRelationText = $languageService->sL($inlineConfiguration['appearance']['createNewRelationLinkTitle'], true);
         } else {
             $createNewRelationText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
         }
-        if (is_array($config['appearance'])) {
-            if (isset($config['appearance']['elementBrowserType'])) {
-                $mode = $config['appearance']['elementBrowserType'];
+        if (is_array($groupFieldConfiguration['appearance'])) {
+            if (isset($groupFieldConfiguration['appearance']['elementBrowserType'])) {
+                $mode = $groupFieldConfiguration['appearance']['elementBrowserType'];
             }
             if ($mode === 'file') {
                 $showUpload = true;
             }
-            if (isset($config['appearance']['fileUploadAllowed'])) {
-                $showUpload = (bool)$config['appearance']['fileUploadAllowed'];
+            if (isset($inlineConfiguration['appearance']['fileUploadAllowed'])) {
+                $showUpload = (bool)$inlineConfiguration['appearance']['fileUploadAllowed'];
             }
-            if (isset($config['appearance']['elementBrowserAllowed'])) {
-                $allowed = $config['appearance']['elementBrowserAllowed'];
+            if (isset($groupFieldConfiguration['appearance']['elementBrowserAllowed'])) {
+                $allowed = $groupFieldConfiguration['appearance']['elementBrowserAllowed'];
             }
         }
         $browserParams = '|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement';
         $onClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) . ', ' . GeneralUtility::quoteJSvalue($browserParams) . '); return false;';
 
         $buttonStyle = '';
-        if (isset($config['inline']['inlineNewRelationButtonStyle'])) {
-            $buttonStyle = ' style="' . $config['inline']['inlineNewRelationButtonStyle'] . '"';
+        if (isset($inlineConfiguration['inline']['inlineNewRelationButtonStyle'])) {
+            $buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineNewRelationButtonStyle'] . '"';
         }
 
         $item = '
@@ -619,68 +517,54 @@ class InlineControlContainer extends AbstractContainer
      * Get a selector as used for the select type, to select from all available
      * records and to create a relation to the embedding record (e.g. like MM).
      *
-     * @param array $selItems Array of all possible records
-     * @param array $conf TCA configuration of the parent(!) field
-     * @param array $PA An array with additional configuration options
+     * @param array $config TCA inline configuration of the parent(!) field
      * @param array $uniqueIds The uids that have already been used and should be unique
      * @return string A HTML <select> box with all possible records
      */
-    protected function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds = array())
+    protected function renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
     {
-        $foreign_table = $conf['foreign_table'];
-        $foreign_selector = $conf['foreign_selector'];
-        $PA = array();
-        $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector];
-        $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $foreign_selector);
-        $config = $PA['fieldConf']['config'];
-        $item = '';
-        // @todo $disabled is not present - should be read from config?
-        $disabled = false;
-        if (!$disabled) {
-            $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
-            ;
-            // Create option tags:
-            $opt = array();
-            foreach ($selItems as $p) {
-                if (!in_array($p[1], $uniqueIds)) {
-                    $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
-                }
+        $possibleRecords = $config['selectorOrUniquePossibleRecords'];
+        $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
+        // Create option tags:
+        $opt = [];
+        foreach ($possibleRecords as $p) {
+            if (!in_array($p[1], $uniqueIds)) {
+                $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
             }
-            // Put together the selector box:
-            $itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : '';
-            $size = (int)$conf['size'];
-            $size = $conf['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $conf['autoSizeMax']) : $size;
-            $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $conf['foreign_table']) . ')';
-            $item = '
-                               <select id="' . $nameObject . '-' . $conf['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($onChange) . '"' . $PA['onFocus'] . $itemListStyle . ($conf['foreign_unique'] ? ' isunique="isunique"' : '') . '>
-                                       ' . implode('', $opt) . '
-                               </select>';
-
-            if ($size <= 1) {
-                // Add a "Create new relation" link for adding new relations
-                // This is necessary, if the size of the selector is "1" or if
-                // there is only one record item in the select-box, that is selected by default
-                // The selector-box creates a new relation on using an onChange event (see some line above)
-                if (!empty($conf['appearance']['createNewRelationLinkTitle'])) {
-                    $createNewRelationText = $this->getLanguageService()->sL($conf['appearance']['createNewRelationLinkTitle'], true);
-                } else {
-                    $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
-                }
-                $item .= '
-                               <span class="input-group-btn">
-                                       <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '">
-                                               ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . $createNewRelationText . '
-                                       </a>
-                               </span>';
+        }
+        // Put together the selector box:
+        $size = (int)$config['size'];
+        $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($possibleRecords) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
+        $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $config['foreign_table']) . ')';
+        $item = '
+            <select id="' . $nameObject . '-' . $config['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '')
+            . ' onchange="' . htmlspecialchars($onChange) . '"' . ($config['foreign_unique'] ? ' isunique="isunique"' : '') . '>
+                ' . implode('', $opt) . '
+            </select>';
+
+        if ($size <= 1) {
+            // Add a "Create new relation" link for adding new relations
+            // This is necessary, if the size of the selector is "1" or if
+            // there is only one record item in the select-box, that is selected by default
+            // The selector-box creates a new relation on using an onChange event (see some line above)
+            if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
+                $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], true);
             } else {
-                $item .= '
-                               <span class="input-group-btn btn"></span>';
+                $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
             }
-
-            // Wrap the selector and add a spacer to the bottom
-
-            $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
+            $item .= '
+            <span class="input-group-btn">
+                <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '">
+                    ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . $createNewRelationText . '
+                </a>
+            </span>';
+        } else {
+            $item .= '
+            <span class="input-group-btn btn"></span>';
         }
+
+        // Wrap the selector and add a spacer to the bottom
+        $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
         return $item;
     }
 
index 587e7d3..02b3e34 100644 (file)
@@ -346,9 +346,12 @@ class InlineRecordContainer extends AbstractContainer
             $recTitle = $params['title'];
         } elseif ($hasForeignLabel || $hasSymmetricLabel) {
             $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label'];
-            $foreignConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $titleCol);
             // Render title for everything else than group/db:
-            if ($foreignConfig['type'] !== 'groupdb') {
+            if (isset($this->data['processedTca']['columns'][$titleCol]['config']['type'])
+                && $this->data['processedTca']['columns'][$titleCol]['config']['type'] === 'group'
+                && isset($this->data['processedTca']['columns'][$titleCol]['config']['internal_type'])
+                && $this->data['processedTca']['columns'][$titleCol]['config']['internal_type'] === 'db'
+            ) {
                 $recTitle = BackendUtility::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false);
             } else {
                 // $recTitle could be something like: "tx_table_123|...",
index 029f4f5..93639ca 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 
 /**
  * Override some child TCA in an inline parent child relation.
@@ -37,6 +38,20 @@ class InlineOverrideChildTca implements FormDataProviderInterface
             }
         }
 
+        // Override config section of foreign_selector field pointer if given
+        if (isset($result['inlineParentConfig']['foreign_selector'])
+            && is_string($result['inlineParentConfig']['foreign_selector'])
+            && isset($result['inlineParentConfig']['foreign_selector_fieldTcaOverride'])
+            && is_array($result['inlineParentConfig']['foreign_selector_fieldTcaOverride'])
+            && isset($result['processedTca']['columns'][$result['inlineParentConfig']['foreign_selector']])
+            && is_array($result['processedTca']['columns'][$result['inlineParentConfig']['foreign_selector']])
+        ) {
+            ArrayUtility::mergeRecursiveWithOverrule(
+                $result['processedTca']['columns'][$result['inlineParentConfig']['foreign_selector']],
+                $result['inlineParentConfig']['foreign_selector_fieldTcaOverride']
+            );
+        }
+
         // Set default values for (new) child if foreign_record_defaults is defined in inlineParentConfig
         if (isset($result['inlineParentConfig']['foreign_record_defaults']) && is_array($result['inlineParentConfig']['foreign_record_defaults'])) {
             $foreignTableConfig = $GLOBALS['TCA'][$result['inlineParentConfig']['foreign_table']];
@@ -70,7 +85,7 @@ class InlineOverrideChildTca implements FormDataProviderInterface
                 }
             }
             foreach ($result['inlineParentConfig']['foreign_record_defaults'] as $fieldName => $defaultValue) {
-                if (isset($foreignTableConfig['columns'][$fieldName]) && !in_array($fieldName, $notSetableFields, TRUE)) {
+                if (isset($foreignTableConfig['columns'][$fieldName]) && !in_array($fieldName, $notSetableFields, true)) {
                     $result['processedTca']['columns'][$fieldName]['config']['default'] = $defaultValue;
                 }
             }
index afa8d53..a77397f 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -45,6 +46,7 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
             $result['processedTca']['columns'][$fieldName]['children'] = [];
             if ($result['inlineResolveExistingChildren']) {
                 $result = $this->resolveRelatedRecords($result, $fieldName);
+                $result = $this->addForeignSelectorAndUniquePossibleRecords($result, $fieldName);
             }
         }
 
@@ -197,6 +199,57 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
     }
 
     /**
+     * If there is a foreign_selector or foreign_unique configuration, fetch
+     * the list of possible records that can be connected and attach the to the
+     * inline configuration.
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @return array Modified item array
+     */
+    protected function addForeignSelectorAndUniquePossibleRecords(array $result, $fieldName)
+    {
+        if (!is_array($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) {
+            return $result;
+        }
+
+        $selectorOrUniqueConfiguration = $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'];
+        $foreignFieldName = $selectorOrUniqueConfiguration['fieldName'];
+        $selectorOrUniquePossibleRecords = [];
+
+        if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
+            // Compile child table data for this field only
+            $selectDataInput = [
+                'tableName' => $result['processedTca']['columns'][$fieldName]['config']['foreign_table'],
+                'command' => 'new',
+                // Since there is no existing record that may have a type, it does not make sense to
+                // do extra handling of pageTsConfig merged here. Just provide "parent" pageTS as is
+                'pageTsConfig' => $result['pageTsConfig'],
+                'userTsConfig' => $result['userTsConfig'],
+                'processedTca' => [
+                    'ctrl' => [],
+                    'columns' => [
+                        $foreignFieldName => [
+                            'config' => $selectorOrUniqueConfiguration['config'],
+                        ],
+                    ],
+                ],
+            ];
+            /** @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);
+            $selectorOrUniquePossibleRecords = $compilerResult['processedTca']['columns'][$foreignFieldName]['config']['items'];
+        }
+
+        $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniquePossibleRecords'] = $selectorOrUniquePossibleRecords;
+
+        return $result;
+    }
+
+    /**
      * Compile a full child record
      *
      * @param array $result Result array of parent
@@ -236,7 +289,6 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
      * @param array $intermediate Full data array of "mm" record
      * @param array $parentConfig TCA configuration of "parent"
      * @return array Full data array of child
-     * @todo: probably foreign_selector_fieldTcaOverride should be merged over here before
      */
     protected function compileCombinationChild(array $intermediate, array $parentConfig)
     {
index ff64988..90ab878 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 
 /**
@@ -47,6 +48,7 @@ class TcaInlineConfiguration extends AbstractItemProvider implements FormDataPro
             $result = $this->initializeMinMaxItems($result, $fieldName);
             $result = $this->initializeLocalizationMode($result, $fieldName);
             $result = $this->initializeAppearance($result, $fieldName);
+            $result = $this->addInlineSelectorAndUniqueConfiguration($result, $fieldName);
         }
         return $result;
     }
@@ -151,7 +153,6 @@ class TcaInlineConfiguration extends AbstractItemProvider implements FormDataPro
         $parentConfig = $result['processedTca']['columns'][$fieldName]['config'];
 
         $isChildTableLocalizable = false;
-        // @todo: Direct $globals access here, but no good idea yet how to get rid of this
         if (isset($GLOBALS['TCA'][$childTableName]['ctrl']) && is_array($GLOBALS['TCA'][$childTableName]['ctrl'])
             && isset($GLOBALS['TCA'][$childTableName]['ctrl']['languageField'])
             && $GLOBALS['TCA'][$childTableName]['ctrl']['languageField']
@@ -192,4 +193,125 @@ class TcaInlineConfiguration extends AbstractItemProvider implements FormDataPro
         $result['processedTca']['columns'][$fieldName]['config']['behaviour']['localizationMode'] = $mode;
         return $result;
     }
+
+    /**
+     * If foreign_selector or foreign_unique is set, this points to a field configuration of the child
+     * table. The InlineControlContainer may render a drop down field or an element browser later from this.
+     *
+     * Fetch configuration from child table configuration, sanitize and merge with
+     * foreign_selector_fieldTcaOverride that allows overriding this field definition again.
+     *
+     * Final configuration is written to selectorOrUniqueConfiguration of inline config section.
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @return array Modified item array
+     * @throws \UnexpectedValueException If configuration is broken
+     */
+    protected function addInlineSelectorAndUniqueConfiguration(array $result, $fieldName)
+    {
+        $config = $result['processedTca']['columns'][$fieldName]['config'];
+
+        // Early return if neither foreign_unique nor foreign_selector are set
+        if (!isset($config['foreign_unique']) && !isset($config['foreign_selector'])) {
+            return $result;
+        }
+
+        // If both are set, they must point to the same field
+        if (isset($config['foreign_unique']) && isset($config['foreign_selector'])
+            && $config['foreign_unique'] !== $config['foreign_selector']
+        ) {
+            throw new \UnexpectedValueException(
+                'Table ' . $result['tableName'] . ' field ' . $fieldName . ': If both foreign_unique and'
+                . ' foreign_selector are set, they must point to the same field',
+                1444995464
+            );
+        }
+
+        if (isset($config['foreign_unique'])) {
+            $fieldNameInChildConfiguration = $config['foreign_unique'];
+        } else {
+            $fieldNameInChildConfiguration = $config['foreign_selector'];
+        }
+
+        // Throw if field name in globals does not exist or is not of type select or group
+        if (!isset($GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'])
+            || ($GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'] !== 'select'
+                && $GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'] !== 'group')
+        ) {
+            throw new \UnexpectedValueException(
+                'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
+                . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . ', but this field'
+                . ' is either not defined or is not of type select or group',
+                1444996537
+            );
+        }
+
+        $selectorOrUniqueConfiguration = [
+            'config' => $GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config'],
+        ];
+
+        // Throw if field is type group, but not internal_type db
+        if ($selectorOrUniqueConfiguration['config']['type'] === 'group'
+            && (!isset($selectorOrUniqueConfiguration['config']['internal_type']) ||  $selectorOrUniqueConfiguration['config']['internal_type'] !== 'db')) {
+            throw new \UnexpectedValueException(
+                'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
+                . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
+                . ' is of type group and must be of internal_type db, which is not the case',
+                1444999130
+            );
+        }
+
+        // Merge foreign_selector_fieldTcaOverride if given
+        if (isset($config['foreign_selector'])
+            && isset($config['foreign_selector_fieldTcaOverride']['config'])
+            && is_array($config['foreign_selector_fieldTcaOverride']['config'])
+        ) {
+            ArrayUtility::mergeRecursiveWithOverrule($selectorOrUniqueConfiguration['config'], $config['foreign_selector_fieldTcaOverride']['config']);
+        }
+
+        // Add field name to config for easy access later
+        $selectorOrUniqueConfiguration['fieldName'] = $fieldNameInChildConfiguration;
+
+        // Add remote table name for easy access later
+        if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
+            if (!isset($selectorOrUniqueConfiguration['config']['foreign_table'])) {
+                throw new \UnexpectedValueException(
+                    'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
+                    . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
+                    . ' is of type select and must define foreign_table',
+                    1445078627
+                );
+            }
+            $foreignTable = $selectorOrUniqueConfiguration['config']['foreign_table'];
+        } else {
+            if (!isset($selectorOrUniqueConfiguration['config']['allowed'])) {
+                throw new \UnexpectedValueException(
+                    'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
+                    . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
+                    . ' is of type select and must define allowed',
+                    1445078628
+                );
+            }
+            $foreignTable = $selectorOrUniqueConfiguration['config']['allowed'];
+        }
+        $selectorOrUniqueConfiguration['foreignTable'] = $foreignTable;
+
+        // If this is a foreign_selector field, mark it as such for data fetching later
+        $selectorOrUniqueConfiguration['isSelector'] = false;
+        if (isset($config['foreign_selector'])) {
+            $selectorOrUniqueConfiguration['isSelector'] = true;
+        }
+
+        // If this is a foreign_unique field, mark it a such for unique data fetching later
+        $selectorOrUniqueConfiguration['isUnique'] = false;
+        if (isset($config['foreign_unique'])) {
+            $selectorOrUniqueConfiguration['isUnique'] = true;
+        }
+
+        // Add field configuration to inline configuration
+        $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'] = $selectorOrUniqueConfiguration;
+
+        return $result;
+    }
 }
index 89076f6..67b4d8b 100644 (file)
@@ -159,66 +159,6 @@ class FormEngineUtility
     }
 
     /**
-     * Determine the configuration and the type of a record selector.
-     * This is a helper method for inline / IRRE handling
-     *
-     * @param array $conf TCA configuration of the parent(!) field
-     * @param string $field Field name
-     * @return array Associative array with the keys 'PA' and 'type', both are FALSE if the selector was not valid.
-     * @internal
-     */
-    public static function getInlinePossibleRecordsSelectorConfig($conf, $field = '')
-    {
-        $foreign_table = $conf['foreign_table'];
-        $foreign_selector = $conf['foreign_selector'];
-        $PA = false;
-        $type = false;
-        $table = false;
-        $selector = false;
-        if ($field) {
-            $PA = array();
-            $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field];
-            if ($PA['fieldConf'] && $conf['foreign_selector_fieldTcaOverride']) {
-                ArrayUtility::mergeRecursiveWithOverrule($PA['fieldConf'], $conf['foreign_selector_fieldTcaOverride']);
-            }
-            $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $field);
-            $config = $PA['fieldConf']['config'];
-            // Determine type of Selector:
-            $type = static::getInlinePossibleRecordsSelectorType($config);
-            // Return table on this level:
-            $table = $type === 'select' ? $config['foreign_table'] : $config['allowed'];
-            // Return type of the selector if foreign_selector is defined and points to the same field as in $field:
-            if ($foreign_selector && $foreign_selector == $field && $type) {
-                $selector = $type;
-            }
-        }
-        return array(
-            'PA' => $PA,
-            'type' => $type,
-            'table' => $table,
-            'selector' => $selector
-        );
-    }
-
-    /**
-     * Determine the type of a record selector, e.g. select or group/db.
-     *
-     * @param array $config TCE configuration of the selector
-     * @return mixed The type of the selector, 'select' or 'groupdb' - FALSE not valid
-     * @internal
-     */
-    protected static function getInlinePossibleRecordsSelectorType($config)
-    {
-        $type = false;
-        if ($config['type'] === 'select') {
-            $type = 'select';
-        } elseif ($config['type'] === 'group' && $config['internal_type'] === 'db') {
-            $type = 'groupdb';
-        }
-        return $type;
-    }
-
-    /**
      * Update expanded/collapsed states on new inline records if any.
      *
      * @param array $uc The uc array to be processed and saved (by reference)
@@ -262,27 +202,6 @@ class FormEngineUtility
     }
 
     /**
-     * Gets an array with the uids of related records out of a list of items.
-     * This list could contain more information than required. This methods just
-     * extracts the uids.
-     *
-     * @param string $itemList The list of related child records
-     * @return array An array with uids
-     * @internal
-     */
-    public static function getInlineRelatedRecordsUidArray($itemList)
-    {
-        $itemArray = GeneralUtility::trimExplode(',', $itemList, true);
-        // Perform modification of the selected items array:
-        foreach ($itemArray as &$value) {
-            $parts = explode('|', $value, 2);
-            $value = $parts[0];
-        }
-        unset($value);
-        return $itemArray;
-    }
-
-    /**
      * Compatibility layer for methods not in FormEngine scope.
      *
      * databaseRow was a flat array with single elements in select and group fields as comma separated list.
index ea41b33..30a293b 100644 (file)
@@ -102,6 +102,59 @@ class InlineOverrrideChildTcaTest extends UnitTestCase
     /**
      * @test
      */
+    public function addDataMergesForeignSelectorFieldTcaOverride()
+    {
+        $input = [
+            'inlineParentConfig' => [
+                'foreign_selector' => 'uid_local',
+                'foreign_selector_fieldTcaOverride' => [
+                    'label' => 'aDifferentLabel',
+                    'config' => [
+                        'aGivenSetting' => 'overrideValue',
+                        'aNewSetting' => 'anotherNewValue',
+                        'appearance' => [
+                            'elementBrowserType' => 'file',
+                            'elementBrowserAllowed' => 'jpg,png'
+                        ],
+                    ],
+                ],
+            ],
+            'processedTca' => [
+                'columns' => [
+                    'uid_local' => [
+                        'label' => 'aLabel',
+                        'config' => [
+                            'aGivenSetting' => 'aValue',
+                            'doNotChangeMe' => 'doNotChangeMe',
+                            'appearance' => [
+                                'elementBrowserType' => 'db',
+                            ],
+                        ],
+                    ]
+                ],
+            ],
+        ];
+
+        $expected = $input;
+        $expected['processedTca']['columns']['uid_local'] = [
+            'label' => 'aDifferentLabel',
+            'config' => [
+                'aGivenSetting' => 'overrideValue',
+                'doNotChangeMe' => 'doNotChangeMe',
+                'appearance' => [
+                    'elementBrowserType' => 'file',
+                    'elementBrowserAllowed' => 'jpg,png',
+                ],
+                'aNewSetting' => 'anotherNewValue',
+            ],
+        ];
+
+        $this->assertSame($expected, $this->subject->addData($input));
+    }
+
+    /**
+     * @test
+     */
     public function addDataSetsDefaultValueForChildRecordColumn()
     {
         $GLOBALS['TCA']['aTable']['columns']['aType'] = [];
index 8a13f0b..8c257c1 100644 (file)
@@ -395,7 +395,7 @@ class TcaInlineConfigurationTest extends UnitTestCase
                         'config' => [
                             'type' => 'inline',
                             'foreign_table' => 'aForeignTableName',
-                            'foreign_selector' => 'foo',
+                            'foreign_selector' => 'aField',
                             'appearance' => [
                                 'levelLinksPosition' => 'both',
                             ],
@@ -404,8 +404,22 @@ class TcaInlineConfigurationTest extends UnitTestCase
                 ],
             ],
         ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'select',
+            'foreign_table' => 'anotherForeignTableName',
+        ];
         $expected['processedTca']['columns']['aField']['config'] = $this->defaultConfig;
-        $expected['processedTca']['columns']['aField']['config']['foreign_selector'] = 'foo';
+        $expected['processedTca']['columns']['aField']['config']['foreign_selector'] = 'aField';
+        $expected['processedTca']['columns']['aField']['config']['selectorOrUniqueConfiguration'] = [
+            'fieldName' => 'aField',
+            'isSelector' => true,
+            'isUnique' => false,
+            'config' => [
+                'type' => 'select',
+                'foreign_table' => 'anotherForeignTableName',
+            ],
+            'foreignTable' => 'anotherForeignTableName',
+        ];
         $expected['processedTca']['columns']['aField']['config']['appearance']['levelLinksPosition'] = 'none';
         $this->assertEquals($expected, $this->subject->addData($input));
     }
@@ -422,7 +436,7 @@ class TcaInlineConfigurationTest extends UnitTestCase
                         'config' => [
                             'type' => 'inline',
                             'foreign_table' => 'aForeignTableName',
-                            'foreign_selector' => 'foo',
+                            'foreign_selector' => 'aField',
                             'appearance' => [
                                 'useCombination' => true,
                                 'levelLinksPosition' => 'both',
@@ -432,8 +446,22 @@ class TcaInlineConfigurationTest extends UnitTestCase
                 ],
             ],
         ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'select',
+            'foreign_table' => 'anotherForeignTableName',
+        ];
         $expected['processedTca']['columns']['aField']['config'] = $this->defaultConfig;
-        $expected['processedTca']['columns']['aField']['config']['foreign_selector'] = 'foo';
+        $expected['processedTca']['columns']['aField']['config']['foreign_selector'] = 'aField';
+        $expected['processedTca']['columns']['aField']['config']['selectorOrUniqueConfiguration'] = [
+            'fieldName' => 'aField',
+            'isSelector' => true,
+            'isUnique' => false,
+            'config' => [
+                'type' => 'select',
+                'foreign_table' => 'anotherForeignTableName',
+            ],
+            'foreignTable' => 'anotherForeignTableName',
+        ];
         $expected['processedTca']['columns']['aField']['config']['appearance']['useCombination'] = true;
         $expected['processedTca']['columns']['aField']['config']['appearance']['levelLinksPosition'] = 'both';
         $this->assertEquals($expected, $this->subject->addData($input));
@@ -538,4 +566,281 @@ class TcaInlineConfigurationTest extends UnitTestCase
         $expected['processedTca']['columns']['aField']['config']['appearance']['showRemovedLocalizationRecords'] = false;
         $this->assertEquals($expected, $this->subject->addData($input));
     }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignSelectorAndForeignUniquePointToDifferentFields()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_selector' => 'aField',
+                            'foreign_unique' => 'aDifferentField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1444995464);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignSelectorPointsToANotExistingField()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_selector' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1444996537);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignUniquePointsToANotExistingField()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1444996537);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignUniqueTargetIsNotTypeSelectOrGroup()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'notSelectOrGroup',
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1444996537);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionForForeignSelectorGroupWithoutInternalTypeDb()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'group',
+            'internal_type' => 'notDb'
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1444999130);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignUniqueSelectDoesNotDefineForeignTable()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'select',
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1445078627);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfForeignUniqueGroupDoesNotDefineForeignTable()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'group',
+            'internal_type' => 'db',
+        ];
+        $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1445078628);
+        $this->subject->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataAddsSelectorOrUniqueConfigurationForForeignUnique()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_unique' => 'aField',
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'select',
+            'foreign_table' => 'anotherForeignTableName',
+        ];
+        $expected['processedTca']['columns']['aField']['config'] = $this->defaultConfig;
+        $expected['processedTca']['columns']['aField']['config']['foreign_unique'] = 'aField';
+        $expected['processedTca']['columns']['aField']['config']['selectorOrUniqueConfiguration'] = [
+            'fieldName' => 'aField',
+            'isSelector' => false,
+            'isUnique' => true,
+            'config' => [
+                'type' => 'select',
+                'foreign_table' => 'anotherForeignTableName',
+            ],
+            'foreignTable' => 'anotherForeignTableName',
+        ];
+        $this->assertEquals($expected, $this->subject->addData($input));
+    }
+
+    /**
+     * @test
+     */
+    public function addDataMergesForeignSelectorFieldTcaOverride()
+    {
+        $input = [
+            'processedTca' => [
+                'columns' => [
+                    'aField' => [
+                        'config' => [
+                            'type' => 'inline',
+                            'foreign_table' => 'aForeignTableName',
+                            'foreign_selector' => 'aField',
+                            'foreign_selector_fieldTcaOverride' => [
+                                'config' => [
+                                    'aGivenSetting' => 'aOverrideValue',
+                                    'aNewSetting' => 'aNewSetting',
+                                    'appearance' => [
+                                        'elementBrowserType' => 'file',
+                                        'elementBrowserAllowed' => 'jpg,png',
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        $GLOBALS['TCA']['aForeignTableName']['columns']['aField']['config'] = [
+            'type' => 'group',
+            'internal_type' => 'db',
+            'allowed' => 'anotherForeignTableName',
+            'doNotChangeMe' => 'doNotChangeMe',
+            'aGivenSetting' => 'aGivenValue',
+        ];
+
+        $expected['processedTca']['columns']['aField']['config'] = $this->defaultConfig;
+        $expected['processedTca']['columns']['aField']['config']['appearance']['levelLinksPosition'] = 'none';
+        $expected['processedTca']['columns']['aField']['config']['foreign_selector'] = 'aField';
+        $expected['processedTca']['columns']['aField']['config']['foreign_selector_fieldTcaOverride'] = [
+            'config' => [
+                'aGivenSetting' => 'aOverrideValue',
+                'aNewSetting' => 'aNewSetting',
+                'appearance' => [
+                    'elementBrowserType' => 'file',
+                    'elementBrowserAllowed' => 'jpg,png',
+                ],
+            ],
+        ];
+
+        $expected['processedTca']['columns']['aField']['config']['selectorOrUniqueConfiguration'] = [
+            'fieldName' => 'aField',
+            'isSelector' => true,
+            'isUnique' => false,
+            'config' => [
+                'type' => 'group',
+                'internal_type' => 'db',
+                'allowed' => 'anotherForeignTableName',
+                'doNotChangeMe' => 'doNotChangeMe',
+                'aGivenSetting' => 'aOverrideValue',
+                'aNewSetting' => 'aNewSetting',
+                'appearance' => [
+                    'elementBrowserType' => 'file',
+                    'elementBrowserAllowed' => 'jpg,png',
+                ],
+            ],
+            'foreignTable' => 'anotherForeignTableName',
+        ];
+        $this->assertEquals($expected, $this->subject->addData($input));
+    }
 }