diff --git a/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php b/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php index 5d21dc566e0860bd221a5cb269987e15e57f905a..dadd54ed7caa68fecb73014b3244afd299d63087 100644 --- a/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php +++ b/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php @@ -18,7 +18,6 @@ namespace TYPO3\CMS\Install\Updates\RowUpdater; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\DataHandling\DataHandler; -use TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor; use TYPO3\CMS\Core\DataHandling\Localization\State; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -70,7 +69,7 @@ class L10nModeUpdater implements RowUpdaterInterface */ public function updateTableRow(string $tableName, array $inputRow): array { - $currentId = $inputRow['uid']; + $currentId = (int)$inputRow['uid']; if (empty($this->payload[$tableName]['localizations'][$currentId])) { return $inputRow; @@ -91,39 +90,21 @@ class L10nModeUpdater implements RowUpdaterInterface // the admin user is required to defined workspace state when working with DataHandler $fakeAdminUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); $fakeAdminUser->user = ['uid' => 0, 'username' => '_migration_', 'admin' => 1]; - $fakeAdminUser->workspace = ($inputRow['t3ver_wsid'] ?? 0); + $fakeAdminUser->workspace = (int)($inputRow['t3ver_wsid'] ?? 0); $GLOBALS['BE_USER'] = $fakeAdminUser; $tablePayload = $this->payload[$tableName]; - $parentId = $tablePayload['localizations'][$currentId]; - $parentTableName = ($tableName === 'pages_language_overlay' ? 'pages' : $tableName); $liveId = $currentId; if (!empty($inputRow['t3ver_wsid']) && !empty($inputRow['t3ver_oid']) && !VersionState::cast($inputRow['t3ver_state']) ->equals(VersionState::NEW_PLACEHOLDER_VERSION)) { - $liveId = $inputRow['t3ver_oid']; + $liveId = (int)$inputRow['t3ver_oid']; } $dataMap = []; - // simulate modifying a parent record to trigger dependent updates - if (in_array('exclude', $tablePayload['fieldModes'])) { - $parentRecord = $this->getRow($parentTableName, $parentId); - foreach ($tablePayload['fieldModes'] as $fieldName => $fieldMode) { - if ($fieldMode !== 'exclude') { - continue; - } - $dataMap[$parentTableName][$parentId][$fieldName] = $parentRecord[$fieldName]; - } - $dataMap = DataMapProcessor::instance($dataMap, $fakeAdminUser)->process(); - unset($dataMap[$parentTableName][$parentId]); - if (empty($dataMap[$parentTableName])) { - unset($dataMap[$parentTableName]); - } - } - // define localization states and thus trigger updates later if (State::isApplicable($tableName)) { $stateUpdates = []; @@ -138,33 +119,38 @@ class L10nModeUpdater implements RowUpdaterInterface } } + // fetch the language state upfront, so that calling DataMapProcessor below + // will handle mergeIfNotBlank fields properly $languageState = State::create($tableName); $languageState->update($stateUpdates); - // only consider field names that still used mergeIfNotBlank - $modifiedFieldNames = array_intersect( - array_keys($tablePayload['fieldModes']), - $languageState->getModifiedFieldNames() - ); - if (!empty($modifiedFieldNames)) { - $dataMap = [ - $tableName => [ - $liveId => [ - 'l10n_state' => $languageState->toArray() - ] + $dataMap = [ + $tableName => [ + $liveId => [ + 'l10n_state' => $languageState->toArray() ] - ]; - } + ] + ]; } - if (empty($dataMap)) { - return $inputRow; + // simulate modifying a parent record to trigger dependent updates + if (in_array('exclude', $tablePayload['fieldModes'], true)) { + $record = $this->getRow($tableName, $liveId); + foreach ($tablePayload['fieldModes'] as $fieldName => $fieldMode) { + if ($fieldMode !== 'exclude') { + continue; + } + $dataMap[$tableName][$liveId][$fieldName] = $record[$fieldName]; + } } - // let DataHandler process all updates, $inputRow won't change - $dataHandler = GeneralUtility::makeInstance(DataHandler::class); - $dataHandler->enableLogging = false; - $dataHandler->start($dataMap, [], $fakeAdminUser); - $dataHandler->process_datamap(); + // in case $dataMap is empty, nothing has to be updated + if (!empty($dataMap)) { + // let DataHandler process all updates, $inputRow won't change + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->enableLogging = false; + $dataHandler->start($dataMap, [], $fakeAdminUser); + $dataHandler->process_datamap(); + } if (!empty($dataHandlerHooks)) { $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'] = $dataHandlerHooks; diff --git a/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultElements.csv b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultElements.csv new file mode 100644 index 0000000000000000000000000000000000000000..5e532ac6e9e54bd9c60f22504aaa72c5c1a42262 --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultElements.csv @@ -0,0 +1,29 @@ +"sys_language",,,,,,,,,,,,,,,,,, +,"uid","pid","hidden","title","flag",,,,,,,,,,,,, +,1,0,0,"Dansk","dk",,,,,,,,,,,,, +,2,0,0,"Deutsch","de",,,,,,,,,,,,, +"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","image","tx_irretutorial_1nff_hotels",, +,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",0,2,, +,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",0,0,, +,299,89,1024,0,1,297,0,0,0,0,0,0,"",0,0,, +,300,89,2048,0,1,298,0,0,0,0,0,0,"Regular Element #2++",0,1,, +"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,, +,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers" +,2,89,1,0,0,0,,0,0,0,0,0,0,"Hotel #0",89,"pages",,0 +,3,89,1,0,0,0,,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2 +,4,89,2,0,0,0,,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1 +,5,89,1,0,1,0,,0,0,0,0,0,0,"Hotel #3",300,"tt_content",,0 +"tx_irretutorial_1nff_offer",,,,,,,,,,,,,,,,,, +,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","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 +"tx_irretutorial_1nff_price",,,,,,,,,,,,,,,,,, +,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","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",, diff --git a/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultPages.csv b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultPages.csv new file mode 100644 index 0000000000000000000000000000000000000000..b40e9e357c374f89040906482da9a91b7aa310d3 --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/LiveDefaultPages.csv @@ -0,0 +1,6 @@ +"pages" +,"uid","pid","sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","tx_irretutorial_hotels" +,1,0,256,0,0,0,0,0,0,0,"FunctionalTest",0 +,88,1,256,0,0,0,0,0,0,0,"DataHandlerTest",0 +,89,88,256,0,0,0,0,0,0,0,"Relations",1 +,90,88,512,0,0,0,0,0,0,0,"Target",0 diff --git a/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/recordsCanBeUpdated.csv b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/recordsCanBeUpdated.csv new file mode 100644 index 0000000000000000000000000000000000000000..169b198410db7a7f4a2d7b4ff94ff5b7abb353e0 --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/recordsCanBeUpdated.csv @@ -0,0 +1,40 @@ +sys_language +,uid,pid,hidden,title,flag +,1,0,0,Dansk,dk +,2,0,0,Deutsch,de +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,image,tx_irretutorial_1nff_hotels,l10n_state +,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",0,2,"\NULL" +,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",0,0,"\NULL" +,299,89,1024,0,1,297,0,0,0,0,0,0,"Regular Element #1",0,2,"{""header"":""parent"",""tx_irretutorial_1nff_hotels"":""parent""}" +,300,89,2048,0,1,298,0,0,0,0,0,0,"Regular Element #2++",0,1,"{""header"":""custom"",""tx_irretutorial_1nff_hotels"":""custom""}" +tx_irretutorial_1nff_hotel +,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l18n_diffsource,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,offers,l10n_state +,2,89,512,0,0,0,,0,0,0,0,0,0,"Hotel #0",89,pages,,0,"\NULL" +,3,89,768,0,0,0,,0,0,0,0,0,0,"Hotel #1",297,tt_content,,2,"\NULL" +,4,89,1536,0,0,0,,0,0,0,0,0,0,"Hotel #2",297,tt_content,,1,"\NULL" +,5,89,1280,0,1,0,,0,0,0,0,0,0,"Hotel #3",300,tt_content,,0,"\NULL" +,6,89,1,0,1,3,,3,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,tt_content,,2,"\NULL" +,7,89,2,0,1,4,,4,0,0,0,0,0,"[Translate to Dansk:] Hotel #2",299,tt_content,,1,"\NULL" +tx_irretutorial_1nff_offer +,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l18n_diffsource,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,prices,l10n_state +,5,89,1280,0,0,0,,0,0,0,0,0,0,"Offer #1.1",3,tx_irretutorial_1nff_hotel,,3,"\NULL" +,6,89,1792,0,0,0,,0,0,0,0,0,0,"Offer #1.2",3,tx_irretutorial_1nff_hotel,,2,"\NULL" +,7,89,1536,0,0,0,,0,0,0,0,0,0,"Offer #2.1",4,tx_irretutorial_1nff_hotel,,1,"\NULL" +,8,89,512,0,1,5,,5,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",6,tx_irretutorial_1nff_hotel,,3,"\NULL" +,9,89,1024,0,1,6,,6,0,0,0,0,0,"[Translate to Dansk:] Offer #1.2",6,tx_irretutorial_1nff_hotel,,2,"\NULL" +,10,89,1,0,1,7,,7,0,0,0,0,0,"[Translate to Dansk:] Offer #2.1",7,tx_irretutorial_1nff_hotel,,1,"\NULL" +tx_irretutorial_1nff_price +,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l18n_diffsource,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,l10n_state +,7,89,2048,0,0,0,,0,0,0,0,0,0,"Price #1.1.1",5,tx_irretutorial_1nff_offer,,"\NULL" +,8,89,2816,0,0,0,,0,0,0,0,0,0,"Price #1.1.2",5,tx_irretutorial_1nff_offer,,"\NULL" +,9,89,3328,0,0,0,,0,0,0,0,0,0,"Price #1.1.3",5,tx_irretutorial_1nff_offer,,"\NULL" +,10,89,2304,0,0,0,,0,0,0,0,0,0,"Price #1.2.1",6,tx_irretutorial_1nff_offer,,"\NULL" +,11,89,3072,0,0,0,,0,0,0,0,0,0,"Price #1.2.2",6,tx_irretutorial_1nff_offer,,"\NULL" +,12,89,2560,0,0,0,,0,0,0,0,0,0,"Price #2.1.1",7,tx_irretutorial_1nff_offer,,"\NULL" +,13,89,1280,0,1,7,,7,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",8,tx_irretutorial_1nff_offer,,"\NULL" +,14,89,1536,0,1,8,,8,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.2",8,tx_irretutorial_1nff_offer,,"\NULL" +,15,89,1792,0,1,9,,9,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.3",8,tx_irretutorial_1nff_offer,,"\NULL" +,16,89,512,0,1,10,,10,0,0,0,0,0,"[Translate to Dansk:] Price #1.2.1",9,tx_irretutorial_1nff_offer,,"\NULL" +,17,89,1024,0,1,11,,11,0,0,0,0,0,"[Translate to Dansk:] Price #1.2.2",9,tx_irretutorial_1nff_offer,,"\NULL" +,18,89,1,0,1,12,,12,0,0,0,0,0,"[Translate to Dansk:] Price #2.1.1",10,tx_irretutorial_1nff_offer,,"\NULL" diff --git a/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/L10nModeUpdaterTest.php b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/L10nModeUpdaterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e8a2d7d1ec32bb6546d3232dda4ef77fe7f439ef --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/RowUpdater/L10nModeUpdaterTest.php @@ -0,0 +1,101 @@ +<?php +namespace TYPO3\CMS\Install\Tests\Functional\Updates\RowUpdater; + +/* + * 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\Utility\GeneralUtility; +use TYPO3\CMS\Install\Updates\RowUpdater\L10nModeUpdater; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +/** + * Test Class for L10nModeUpdater + */ +class L10nModeUpdaterTest extends FunctionalTestCase +{ + /** + * @var string + */ + protected $scenarioDataSetDirectory = 'typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/'; + + /** + * @var string + */ + protected $assertionDataSetDirectory = 'typo3/sysext/install/Tests/Functional/Updates/RowUpdater/DataSet/'; + + /** + * @var string[] + */ + protected $coreExtensionsToLoad = [ + 'workspaces', + ]; + + /** + * @var string[] + */ + protected $testExtensionsToLoad = [ + 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial', + ]; + + protected function setUp() + { + parent::setUp(); + $this->importScenarioDataSet('LiveDefaultPages'); + $this->importScenarioDataSet('LiveDefaultElements'); + + $GLOBALS['TCA']['tt_content']['columns']['image']['l10n_mode'] = 'exclude'; + $GLOBALS['TCA']['tt_content']['columns']['header']['config']['behaviour']['allowLanguageSynchronization'] = true; + $GLOBALS['TCA']['tt_content']['columns']['tx_irretutorial_1nff_hotels']['config']['behaviour']['allowLanguageSynchronization'] = true; + } + + /** + * @param string $dataSetName + */ + protected function importScenarioDataSet($dataSetName) + { + $fileName = rtrim($this->scenarioDataSetDirectory, '/') . '/' . $dataSetName . '.csv'; + $fileName = GeneralUtility::getFileAbsFileName($fileName); + $this->importCSVDataSet($fileName); + } + + protected function assertAssertionDataSet($dataSetName) + { + $fileName = rtrim($this->assertionDataSetDirectory, '/') . '/' . $dataSetName . '.csv'; + $fileName = GeneralUtility::getFileAbsFileName($fileName); + $this->assertCSVDataSet($fileName); + } + + /** + * @return array + */ + protected function getTableNames(): array + { + return array_keys($GLOBALS['TCA']); + } + + /** + * @test + */ + public function recordsCanBeUpdated() + { + $updater = new L10nModeUpdater(); + foreach ($this->getTableNames() as $tableName) { + $updater->hasPotentialUpdateForTable($tableName); + foreach ($this->getAllRecords($tableName) as $record) { + $updater->updateTableRow($tableName, $record); + } + } + + $this->assertAssertionDataSet('recordsCanBeUpdated'); + } +}