Commit 8b143fa6 authored by Oliver Hader's avatar Oliver Hader Committed by Oliver Hader
Browse files

[BUGFIX] DataMapProcessor::synchronizeInlineRelations removes entities

In case RelationHandler::readForeignField() is called with invalid UID
values for a parent record (e.g. zero, or non-numeric values), all records
might be selected if the database default value for a parent pointer field
is set to zero instead of NULL.

Besides that DataMapProcessor::synchronizeInlineRelations() uses the
mentioned RelationHandler method to resolve suggested and actually
persisted relations. The processing is adjusted to avoid invoking
RelationHandler using non-numeric parent pointer values.

Resolves: #81915
Releases: master, 8.7
Change-Id: I108501c69c9cdb732bb88526830f0f73f2d680af
Reviewed-on: https://review.typo3.org/53544


Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog's avatarSusanne Moog <susanne.moog@typo3.org>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent a57ea45b
......@@ -494,11 +494,9 @@ class DataMapProcessor
*/
protected function synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
{
$fromId = $fromRecord['uid'];
$configuration = $GLOBALS['TCA'][$item->getFromTableName()]['columns'][$fieldName];
$isLocalizationModeExclude = ($configuration['l10n_mode'] ?? null) === 'exclude';
$foreignTableName = $configuration['config']['foreign_table'];
$manyToManyTable = ($configuration['config']['MM'] ?? '');
$fieldNames = [
'language' => ($GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null),
......@@ -507,38 +505,17 @@ class DataMapProcessor
];
$isTranslatable = (!empty($fieldNames['language']) && !empty($fieldNames['parent']));
// determine suggested elements of either translation parent or source record
// from data-map, in case the accordant language parent/source record was modified
if ($this->isSetInDataMap($item->getFromTableName(), $fromId, $fieldName)) {
$suggestedAncestorIds = GeneralUtility::trimExplode(
',',
$this->allDataMap[$item->getFromTableName()][$fromId][$fieldName],
true
);
// determine suggested elements of either translation parent or source record from storage
} else {
$relationHandler = $this->createRelationHandler();
$relationHandler->start(
$fromRecord[$fieldName],
$foreignTableName,
$manyToManyTable,
$fromId,
$item->getFromTableName(),
$configuration['config']
);
$suggestedAncestorIds = $this->mapRelationItemId($relationHandler->itemArray);
}
// determine persisted elements for the current data-map item
$relationHandler = $this->createRelationHandler();
$relationHandler->start(
$forRecord[$fieldName] ?? '',
$foreignTableName,
$manyToManyTable,
$item->getId(),
$item->getTableName(),
$configuration['config']
$suggestedAncestorIds = $this->resolveSuggestedInlineRelations(
$item,
$fieldName,
$fromRecord
);
$persistedIds = $this->mapRelationItemId($relationHandler->itemArray);
$persistedIds = $this->resolvePersistedInlineRelations(
$item,
$fieldName,
$forRecord
);
// The dependent ID map points from language parent/source record to
// localization, thus keys: parents/sources & values: localizations
$dependentIdMap = $this->fetchDependentIdMap($foreignTableName, $suggestedAncestorIds, $item->getLanguage());
......@@ -661,6 +638,81 @@ class DataMapProcessor
);
}
/**
* Determines suggest inline relations of either translation parent or
* source record from data-map or storage in case records have been
* persisted already.
*
* @param DataMapItem $item
* @param string $fieldName
* @param array $fromRecord
* @return int[]|string[]
*/
protected function resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord): array
{
$suggestedAncestorIds = [];
$fromId = $fromRecord['uid'];
$configuration = $GLOBALS['TCA'][$item->getFromTableName()]['columns'][$fieldName];
$foreignTableName = $configuration['config']['foreign_table'];
$manyToManyTable = ($configuration['config']['MM'] ?? '');
// determine suggested elements of either translation parent or source record
// from data-map, in case the accordant language parent/source record was modified
if ($this->isSetInDataMap($item->getFromTableName(), $fromId, $fieldName)) {
$suggestedAncestorIds = GeneralUtility::trimExplode(
',',
$this->allDataMap[$item->getFromTableName()][$fromId][$fieldName],
true
);
// determine suggested elements of either translation parent or source record from storage
} elseif (MathUtility::canBeInterpretedAsInteger($fromId)) {
$relationHandler = $this->createRelationHandler();
$relationHandler->start(
$fromRecord[$fieldName],
$foreignTableName,
$manyToManyTable,
$fromId,
$item->getFromTableName(),
$configuration['config']
);
$suggestedAncestorIds = $this->mapRelationItemId($relationHandler->itemArray);
}
return $suggestedAncestorIds;
}
/**
* Determine persisted inline relations for current data-map-item.
*
* @param DataMapItem $item
* @param string $fieldName
* @param array $forRecord
* @return int[]
*/
private function resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord): array
{
$persistedIds = [];
$configuration = $GLOBALS['TCA'][$item->getFromTableName()]['columns'][$fieldName];
$foreignTableName = $configuration['config']['foreign_table'];
$manyToManyTable = ($configuration['config']['MM'] ?? '');
// determine persisted elements for the current data-map item
if (!$item->isNew()) {
$relationHandler = $this->createRelationHandler();
$relationHandler->start(
$forRecord[$fieldName] ?? '',
$foreignTableName,
$manyToManyTable,
$item->getId(),
$item->getTableName(),
$configuration['config']
);
$persistedIds = $this->mapRelationItemId($relationHandler->itemArray);
}
return $persistedIds;
}
/**
* Determines whether a combination of table name, id and field name is
* set in data-map. This method considers null values as well, that would
......
......@@ -906,6 +906,12 @@ class RelationHandler
$key = 0;
$uid = (int)$uid;
// skip further processing if $uid does not
// point to a valid parent record
if ($uid === 0) {
return;
}
$foreign_table = $conf['foreign_table'];
$foreign_table_field = $conf['foreign_table_field'];
$useDeleteClause = !$this->undeleteRecord;
......
......@@ -306,6 +306,29 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
$this->recordIds['newPriceId'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newPriceId];
}
public function localizeChildrenHavingStandaloneChildrenInSelectModeAndLanguageSynchronization()
{
$GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizationMode'] = 'select';
$GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = true;
unset($GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizeChildrenAtParentLocalization']);
unset($GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['localizeChildrenAtParentLocalization']);
$this->actionService->createNewRecords(self::VALUE_PageId, [
self::TABLE_Hotel => ['title' => 'Hotel Standalone', 'parenttable' => 'tt_content'],
self::TABLE_Offer => ['title' => 'Offer Standalone', 'parenttable' => 'tt_content'],
]);
$newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
$this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
$this->actionService->modifyRecords(
self::VALUE_PageId,
[
self::TABLE_Content => ['uid' => self::VALUE_ContentIdLast, self::FIELD_ContentHotel => '5,__nextUid'],
self::TABLE_Hotel => ['uid' => '__NEW', 'title' => 'Hotel #2'],
]
);
}
/**
* @see DataSet/changeParentContentRecordSorting.csv
*/
......
......@@ -288,6 +288,22 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1', '[Translate to Dansk:] New Hotel #1'));
}
/**
* @test
* @see DataSet/localizeChildrenHStandaloneChildrenWAllChildrenSelectNLanguageSynchronization.csv
* @see https://forge.typo3.org/issues/81915
*/
public function localizeChildrenHavingStandaloneChildrenInSelectModeAndLanguageSynchronization()
{
parent::localizeChildrenHavingStandaloneChildrenInSelectModeAndLanguageSynchronization();
$this->assertAssertionDataSet('localizeChildrenHStandaloneChildrenWAllChildrenSelectNLanguageSynchronization');
$responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections('Default', 'Extbase:list()');
$this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentHotel)
->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1', '[Translate to Dansk:] Hotel #2'));
}
/**
* @test
* @see DataSet/changeParentContentRecordSorting.csv
......
tt_content
,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,header,tx_irretutorial_1nff_hotels,l10n_state
,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,
,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",2,
,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2",2,"{""tx_irretutorial_1nff_hotels"":""parent""}"
tx_irretutorial_1nff_hotel
,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,offers
,2,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #0",89,pages,,0
,3,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #1",297,tt_content,,2
,4,89,2048,0,0,0,0,0,0,0,0,0,"Hotel #2",297,tt_content,,1
,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,tt_content,,1
,6,89,1024,0,0,0,0,0,0,0,0,0,"Hotel Standalone",0,tt_content,,0
,7,89,1,0,1,5,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,tt_content,,0
,8,89,2,0,0,0,0,0,0,0,0,0,"Hotel #2",298,tt_content,,0
,9,89,2,0,1,8,0,0,0,0,0,0,"[Translate to Dansk:] Hotel #2",299,tt_content,,0
tx_irretutorial_1nff_offer
,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,prices
,5,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,tx_irretutorial_1nff_hotel,,3
,6,89,2,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,tx_irretutorial_1nff_hotel,,2
,7,89,1,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,tx_irretutorial_1nff_hotel,,1
,8,89,1,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,tx_irretutorial_1nff_hotel,,1
,9,89,0,0,0,0,0,0,0,0,0,0,"Offer Standalone",0,tt_content,,0
tx_irretutorial_1nff_price
,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier
,7,89,1,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,tx_irretutorial_1nff_offer,
,8,89,2,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,tx_irretutorial_1nff_offer,
,9,89,3,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,tx_irretutorial_1nff_offer,
,10,89,1,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,tx_irretutorial_1nff_offer,
,11,89,2,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,tx_irretutorial_1nff_offer,
,12,89,1,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,tx_irretutorial_1nff_offer,
,13,89,1,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,tx_irretutorial_1nff_offer,
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