Commit 60c11074 authored by Sebastian Fischer's avatar Sebastian Fischer Committed by Christian Kuhn
Browse files

[BUGFIX] Inline children need additional state flags

Each inline child gets flags added that reflect the state they
are in while being processed. Another patch will later
use more of these flags.

Resolves: #71136
Releases: master
Change-Id: Id86c5efdc6660caba8836691967f09cd69ae1ca7
Reviewed-on: https://review.typo3.org/44379

Reviewed-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 30b50f3b
......@@ -81,8 +81,6 @@ class FormInlineAjaxController
'columnsToProcess' => [
$parentFieldName
],
// @todo: still needed?
'inlineStructure' => $inlineStackProcessor->getStructure(),
// Do not resolve existing children, we don't need them now
'inlineResolveExistingChildren' => false,
];
......@@ -112,6 +110,8 @@ class FormInlineAjaxController
'command' => 'new',
'tableName' => $childTableName,
'vanillaUid' => $childVanillaUid,
'isInlineChild' => true,
'inlineStructure' => $inlineStackProcessor->getStructure(),
'inlineFirstPid' => $inlineFirstPid,
'inlineParentConfig' => $parentConfig,
];
......@@ -162,10 +162,6 @@ class FormInlineAjaxController
}
$childData['inlineParentUid'] = (int)$parent['uid'];
// @todo: needed?
$childData['inlineStructure'] = $inlineStackProcessor->getStructure();
// @todo: needed?
$childData['inlineExpandCollapseStateArray'] = $parentData['inlineExpandCollapseStateArray'];
$childData['renderType'] = 'inlineRecordContainer';
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$childResult = $nodeFactory->create($childData)->render();
......@@ -258,13 +254,9 @@ class FormInlineAjaxController
// Child, a record from this table should be rendered
$child = $inlineStackProcessor->getUnstableStructure();
$childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid']);
$childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
$childData['inlineParentUid'] = (int)$parent['uid'];
// @todo: needed?
$childData['inlineStructure'] = $inlineStackProcessor->getStructure();
// @todo: needed?
$childData['inlineExpandCollapseStateArray'] = $parentData['inlineExpandCollapseStateArray'];
$childData['renderType'] = 'inlineRecordContainer';
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$childResult = $nodeFactory->create($childData)->render();
......@@ -339,7 +331,7 @@ class FormInlineAjaxController
'columnsToProcess' => [
$parentFieldName
],
// @todo: still needed?
// @todo: still needed? NO!
'inlineStructure' => $inlineStackProcessor->getStructure(),
// Do not compile existing children, we don't need them now
'inlineCompileExistingChildren' => false,
......@@ -382,13 +374,9 @@ class FormInlineAjaxController
$localizedItems = array_diff($newItems, $oldItems);
foreach ($localizedItems as $childUid) {
$childData = $this->compileChild($parentData, $parentFieldName, (int)$childUid);
$childData = $this->compileChild($parentData, $parentFieldName, (int)$childUid, $inlineStackProcessor->getStructure());
$childData['inlineParentUid'] = (int)$parent['uid'];
// @todo: needed?
$childData['inlineStructure'] = $inlineStackProcessor->getStructure();
// @todo: needed?
$childData['inlineExpandCollapseStateArray'] = $parentData['inlineExpandCollapseStateArray'];
$childData['renderType'] = 'inlineRecordContainer';
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$childResult = $nodeFactory->create($childData)->render();
......@@ -487,13 +475,14 @@ class FormInlineAjaxController
* @param array $parentData Result array of parent
* @param string $parentFieldName Name of parent field
* @param int $childUid Uid of child to compile
* @param array $inlineStructure Current inline structure
* @return array Full result array
*
* @todo: This clones methods compileChild and compileCombinationChild from TcaInline Provider.
* @todo: Find something around that, eg. some option to force TcaInline provider to calculate a
* @todo: specific forced-open element only :)
*/
protected function compileChild(array $parentData, $parentFieldName, $childUid)
protected function compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
{
$parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
$childTableName = $parentConfig['foreign_table'];
......@@ -505,15 +494,18 @@ class FormInlineAjaxController
'command' => 'edit',
'tableName' => $childTableName,
'vanillaUid' => (int)$childUid,
'isInlineChild' => true,
'inlineStructure' => $inlineStructure,
'inlineFirstPid' => $parentData['inlineFirstPid'],
'inlineParentConfig' => $parentConfig,
'isInlineAjaxOpeningContext' => true,
];
// 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);
$mainChild['combinationChild'] = $this->compileCombinationChild($mainChild, $parentConfig, $inlineStructure);
}
return $mainChild;
}
......@@ -524,13 +516,14 @@ class FormInlineAjaxController
*
* @param array $intermediate Full data array of "mm" record
* @param array $parentConfig TCA configuration of "parent"
* @param array $inlineStructure Current inline structure
* @return array Full data array of child
*/
protected function compileCombinationChild(array $intermediate, array $parentConfig)
protected function compileCombinationChild(array $intermediate, array $parentConfig, array $inlineStructure)
{
// foreign_selector on intermediate is probably type=select, so data provider of this table resolved that to the uid already
$intermediateUid = $intermediate['databaseRow'][$parentConfig['foreign_selector']][0];
$combinationChild = $this->compileChild($intermediate, $parentConfig['foreign_selector'], $intermediateUid);
$combinationChild = $this->compileChild($intermediate, $parentConfig['foreign_selector'], $intermediateUid, $inlineStructure);
return $combinationChild;
}
......
......@@ -170,7 +170,7 @@ class InlineControlContainer extends AbstractContainer
$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']) {
if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
$value = $child['databaseRow'][$config['foreign_unique']];
// We're assuming there is only one connected value here for both select and group
if ($type === 'select') {
......@@ -226,7 +226,7 @@ class InlineControlContainer extends AbstractContainer
$numberOfFullChildren = 0;
foreach ($this->data['parameterArray']['fieldConf']['children'] as $child) {
if (!$child['inlineIsDefaultLanguage']) {
if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
$numberOfFullChildren ++;
}
}
......@@ -272,7 +272,7 @@ class InlineControlContainer extends AbstractContainer
$html .= $childResult['html'];
$childArray['html'] = '';
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult);
if (!$options['inlineIsDefaultLanguage']) {
if (!$options['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
// 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'];
......
......@@ -117,7 +117,7 @@ class InlineRecordContainer extends AbstractContainer
// Set this variable if we handle a brand new unsaved record:
$isNewRecord = !MathUtility::canBeInterpretedAsInteger($record['uid']);
// Set this variable if the only the default language record and inline child has not been localized yet
$isDefaultLanguageRecord = $this->data['inlineIsDefaultLanguage'];
$isDefaultLanguageRecord = $this->data['isInlineDefaultLanguageRecordInLocalizedParentContext'];
// If there is a selector field, normalize it:
if ($foreign_selector) {
$valueToNormalize = $record[$foreign_selector];
......@@ -452,7 +452,7 @@ class InlineRecordContainer extends AbstractContainer
/** @var InlineElementHookInterface $hookObj */
$hookObj->renderForeignRecordHeaderControl_preProcess($parentUid, $foreign_table, $rec, $config, $isVirtualRecord, $enabledControls);
}
if ($data['inlineIsDefaultLanguage']) {
if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
$cells['localize.isLocalizable'] = '<span title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize.isLocalizable', true) . '">'
. $this->iconFactory->getIcon('actions-edit-localize-status-low', Icon::SIZE_SMALL)->render()
. '</span>';
......@@ -571,7 +571,7 @@ class InlineRecordContainer extends AbstractContainer
</span>';
}
} elseif ($isVirtualRecord && $isParentExisting) {
if ($enabledControls['localize'] && $data['inlineIsDefaultLanguage']) {
if ($enabledControls['localize'] && $data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
$onClick = 'inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ', ' . GeneralUtility::quoteJSvalue($rec['uid']) . ');';
$cells['localize'] = '
<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize', true) . '">
......
......@@ -191,18 +191,22 @@ class FormDataCompiler
'columnsToProcess' => [],
// If set to TRUE, no wizards are calculated and rendered later
'disabledWizards' => false,
// BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items
// @todo: better documentation of nesting behaviour and bug fixing in this area
'inlineExpandCollapseStateArray' => [],
// 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' => [],
// The "config" section of an inline parent, prepared and sanitized by TcaInlineConfiguration provider
'inlineParentConfig' => [],
// Flag that is enabled if a records is child of an inline parent
'isInlineChild' => false,
// Flag if an inline child is expanded so that additional fields need to be rendered
'isInlineChildExpanded' => false,
// Flag if the inline is in an ajax context that wants to expand the element
'isInlineAjaxOpeningContext' => false,
// Uid of a "child-child" if a new record of an intermediate table is compiled to an existing child. This
// happens if foreign_selector in parent inline config is set. It will be used by default database row
// data providers to set this as value for the foreign_selector field on the intermediate table. One use
......@@ -214,7 +218,7 @@ class FormDataCompiler
// 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,
'isInlineDefaultLanguageRecordInLocalizedParentContext' => false,
// If set, inline children will be resolved. This is set to FALSE in inline ajax context where new children
// are created and existing children don't matter much.
'inlineResolveExistingChildren' => true,
......@@ -222,13 +226,15 @@ class FormDataCompiler
// @todo compilation of certain fields is possible
'inlineCompileExistingChildren' => true,
// @todo: must be handled / further defined
// @todo: keys below must be handled / further defined
'elementBaseName' => '',
'flexFormFieldIdentifierPrefix' => 'ID',
'tabAndInlineStack' => [],
'inlineData' => [],
'inlineStructure' => [],
// 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' => [],
];
}
}
......@@ -184,7 +184,7 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
// 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;
$compiledChild['isInlineDefaultLanguageRecordInLocalizedParentContext'] = true;
$result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
}
}
......@@ -230,6 +230,7 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
],
],
],
'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
];
/** @var OnTheFly $formDataGroup */
$formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
......@@ -265,6 +266,9 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
'command' => 'edit',
'tableName' => $childTableName,
'vanillaUid' => (int)$childUid,
'isInlineChild' => true,
'inlineStructure' => $result['inlineStructure'],
'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
'inlineFirstPid' => $result['inlineFirstPid'],
'inlineParentConfig' => $parentConfig,
];
......
......@@ -15,6 +15,8 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
*/
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
/**
......@@ -31,19 +33,44 @@ class TcaInlineExpandCollapseState implements FormDataProviderInterface
*/
public function addData(array $result)
{
$fullInlineState = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($fullInlineState)) {
$fullInlineState = [];
}
$inlineStateForTable = [];
if ($result['command'] !== 'new') {
$table = $result['tableName'];
$uid = $result['databaseRow']['uid'];
if (!empty($fullInlineState[$table][$uid])) {
$inlineStateForTable = $fullInlineState[$table][$uid];
// Early return if a parent record has already set this
if (!empty($result['inlineExpandCollapseStateArray'])) {
return $result;
} elseif (!empty($result['inlineStructure'])) {
/** @var InlineStackProcessor $inlineStackProcessor */
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
$inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
// Top parent
$parent = $inlineStackProcessor->getStructureLevel(0);
$fullInlineState = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($fullInlineState)) {
$fullInlineState = [];
}
$inlineStateForTable = [];
if ($result['command'] !== 'new') {
$table = $parent['table'];
$uid = $parent['uid'];
if (!empty($fullInlineState[$table][$uid])) {
$inlineStateForTable = $fullInlineState[$table][$uid];
}
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
} else {
$fullInlineState = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($fullInlineState)) {
$fullInlineState = [];
}
$inlineStateForTable = [];
if ($result['command'] !== 'new') {
$table = $result['tableName'];
$uid = $result['databaseRow']['uid'];
if (!empty($fullInlineState[$table][$uid])) {
$inlineStateForTable = $fullInlineState[$table][$uid];
}
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
return $result;
}
......
......@@ -63,4 +63,50 @@ class TcaInlineExpandCollapseStateTest extends UnitTestCase
$expected['inlineExpandCollapseStateArray'] = $inlineState['aParentTable'][5];
$this->assertSame($expected, $this->subject->addData($input));
}
/**
* @test
*/
public function addDataAddsInlineStatusForSecondLevelChild()
{
$input = [
'command' => 'edit',
'tableName' => 'bChildTable',
'databaseRow' => [
'uid' => 13,
],
'inlineStructure' => [
'stable' => [
[
'table' => 'aParentTable',
'uid' => 5,
'field' => 'inline_2',
],
],
],
];
$inlineState = [
'aParentTable' => [
5 => [
'aChildTable' => [
// Records 23 and 42 are expanded
23,
42,
],
'bChildTable' => [
// Records 13 and 66 are expanded
13,
66,
],
],
],
];
$GLOBALS['BE_USER'] = new \stdClass();
$GLOBALS['BE_USER']->uc = [
'inlineView' => serialize($inlineState),
];
$expected = $input;
$expected['inlineExpandCollapseStateArray'] = $inlineState['aParentTable'][5];
$this->assertSame($expected, $this->subject->addData($input));
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment