Commit e3e57cbb authored by Andreas Allacher's avatar Andreas Allacher Committed by Morton Jonuschat
Browse files

[BUGFIX] FormEngine: Determine IRRE expanded state in data provider

The expanded/collapsed state of inline and combination child records
is determined within the TcaInlineExpandCollapseState provider instead
of the display container so that the concerns are appropriately
separated.

Change-Id: I8152a9f4f689b1c409a9f14d2e29515ab3d91860
Resolves: #71728
Releases: master
Reviewed-on: https://review.typo3.org/44826

Reviewed-by: Wouter Wolters's avatarWouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: default avatarMarkus Sommer <markussom@posteo.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: default avatarMorton Jonuschat <m.jonuschat@mojocode.de>
parent f5ba2128
......@@ -128,19 +128,7 @@ class InlineRecordContainer extends AbstractContainer
$combinationHtml = '';
$isNewRecord = $data['command'] === 'new';
if (!$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
$collapseAll = isset($inlineConfig['appearance']['collapseAll']) && $inlineConfig['appearance']['collapseAll'];
$expandAll = isset($inlineConfig['appearance']['collapseAll']) && !$inlineConfig['appearance']['collapseAll'];
if ($isNewRecord) {
$isExpanded = $expandAll || !$collapseAll;
} else {
$expandCollapseStateArray = $data['inlineExpandCollapseStateArray'];
$isExpandedByUcState = isset($expandCollapseStateArray[$foreignTable])
&& is_array($expandCollapseStateArray[$foreignTable])
&& in_array($record['uid'], $expandCollapseStateArray[$foreignTable]) !== false;
$isExpanded = $inlineConfig['renderFieldsOnly'] || !$collapseAll && $isExpandedByUcState || $expandAll;
}
if ($isNewRecord || $isExpanded) {
if ($isNewRecord || $data['isInlineChildExpanded']) {
// Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed
$combinationHtml = '';
if (isset($data['combinationChild'])) {
......@@ -159,26 +147,31 @@ class InlineRecordContainer extends AbstractContainer
}
if ($isNewRecord) {
// Add pid of record as hidden field
$html .= '<input type="hidden" name="data' . $appendFormFieldNames . '[pid]" value="' . $record['pid'] . '"/>';
$html .= '<input type="hidden" name="data' . htmlspecialchars($appendFormFieldNames)
. '[pid]" value="' . htmlspecialchars($record['pid']) . '"/>';
// Tell DataHandler this record is expanded
$ucFieldName = 'uc[inlineView]'
. '[' . $data['inlineTopMostParentTableName'] . ']'
. '[' . $data['inlineTopMostParentUid'] . ']'
. $appendFormFieldNames;
$html .= '<input type="hidden" name="' . $ucFieldName . '" value="' . $isExpanded . '" />';
$html .= '<input type="hidden" name="' . htmlspecialchars($ucFieldName)
. '" value="' . (int)$data['isInlineChildExpanded'] . '" />';
} else {
// Set additional field for processing for saving
$html .= '<input type="hidden" name="cmd' . $appendFormFieldNames . '[delete]" value="1" disabled="disabled" />';
if (!$isExpanded
$html .= '<input type="hidden" name="cmd' . htmlspecialchars($appendFormFieldNames)
. '[delete]" value="1" disabled="disabled" />';
if (!$data['isInlineChildExpanded']
&& !empty($GLOBALS['TCA'][$foreignTable]['ctrl']['enablecolumns']['disabled'])
) {
$checked = !empty($record['hidden']) ? ' checked="checked"' : '';
$html .= '<input type="checkbox" name="data' . $appendFormFieldNames . '[hidden]_0" value="1"' . $checked . ' />';
$html .= '<input type="input" name="data' . $appendFormFieldNames . '[hidden]" value="' . $record['hidden'] . '" />';
$html .= '<input type="checkbox" name="data' . htmlspecialchars($appendFormFieldNames)
. '[hidden]_0" value="1"' . $checked . ' />';
$html .= '<input type="input" name="data' . htmlspecialchars($appendFormFieldNames)
. '[hidden]" value="' . htmlspecialchars($record['hidden']) . '" />';
}
}
// If this record should be shown collapsed
$class = $isExpanded ? 'panel-visible' : 'panel-collapsed';
$class = $data['isInlineChildExpanded'] ? 'panel-visible' : 'panel-collapsed';
}
if ($inlineConfig['renderFieldsOnly']) {
// Render "body" part only
......@@ -193,8 +186,8 @@ class InlineRecordContainer extends AbstractContainer
}
$class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
$html = '
<div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . $objectId . '_div">
<div class="panel-heading" data-toggle="formengine-inline" id="' . $objectId . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
<div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . htmlspecialchars($objectId) . '_div">
<div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($objectId) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
<div class="form-irre-header">
<div class="form-irre-header-cell form-irre-header-icon">
<span class="caret"></span>
......@@ -202,7 +195,7 @@ class InlineRecordContainer extends AbstractContainer
' . $this->renderForeignRecordHeader($data) . '
</div>
</div>
<div class="panel-collapse" id="' . $objectId . '_fields" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '" data-returnURL="' . htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI')) . '">' . $html . $combinationHtml . '</div>
<div class="panel-collapse" id="' . htmlspecialchars($objectId) . '_fields" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '" data-returnURL="' . htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI')) . '">' . $html . $combinationHtml . '</div>
</div>';
}
......
......@@ -47,24 +47,8 @@ class TcaColumnsProcessShowitem implements FormDataProviderInterface
return $result;
}
$addShowItemFieldsToColumnsToProcess = true;
if ($result['isInlineChild']) {
if ($result['isInlineChild'] && !$result['isInlineChildExpanded']) {
// If the record is an inline child that is not expanded, it is not necessary to calculate all fields
$isExistingRecord = $result['command'] === 'edit';
$inlineConfig = $result['inlineParentConfig'];
$collapseAll = isset($inlineConfig['appearance']['collapseAll']) && $inlineConfig['appearance']['collapseAll'];
$expandAll = isset($inlineConfig['appearance']['collapseAll']) && !$inlineConfig['appearance']['collapseAll'];
$expandCollapseStateArray = $result['inlineExpandCollapseStateArray'];
$foreignTable = $result['inlineParentConfig']['foreign_table'];
$isExpandedByUcState = isset($expandCollapseStateArray[$foreignTable])
&& is_array($expandCollapseStateArray[$foreignTable])
&& in_array($result['databaseRow']['uid'], $expandCollapseStateArray[$foreignTable]) !== false;
if ($isExistingRecord && ($collapseAll || !$isExpandedByUcState) && !$expandAll && !$result['isInlineAjaxOpeningContext']) {
$addShowItemFieldsToColumnsToProcess = false;
}
}
if (!$addShowItemFieldsToColumnsToProcess) {
return $result;
}
......
......@@ -295,7 +295,7 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
// 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->compileChildChild($mainChild, $parentConfig);
}
return $mainChild;
}
......@@ -304,16 +304,37 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid
* With useCombination set, not only content of the intermediate table, but also
* the connected child should be rendered in one go. Prepare this here.
*
* @param array $intermediate Full data array of "mm" record
* @param array $child Full data array of "mm" record
* @param array $parentConfig TCA configuration of "parent"
* @return array Full data array of child
*/
protected function compileCombinationChild(array $intermediate, array $parentConfig)
protected function compileChildChild(array $child, array $parentConfig)
{
// 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);
return $combinationChild;
$childChildUid = $child['databaseRow'][$parentConfig['foreign_selector']][0];
// child-child table name is set in child tca "the selector field" foreign_table
$childChildTableName = $child['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'];
/** @var TcaDatabaseRecord $formDataGroup */
$formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
/** @var FormDataCompiler $formDataCompiler */
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$formDataCompilerInput = [
'command' => 'edit',
'tableName' => $childChildTableName,
'vanillaUid' => (int)$childChildUid,
'isInlineChild' => true,
'isInlineChildExpanded' => $child['isInlineChildExpanded'],
// @todo: this is the wrong inline structure, isn't it? Shouldn't it contain the part from child child, too?
'inlineStructure' => $child['inlineStructure'],
'inlineFirstPid' => $child['inlineFirstPid'],
// values of the top most parent element set on first level and not overridden on following levels
'inlineTopMostParentUid' => $child['inlineTopMostParentUid'],
'inlineTopMostParentTableName' => $child['inlineTopMostParentTableName'],
'inlineTopMostParentFieldName' => $child['inlineTopMostParentFieldName'],
];
$childChild = $formDataCompiler->compile($formDataCompilerInput);
return $childChild;
}
/**
......
......@@ -31,40 +31,55 @@ class TcaInlineExpandCollapseState implements FormDataProviderInterface
*/
public function addData(array $result)
{
if (!empty($result['inlineExpandCollapseStateArray'])) {
// Early return if a parent record has already set this, happens for existing inline children
// when opening a parent record.
return $result;
} elseif (!empty($result['inlineTopMostParentUid']) && !empty($result['inlineTopMostParentTableName'])) {
// Happens in inline ajax context, top parent uid and top parent table are set
$fullInlineState = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($fullInlineState)) {
$fullInlineState = [];
}
$inlineStateForTable = [];
if ($result['command'] !== 'new') {
$table = $result['inlineTopMostParentTableName'];
$uid = $result['inlineTopMostParentUid'];
if (!empty($fullInlineState[$table][$uid])) {
$inlineStateForTable = $fullInlineState[$table][$uid];
if (empty($result['inlineExpandCollapseStateArray'])) {
if (!empty($result['inlineTopMostParentUid']) && !empty($result['inlineTopMostParentTableName'])) {
// Happens in inline ajax context, top parent uid and top parent table are set
$fullInlineState = unserialize($this->getBackendUser()->uc['inlineView']);
if (!is_array($fullInlineState)) {
$fullInlineState = [];
}
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
} else {
// Default case for a single record
$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];
$inlineStateForTable = [];
if ($result['command'] !== 'new') {
$table = $result['inlineTopMostParentTableName'];
$uid = $result['inlineTopMostParentUid'];
if (!empty($fullInlineState[$table][$uid])) {
$inlineStateForTable = $fullInlineState[$table][$uid];
}
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
} else {
// Default case for a single record
$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;
}
}
if (!$result['isInlineChildExpanded']) {
// If the record is an inline child that is not expanded, it is not necessary to calculate all fields
$isExistingRecord = $result['command'] === 'edit';
$inlineConfig = $result['inlineParentConfig'];
$collapseAll = isset($inlineConfig['appearance']['collapseAll']) && $inlineConfig['appearance']['collapseAll'];
$expandAll = isset($inlineConfig['appearance']['collapseAll']) && !$inlineConfig['appearance']['collapseAll'];
$expandCollapseStateArray = $result['inlineExpandCollapseStateArray'];
$foreignTable = $result['inlineParentConfig']['foreign_table'];
$isExpandedByUcState = isset($expandCollapseStateArray[$foreignTable])
&& is_array($expandCollapseStateArray[$foreignTable])
&& in_array($result['databaseRow']['uid'], $expandCollapseStateArray[$foreignTable]) !== false;
if (!$isExistingRecord || ($isExpandedByUcState && !$collapseAll) || $expandAll || $result['isInlineAjaxOpeningContext']) {
$result['isInlineChildExpanded'] = true;
}
$result['inlineExpandCollapseStateArray'] = $inlineStateForTable;
}
return $result;
......
......@@ -244,6 +244,7 @@ class TcaColumnsProcessShowitemTest extends UnitTestCase
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'isInlineAjaxOpeningContext' => false,
'inlineExpandCollapseStateArray' => [
'aTable' => [
......@@ -284,6 +285,7 @@ class TcaColumnsProcessShowitemTest extends UnitTestCase
'foreign_table' => 'aTable',
],
'isInlineChild' => true,
'isInlineChildExpanded' => true,
'isInlineAjaxOpeningContext' => false,
'inlineExpandCollapseStateArray' => [
'aTable' => [
......@@ -295,84 +297,4 @@ class TcaColumnsProcessShowitemTest extends UnitTestCase
$expected['columnsToProcess'] = ['aField'];
$this->assertSame($expected, $this->subject->addData($input));
}
/**
* @test
*/
public function addDataAddsColumnsForAjaxOpeningContextInlineChild()
{
$input = [
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineAjaxOpeningContext' => true,
'inlineExpandCollapseStateArray' => [],
];
$expected = $input;
$expected['columnsToProcess'] = [ 'aField' ];
$this->assertSame($expected, $this->subject->addData($input));
}
/**
* @test
*/
public function addDataAddsColumnsForNewInlineChild()
{
$input = [
'command' => 'new',
'databaseRow' => [
'uid' => 'NEW1234',
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineAjaxOpeningContext' => false,
'inlineExpandCollapseStateArray' => [],
];
$expected = $input;
$expected['columnsToProcess'] = [ 'aField' ];
$this->assertSame($expected, $this->subject->addData($input));
}
}
......@@ -102,4 +102,229 @@ class TcaInlineExpandCollapseStateTest extends UnitTestCase
$expected['inlineExpandCollapseStateArray'] = $inlineState['aParentTable'][5];
$this->assertSame($expected, $this->subject->addData($input));
}
/**
* @return array
*/
public function addDataAddsCorrectIsInlineChildExpandedDataProvider()
{
return [
'Inline child is expanded because of state in expandCollapseStateArray' => [
[
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'isInlineAjaxOpeningContext' => false,
'inlineExpandCollapseStateArray' => [
'aTable' => [
42,
],
],
],
true
],
'Inline child is expanded because of ajax opening context' => [
[
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'isInlineAjaxOpeningContext' => true,
'inlineExpandCollapseStateArray' => [],
],
true
],
'Inline child is collapsed because of collapseAll' => [
[
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'inlineExpandCollapseStateArray' => [],
],
false
],
'Inline child is expanded because of expandAll (inverse collapseAll setting)' => [
[
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => false,
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'inlineExpandCollapseStateArray' => [],
],
true
],
'New inline child is expanded' => [
[
'command' => 'new',
'databaseRow' => [
'uid' => 'NEW1234',
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => false,
'isInlineAjaxOpeningContext' => false,
'inlineExpandCollapseStateArray' => [],
],
true
],
'Inline child marked as expanded stays expanded (e.g. combination child)' => [
[
'command' => 'edit',
'databaseRow' => [
'uid' => 42,
],
'recordTypeValue' => 'aType',
'processedTca' => [
'types' => [
'aType' => [
'showitem' => 'aField',
],
],
'columns' => [
'aField' => [
'config' => [
'type' => 'input',
]
],
],
],
'inlineParentConfig' => [
'foreign_table' => 'aTable',
'appearance' => [
'collapseAll' => true,
],
],
'isInlineChild' => true,
'isInlineChildExpanded' => true,
'inlineExpandCollapseStateArray' => [],
],
true
],
];
}