From 519464a822736bc802fcc342820dfa20415d09f8 Mon Sep 17 00:00:00 2001
From: Nicole Cordes <typo3@cordes.co>
Date: Sun, 20 Sep 2015 19:26:06 +0200
Subject: [PATCH] [TASK] Add override values to record information

This patch cares about overrideVals which might be set. They have to be
added to the database record information and should turn the field into
a hidden input element.

Resolves: #70058
Releases: master
Change-Id: I116177e5bdf3511621842221dd7289446337cd0c
Reviewed-on: http://review.typo3.org/43441
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Controller/EditDocumentController.php     |  10 +-
 .../Form/Container/SingleFieldContainer.php   | 316 +++++++++---------
 .../DatabaseRecordOverrideValues.php          |  45 +++
 .../DatabaseRecordOverrideValuesTest.php      | 104 ++++++
 .../Configuration/DefaultConfiguration.php    |   8 +-
 5 files changed, 316 insertions(+), 167 deletions(-)
 create mode 100644 typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php
 create mode 100644 typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordOverrideValuesTest.php

diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
index 396bacea3fc5..bc72d43e87e5 100644
--- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
+++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
@@ -947,6 +947,10 @@ class EditDocumentController {
 									'command' => $command,
 									'returnUrl' => $this->R_URI,
 								];
+								if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
+									$formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
+								}
+
 								$formData = $formDataCompiler->compile($formDataCompilerInput);
 
 								// Set this->viewId if possible
@@ -1007,12 +1011,6 @@ class EditDocumentController {
 									'deleteAccess' => $deleteAccess
 								);
 
-								// Set additional FormData
-								// @todo: This is a hack and should be done differently
-								if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
-									$formData['overrideValues'] = $this->overrideVals[$table];
-								}
-
 								if ($command !== 'new') {
 									BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
 								}
diff --git a/typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php b/typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php
index eb08985b060e..98c9095e124e 100644
--- a/typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php
+++ b/typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php
@@ -127,174 +127,170 @@ class SingleFieldContainer extends AbstractContainer {
 			$alertMsgOnChange = '';
 		}
 
-		if (array_key_exists($fieldName, $this->data['overrideValues'])) {
-			$options = [
-				'parameterArray' => [
-						'itemFormElName' => 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']',
-						'itemFormElValue' => $this->data['overrideValues'][$fieldName]
-				],
-				'renderType' => 'hidden'
-			];
-			$resultArray = $this->nodeFactory->create($options)->render();
+		// JavaScript code for event handlers:
+		$parameterArray['fieldChangeFunc'] = array();
+		$parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ',' . GeneralUtility::quoteJSvalue($fieldName) . ',' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ');';
+		$parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
+
+		// If this is the child of an inline type and it is the field creating the label
+		if ($this->isInlineChildAndLabelField($table, $fieldName)) {
+			/** @var InlineStackProcessor $inlineStackProcessor */
+			$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+			$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
+			$inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
+			$inlineObjectId = implode(
+				'-',
+				array(
+					$inlineDomObjectId,
+					$table,
+					$row['uid']
+				)
+			);
+			$parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue($inlineObjectId) . ');';
+		}
+
+		// Based on the type of the item, call a render function on a child element
+		$options = $this->data;
+		$options['parameterArray'] = $parameterArray;
+		$options['elementBaseName'] = $newElementBaseName;
+		if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
+			$options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
 		} else {
-			// JavaScript code for event handlers:
-			$parameterArray['fieldChangeFunc'] = array();
-			$parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ',' . GeneralUtility::quoteJSvalue($fieldName) . ',' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ');';
-			$parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
-
-			// If this is the child of an inline type and it is the field creating the label
-			if ($this->isInlineChildAndLabelField($table, $fieldName)) {
-				/** @var InlineStackProcessor $inlineStackProcessor */
-				$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
-				$inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
-				$inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
-				$inlineObjectId = implode(
-					'-',
-					array(
-						$inlineDomObjectId,
-						$table,
-						$row['uid']
-					)
-				);
-				$parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue($inlineObjectId) . ');';
-			}
+			// Fallback to type if no renderType is given
+			$options['renderType'] = $parameterArray['fieldConf']['config']['type'];
+		}
+		$resultArray = $this->nodeFactory->create($options)->render();
 
-			// Based on the type of the item, call a render function on a child element
-			$options = $this->data;
-			$options['parameterArray'] = $parameterArray;
-			$options['elementBaseName'] = $newElementBaseName;
-			if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
-				$options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
-			} else {
-				// Fallback to type if no renderType is given
-				$options['renderType'] = $parameterArray['fieldConf']['config']['type'];
-			}
-			$resultArray = $this->nodeFactory->create($options)->render();
-			$html = $resultArray['html'];
-
-			// @todo: the language handling, the null and the placeholder stuff should be embedded in the single
-			// @todo: element classes. Basically, this method should return here and have the element classes
-			// @todo: decide on language stuff and other wraps already.
-
-			// Add language + diff
-			$renderLanguageDiff = TRUE;
-			if (
-				$parameterArray['fieldConf']['l10n_display'] && (GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'hideDiff')
-				|| GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly'))
-			) {
-				$renderLanguageDiff = FALSE;
-			}
-			if ($renderLanguageDiff) {
-				$html = $this->renderDefaultLanguageContent($table, $fieldName, $row, $html);
-				$html = $this->renderDefaultLanguageDiff($table, $fieldName, $row, $html);
-			}
+		// If output is empty stop further processing.
+		// This means there was internal processing only and we don't need to add additional information
+		if (empty($resultArray['html'])) {
+			return $resultArray;
+		}
 
-			$fieldItemClasses = array(
-				't3js-formengine-field-item'
-			);
+		$html = $resultArray['html'];
 
-			// NULL value and placeholder handling
-			$nullControlNameAttribute = ' name="' . htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']') . '"';
-			if (!empty($parameterArray['fieldConf']['config']['eval']) && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
-				&& (empty($parameterArray['fieldConf']['config']['mode']) || $parameterArray['fieldConf']['config']['mode'] !== 'useOrOverridePlaceholder')
-			) {
-				// This field has eval=null set, but has no useOverridePlaceholder defined.
-				// Goal is to have a field that can distinct between NULL and empty string in the database.
-				// A checkbox and an additional hidden field will be created, both with the same name
-				// and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual
-				// input field will be written to the database. If the checkbox is not set, the hidden field
-				// transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the
-				// DataHandler at an early point in processing, so NULL will be written to DB as field value.
-
-				// If the value of the field *is* NULL at the moment, an additional class is set
-				// @todo: This does not work well at the moment, but is kept for now. see input_14 of ext:styleguide as example
-				$checked = ' checked="checked"';
-				if ($this->data['databaseRow'][$fieldName] === NULL) {
-					$fieldItemClasses[] = 'disabled';
-					$checked = '';
-				}
+		// @todo: the language handling, the null and the placeholder stuff should be embedded in the single
+		// @todo: element classes. Basically, this method should return here and have the element classes
+		// @todo: decide on language stuff and other wraps already.
+
+		// Add language + diff
+		$renderLanguageDiff = TRUE;
+		if (
+			$parameterArray['fieldConf']['l10n_display'] && (GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'hideDiff')
+			|| GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly'))
+		) {
+			$renderLanguageDiff = FALSE;
+		}
+		if ($renderLanguageDiff) {
+			$html = $this->renderDefaultLanguageContent($table, $fieldName, $row, $html);
+			$html = $this->renderDefaultLanguageDiff($table, $fieldName, $row, $html);
+		}
+
+		$fieldItemClasses = array(
+			't3js-formengine-field-item'
+		);
 
-				$formElementName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
-				$onChange = htmlspecialchars(
-					'typo3form.fieldSetNull(' . GeneralUtility::quoteJSvalue($formElementName) . ', !this.checked)'
-				);
-
-				$nullValueWrap = array();
-				$nullValueWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
-				$nullValueWrap[] = 	'<div class="t3-form-field-disable"></div>';
-				$nullValueWrap[] = 	'<div class="checkbox">';
-				$nullValueWrap[] = 		'<label>';
-				$nullValueWrap[] = 			'<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
-				$nullValueWrap[] = 			'<input type="checkbox"' . $nullControlNameAttribute . ' value="1" onchange="' . $onChange . '"' . $checked . ' /> &nbsp;';
-				$nullValueWrap[] = 		'</label>';
-				$nullValueWrap[] = 		$html;
-				$nullValueWrap[] = 	'</div>';
-				$nullValueWrap[] = '</div>';
-
-				$html = implode(LF, $nullValueWrap);
-			} elseif (isset($parameterArray['fieldConf']['config']['mode']) && $parameterArray['fieldConf']['config']['mode'] === 'useOrOverridePlaceholder') {
-				// This field has useOverridePlaceholder set.
-				// Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a
-				// local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata,
-				// the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string
-				// or an empty string is then written to the field of sys_file_reference.
-				// The situation is similar to the NULL handling above, but additionally a "default" value should be shown.
-				// To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name
-				// to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as
-				// value in control[active], meaning the overridden value should be used.
-				// Additionally to the casual input field, a second field is added containing the "placeholder" value. This
-				// field has no name attribute and is not transferred at all. Those two are then hidden / shown depending
-				// on the state of the above checkbox in via JS.
-
-				$placeholder = $this->getPlaceholderValue($table, $parameterArray['fieldConf']['config'], $row);
-				$onChange = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', !this.checked)';
-				$checked = $parameterArray['itemFormElValue'] === NULL ? '' : ' checked="checked"';
-
-				$resultArray['additionalJavaScriptPost'][] = 'typo3form.fieldTogglePlaceholder('
-					. GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', ' . ($checked ? 'false' : 'true') . ');';
-
-				// Renders an input or textarea field depending on type of "parent"
-				$options = array();
-				$options['databaseRow'] = array();
-				$options['table'] = '';
-				$options['parameterArray'] = $parameterArray;
-				$options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
-				$options['renderType'] = 'none';
-				$noneElementResult = $this->nodeFactory->create($options)->render();
-				$noneElementHtml = $noneElementResult['html'];
-
-				$placeholderWrap = array();
-				$placeholderWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
-				$placeholderWrap[] = 	'<div class="t3-form-field-disable"></div>';
-				$placeholderWrap[] = 	'<div class="checkbox">';
-				$placeholderWrap[] = 		'<label>';
-				$placeholderWrap[] = 			'<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
-				$placeholderWrap[] = 			'<input type="checkbox"' . $nullControlNameAttribute . ' value="1" id="tce-forms-textfield-use-override-' . $fieldName . '-' . $row['uid'] . '" onchange="' . htmlspecialchars($onChange) . '"' . $checked . ' />';
-				$placeholderWrap[] =		 	sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.placeholder.override'), BackendUtility::getRecordTitlePrep($placeholder, 20));
-				$placeholderWrap[] = 		'</label>';
-				$placeholderWrap[] = 	'</div>';
-				$placeholderWrap[] = 	'<div class="t3js-formengine-placeholder-placeholder">';
-				$placeholderWrap[] = 		$noneElementHtml;
-				$placeholderWrap[] = 	'</div>';
-				$placeholderWrap[] = 	'<div class="t3js-formengine-placeholder-formfield">';
-				$placeholderWrap[] = 		$html;
-				$placeholderWrap[] = 	'</div>';
-				$placeholderWrap[] = '</div>';
-
-				$html = implode(LF, $placeholderWrap);
-			} elseif ($parameterArray['fieldConf']['config']['type'] !== 'user' || empty($parameterArray['fieldConf']['config']['noTableWrapping'])) {
-				// Add a casual wrap if the field is not of type user with no wrap requested.
-				$standardWrap = array();
-				$standardWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
-				$standardWrap[] = 	'<div class="t3-form-field-disable"></div>';
-				$standardWrap[] = 	$html;
-				$standardWrap[] = '</div>';
-
-				$html = implode(LF, $standardWrap);
+		// NULL value and placeholder handling
+		$nullControlNameAttribute = ' name="' . htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']') . '"';
+		if (!empty($parameterArray['fieldConf']['config']['eval']) && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
+			&& (empty($parameterArray['fieldConf']['config']['mode']) || $parameterArray['fieldConf']['config']['mode'] !== 'useOrOverridePlaceholder')
+		) {
+			// This field has eval=null set, but has no useOverridePlaceholder defined.
+			// Goal is to have a field that can distinct between NULL and empty string in the database.
+			// A checkbox and an additional hidden field will be created, both with the same name
+			// and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual
+			// input field will be written to the database. If the checkbox is not set, the hidden field
+			// transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the
+			// DataHandler at an early point in processing, so NULL will be written to DB as field value.
+
+			// If the value of the field *is* NULL at the moment, an additional class is set
+			// @todo: This does not work well at the moment, but is kept for now. see input_14 of ext:styleguide as example
+			$checked = ' checked="checked"';
+			if ($this->data['databaseRow'][$fieldName] === NULL) {
+				$fieldItemClasses[] = 'disabled';
+				$checked = '';
 			}
 
-			$resultArray['html'] = $html;
+			$formElementName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
+			$onChange = htmlspecialchars(
+				'typo3form.fieldSetNull(' . GeneralUtility::quoteJSvalue($formElementName) . ', !this.checked)'
+			);
+
+			$nullValueWrap = array();
+			$nullValueWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
+			$nullValueWrap[] = 	'<div class="t3-form-field-disable"></div>';
+			$nullValueWrap[] = 	'<div class="checkbox">';
+			$nullValueWrap[] = 		'<label>';
+			$nullValueWrap[] = 			'<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
+			$nullValueWrap[] = 			'<input type="checkbox"' . $nullControlNameAttribute . ' value="1" onchange="' . $onChange . '"' . $checked . ' /> &nbsp;';
+			$nullValueWrap[] = 		'</label>';
+			$nullValueWrap[] = 		$html;
+			$nullValueWrap[] = 	'</div>';
+			$nullValueWrap[] = '</div>';
+
+			$html = implode(LF, $nullValueWrap);
+		} elseif (isset($parameterArray['fieldConf']['config']['mode']) && $parameterArray['fieldConf']['config']['mode'] === 'useOrOverridePlaceholder') {
+			// This field has useOverridePlaceholder set.
+			// Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a
+			// local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata,
+			// the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string
+			// or an empty string is then written to the field of sys_file_reference.
+			// The situation is similar to the NULL handling above, but additionally a "default" value should be shown.
+			// To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name
+			// to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as
+			// value in control[active], meaning the overridden value should be used.
+			// Additionally to the casual input field, a second field is added containing the "placeholder" value. This
+			// field has no name attribute and is not transferred at all. Those two are then hidden / shown depending
+			// on the state of the above checkbox in via JS.
+
+			$placeholder = $this->getPlaceholderValue($table, $parameterArray['fieldConf']['config'], $row);
+			$onChange = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', !this.checked)';
+			$checked = $parameterArray['itemFormElValue'] === NULL ? '' : ' checked="checked"';
+
+			$resultArray['additionalJavaScriptPost'][] = 'typo3form.fieldTogglePlaceholder('
+				. GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', ' . ($checked ? 'false' : 'true') . ');';
+
+			// Renders an input or textarea field depending on type of "parent"
+			$options = array();
+			$options['databaseRow'] = array();
+			$options['table'] = '';
+			$options['parameterArray'] = $parameterArray;
+			$options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
+			$options['renderType'] = 'none';
+			$noneElementResult = $this->nodeFactory->create($options)->render();
+			$noneElementHtml = $noneElementResult['html'];
+
+			$placeholderWrap = array();
+			$placeholderWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
+			$placeholderWrap[] = 	'<div class="t3-form-field-disable"></div>';
+			$placeholderWrap[] = 	'<div class="checkbox">';
+			$placeholderWrap[] = 		'<label>';
+			$placeholderWrap[] = 			'<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
+			$placeholderWrap[] = 			'<input type="checkbox"' . $nullControlNameAttribute . ' value="1" id="tce-forms-textfield-use-override-' . $fieldName . '-' . $row['uid'] . '" onchange="' . htmlspecialchars($onChange) . '"' . $checked . ' />';
+			$placeholderWrap[] =		 	sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.placeholder.override'), BackendUtility::getRecordTitlePrep($placeholder, 20));
+			$placeholderWrap[] = 		'</label>';
+			$placeholderWrap[] = 	'</div>';
+			$placeholderWrap[] = 	'<div class="t3js-formengine-placeholder-placeholder">';
+			$placeholderWrap[] = 		$noneElementHtml;
+			$placeholderWrap[] = 	'</div>';
+			$placeholderWrap[] = 	'<div class="t3js-formengine-placeholder-formfield">';
+			$placeholderWrap[] = 		$html;
+			$placeholderWrap[] = 	'</div>';
+			$placeholderWrap[] = '</div>';
+
+			$html = implode(LF, $placeholderWrap);
+		} elseif ($parameterArray['fieldConf']['config']['type'] !== 'user' || empty($parameterArray['fieldConf']['config']['noTableWrapping'])) {
+			// Add a casual wrap if the field is not of type user with no wrap requested.
+			$standardWrap = array();
+			$standardWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
+			$standardWrap[] = 	'<div class="t3-form-field-disable"></div>';
+			$standardWrap[] = 	$html;
+			$standardWrap[] = '</div>';
+
+			$html = implode(LF, $standardWrap);
 		}
+
+		$resultArray['html'] = $html;
 		return $resultArray;
 	}
 
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php
new file mode 100644
index 000000000000..ae4f647ac99b
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php
@@ -0,0 +1,45 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\FormDataProvider;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+
+/**
+ * Determine the final TCA type value
+ */
+class DatabaseRecordOverrideValues implements FormDataProviderInterface {
+
+	/**
+	 * Add override values to the databaseRow fields. As those values are not meant to
+	 * be overwritten by the user, the TCA of the field is set to type hidden.
+	 *
+	 * @param array $result
+	 * @return array
+	 */
+	public function addData(array $result) {
+		foreach ($result['overrideValues'] as $fieldName => $fieldValue) {
+			if (isset($result['vanillaTableTca']['columns'][$fieldName])) {
+				$result['databaseRow'][$fieldName] = $fieldValue;
+				$result['vanillaTableTca']['columns'][$fieldName]['config'] = array(
+					'type' => 'hidden',
+					'renderType' => 'hidden',
+				);
+			}
+		}
+
+		return $result;
+	}
+
+}
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordOverrideValuesTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordOverrideValuesTest.php
new file mode 100644
index 000000000000..5b7c34115f31
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordOverrideValuesTest.php
@@ -0,0 +1,104 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues;
+
+/**
+ * Test case
+ */
+class DatabaseRecordOverrideValuesTest extends UnitTestCase {
+
+	/**
+	 * @var DatabaseRecordOverrideValues
+	 */
+	protected $subject;
+
+	protected function setUp() {
+		$this->subject = new DatabaseRecordOverrideValues();
+	}
+
+	/**
+	 * @test
+	 */
+	public function addDataReturnSameDataIfNoOverrideValuesSet() {
+		$input = [
+			'tableName' => 'aTable',
+			'vanillaTableTca' => [
+				'columns' => [
+					'aField' => [
+						'config' => [
+							'type' => 'input',
+						],
+					],
+				],
+			],
+			'databaseRow' => [
+				'uid' => 42,
+			],
+			'overrideValues' => [
+				'anotherField' => 13,
+			]
+		];
+
+		$this->assertSame($input, $this->subject->addData($input));
+	}
+
+	/**
+	 * @test
+	 */
+	public function addDataSetsDatabaseRowAndTcaType() {
+		$input = [
+			'tableName' => 'aTable',
+			'vanillaTableTca' => [
+				'columns' => [
+					'aField' => [
+						'config' => [
+							'type' => 'input',
+						],
+					],
+					'anotherField' => [
+						'config' => [
+							'type' => 'input',
+						],
+					],
+				],
+			],
+			'databaseRow' => [
+				'uid' => 42,
+			],
+			'overrideValues' => [
+				'aField' => 256,
+				'anotherField' => 13,
+			]
+		];
+
+		$expected = $input;
+		$expected['databaseRow']['aField'] = 256;
+		$expected['databaseRow']['anotherField'] = 13;
+		$expected['vanillaTableTca']['columns']['aField']['config'] = [
+			'type' => 'hidden',
+			'renderType' => 'hidden',
+		];
+		$expected['vanillaTableTca']['columns']['anotherField']['config'] = [
+			'type' => 'hidden',
+			'renderType' => 'hidden',
+		];
+
+		$this->assertSame($expected, $this->subject->addData($input));
+	}
+
+}
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index f4cf33ca5bcd..cc8dbedcbea0 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -355,14 +355,20 @@ return array(
 							\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDateTimeFields::class
 						),
 					),
-					\TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class => array(
+					\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class => array(
 						'depends' => array(
 							\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class,
 						),
 					),
+					\TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class => array(
+						'depends' => array(
+							\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
+						),
+					),
 					\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => array(
 						'depends' => array(
 							\TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class,
+							\TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
 						),
 					),
 					\TYPO3\CMS\Backend\Form\FormDataProvider\DatabasePageLanguageOverlayRows::class => array(
-- 
GitLab