[BUGFIX] FormEngine: Fix broken invalid value handling for selects 40/44540/2
authorMorton Jonuschat <m.jonuschat@mojocode.de>
Wed, 4 Nov 2015 19:22:08 +0000 (20:22 +0100)
committerBenni Mack <benni@typo3.org>
Thu, 5 Nov 2015 14:47:08 +0000 (15:47 +0100)
The handling of invalid or non-matching items was handled inconsistently
in the different select elements. Determining the invalid items has been
moved into the data provider for select boxes so that the invalid values
can be correctly determined based on intermediate processing steps.

Resolves: #71257
Releases: master
Change-Id: I577089b29265a789612c7b3baaf60b66f4339b35
Reviewed-on: https://review.typo3.org/44540
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
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/FormDataProvider/AbstractItemProvider.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectItems.php
typo3/sysext/backend/Classes/Form/FormDataProvider/TcaSelectTreeItems.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php

index 8d1f221..bbe7e9a 100644 (file)
@@ -110,29 +110,6 @@ class SelectCheckBoxElement extends AbstractFormElement
                     }
                 }
             }
-            // Remaining values (invalid):
-            if (!empty($itemArray) && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                $currentGroup++;
-                // Creating the label for the "No Matching Value" entry.
-                $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                    ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                    : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
-                foreach ($itemArray as $theNoMatchValue => $temp) {
-                    // Build item array
-                    $groups[$currentGroup]['items'][] = array(
-                        'id' => StringUtility::getUniqueId('select_checkbox_row_'),
-                        'name' => $parameterArray['itemFormElName'] . '[' . $c . ']',
-                        'value' => $theNoMatchValue,
-                        'checked' => 1,
-                        'disabled' => $disabled,
-                        'class' => 'danger',
-                        'icon' => '',
-                        'title' => htmlspecialchars(@sprintf($noMatchingLabel, $theNoMatchValue), ENT_COMPAT, 'UTF-8', false),
-                        'help' => ''
-                    );
-                    $c++;
-                }
-            }
             // Add an empty hidden field which will send a blank value if all items are unselected.
             $html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
 
index e72d953..1e6e67f 100644 (file)
@@ -60,38 +60,24 @@ class SelectMultipleSideBySideElement extends AbstractFormElement
         if (!$maxitems) {
             $maxitems = 100000;
         }
-        // Get "removeItems":
-        $removeItems = GeneralUtility::trimExplode(',', $parameterArray['fieldTSConfig']['removeItems'], true);
         // Get the array with selected items:
         $itemsArray = $parameterArray['itemFormElValue'] ?: [];
 
         // Perform modification of the selected items array:
-        // @todo: this part should probably be moved to TcaSelectItems provider?!
         foreach ($itemsArray as $itemNumber => $itemValue) {
             $itemArray = array(
                 0 => $itemValue,
                 1 => '',
             );
-            $itemIcon = null;
-            $isRemoved = in_array($itemValue, $removeItems)
-                || $config['type'] == 'select' && $config['authMode']
-                && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $itemValue, $config['authMode']);
-            if ($isRemoved && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-                $itemArray[1] = rawurlencode(@sprintf($noMatchingLabel, $itemValue));
-            } else {
-                if (isset($parameterArray['fieldTSConfig']['altLabels.'][$itemValue])) {
-                    $itemArray[1] = rawurlencode($this->getLanguageService()->sL(trim($parameterArray['fieldTSConfig']['altLabels.'][$itemValue])));
-                }
-                if (isset($parameterArray['fieldTSConfig']['altIcons.'][$itemValue])) {
-                    $itemArray[2] = $parameterArray['fieldTSConfig']['altIcons.'][$itemValue];
-                }
+
+            if (isset($parameterArray['fieldTSConfig']['altIcons.'][$itemValue])) {
+                $itemArray[2] = $parameterArray['fieldTSConfig']['altIcons.'][$itemValue];
             }
-            if ($itemArray[1] === '') {
-                foreach ($selItems as $selItem) {
-                    if ($selItem[1] == $itemValue) {
-                        $itemArray[1] = $selItem[0];
-                        break;
-                    }
+
+            foreach ($selItems as $selItem) {
+                if ($selItem[1] == $itemValue) {
+                    $itemArray[1] = $selItem[0];
+                    break;
                 }
             }
             $itemsArray[$itemNumber] = implode('|', $itemArray);
index 7167404..664f14f 100644 (file)
@@ -64,27 +64,6 @@ class SelectSingleBoxElement extends AbstractFormElement
             $optionElements[] = $this->renderOptionElement($value, $item[0], $attributes);
         }
 
-        // Remaining values:
-        if (!empty($itemArray)
-            && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement']
-            && !$config['disableNoMatchingValueElement']) {
-
-            // Creating the label for the "No Matching Value" entry.
-            $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-                : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
-
-            foreach ($itemArray as $unmatchedValue => $temp) {
-                // Compile <option> tag:
-                array_unshift($optionElements, $this->renderOptionElement(
-                    $unmatchedValue,
-                    // @todo Check if we can get rid of "@" here (catches missing %s in $noMatchingLabel)
-                    @sprintf($noMatchingLabel, $unmatchedValue),
-                    ['selected' => 'selected']
-                ));
-            }
-        }
-
         $selectElement = $this->renderSelectElement($optionElements, $parameterArray, $config);
         $resetButtonElement = $this->renderResetButtonElement($parameterArray['itemFormElName'] . '[]', $initiallySelectedIndices);
         $html = [];
index 7731cb9..8d73d7a 100644 (file)
@@ -43,11 +43,6 @@ class SelectSingleElement extends AbstractFormElement
 
         $selectItems = $parameterArray['fieldConf']['config']['items'];
 
-        // Creating the label for the "No Matching Value" entry.
-        $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
-            ? $this->getLanguageService()->sL(trim($parameterArray['fieldTSConfig']['noMatchingValue_label']))
-            : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
-
         // Check against inline uniqueness
         /** @var InlineStackProcessor $inlineStackProcessor */
         $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
@@ -78,9 +73,7 @@ class SelectSingleElement extends AbstractFormElement
 
         // Initialization:
         $selectId = StringUtility::getUniqueId('tceforms-select-');
-        $selectedIndex = 0;
         $selectedIcon = '';
-        $selectedValueFound = false;
         $size = (int)$config['size'];
 
         // Style set on <select/>
@@ -118,9 +111,7 @@ class SelectSingleElement extends AbstractFormElement
                 $selected = $selectedValue === (string)$item[1];
 
                 if ($selected) {
-                    $selectedIndex = $selectItemCounter;
                     $selectedIcon = $icon;
-                    $selectedValueFound = true;
                 }
 
                 $selectItemGroups[$selectItemGroupCount]['items'][] = array(
@@ -144,11 +135,9 @@ class SelectSingleElement extends AbstractFormElement
             }
         }
 
-        // No-matching-value:
-        if ($selectedValue && !$selectedValueFound && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
-            $noMatchingLabel = @sprintf($noMatchingLabel, $selectedValue);
-            $options = '<option value="' . htmlspecialchars($selectedValue) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
-        } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
+        // Fallback icon
+        // @todo: assign a special icon for non matching values?
+        if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
             $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
         }
 
index be68154..224b460 100644 (file)
@@ -1024,6 +1024,21 @@ abstract class AbstractItemProvider
     }
 
     /**
+     * Convert the current database values into an array
+     *
+     * @param array $row database row
+     * @param string $fieldName fieldname to process
+     * @return array
+     */
+    protected function processDatabaseFieldValue(array $row, $fieldName)
+    {
+        $currentDatabaseValues = array_key_exists($fieldName, $row)
+            ? $row[$fieldName]
+            : '';
+        return GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
+    }
+
+    /**
      * Validate and sanitize database row values of the select field with the given name.
      * Creates an array out of databaseRow[selectField] values.
      *
@@ -1038,24 +1053,12 @@ abstract class AbstractItemProvider
     {
         $fieldConfig = $result['processedTca']['columns'][$fieldName];
 
-        // For single select fields we just keep the current value because the renderer
-        // will take care of showing the "Invalid value" text.
-        // For maxitems=1 select fields is is also possible to select empty values.
-        // @todo: move handling of invalid values to this data provider.
-        if ($fieldConfig['config']['maxitems'] === 1 && empty($fieldConfig['config']['MM'])) {
-            return [$result['databaseRow'][$fieldName]];
-        }
-
-        $currentDatabaseValues = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : '';
-        // Selecting empty values does not make sense for fields that can contain more than one item
-        // because it is impossible to determine if the empty value or nothing is selected.
-        // This is why empty values will be removed for multi value fields.
-        $currentDatabaseValuesArray = GeneralUtility::trimExplode(',', $currentDatabaseValues, true);
+        $currentDatabaseValueArray = array_key_exists($fieldName, $result['databaseRow']) ? $result['databaseRow'][$fieldName] : [];
         $newDatabaseValueArray = [];
 
         // Add all values that were defined by static methods and do not come from the relation
         // e.g. TCA, TSconfig, itemProcFunc etc.
-        foreach ($currentDatabaseValuesArray as $value) {
+        foreach ($currentDatabaseValueArray as $value) {
             if (isset($staticValues[$value])) {
                 $newDatabaseValueArray[] = $value;
             }
@@ -1068,7 +1071,7 @@ abstract class AbstractItemProvider
             if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') {
                 // MM relation
                 $relationHandler->start(
-                    $currentDatabaseValues,
+                    implode(',', $currentDatabaseValueArray),
                     $fieldConfig['config']['foreign_table'],
                     $fieldConfig['config']['MM'],
                     $result['databaseRow']['uid'],
@@ -1079,7 +1082,7 @@ abstract class AbstractItemProvider
                 // Non MM relation
                 // If not dealing with MM relations, use default live uid, not versioned uid for record relations
                 $relationHandler->start(
-                    $currentDatabaseValues,
+                    implode(',', $currentDatabaseValueArray),
                     $fieldConfig['config']['foreign_table'],
                     '',
                     $this->getLiveUid($result),
index 52f15a7..118a2ca 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
@@ -53,12 +54,16 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
             $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']);
             $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems);
 
+            $removedItems = $fieldConfig['config']['items'];
+
             $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
             $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
             $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']);
             $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']);
             $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']);
 
+            $removedItems = array_diff_key($removedItems, $fieldConfig['config']['items']);
+
             // Resolve "itemsProcFunc"
             if (!empty($fieldConfig['config']['itemsProcFunc'])) {
                 $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']);
@@ -66,22 +71,80 @@ class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInt
                 unset($fieldConfig['config']['itemsProcFunc']);
             }
 
-            // Translate labels
-            $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
+            // needed to determine the items for invalid values
+            $currentDatabaseValuesArray = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
+            $result['databaseRow'][$fieldName] = $currentDatabaseValuesArray;
 
             $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems);
+            $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
+
+            $fieldConfig['config']['items'] = $this->addInvalidItemsFromDatabase(
+                $result,
+                $table,
+                $fieldName,
+                $fieldConfig,
+                $currentDatabaseValuesArray,
+                $removedItems
+            );
+
+            // Translate labels
+            $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
 
             // Keys may contain table names, so a numeric array is created
             $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']);
 
             $result['processedTca']['columns'][$fieldName] = $fieldConfig;
-            $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
         }
 
         return $result;
     }
 
     /**
+     * Add values that are currently listed in the database columns but not in the selectable items list
+     * back to the list.
+     *
+     * @param array $result The current result array.
+     * @param string $table The current table name
+     * @param string $fieldName The current field name
+     * @param array $fieldConf The configuration of the current field.
+     * @param array $databaseValues The item values from the database, can contain invalid items!
+     * @param array $removedItems Items removed by access checks and restrictions, must not be added as invalid values
+     * @return array
+     */
+    public function addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems)
+    {
+        // Early return if there are no items or invalid values should not be displayed
+        if (empty($fieldConf['config']['items'])
+            || $fieldConf['config']['renderType'] !== 'selectSingle'
+            || $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['disableNoMatchingValueElement']
+            || $fieldConf['config']['disableNoMatchingValueElement']
+        ) {
+            return $fieldConf['config']['items'];
+        }
+
+        $languageService = $this->getLanguageService();
+        $noMatchingLabel = isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label'])
+            ? $languageService->sL(trim($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label']))
+            : '[ ' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
+
+        $unmatchedValues = array_diff(
+            array_values($databaseValues),
+            array_column($fieldConf['config']['items'], 1),
+            array_column($removedItems, 1)
+        );
+
+        foreach ($unmatchedValues as $unmatchedValue) {
+            $invalidItem = [
+                @sprintf($noMatchingLabel, $unmatchedValue),
+                $unmatchedValue
+            ];
+            array_unshift($fieldConf['config']['items'], $invalidItem);
+        }
+
+        return $fieldConf['config']['items'];
+    }
+
+    /**
      * Determines whether the current field is a valid target for this DataProvider
      *
      * @param array $fieldConfig
index 820265f..078fa59 100644 (file)
@@ -74,6 +74,7 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide
             $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
 
             $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems);
+            $result['databaseRow'][$fieldName] = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
             $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
 
             // Keys may contain table names, so a numeric array is created
@@ -111,7 +112,7 @@ class TcaSelectTreeItems extends AbstractItemProvider implements FormDataProvide
             $fieldName,
             $result['databaseRow']
         );
-        $treeDataProvider->setSelectedList(implode(',', $result['databaseRow'][$fieldName]));
+        $treeDataProvider->setSelectedList(is_array($result['databaseRow'][$fieldName]) ? implode(',', $result['databaseRow'][$fieldName]) : $result['databaseRow'][$fieldName]);
         $treeDataProvider->setItemWhiteList($allowedUids);
         $treeDataProvider->initializeTreeData();
 
index db6d664..9c90e41 100644 (file)
@@ -156,6 +156,7 @@ class TcaSelectItemsTest extends UnitTestCase
         /** @var LanguageService|ObjectProphecy $languageService */
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
 
         $languageService->sL('aLabel')->shouldBeCalled()->willReturn('translated');
 
@@ -242,7 +243,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue',
+                'aField' => '',
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -275,12 +276,14 @@ class TcaSelectItemsTest extends UnitTestCase
         /** @var LanguageService|ObjectProphecy $languageService */
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
+        $languageService->sL(Argument::containingString('INVALID VALUE'))->willReturnArgument(0);
 
         $languageService->sL('aTitle')->shouldBeCalled()->willReturnArgument(0);
         $languageService->loadSingleTableDescription('aTable')->shouldBeCalled();
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = [];
         $expected['processedTca']['columns']['aField']['config']['items'] = [
             0 => [
                 0 => 'aTitle',
@@ -339,6 +342,7 @@ class TcaSelectItemsTest extends UnitTestCase
         /** @var LanguageService|ObjectProphecy $languageService */
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
 
         $languageService->sL('aLabel')->shouldBeCalled()->willReturnArgument(0);
 
@@ -482,6 +486,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -514,6 +519,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -603,6 +609,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -673,6 +680,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -743,6 +751,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -834,6 +843,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -925,6 +935,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -971,6 +982,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -1027,6 +1039,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -1092,6 +1105,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $directory = $this->getUniqueId('typo3temp/test-') . '/';
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -1379,6 +1393,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'tableName' => 'aTable',
+            'databaseRow' => [],
             'processedTca' => [
                 'columns' => [
                     'aField' => [
@@ -1433,7 +1448,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue',
+                'aField' => '',
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1492,7 +1507,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = [];
 
         $this->assertEquals($expected, $this->subject->addData($input));
     }
@@ -1500,11 +1515,11 @@ class TcaSelectItemsTest extends UnitTestCase
     /**
      * @test
      */
-    public function addDataForeignTableHandlesForegnTableRows()
+    public function addDataForeignTableHandlesForeignTableRows()
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue',
+                'aField' => '',
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1571,7 +1586,7 @@ class TcaSelectItemsTest extends UnitTestCase
             ],
         ];
 
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = [];
 
         $this->assertEquals($expected, $this->subject->addData($input));
     }
@@ -1583,7 +1598,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue',
+                'aField' => '',
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1626,7 +1641,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = [];
         unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
 
         $this->assertEquals($expected, $this->subject->addData($input));
@@ -1639,7 +1654,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue'
+                'aField' => ''
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1682,7 +1697,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = [];
         unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
 
         $this->assertEquals($expected, $this->subject->addData($input));
@@ -1695,7 +1710,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue'
+                'aField' => 'aValue,remove'
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1729,6 +1744,7 @@ class TcaSelectItemsTest extends UnitTestCase
         /** @var LanguageService|ObjectProphecy $languageService */
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
 
         /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
@@ -1738,8 +1754,11 @@ class TcaSelectItemsTest extends UnitTestCase
         $backendUserProphecy->checkLanguageAccess('remove')->shouldBeCalled()->willReturn(false);
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
-        unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
+        $expected['databaseRow']['aField'] = [];
+        $expected['processedTca']['columns']['aField']['config']['items'] = [
+            [ '[ INVALID VALUE "aValue" ]', 'aValue', null, null ],
+            [ 'keepMe', 'keep', null, null ],
+        ];
 
         $this->assertEquals($expected, $this->subject->addData($input));
     }
@@ -1751,7 +1770,7 @@ class TcaSelectItemsTest extends UnitTestCase
     {
         $input = [
             'databaseRow' => [
-                'aField' => 'aValue'
+                'aField' => 'keep,remove'
             ],
             'tableName' => 'aTable',
             'processedTca' => [
@@ -1792,7 +1811,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $backendUserProphecy->checkAuthMode('aTable', 'aField', 'remove', 'explicitAllow')->shouldBeCalled()->willReturn(false);
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['aValue'];
+        $expected['databaseRow']['aField'] = ['keep'];
         unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
 
         $this->assertEquals($expected, $this->subject->addData($input));
@@ -2133,8 +2152,8 @@ class TcaSelectItemsTest extends UnitTestCase
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
         $languageService->sL('aLabel')->willReturnArgument(0);
-
         $languageService->sL('labelOverride')->shouldBeCalled()->willReturnArgument(0);
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
 
         $expected = $input;
         $expected['databaseRow']['aField'] = ['aValue'];
@@ -2289,7 +2308,7 @@ class TcaSelectItemsTest extends UnitTestCase
                     'aField' => [
                         'config' => [
                             'type' => 'select',
-                            'renderType' => 'selectSingle',
+                            'renderType' => 'selectSingleBox',
                             'foreign_table' => 'foreignTable',
                             'maxitems' => 999,
                             'items' => [
@@ -2376,9 +2395,7 @@ class TcaSelectItemsTest extends UnitTestCase
         ];
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = [
-            '',
-        ];
+        $expected['databaseRow']['aField'] = [];
 
         $this->assertEquals($expected, $this->subject->addData($input));
     }
@@ -2441,7 +2458,7 @@ class TcaSelectItemsTest extends UnitTestCase
         $input = [
             'tableName' => 'aTable',
             'databaseRow' => [
-                'aField' => '1,2,bar,foo',
+                'aField' => 'foo',
             ],
             'processedTca' => [
                 'columns' => [
@@ -2468,10 +2485,11 @@ class TcaSelectItemsTest extends UnitTestCase
     /**
      * @test
      */
-    public function processSelectFieldValueDoesNotTouchValueForSingleSelects()
+    public function processSelectFieldValueAddsInvalidValuesToItemsForSingleSelects()
     {
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
+        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
 
         $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
@@ -2501,8 +2519,13 @@ class TcaSelectItemsTest extends UnitTestCase
         ];
 
         $expected = $input;
-        $expected['databaseRow']['aField'] = ['1,2,bar,foo'];
-
+        $expected['databaseRow']['aField'] = ['foo'];
+        $expected['processedTca']['columns']['aField']['config']['items'] = [
+            ['[ INVALID VALUE "bar" ]', 'bar', null, null],
+            ['[ INVALID VALUE "2" ]', '2', null, null],
+            ['[ INVALID VALUE "1" ]', '1', null, null],
+            ['foo', 'foo', null, null],
+        ];
         $this->assertEquals($expected, $this->subject->addData($input));
     }