From a4503ed345f917d832ba6601f0fbc38fb890d0f2 Mon Sep 17 00:00:00 2001 From: Nicole Cordes <typo3@cordes.co> Date: Sat, 19 May 2018 01:10:49 +0200 Subject: [PATCH] [BUGFIX] Correctly handle workspace overlays This patch ensures Extbase correctly fetches workspace overlays in reading process. Resolves: #85061 Releases: master, 10.4 Change-Id: Iebfe49ac0b7733bd42c7dc3070ff0cb32a825bef Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/57012 Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> --- .../Generic/Storage/Typo3DbBackend.php | 40 +++- .../Generic/Storage/Typo3DbQueryParser.php | 10 +- .../Functional/Persistence/Fixtures/blogs.xml | 40 ++++ .../Functional/Persistence/Fixtures/posts.xml | 58 +++++ .../Functional/Persistence/RelationTest.php | 3 +- .../Functional/Persistence/WorkspaceTest.php | 208 ++++++++++++++++++ .../Generic/Storage/Typo3DbBackendTest.php | 58 ++++- .../Storage/Typo3DbQueryParserTest.php | 21 +- 8 files changed, 403 insertions(+), 35 deletions(-) create mode 100644 typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php index 96473cfba773..d69b390b7658 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php @@ -29,6 +29,7 @@ use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; +use TYPO3\CMS\Core\Versioning\VersionState; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; @@ -555,17 +556,15 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface return $rows; } - /** @var Context $context */ - $context = $this->objectManager->get(Context::class); + $context = GeneralUtility::makeInstance(Context::class); if ($workspaceUid === null) { $workspaceUid = (int)$context->getPropertyFromAspect('workspace', 'id'); } else { // A custom query is needed, so a custom context is cloned $context = clone $context; - $context->setAspect('workspace', $this->objectManager->get(WorkspaceAspect::class, $workspaceUid)); + $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $workspaceUid)); } - /** @var PageRepository $pageRepository */ - $pageRepository = $this->objectManager->get(PageRepository::class, $context); + $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context); // Fetches the move-placeholder in case it is supported // by the table and if there's only one row in the result set @@ -579,10 +578,10 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName); $queryBuilder->getRestrictions()->removeAll(); $movePlaceholder = $queryBuilder - ->select($tableName . '.*') + ->select('*') ->from($tableName) ->where( - $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)), + $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(VersionState::MOVE_PLACEHOLDER, \PDO::PARAM_INT)), $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($versionId, \PDO::PARAM_INT)), $queryBuilder->expr()->eq('t3ver_move_id', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT)) ) @@ -594,9 +593,25 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface } } $overlaidRows = []; + $querySettings = $query->getQuerySettings(); foreach ($rows as $row) { + // If current row is a translation select its parent + $languageOfCurrentRecord = 0; + if ($GLOBALS['TCA'][$tableName]['ctrl']['languageField'] ?? null + && $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] ?? 0) { + $languageOfCurrentRecord = $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]; + } + if ($querySettings->getLanguageOverlayMode() + && $languageOfCurrentRecord > 0 + && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) + && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0) { + $row = $pageRepository->getRawRecord( + $tableName, + (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] + ); + } + // Handle workspace overlays $pageRepository->versionOL($tableName, $row, true); - $querySettings = $query->getQuerySettings(); if (is_array($row) && $querySettings->getLanguageOverlayMode()) { if ($tableName === 'pages') { $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid()); @@ -604,17 +619,18 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface // todo: remove type cast once getLanguageUid strictly returns an int $languageUid = (int)$querySettings->getLanguageUid(); if (!$querySettings->getRespectSysLanguage() - && isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]) - && $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] > 0 + && $languageOfCurrentRecord > 0 && (!$query instanceof Query || !$query->getParentQuery()) ) { //no parent query means we're processing the aggregate root. //respectSysLanguage is false which means that records returned by the query //might be from different languages (which is desired). //So we need to force language used for overlay to the language of the current record. - $languageUid = $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]; + $languageUid = $languageOfCurrentRecord; } - if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0) { + if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) + && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0 + && $languageOfCurrentRecord > 0) { //force overlay by faking default language record, as getRecordOverlay can only handle default language records $row['uid'] = $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']]; $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = 0; diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php index f57f88767aa4..c419427ab1db 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression; @@ -715,6 +716,9 @@ class Typo3DbQueryParser if (!empty($pageIdStatement)) { $whereClause[] = $pageIdStatement; } + } elseif (!empty($GLOBALS['TCA'][$tableName]['ctrl']['versioningWS'])) { + // Always prevent workspace records from being returned + $whereClause[] = $this->queryBuilder->expr()->eq($tableAlias . '.t3ver_oid', 0); } return $whereClause; @@ -765,7 +769,7 @@ class Typo3DbQueryParser if ($ignoreEnableFields && !$includeDeleted) { if (!empty($enableFieldsToBeIgnored)) { // array_combine() is necessary because of the way \TYPO3\CMS\Core\Domain\Repository\PageRepository::enableFields() is implemented - $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored)); + $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored), true); } elseif (!empty($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) { $statement .= ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0'; } @@ -788,7 +792,9 @@ class Typo3DbQueryParser protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted) { $statement = ''; - if (!$ignoreEnableFields) { + // In case of versioning-preview, enableFields are ignored (checked in Typo3DbBackend::doLanguageAndWorkspaceOverlay) + $isUserInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline'); + if (!$ignoreEnableFields && !$isUserInWorkspace) { $statement .= BackendUtility::BEenableFields($tableName); } if (!$includeDeleted && !empty($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) { diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml index bdf9e6128336..a7bf622a742b 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml @@ -54,4 +54,44 @@ <deleted>1</deleted> <posts>1</posts> </tx_blogexample_domain_model_blog> + <tx_blogexample_domain_model_blog> + <uid>6</uid> + <pid>0</pid> + <title>Blog6Hidden</title> + <description>Blog6 Description</description> + <logo></logo> + <l18n_diffsource></l18n_diffsource> + <hidden>1</hidden> + <deleted>0</deleted> + <posts>1</posts> + </tx_blogexample_domain_model_blog> + + + <tx_blogexample_domain_model_blog> + <uid>101</uid> + <pid>0</pid> + <title>WorkspaceOverlay Blog1</title> + <description>WorkspaceOverlay Blog1 Description</description> + <logo></logo> + <l18n_diffsource></l18n_diffsource> + <deleted>0</deleted> + <posts>10</posts> + <t3ver_oid>1</t3ver_oid> + <t3ver_state>-1</t3ver_state> + <t3ver_wsid>1</t3ver_wsid> + </tx_blogexample_domain_model_blog> + <tx_blogexample_domain_model_blog> + <uid>102</uid> + <pid>0</pid> + <title>WorkspaceOverlay Blog6Enabled</title> + <description>WorkspaceOverlay Blog6 Description</description> + <logo></logo> + <l18n_diffsource></l18n_diffsource> + <hidden>0</hidden> + <deleted>0</deleted> + <posts>1</posts> + <t3ver_oid>6</t3ver_oid> + <t3ver_state>-1</t3ver_state> + <t3ver_wsid>1</t3ver_wsid> + </tx_blogexample_domain_model_blog> </dataset> diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml index c83e4e7e7e10..4bc8645bf498 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml @@ -247,4 +247,62 @@ <deleted>1</deleted> <hidden>0</hidden> </tx_blogexample_domain_model_post> + + + <tx_blogexample_domain_model_post> + <uid>101</uid> + <pid>-1</pid> + <tstamp>121319</tstamp> + <blog>1</blog> + <author>3</author> + <reviewer>2</reviewer> + <tags>10</tags> + <date>1502275450</date> + <categories>3</categories> + <title>WorkspaceOverlay Post1</title> + <content>WorkspaceOverlay Lorem ipsum...</content> + <l18n_diffsource></l18n_diffsource> + <sorting>1</sorting> + <deleted>0</deleted> + <related_posts>1</related_posts> + <t3ver_oid>1</t3ver_oid> + <t3ver_state>-1</t3ver_state> + <t3ver_wsid>1</t3ver_wsid> + </tx_blogexample_domain_model_post> + <tx_blogexample_domain_model_post> + <uid>102</uid> + <pid>-1</pid> + <blog>1</blog> + <author>2</author> + <reviewer>2</reviewer> + <tags>1</tags> + <date>1502275450</date> + <categories>0</categories> + <title>WorkspaceOverlay Post2</title> + <content>WorkspaceOverlay Lorem ipsum...</content> + <l18n_diffsource></l18n_diffsource> + <sorting>2</sorting> + <deleted>0</deleted> + <t3ver_oid>2</t3ver_oid> + <t3ver_state>-1</t3ver_state> + <t3ver_wsid>1</t3ver_wsid> + </tx_blogexample_domain_model_post> + <tx_blogexample_domain_model_post> + <uid>103</uid> + <pid>-1</pid> + <blog>1</blog> + <author>2</author> + <reviewer>1</reviewer> + <tags>1</tags> + <date>1502275450</date> + <categories>0</categories> + <title>WorkspaceOverlay Post3</title> + <content>WorkspaceOverlay Lorem ipsum...</content> + <l18n_diffsource></l18n_diffsource> + <sorting>3</sorting> + <deleted>0</deleted> + <t3ver_oid>3</t3ver_oid> + <t3ver_state>-1</t3ver_state> + <t3ver_wsid>1</t3ver_wsid> + </tx_blogexample_domain_model_post> </dataset> diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php index ca68414aed2c..af0e026a9cc6 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php @@ -23,6 +23,7 @@ use ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository; use ExtbaseTeam\BlogExample\Domain\Repository\PostRepository; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\Category; @@ -89,7 +90,7 @@ class RelationTest extends FunctionalTestCase public function attachPostToBlogAtTheEnd() { $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post'); - $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder->getRestrictions()->removeAll()->add(new BackendWorkspaceRestriction(0, false)); $countPostsOriginal = $queryBuilder ->count('*') ->from('tx_blogexample_domain_model_post') diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php new file mode 100644 index 000000000000..e70b2bfa7f09 --- /dev/null +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php @@ -0,0 +1,208 @@ +<?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\Extbase\Tests\Functional\Persistence; + +use ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\UserAspect; +use TYPO3\CMS\Core\Context\WorkspaceAspect; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Extbase\Persistence\QueryInterface; +use TYPO3\CMS\Extbase\Service\EnvironmentService; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class WorkspaceTest extends FunctionalTestCase +{ + /** + * @var array + */ + protected $testExtensionsToLoad = [ + 'typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example', + ]; + + /** + * @var array + */ + protected $coreExtensionsToLoad = [ + 'extbase', + 'fluid', + 'workspaces', + ]; + + /** + * @var BlogRepository + */ + protected $blogRepository; + + /** + * Sets up this test suite. + */ + protected function setUp(): void + { + parent::setUp(); + $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/pages.xml'); + $this->importDataSet('EXT:extbase/Tests/Functional/Persistence/Fixtures/blogs.xml'); + $this->importDataSet('EXT:extbase/Tests/Functional/Persistence/Fixtures/posts.xml'); + } + + protected function tearDown(): void + { + // Reset to TYPO3_MODE = BE as defined in functional tests + GeneralUtility::makeInstance(EnvironmentService::class)->setFrontendMode(false); + parent::tearDown(); // TODO: Change the autogenerated stub + } + + public function contextDataProvider(): array + { + return [ + 'test frontend context' => [ + 'context' => 'FE', + ], + 'test backend context' => [ + 'context' => 'BE', + ], + ]; + } + + /** + * @test + * @dataProvider contextDataProvider + * @param string $context + */ + public function countReturnsCorrectNumberOfBlogs(string $context) + { + if ($context === 'FE') { + $this->setupSubjectInFrontend(); + } else { + $this->setupSubjectInBackend(); + } + + $query = $this->blogRepository->createQuery(); + + $querySettings = $query->getQuerySettings(); + $querySettings->setRespectStoragePage(false); + + // In workspace all records need to be fetched, thus enableFields is ignored + // This means we select even hidden (but not deleted) records for count() + self::assertSame(5, $query->execute()->count()); + } + + /** + * @test + * @dataProvider contextDataProvider + * @param string $context + */ + public function fetchingAllBlogsReturnsCorrectNumberOfBlogs(string $context) + { + if ($context === 'FE') { + $this->setupSubjectInFrontend(); + } else { + $this->setupSubjectInBackend(); + } + + $query = $this->blogRepository->createQuery(); + + $querySettings = $query->getQuerySettings(); + $querySettings->setRespectStoragePage(false); + + $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]); + + $blogs = $query->execute()->toArray(); + + self::assertSame(4, count($blogs)); + + // Check first blog was overlaid with workspace preview + $firstBlog = array_shift($blogs); + self::assertSame(1, $firstBlog->getUid()); + self::assertSame('WorkspaceOverlay Blog1', $firstBlog->getTitle()); + + // Check last blog was enabled in workspace preview + $lastBlog = array_pop($blogs); + self::assertSame(6, $lastBlog->getUid()); + self::assertSame('WorkspaceOverlay Blog6Enabled', $lastBlog->getTitle()); + } + + /** + * @test + * @dataProvider contextDataProvider + * @param string $context + */ + public function fetchingBlogReturnsOverlaidWorkspaceVersionForRelations(string $context) + { + if ($context === 'FE') { + $this->setupSubjectInFrontend(); + } else { + $this->setupSubjectInBackend(); + } + + $query = $this->blogRepository->createQuery(); + + $querySettings = $query->getQuerySettings(); + $querySettings->setStoragePageIds([0]); + + $query->matching($query->equals('uid', 1)); + + $blog = $query->execute()->getFirst(); + $posts = $blog->getPosts()->toArray(); + + self::assertSame('WorkspaceOverlay Blog1', $blog->getTitle()); + self::assertCount(10, (array)$posts); + self::assertSame('WorkspaceOverlay Post1', $posts[0]->getTitle()); + self::assertSame('WorkspaceOverlay Post2', $posts[1]->getTitle()); + self::assertSame('WorkspaceOverlay Post3', $posts[2]->getTitle()); + } + + /** + * Minimal frontend environment to satisfy Extbase Typo3DbBackend + */ + protected function setupSubjectInFrontend() + { + $context = new Context( + [ + 'workspace' => new WorkspaceAspect(1), + ] + ); + GeneralUtility::setSingletonInstance(Context::class, $context); + GeneralUtility::makeInstance(EnvironmentService::class)->setFrontendMode(true); + + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->blogRepository = $objectManager->get(BlogRepository::class); + } + + /** + * Minimal backend user configuration to satisfy Extbase Typo3DbBackend + */ + protected function setupSubjectInBackend() + { + $backendUser = new BackendUserAuthentication(); + $backendUser->workspace = 1; + $GLOBALS['BE_USER'] = $backendUser; + $context = new Context( + [ + 'backend.user' => new UserAspect($backendUser), + 'workspace' => new WorkspaceAspect(1), + ] + ); + GeneralUtility::setSingletonInstance(Context::class, $context); + + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->blogRepository = $objectManager->get(BlogRepository::class); + } +} diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php index 795aaf3fa8fe..17f97e864c84 100644 --- a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php @@ -17,15 +17,21 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Storage; use Doctrine\DBAL\Driver\Statement; use Prophecy\Argument; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; +use TYPO3\CMS\Core\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; +use TYPO3\CMS\Extbase\Persistence\Generic\Query; use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbBackend; +use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; use TYPO3\CMS\Extbase\Service\EnvironmentService; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -40,12 +46,6 @@ class Typo3DbBackendTest extends UnitTestCase */ protected $resetSingletonInstances = true; - public function setUp(): void - { - parent::setUp(); - $GLOBALS['TSFE'] = new \stdClass(); - } - /** * @return array */ @@ -134,4 +134,50 @@ class Typo3DbBackendTest extends UnitTestCase $result = $mockTypo3DbBackend->getUidOfAlreadyPersistedValueObject($mockValueObject); self::assertSame($expectedUid, $result); } + + /** + * @test + */ + public function overlayLanguageAndWorkspaceChangesUidIfInPreview() + { + $comparisonRow = [ + 'uid' => '42', + 'pid' => '42', + '_ORIG_pid' => '42', + '_ORIG_uid' => '43' + ]; + $row = [ + 'uid' => '42', + 'pid' => '42' + ]; + $workspaceVersion = [ + 'uid' => '43', + 'pid' => '42' + ]; + $mockQuerySettings = $this->getMockBuilder(Typo3QuerySettings::class) + ->setMethods(['dummy']) + ->disableOriginalConstructor() + ->getMock(); + + $workspaceUid = 2; + + $sourceMock = new \TYPO3\CMS\Extbase\Persistence\Generic\Qom\Selector('tx_foo', 'Tx_Foo'); + $context = new Context([ + 'workspace' => new WorkspaceAspect($workspaceUid) + ]); + $pageRepositoryMock = $this->getMockBuilder(PageRepository::class) + ->setMethods(['movePlhOL', 'getWorkspaceVersionOfRecord']) + ->setConstructorArgs([$context]) + ->getMock(); + $query = new Query('random'); + $query->setQuerySettings($mockQuerySettings); + $pageRepositoryMock->expects(self::once())->method('getWorkspaceVersionOfRecord')->with($workspaceUid, 'tx_foo', '42')->willReturn($workspaceVersion); + $environmentService = new EnvironmentService(); + $environmentService->setFrontendMode(true); + GeneralUtility::setSingletonInstance(Context::class, $context); + GeneralUtility::addInstance(PageRepository::class, $pageRepositoryMock); + $mockTypo3DbBackend = $this->getAccessibleMock(Typo3DbBackend::class, ['dummy'], [], '', false); + $mockTypo3DbBackend->injectEnvironmentService($environmentService); + self::assertSame([$comparisonRow], $mockTypo3DbBackend->_call('overlayLanguageAndWorkspace', $sourceMock, [$row], $query)); + } } diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php index 6b9bd0562adf..087a1ad2f640 100644 --- a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php @@ -727,14 +727,10 @@ class Typo3DbQueryParserTest extends UnitTestCase $mockQuerySettings->setIgnoreEnableFields(!$respectEnableFields); $mockQuerySettings->setIncludeDeleted(!$respectEnableFields); - /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit\Framework\MockObject\MockObject */ - $mockEnvironmentService = $this->getMockBuilder(EnvironmentService::class) - ->setMethods(['isEnvironmentInFrontendMode']) - ->getMock(); - $mockEnvironmentService->expects(self::any())->method('isEnvironmentInFrontendMode')->willReturn($mode === 'FE'); - + $environmentService = new EnvironmentService(); + $environmentService->setFrontendMode($mode === 'FE'); $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false); - $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService); + $mockTypo3DbQueryParser->injectEnvironmentService($environmentService); $actualSql = $mockTypo3DbQueryParser->_call('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName); self::assertSame($expectedSql, $actualSql); unset($GLOBALS['TCA'][$tableName]); @@ -762,14 +758,11 @@ class Typo3DbQueryParserTest extends UnitTestCase $mockQuerySettings->expects(self::once())->method('getEnableFieldsToBeIgnored')->willReturn([]); $mockQuerySettings->expects(self::once())->method('getIncludeDeleted')->willReturn(true); - /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit\Framework\MockObject\MockObject */ - $mockEnvironmentService = $this->getMockBuilder(EnvironmentService::class) - ->setMethods(['isEnvironmentInFrontendMode']) - ->getMock(); - $mockEnvironmentService->expects(self::any())->method('isEnvironmentInFrontendMode')->willReturn(true); - + $environmentService = new EnvironmentService(); + $environmentService->setFrontendMode(true); $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false); - $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService); + $mockTypo3DbQueryParser->injectEnvironmentService($environmentService); + $mockTypo3DbQueryParser->_call('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName); unset($GLOBALS['TCA'][$tableName]); } -- GitLab