diff --git a/typo3/sysext/core/Classes/DataHandling/SlugHelper.php b/typo3/sysext/core/Classes/DataHandling/SlugHelper.php index 76fc8833e145bf4a76a1f35347cc2d0870187447..f5c878e8ff2ec5258e94ffffcd1cedfcfef48bd3 100644 --- a/typo3/sysext/core/Classes/DataHandling/SlugHelper.php +++ b/typo3/sysext/core/Classes/DataHandling/SlugHelper.php @@ -489,8 +489,9 @@ class SlugHelper } /** - * @param QueryBuilder $queryBuilder - * @param int $languageId + * Apply constraint to fetch records with same language (Slug / language should be unique). + * If language is -1 (all languages), there should not be any other records with the + * same slug of any language (or -1). */ protected function applyLanguageConstraint(QueryBuilder $queryBuilder, int $languageId) { @@ -498,12 +499,23 @@ class SlugHelper if (!is_string($languageFieldName)) { return; } + if ($languageId === -1) { + // if language is -1 "all languages" we need to check against all languages, thus not adding + // any kind of language constraints. + return; + } - // Only check records of the given language + // Only check records of the given language or -1 (all languages) $queryBuilder->andWhere( - $queryBuilder->expr()->eq( - $languageFieldName, - $queryBuilder->createNamedParameter($languageId, Connection::PARAM_INT) + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq( + $languageFieldName, + $queryBuilder->createNamedParameter($languageId, Connection::PARAM_INT) + ), + $queryBuilder->expr()->eq( + $languageFieldName, + $queryBuilder->createNamedParameter(-1, Connection::PARAM_INT) + ) ) ); } diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguages.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguages.csv new file mode 100644 index 0000000000000000000000000000000000000000..834a85bf20013421f86eafc5b3019619eb1cebf2 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguages.csv @@ -0,0 +1,6 @@ +"pages",,,,,,,,,,,,,, +,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","l10n_source","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title" +,1,0,0,0,0,0,0,0,0,0,0,0,"Root" +"tx_testdatahandler_slug",,,,,, +,"uid","pid","sys_language_uid","l10n_parent","title","slug" +,1,1,0,0,"Unique slug","unique-slug" diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguagesResult.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguagesResult.csv new file mode 100644 index 0000000000000000000000000000000000000000..2028d41e5660936a1d3c9750553243cebdb4b042 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/DataSet/TestSlugUniqueWithLanguagesResult.csv @@ -0,0 +1,4 @@ +"tx_testdatahandler_slug", +,"uid","slug" +,1,"unique-slug" +,2,"unique-slug" diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Slug/SlugHelperUniqueWithLanguageTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/SlugHelperUniqueWithLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f7faa16ace417f1e1715374c7804a9631509f57e --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/Slug/SlugHelperUniqueWithLanguageTest.php @@ -0,0 +1,115 @@ +<?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\Core\Tests\Functional\DataHandling\Slug; + +use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory; +use TYPO3\CMS\Core\DataHandling\SlugHelper; +use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class SlugHelperUniqueWithLanguageTest extends AbstractDataHandlerActionTestCase +{ + protected $testExtensionsToLoad = [ + 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug', + ]; + + protected function setUp(): void + { + parent::setUp(); + + $this->importCSVDataSet(__DIR__ . '/DataSet/TestSlugUniqueWithLanguages.csv'); + $this->setUpFrontendSite(1); + $this->setUpFrontendRootPage(1, ['typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript']); + } + + public function buildSlugForUniqueRespectsLanguageDataProvider(): array + { + return [ + 'sameLanguageSameSlug' => [ + 'expectedSlug' => 'unique-slug-1', + 'recordData' => [ + 'uid' => 2, + 'pid' => 1, + 'sys_language_uid' => 0, + 'title' => 'Some title', + 'slug' => 'unique-slug', + ], + ], + 'sameLanguageDifferentSlug' => [ + 'expectedSlug' => 'other-slug', + 'recordData' => [ + 'uid' => 2, + 'pid' => 1, + 'sys_language_uid' => 0, + 'title' => 'Some title', + 'slug' => 'other-slug', + ], + ], + 'otherLanguageSameSlug' => [ + 'expectedSlug' => 'unique-slug', + 'recordData' => [ + 'uid' => 2, + 'pid' => 1, + 'sys_language_uid' => 1, + 'title' => 'Some title', + 'slug' => 'unique-slug', + ], + ], + 'allLanguagesSameSlug' => [ + 'expectedSlug' => 'unique-slug-1', + 'recordData' => [ + 'uid' => 2, + 'pid' => 1, + 'sys_language_uid' => -1, + 'title' => 'Some title', + 'slug' => 'unique-slug', + ], + ], + ]; + } + + /** + * @test + * @dataProvider buildSlugForUniqueRespectsLanguageDataProvider + */ + public function buildSlugForUniqueRespectsLanguage(string $expectedSlug, array $recordData): void + { + $subject = GeneralUtility::makeInstance( + SlugHelper::class, + 'tx_testdatahandler_slug', + 'slug', + [ + 'generatorOptions' => [ + 'fields' => ['title'], + 'fieldSeparator' => '/', + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + 'default' => '', + ] + ); + + $state = RecordStateFactory::forName('tx_testdatahandler_slug')->fromArray($recordData); + $resultSlug = $subject->buildSlugForUniqueInTable($recordData['slug'], $state); + self::assertSame($expectedSlug, $resultSlug); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/Configuration/TCA/tx_testdatahandler_slug.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/Configuration/TCA/tx_testdatahandler_slug.php new file mode 100644 index 0000000000000000000000000000000000000000..337c4e835de53bcb8f238fd25d8d1357cf1eaa95 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/Configuration/TCA/tx_testdatahandler_slug.php @@ -0,0 +1,90 @@ +<?php + +return [ + 'ctrl' => [ + 'title' => 'tx_testdatahandler_slug', + 'label' => 'title', + 'hideAtCopy' => false, + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'prependAtCopy' => '', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + ], + 'searchFields' => 'uid,title', + ], + 'types' => [ + '0' => [ + 'showitem' => 'title,slug,hidden,sys_language_uid,l10n_parent', + ], + ], + 'columns' => [ + 'slug' => [ + 'exclude' => false, + 'label' => 'slug', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['title'], + 'fieldSeparator' => '/', + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + 'default' => '', + ], + ], + 'title' => [ + 'exclude' => false, + 'l10n_mode' => 'prefixLangTitle', + 'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_formlabel', + 'config' => [ + 'type' => 'input', + 'size' => 60, + 'max' => 255, + ], + ], + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'language', + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_testdatahandler_slug', + 'foreign_table_where' => 'AND {#tx_testdatahandler_slug}.{#pid}=###CURRENT_PID### AND {#tx_testdatahandler_slug}.{#sys_language_uid} IN (-1,0)', + 'default' => 0, + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + 'default' => '', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + 'default' => 0, + ], + ], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_emconf.php new file mode 100644 index 0000000000000000000000000000000000000000..1ff72a404f465f7af827e8714e4d5fbdde61afc7 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_emconf.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +$EM_CONF[$_EXTKEY] = [ + 'title' => 'Test DataHandler with slug fields', + 'description' => 'TYPO3 extension to be used for functional tests in TYPO3 core', + 'version' => '12.0.0', + 'state' => 'beta', + 'author' => '', + 'author_email' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '12.2.0', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_tables.sql b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_tables.sql new file mode 100644 index 0000000000000000000000000000000000000000..75f155fd2945b04dfc198bcc3b076883ba58b445 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler_slug/ext_tables.sql @@ -0,0 +1,9 @@ +# +# Table structure for table 'tx_testdatahandler_slug' for testing slug fields +# +CREATE TABLE tx_testdatahandler_slug ( + title VARCHAR(100) DEFAULT '' NOT NULL, + slug varchar(2048), + + KEY slug (slug(127)) +);