diff --git a/typo3/sysext/core/Tests/Functional/DataScenarios/Regular/Modify/DataSet/modifyTranslatedContent.csv b/typo3/sysext/core/Tests/Functional/DataScenarios/Regular/Modify/DataSet/modifyTranslatedContent.csv index 373cacbcb728091531db14e073cd69a36f43f67b..468d684c7b1c42a71b4cd782e4d92614b5666a6c 100644 --- a/typo3/sysext/core/Tests/Functional/DataScenarios/Regular/Modify/DataSet/modifyTranslatedContent.csv +++ b/typo3/sysext/core/Tests/Functional/DataScenarios/Regular/Modify/DataSet/modifyTranslatedContent.csv @@ -15,7 +15,7 @@ ,297,89,384,0,0,0,0,0,0,0,0,"Regular Element #1", ,298,89,512,0,0,0,0,0,0,0,0,"Regular Element #2", ,299,89,768,0,0,0,0,0,0,0,0,"Regular Element #3", -,300,89,1024,0,1,299,299,0,0,0,0,"Testing Translation #3","{""header"":""Regular Element #3"",""starttime"":""0"",""endtime"":""0""}" +,300,89,1024,0,1,299,299,0,0,0,0,"Testing Translation #3","{""header"":""Regular Element #3"",""CType"":""text"",""colPos"":""0"",""starttime"":""0"",""endtime"":""0""}" ,301,89,384,0,1,297,297,0,0,0,0,"[Translate to Dansk:] Regular Element #1", ,302,89,448,0,2,297,301,0,0,0,0,"[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1", "sys_refindex" diff --git a/typo3/sysext/frontend/Configuration/TCA/tt_content.php b/typo3/sysext/frontend/Configuration/TCA/tt_content.php index d54256e53337639440a3d261583b0c468804bbd8..84df3d84a6bb8a635d362733e886ceddc61a45b3 100644 --- a/typo3/sysext/frontend/Configuration/TCA/tt_content.php +++ b/typo3/sysext/frontend/Configuration/TCA/tt_content.php @@ -44,6 +44,8 @@ return [ 'columns' => [ 'CType' => [ 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.type', + 'l10n_mode' => 'exclude', + 'l10n_display' => 'defaultAsReadonly', 'config' => [ 'type' => 'select', 'renderType' => 'selectSingle', @@ -171,6 +173,8 @@ return [ ], 'colPos' => [ 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos', + 'l10n_mode' => 'exclude', + 'l10n_display' => 'defaultAsReadonly', 'config' => [ 'type' => 'select', 'renderType' => 'selectSingle', diff --git a/typo3/sysext/install/Classes/Updates/SynchronizeColPosAndCTypeWithDefaultLanguage.php b/typo3/sysext/install/Classes/Updates/SynchronizeColPosAndCTypeWithDefaultLanguage.php new file mode 100644 index 0000000000000000000000000000000000000000..22cd728f514684b950e0caeb4ccddd0fcffc39f8 --- /dev/null +++ b/typo3/sysext/install/Classes/Updates/SynchronizeColPosAndCTypeWithDefaultLanguage.php @@ -0,0 +1,132 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Install\Updates; + +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; + +/** + * @since 13.3 + * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. + */ +#[UpgradeWizard('synchronizeColPosAndCTypeWithDefaultLanguage')] +class SynchronizeColPosAndCTypeWithDefaultLanguage implements UpgradeWizardInterface +{ + protected const TABLE_NAME = 'tt_content'; + + public function getTitle(): string + { + return 'Migrate "colPos" and "CType" of "tt_content" translations to match their parent.'; + } + + public function getDescription(): string + { + return 'Inherit "colPos" and "CType" for "tt_content" translations from their parent elements for consistent translation behavior.'; + } + + public function getPrerequisites(): array + { + return [ + DatabaseUpdatedPrerequisite::class, + ]; + } + + public function updateNecessary(): bool + { + return $this->getRecordsToUpdate() !== []; + } + + public function executeUpdate(): bool + { + $connection = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME); + foreach ($this->getRecordsToUpdate() as $record) { + $parent = $this->getParentRecord((int)$record['l18n_parent']); + if ($parent === [] || $parent === false) { + continue; + } + $connection + ->update( + self::TABLE_NAME, + [ + 'colPos' => (int)$parent['colPos'], + 'CType' => (string)$parent['CType'], + ], + [ + 'uid' => (int)$record['uid'], + ] + ); + } + + return true; + } + + protected function getRecordsToUpdate(): array + { + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder + ->select( + 'translation.uid', + 'translation.l18n_parent', + 'translation.deleted', + 'translation.colPos', + 'translation.CType', + 'parent.deleted', + 'parent.colPos', + 'parent.CType' + ) + ->from(self::TABLE_NAME, 'translation') + ->leftJoin( + 'translation', + self::TABLE_NAME, + 'parent', + $queryBuilder->expr()->eq('translation.l18n_parent', 'parent.uid') + ) + ->where( + $queryBuilder->expr()->neq( + 'translation.l18n_parent', + $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) + ), + $queryBuilder->expr()->or( + $queryBuilder->expr()->neq('translation.colPos', $queryBuilder->quoteIdentifier('parent.colPos')), + $queryBuilder->expr()->neq('translation.CType', $queryBuilder->quoteIdentifier('parent.CType')), + ), + $queryBuilder->expr()->eq('translation.deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), + $queryBuilder->expr()->eq('parent.deleted', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)), + ); + + return $queryBuilder->executeQuery()->fetchAllAssociative() ?: []; + } + + protected function getParentRecord(int $uid): array|false + { + return $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME) + ->select( + ['colPos', 'CType'], + self::TABLE_NAME, + ['uid' => $uid] + )->fetchAssociative(); + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +} diff --git a/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeBase.csv b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeBase.csv new file mode 100644 index 0000000000000000000000000000000000000000..659c0c40396ad5c5b4058707e50291de0ed8cead --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeBase.csv @@ -0,0 +1,18 @@ +"tt_content",,,,,,,, +,"uid","pid","colPos","CType","sys_language_uid","l18n_parent","l10n_source","deleted" +,39,31,0,"header",0,0,0,0 +,41,31,0,"header",0,0,0,0 +,40,31,1,"text",22,39,39,0 +,42,31,1,"text",22,41,41,0 +,43,31,2,"text",23,0,39,0 +,44,31,2,"text",23,0,41,0 +,45,31,2,"text",24,40,43,0 +,46,31,2,"text",24,42,44,0 +,47,31,2,"text",25,0,45,0 +,48,31,2,"text",25,0,46,0 +,49,31,2,"text",22,1234,39,0 +,50,31,2,"text",22,1234,39,0 +,51,31,1,"text",22,39,39,1 +,52,31,1,"text",26,0,39,1 +,53,31,0,"text",27,39,39,0 +,54,31,1,"header",27,39,39,0 diff --git a/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeResult.csv b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeResult.csv new file mode 100644 index 0000000000000000000000000000000000000000..e850c0e38f3dddd934d445acd385f207c3315dec --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/SynchronizeColPosAndCTypeResult.csv @@ -0,0 +1,18 @@ +"tt_content",,,,,,,, +,"uid","pid","colPos","CType","sys_language_uid","l18n_parent","l10n_source","deleted" +,39,31,0,"header",0,0,0,0 +,41,31,0,"header",0,0,0,0 +,40,31,0,"header",22,39,39,0 +,42,31,0,"header",22,41,41,0 +,43,31,2,"text",23,0,39,0 +,44,31,2,"text",23,0,41,0 +,45,31,0,"header",24,40,43,0 +,46,31,0,"header",24,42,44,0 +,47,31,2,"text",25,0,45,0 +,48,31,2,"text",25,0,46,0 +,49,31,2,"text",22,1234,39,0 +,50,31,2,"text",22,1234,39,0 +,51,31,1,"text",22,39,39,1 +,52,31,1,"text",26,0,39,1 +,53,31,0,"header",27,39,39,0 +,54,31,0,"header",27,39,39,0 diff --git a/typo3/sysext/install/Tests/Functional/Updates/SynchronizeColPosAndCTypeWithDefaultLanguageTest.php b/typo3/sysext/install/Tests/Functional/Updates/SynchronizeColPosAndCTypeWithDefaultLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e1df5d87832de465859a2d9f496ebf19d94ce47d --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/SynchronizeColPosAndCTypeWithDefaultLanguageTest.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Install\Tests\Functional\Updates; + +use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Install\Updates\SynchronizeColPosAndCTypeWithDefaultLanguage; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class SynchronizeColPosAndCTypeWithDefaultLanguageTest extends FunctionalTestCase +{ + protected string $baseDataSet = __DIR__ . '/Fixtures/SynchronizeColPosAndCTypeBase.csv'; + protected string $resultDataSet = __DIR__ . '/Fixtures/SynchronizeColPosAndCTypeResult.csv'; + + #[Test] + public function synchronizeColPosWithDefaultLanguage(): void + { + $subject = new SynchronizeColPosAndCTypeWithDefaultLanguage(); + + $this->importCSVDataSet($this->baseDataSet); + self::assertTrue($subject->updateNecessary()); + $subject->executeUpdate(); + $this->assertCSVDataSet($this->resultDataSet); + self::assertFalse($subject->updateNecessary()); + } +}