Commit efa8c85d authored by Christian Kuhn's avatar Christian Kuhn Committed by Benni Mack
Browse files

[TASK] FormEngine inline refactoring

FormEngine splits in two parts: Data compilation and preparation
and rendering.

The patch separates the inline related data preparation out
of the render containers and moves it into the data provider.
TcaInline provider now resolves and compiles children and adds them
to processedTca[columns][$field][children], so InlineControlContainer
can just loop over them to render children.

InlineRecordContainer, the second inline container that takes
care of rendering single children records now no longer receives
the full parent data, but only the specific child data array it should
render. This leads to better encapsulation and allows some future
performance improvements.

While the inline stuff is still a very complex thing, this last
main structural FormEngine change takes the opportunity to comment
further details and dependencies and it simplifies the structures
by better separation of concerns.

Change-Id: Ia0ed276d7fc6f541f8ae27eaac3e17e3b8714ddf
Resolves: #70490
Releases: master
Reviewed-on: http://review.typo3.org/43755

Reviewed-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 9b52da33
......@@ -107,6 +107,9 @@ class FlexFormElementContainer extends AbstractContainer {
$originalFieldName = $parameterArray['itemFormElName'];
$fakeParameterArray['itemFormElName'] = $parameterArray['itemFormElName'] . $flexFormFormPrefix . '[' . $flexFormFieldName . '][vDEF]';
if ($fakeParameterArray['itemFormElName'] !== $originalFieldName) {
// If calculated itemFormElName is different from originalFieldName
// change the originalFieldName in TBE_EDITOR_fieldChanged. This is
// especially relevant for wizards writing their content back to hidden fields
if (!empty($fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'])) {
$fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = str_replace($originalFieldName, $fakeParameterArray['itemFormElName'], $fakeParameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged']);
}
......
......@@ -30,8 +30,6 @@ use TYPO3\CMS\Lang\LanguageService;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
/**
* Inline element entry container.
......@@ -95,28 +93,14 @@ class InlineControlContainer extends AbstractContainer {
$parameterArray = $this->data['parameterArray'];
$resultArray = $this->initializeResultArray();
$html = '';
// An inline field must have a foreign_table, if not, stop all further inline actions for this field
if (
!$parameterArray['fieldConf']['config']['foreign_table']
|| !is_array($GLOBALS['TCA'][$parameterArray['fieldConf']['config']['foreign_table']])
) {
return $resultArray;
}
$config = FormEngineUtility::mergeInlineConfiguration($parameterArray['fieldConf']['config']);
$config = $parameterArray['fieldConf']['config'];
$foreign_table = $config['foreign_table'];
$language = 0;
if (BackendUtility::isTableLocalizable($table)) {
$language = (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
}
$minItems = MathUtility::forceIntegerInRange($config['minitems'], 0);
$maxItems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
if (!$maxItems) {
$maxItems = 100000;
}
// Add the current inline job to the structure stack
$newStructureItem = array(
......@@ -128,7 +112,7 @@ class InlineControlContainer extends AbstractContainer {
);
// Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
if (!empty($parameterArray['itemFormElName'])) {
$flexFormParts = FormEngineUtility::extractFlexFormParts($parameterArray['itemFormElName']);
$flexFormParts = $this->extractFlexFormParts($parameterArray['itemFormElName']);
if ($flexFormParts !== NULL) {
$newStructureItem['flexform'] = $flexFormParts;
}
......@@ -140,15 +124,20 @@ class InlineControlContainer extends AbstractContainer {
// e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
$nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
// Get the records related to this inline record
/** @var InlineRelatedRecordResolver $inlineRelatedRecordResolver */
$inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
$relatedRecords = $inlineRelatedRecordResolver->getRelatedRecords($table, $field, $row, $parameterArray, $config, $this->data['inlineFirstPid']);
// Set the first and last record to the config array
$relatedRecordsUids = array_keys($relatedRecords['records']);
$config['inline']['first'] = reset($relatedRecordsUids);
$config['inline']['last'] = end($relatedRecordsUids);
$config['inline']['first'] = FALSE;
// @todo: This initialization shouldn't be required data provider should take care this is set?
if (!is_array($this->data['parameterArray']['fieldConf']['children'])) {
$this->data['parameterArray']['fieldConf']['children'] = array();
}
$firstChild = reset($this->data['parameterArray']['fieldConf']['children']);
if (isset($firstChild['databaseRow']['uid'])) {
$config['inline']['first'] = $firstChild['databaseRow']['uid'];
}
$config['inline']['last'] = FALSE;
$lastChild = end($this->data['parameterArray']['fieldConf']['children']);
if (isset($lastChild['databaseRow']['uid'])) {
$config['inline']['last'] = $lastChild['databaseRow']['uid'];
}
$top = $inlineStackProcessor->getStructureLevel(0);
......@@ -157,8 +146,8 @@ class InlineControlContainer extends AbstractContainer {
'md5' => md5($nameObject)
);
$this->inlineData['config'][$nameObject . '-' . $foreign_table] = array(
'min' => $minItems,
'max' => $maxItems,
'min' => $config['minitems'],
'max' => $config['maxitems'],
'sortable' => $config['appearance']['useSortable'],
'top' => array(
'table' => $top['table'],
......@@ -179,7 +168,7 @@ class InlineControlContainer extends AbstractContainer {
// 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($relatedRecords['records'], $config, $selConfig['type'] == 'groupdb');
$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);
$this->inlineData['unique'][$nameObject . '-' . $foreign_table] = array(
......@@ -210,8 +199,14 @@ class InlineControlContainer extends AbstractContainer {
}
}
$numberOfFullChildren = 0;
foreach ($this->data['parameterArray']['fieldConf']['children'] as $child) {
if (!$child['inlineIsDefaultLanguage']) {
$numberOfFullChildren ++;
}
}
// Define how to show the "Create new record" link - if there are more than maxitems, hide it
if ($relatedRecords['count'] >= $maxItems || $uniqueMax > 0 && $relatedRecords['count'] >= $uniqueMax) {
if ($numberOfFullChildren >= $config['maxitems'] || $uniqueMax > 0 && $numberOfFullChildren >= $uniqueMax) {
$config['inline']['inlineNewButtonStyle'] = 'display: none;';
$config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
}
......@@ -220,11 +215,12 @@ class InlineControlContainer extends AbstractContainer {
$levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
// Wrap all inline fields of a record with a <div> (like a container)
$html .= '<div class="form-group" id="' . $nameObject . '">';
$html = '<div class="form-group" id="' . $nameObject . '">';
// Add the level links before all child records:
if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
$html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
}
// 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
......@@ -235,34 +231,31 @@ class InlineControlContainer extends AbstractContainer {
$selectorBox = $this->renderPossibleRecordsSelector($possibleRecords, $config, $uniqueIds);
$html .= $selectorBox . $localizationLinks;
}
$title = $languageService->sL($parameterArray['fieldConf']['label']);
$html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
$relationList = array();
if (!empty($relatedRecords['records'])) {
foreach ($relatedRecords['records'] as $rec) {
$options = $this->data;
$options['inlineRelatedRecordToRender'] = $rec;
$options['inlineRelatedRecordConfig'] = $config;
$options['inlineData'] = $this->inlineData;
$options['inlineStructure'] = $inlineStackProcessor->getStructure();
$options['renderType'] = 'inlineRecordContainer';
try {
// This container may raise an access denied exception, to not kill further processing,
// just a simple "empty" return is created here to ignore this field.
$childArray = $this->nodeFactory->create($options)->render();
} catch (AccessDeniedException $e) {
$childArray = $this->initializeResultArray();
}
$html .= $childArray['html'];
$childArray['html'] = '';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
if (!isset($rec['__virtual']) || !$rec['__virtual']) {
$relationList[] = $rec['uid'];
}
$sortableRecordUids = [];
foreach ($this->data['parameterArray']['fieldConf']['children'] as $options) {
$options['inlineParentUid'] = $row['uid'];
$options['inlineParentConfig'] = $config;
$options['inlineData'] = $this->inlineData;
$options['inlineStructure'] = $inlineStackProcessor->getStructure();
$options['inlineExpandCollapseStateArray'] = $this->data['inlineExpandCollapseStateArray'];
$options['renderType'] = 'inlineRecordContainer';
$childResult = $this->nodeFactory->create($options)->render();
$html .= $childResult['html'];
$childArray['html'] = '';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult);
if (!$options['inlineIsDefaultLanguage']) {
// Don't add record to list of "valid" uids if it is only the default
// language record of a not yet localized child
$sortableRecordUids[] = $options['databaseRow']['uid'];
}
}
$html .= '</div>';
// Add the level links after all child records:
if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
$html .= $levelLinks . $localizationLinks;
......@@ -283,11 +276,13 @@ class InlineControlContainer extends AbstractContainer {
$html .= '</div>';
}
// Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
if (count($relationList) > 1 && $config['appearance']['useSortable']) {
if (count($sortableRecordUids) > 1 && $config['appearance']['useSortable']) {
$resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
}
// Publish the uids of the child records in the given order to the browser
$html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $relationList) . '" ' . $this->getValidationDataAsDataAttribute(array('type' => 'inline', 'minitems' => $minItems, 'maxitems' => $maxItems)) . ' class="inlineRecord" />';
$html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $sortableRecordUids) . '" '
. $this->getValidationDataAsDataAttribute(array('type' => 'inline', 'minitems' => $config['minitems'], 'maxitems' => $config['maxitems']))
. ' class="inlineRecord" />';
// Close the wrap for all inline fields (container)
$html .= '</div>';
......@@ -298,18 +293,21 @@ class InlineControlContainer extends AbstractContainer {
/**
* Gets the uids of a select/selector that should be unique and have already been used.
*
* @param array $records All inline records on this level
* @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($records, $conf = array(), $splitValue = FALSE) {
protected function getUniqueIds($children, $conf = array(), $splitValue = FALSE) {
$uniqueIds = array();
if (isset($conf['foreign_unique']) && $conf['foreign_unique'] && !empty($records)) {
foreach ($records as $rec) {
if (isset($conf['foreign_unique']) && $conf['foreign_unique'] && !empty($children)) {
foreach ($children as $child) {
// Skip virtual records (e.g. shown in localization mode):
if (!isset($rec['__virtual']) || !$rec['__virtual']) {
$value = $rec[$conf['foreign_unique']];
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);
......@@ -319,7 +317,7 @@ class InlineControlContainer extends AbstractContainer {
'table' => implode('_', $itemParts)
);
}
$uniqueIds[$rec['uid']] = $value;
$uniqueIds[$child['uid']] = $value;
}
}
}
......@@ -671,6 +669,26 @@ class InlineControlContainer extends AbstractContainer {
return $item;
}
/**
* Extracts FlexForm parts of a form element name like
* data[table][uid][field][sDEF][lDEF][FlexForm][vDEF]
* Helper method used in inline
*
* @param string $formElementName The form element name
* @return array|NULL
*/
protected function extractFlexFormParts($formElementName) {
$flexFormParts = NULL;
$matches = array();
if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
$flexFormParts = GeneralUtility::trimExplode(
'][',
trim($matches[1], '[]')
);
}
return $flexFormParts;
}
/**
* @return BackendUserAuthentication
*/
......
......@@ -174,12 +174,27 @@ class FormDataCompiler {
// BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items
'inlineExpandCollapseStateArray' => array(),
// The "entry" pid for inline records. Nested inline records can potentially hang around on different
// * pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes place on the page structure.
// pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes place on the page structure.
'inlineFirstPid' => NULL,
// This array of fields will be set as hidden-fields instead of rendered normally!
// This is used by EditDocumentController to force some field values if set as "overrideVals" in _GP
'overrideValues' => [],
// Inline scenario: A localized parent record is handled and localizationMode is set to "select", so inline
// parents can have localized children. This value is set to TRUE if this array represents a localized child
// overlay record that has no default language record.
'inlineIsDanglingLocalization' => FALSE,
// Inline scenario: A localized parent record is handled and localizationMode is set to "select", so inline
// parents can have localized childen. This value is set to TRUE if this array represents a default language
// child record that was not yet localized.
'inlineIsDefaultLanguage' => FALSE,
// If set, inline children will be resolved. This is set to FALSE in inline ajax context where new children
// are created an existing children don't matter much.
'inlineResolveExistingChildren' => TRUE,
// @todo - for input placeholder inline to suppress an infinite loop, this *may* become obsolete if
// @todo compilation of certain fields is possible
'inlineCompileExistingChildren' => TRUE,
// @todo: must be handled / further defined
'elementBaseName' => '',
'flexFormFieldIdentifierPrefix' => 'ID',
......
......@@ -20,8 +20,6 @@ use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException;
/**
* Extended by other provider that fetch records from database
*
* @todo: This abstract is rather semi useful and may get removed later again
*/
abstract class AbstractDatabaseRecordProvider {
......
......@@ -51,6 +51,8 @@ class DatabaseRowInitializeNew implements FormDataProviderInterface {
}
// Apply defaults from pageTsConfig
// @todo: For inline records it should be possible to set the pid here via TCAdefaults, but
// @todo: those values would be overwritten by 'pid' setter later below again.
if (isset($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot])
&& is_array($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot])
) {
......
......@@ -72,6 +72,8 @@ class DatabaseUserPermissionCheck implements FormDataProviderInterface {
$userPermissionOnPage = Permission::NOTHING;
if ($result['command'] === 'new') {
// A new record is created. Access rights of parent record are important here
// @todo: In case of new inline child, parentPageRow should probably be the
// @todo: "inlineFirstPid" page - Maybe effectivePid and parentPageRow should be calculated differently then?
if (is_array($result['parentPageRow'])) {
// Record is added below an existing page
$userPermissionOnPage = $backendUser->calcPerms($result['parentPageRow']);
......
......@@ -14,20 +14,20 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Form\FormDataCompiler;
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\RelationHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
use TYPO3\CMS\Core\Versioning\VersionState;
/**
* Resolve and prepare inline data.
*
* @todo: This class is currently only a stub and lots of data preparation is still done in render containers
*/
class TcaInline extends AbstractItemProvider implements FormDataProviderInterface {
class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProviderInterface {
/**
* Resolve inline fields
......@@ -36,39 +36,18 @@ class TcaInline extends AbstractItemProvider implements FormDataProviderInterfac
* @return array
*/
public function addData(array $result) {
$result = $this->addInlineExpandCollapseState($result);
$result = $this->addInlineFirstPid($result);
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'inline') {
continue;
}
$result = $this->resolveConnectedRecordUids($result, $fieldName);
}
return $result;
}
/**
* Add expand / collapse state of inline items for this parent table / uid combination
*
* @param array $result Incoming result
* @return array Modified result
*/
protected function addInlineExpandCollapseState(array $result) {
$inlineView = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($inlineView)) {
$inlineView = [];
}
if ($result['command'] !== 'new') {
$table = $result['tableName'];
$uid = $result['databaseRow']['uid'];
if (!empty($inlineView[$table][$uid])) {
$inlineView = $inlineView[$table][$uid];
$result['processedTca']['columns'][$fieldName]['children'] = [];
if ($result['inlineResolveExistingChildren']) {
$result = $this->resolveRelatedRecords($result, $fieldName);
}
}
$result['inlineExpandCollapseStateArray'] = $inlineView;
return $result;
}
......@@ -100,48 +79,234 @@ class TcaInline extends AbstractItemProvider implements FormDataProviderInterfac
}
/**
* Use RelationHandler to resolve connected uids
* Substitute the value in databaseRow of this inline field with an array
* that contains the databaseRows of currently connected records and some meta information.
*
* @param array $result Result array
* @param string $fieldName Current handle field name
* @return array Modified item array
*/
protected function resolveConnectedRecordUids(array $result, $fieldName) {
$localTable = $result['tableName'];
$localUid = $result['databaseRow']['uid'];
$localTca = $result['processedTca']['columns'][$fieldName];
$localFieldcontent = $result['databaseRow'][$fieldName];
$directlyConnectedIds = GeneralUtility::trimExplode(',', $localFieldcontent);
if (StringUtility::beginsWith((string)$localUid, 'NEW')) {
return $result;
protected function resolveRelatedRecords(array $result, $fieldName) {
$childTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
// localizationMode is either "none", "keep" or "select":
// * none: Handled parent row is not a localized record, or if it is a localized row, this is ignored.
// Default language records and overlays have distinct children that are not connected to each other.
// * keep: Handled parent row is a localized record, but child table is either not localizable, or
// "keep" is explicitly set. A localized parent and its default language row share the same
// children records. Editing a child from a localized record will change this record for the
// default language row, too.
// * select: Handled parent row is a localized record, child table is localizable. Children records are
// localized overlays of a default language record. Three scenarios can happen:
// ** Localized child overlay and its default language row exist - show localized overlay record
// ** Default child language row exists but child overlay doesn't - show a "synchronize this record" button
// ** Localized child overlay exists but default language row does not - this dangling child is a data inconsistency
// Mode was prepared by TcaInlineConfiguration provider
$mode = $result['processedTca']['columns'][$fieldName]['config']['behaviour']['localizationMode'];
if ($mode === 'none') {
$connectedUids = [];
// A new record that has distinct children can not have children yet, fetch connected uids for existing only
if ($result['command'] === 'edit') {
$connectedUids = $this->resolveConnectedRecordUids(
$result['processedTca']['columns'][$fieldName]['config'],
$result['tableName'],
$result['databaseRow']['uid'],
$result['databaseRow'][$fieldName]
);
}
$result['databaseRow'][$fieldName] = implode(',', $connectedUids);
$connectedUids = $this->getWorkspacedUids($connectedUids, $childTableName);
// @todo: If inlineCompileExistingChildren must be kept, it might be better to change the data
// @todo: format of databaseRow for this field and separate the child compilation to an own provider?
if ($result['inlineCompileExistingChildren']) {
foreach ($connectedUids as $childUid) {
$result['processedTca']['columns'][$fieldName]['children'][] = $this->compileChild($result, $fieldName, $childUid);
}
}
} elseif ($mode === 'keep') {
// Fetch connected uids of default language record
$connectedUids = $this->resolveConnectedRecordUids(
$result['processedTca']['columns'][$fieldName]['config'],
$result['tableName'],
$result['defaultLanguageRow']['uid'],
$result['defaultLanguageRow'][$fieldName]
);
$result['databaseRow'][$fieldName] = implode(',', $connectedUids);
$connectedUids = $this->getWorkspacedUids($connectedUids, $childTableName);
if ($result['inlineCompileExistingChildren']) {
foreach ($connectedUids as $childUid) {
$result['processedTca']['columns'][$fieldName]['children'][] = $this->compileChild($result, $fieldName, $childUid);
}
}
} else {
$connectedUidsOfLocalizedOverlay = [];
if ($result['command'] === 'edit') {
$connectedUidsOfLocalizedOverlay = $this->resolveConnectedRecordUids(
$result['processedTca']['columns'][$fieldName]['config'],
$result['tableName'],
$result['databaseRow']['uid'],
$result['databaseRow'][$fieldName]
);
}
$result['databaseRow'][$fieldName] = implode(',', $connectedUidsOfLocalizedOverlay);
if ($result['inlineCompileExistingChildren']) {
$connectedUidsOfDefaultLanguageRecord = $this->resolveConnectedRecordUids(
$result['processedTca']['columns'][$fieldName]['config'],
$result['tableName'],
$result['defaultLanguageRow']['uid'],
$result['defaultLanguageRow'][$fieldName]
);
$showPossible = $result['processedTca']['columns'][$fieldName]['config']['appearance']['showPossibleLocalizationRecords'];
$showRemoved = $result['processedTca']['columns'][$fieldName]['config']['appearance']['showRemovedLocalizationRecords'];
if ($showPossible || $showRemoved) {
// Find which records are localized, which records are not localized and which are
// localized but miss default language record
$fieldNameWithDefaultLanguageUid = $GLOBALS['TCA'][$childTableName]['ctrl']['transOrigPointerField'];
foreach ($connectedUidsOfLocalizedOverlay as $localizedUid) {
$localizedRecord = $this->getRecordFromDatabase($childTableName, $localizedUid);
$uidOfDefaultLanguageRecord = $localizedRecord[$fieldNameWithDefaultLanguageUid];
if (in_array($uidOfDefaultLanguageRecord, $connectedUidsOfDefaultLanguageRecord, TRUE)) {
// This localized child has a default language record. Remove this record from list of default language records
$connectedUidsOfDefaultLanguageRecord = array_diff($connectedUidsOfDefaultLanguageRecord, array($uidOfDefaultLanguageRecord));
// Compile localized record
$result['processedTca']['columns'][$fieldName]['children'][] = $this->compileChild($result, $fieldName, $localizedUid);
} elseif ($showRemoved) {
// This localized child has no default language record. Compile child and mark it as such
$compiledChild = $this->compileChild($result, $fieldName, $localizedUid);
$compiledChild['inlineIsDanglingLocalization'] = TRUE;
$result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
} // Discard child if default language is missing and no showRemoved is set
}
if ($showPossible) {
foreach ($connectedUidsOfDefaultLanguageRecord as $defaultLanguageUid) {
// If there are still uids in $connectedUidsOfDefaultLanguageRecord, these are records that
// exist in default language, but are not localized yet. Compile and mark those
$compiledChild = $this->compileChild($result, $fieldName, $defaultLanguageUid);
$compiledChild['inlineIsDefaultLanguage'] = TRUE;
$result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
}
}
}
}
}
return $result;
}
/**
* Compile a full child record
*
* @param array $result Result array of parent
* @param string $parentFieldName Name of parent field
* @param int $childUid Uid of child to compile
* @return array Full result array
*/
protected function compileChild(array $result, $parentFieldName, $childUid) {
$parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
$childTableName = $parentConfig['foreign_table'];
$overruleTypesArray = [];
if (isset($parentConfig['foreign_types'])) {
$overruleTypesArray = $parentConfig['foreign_types'];
}
/** @var TcaDatabaseRecord $formDataGroup */
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
/** @var FormDataCompiler $formDataCompiler */
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$formDataCompilerInput = [
'command' => 'edit',
'tableName' => $childTableName,
'vanillaUid' => (int)$childUid,
'inlineFirstPid' => $result['inlineFirstPid'],
'overruleTypesArray' => $overruleTypesArray,
];
// For foreign_selector with useCombination $mainChild is the mm record
// and $combinationChild is the child-child. For "normal" relations, $mainChild
// is just the normal child record and $combinationChild is empty.
$mainChild = $formDataCompiler->compile($formDataCompilerInput);
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
$mainChild['combinationChild'] = $this->compileCombinationChild($mainChild, $parentConfig);
}
return $mainChild;
}