From 6880c7f497cae31a02dea0a9f591b4558edacb34 Mon Sep 17 00:00:00 2001 From: Christian Weiske Date: Fri, 29 Sep 2017 11:16:24 +0200 Subject: [PATCH] [BUGFIX] Support mixing of fluidpages with other backend layouts When mixing both fluidpages and templavoila page layouts, the page's backend_layout setting needs to be checked to make sure that fluidpages is responsible for handling the rendering. Resolves: https://github.com/FluidTYPO3/fluidpages/issues/366 --- Classes/Provider/PageProvider.php | 15 +- Classes/Service/PageService.php | 58 +----- Classes/UserFunction/LayoutSelect.php | 182 +++++++++++++++++++ Configuration/TCA/Overrides/pages.php | 12 +- Tests/Unit/Provider/PageProviderTest.php | 89 ++++++++- Tests/Unit/Service/PageServiceTest.php | 41 ----- Tests/Unit/UserFunction/LayoutSelectTest.php | 168 +++++++++++++++++ 7 files changed, 462 insertions(+), 103 deletions(-) create mode 100644 Classes/UserFunction/LayoutSelect.php create mode 100644 Tests/Unit/UserFunction/LayoutSelectTest.php diff --git a/Classes/Provider/PageProvider.php b/Classes/Provider/PageProvider.php index 58cda02f..9f48e69d 100644 --- a/Classes/Provider/PageProvider.php +++ b/Classes/Provider/PageProvider.php @@ -11,6 +11,7 @@ use FluidTYPO3\Fluidpages\Controller\PageControllerInterface; use FluidTYPO3\Fluidpages\Service\ConfigurationService; use FluidTYPO3\Fluidpages\Service\PageService; +use FluidTYPO3\Fluidpages\UserFunction\LayoutSelect; use FluidTYPO3\Flux\Form; use FluidTYPO3\Flux\Provider\AbstractProvider; use FluidTYPO3\Flux\Provider\ProviderInterface; @@ -106,6 +107,15 @@ public function injectPageService(PageService $pageService) $this->pageService = $pageService; } + /** + * @param LayoutSelect $layoutSelect + * @return void + */ + public function injectLayoutSelect(LayoutSelect $layoutSelect) + { + $this->layoutSelect = $layoutSelect; + } + /** * @param ConfigurationService $pageConfigurationService * @return void @@ -199,8 +209,9 @@ public function getControllerActionFromRecord(array $row) */ public function getControllerActionReferenceFromRecord(array $row) { - if (true === empty($row[self::FIELD_ACTION_MAIN])) { - $row = $this->pageService->getPageTemplateConfiguration($row['uid']); + $useFluidpages = $this->layoutSelect->isFluidpagesBackendLayout($row['backend_layout']); + if (true === empty($row[self::FIELD_ACTION_MAIN]) || false === $useFluidpages) { + $row = $this->layoutSelect->getPageTemplateConfiguration($row['uid']); } return $row[self::FIELD_ACTION_MAIN]; } diff --git a/Classes/Service/PageService.php b/Classes/Service/PageService.php index 547ecfaa..96f016a7 100755 --- a/Classes/Service/PageService.php +++ b/Classes/Service/PageService.php @@ -8,6 +8,7 @@ * LICENSE.md file that was distributed with this source code. */ +use FluidTYPO3\Fluidpages\UserFunction\LayoutSelect; use FluidTYPO3\Flux\Form; use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService; use FluidTYPO3\Flux\Utility\ExtensionNamingUtility; @@ -96,63 +97,12 @@ public function injectWorkspacesAwareRecordService(WorkspacesAwareRecordService * @param integer $pageUid * @return array|NULL * @api + * @deprecated */ public function getPageTemplateConfiguration($pageUid) { - $pageUid = (integer) $pageUid; - if (1 > $pageUid) { - return null; - } - $cacheId = 'fluidpages-template-configuration-' . $pageUid; - $runtimeCache = $this->getRuntimeCache(); - $fromCache = $runtimeCache->get($cacheId); - if ($fromCache) { - return $fromCache; - } - $fieldList = 'tx_fed_page_controller_action_sub,t3ver_oid,pid,uid'; - $page = $this->workspacesAwareRecordService->getSingle( - 'pages', - 'tx_fed_page_controller_action,' . $fieldList, - $pageUid - ); - - // Initialize with possibly-empty values and loop root line - // to fill values as they are detected. - $resolvedMainTemplateIdentity = $page['tx_fed_page_controller_action']; - $resolvedSubTemplateIdentity = $page['tx_fed_page_controller_action_sub']; - do { - $containsSubDefinition = (false !== strpos($page['tx_fed_page_controller_action_sub'], '->')); - $isCandidate = ((integer) $page['uid'] !== $pageUid); - if (true === $containsSubDefinition && true === $isCandidate) { - $resolvedSubTemplateIdentity = $page['tx_fed_page_controller_action_sub']; - if (true === empty($resolvedMainTemplateIdentity)) { - // Conditions met: current page is not $pageUid, original page did not - // contain a "this page" layout, current rootline page has "sub" selection. - // Then, set our "this page" value to use the "sub" selection that was detected. - $resolvedMainTemplateIdentity = $resolvedSubTemplateIdentity; - } - break; - } - // Note: 't3ver_oid' is analysed in order to make versioned records inherit the original record's - // configuration as an emulated first parent page. - $resolveParentPageUid = (integer) (0 > $page['pid'] ? $page['t3ver_oid'] : $page['pid']); - $page = $this->workspacesAwareRecordService->getSingle( - 'pages', - $fieldList, - $resolveParentPageUid - ); - } while (null !== $page); - if (true === empty($resolvedMainTemplateIdentity) && true === empty($resolvedSubTemplateIdentity)) { - // Neither directly configured "this page" nor inherited "sub" contains a valid value; - // no configuration was detected at all. - return null; - } - $configurarion = [ - 'tx_fed_page_controller_action' => $resolvedMainTemplateIdentity, - 'tx_fed_page_controller_action_sub' => $resolvedSubTemplateIdentity - ]; - $runtimeCache->set($cacheId, $configurarion); - return $configurarion; + $layoutSelect = GeneralUtility::makeInstance(LayoutSelect::class); + return $layoutSelect->getPageTemplateConfiguration($pageUid); } /** diff --git a/Classes/UserFunction/LayoutSelect.php b/Classes/UserFunction/LayoutSelect.php new file mode 100644 index 00000000..9f1565b7 --- /dev/null +++ b/Classes/UserFunction/LayoutSelect.php @@ -0,0 +1,182 @@ +getPageTemplateConfiguration($parameters['record']['uid']); + if (null === $conf) { + return false; + } + + if ($sub) { + return false === empty($conf['tx_fed_page_controller_action_sub']); + } else { + return false === empty($conf['tx_fed_page_controller_action']); + } + } + + /** + * Process RootLine to find first usable, configured Fluid Page Template. + * WARNING: do NOT use the output of this feature to overwrite $row - the + * record returned may or may not be the same record as defined in $id. + * + * @param integer $pageUid + * @return array|NULL Array with two keys: + * - tx_fed_page_controller_action + * - tx_fed_page_controller_action_sub + * @api + */ + public function getPageTemplateConfiguration($pageUid) + { + $pageUid = (integer) $pageUid; + if (1 > $pageUid) { + return null; + } + $cacheId = 'fluidpages-template-configuration-' . $pageUid; + $runtimeCache = $this->getRuntimeCache(); + $fromCache = $runtimeCache->get($cacheId); + if ($fromCache) { + return $fromCache; + } + $fieldList = 'tx_fed_page_controller_action_sub,backend_layout,backend_layout_next_level,t3ver_oid,pid,uid'; + $page = $this->getWorkspacesAwareRecordService()->getSingle( + 'pages', + 'tx_fed_page_controller_action,' . $fieldList, + $pageUid + ); + + $checkUsage = true; + $useMainTemplate = $this->isFluidpagesBackendLayout($page['backend_layout']); + $useSubTemplate = $this->isFluidpagesBackendLayout($page['backend_layout_next_level']); + $mainTemplate = $page['tx_fed_page_controller_action']; + $subTemplate = $page['tx_fed_page_controller_action_sub']; + $isFirstPage = true; + + do { + if (false === $isFirstPage) { + if ($checkUsage) { + if ($this->isFluidpagesBackendLayout($page['backend_layout_next_level'])) { + $useMainTemplate = true; + $useSubTemplate = true; + } else if (false === empty($page['backend_layout_next_level'])) { + //we have a different layout in between, so do not look further up + $checkUsage = false; + } + } + $containsSubDefinition = (false !== strpos($page['tx_fed_page_controller_action_sub'], '->')); + if ($containsSubDefinition) { + if (empty($mainTemplate)) { + $mainTemplate = $page['tx_fed_page_controller_action_sub']; + } + if (empty($subTemplate)) { + $subTemplate = $page['tx_fed_page_controller_action_sub']; + } + } + } + // Note: 't3ver_oid' is analysed in order to make versioned records inherit the original record's + // configuration as an emulated first parent page. + $resolveParentPageUid = (integer) (0 > $page['pid'] ? $page['t3ver_oid'] : $page['pid']); + $page = $this->getWorkspacesAwareRecordService()->getSingle( + 'pages', + $fieldList, + $resolveParentPageUid + ); + $isFirstPage = false; + } while ($page); + + if (empty($mainTemplate)) { + $useMainTemplate = false; + } + if (empty($subTemplate)) { + $useSubTemplate = false; + } + if (false === $useMainTemplate && false === $useSubTemplate) { + //BC return value + return null; + } + $configuration = [ + 'tx_fed_page_controller_action' => $useMainTemplate ? $mainTemplate : null, + 'tx_fed_page_controller_action_sub' => $useSubTemplate ? $subTemplate : null + ]; + $runtimeCache->set($cacheId, $configuration); + return $configuration; + } + + /** + * Determine if the given backend layout string is a fluidpages layout + * + * @param string $belayout Page row backend_layout value + * + * @return boolean True if fluidpages should be used to render + */ + public function isFluidpagesBackendLayout($belayout) + { + return substr($belayout, 0, 12) == 'fluidpages__'; + } + + /** + * @return \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend + */ + protected function getRuntimeCache() + { + return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime'); + } + + /** + * @return WorkspacesAwareRecordService + */ + protected function getWorkspacesAwareRecordService() + { + if (null === $this->workspacesAwareRecordService) { + $this->workspacesAwareRecordService = GeneralUtility::makeInstance(ObjectManager::class) + ->get(WorkspacesAwareRecordService::class); + } + return $this->workspacesAwareRecordService; + } + + /** + * Used in unit tests. + * + * @param WorkspacesAwareRecordService $workspacesAwareRecordService + * + * @return void + */ + public function setWorkspacesAwareRecordService(WorkspacesAwareRecordService $workspacesAwareRecordService) + { + $this->workspacesAwareRecordService = $workspacesAwareRecordService; + } +} +?> diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 7c6058a1..61418c5a 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -17,7 +17,8 @@ 'disabled' => false ] ] - ] + ], + 'displayCond' => 'USER:FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect->doShowPageConfiguration:0', ], 'tx_fed_page_controller_action_sub' => [ 'exclude' => 1, @@ -32,7 +33,8 @@ 'disabled' => false ] ] - ] + ], + 'displayCond' => 'USER:FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect->doShowPageConfiguration:1', ], 'tx_fed_page_flexform' => [ 'exclude' => 1, @@ -42,7 +44,8 @@ 'ds' => [ 'default' => '' ] - ] + ], + 'displayCond' => 'USER:FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect->doShowPageConfiguration:0', ], 'tx_fed_page_flexform_sub' => [ 'exclude' => 1, @@ -52,7 +55,8 @@ 'ds' => [ 'default' => '' ] - ] + ], + 'displayCond' => 'USER:FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect->doShowPageConfiguration:1', ], ]); diff --git a/Tests/Unit/Provider/PageProviderTest.php b/Tests/Unit/Provider/PageProviderTest.php index bb6aa3a2..d075e292 100644 --- a/Tests/Unit/Provider/PageProviderTest.php +++ b/Tests/Unit/Provider/PageProviderTest.php @@ -69,7 +69,13 @@ public function testGetTemplatePathAndFilename() $instance = new PageProvider(); $instance->setTemplatePaths(array('templateRootPaths' => array('EXT:fluidpages/Tests/Fixtures/Templates/'))); $instance->injectPageService($service); + + /** @var LayoutSelect $layoutSelectMock */ + $layoutSelectMock = $this->getMockBuilder('FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect')->setMethods(array('getPageTemplateConfiguration'))->getMock(); + $instance->injectLayoutSelect($layoutSelectMock); + $record = array( + 'backend_layout' => 'fluidpages__fluidpages', $fieldName => 'Fluidpages->dummy', ); $service->expects($this->any())->method('getPageTemplateConfiguration')->willReturn($record); @@ -146,6 +152,10 @@ public function testGetControllerActionFromRecord(array $record, $fieldName, $ex /** @var PageService $service */ $service = $this->getMockBuilder('FluidTYPO3\\Fluidpages\\Service\\PageService')->setMethods(array('getPageTemplateConfiguration'))->getMock(); $instance->injectPageService($service); + + /** @var LayoutSelect $layoutSelectMock */ + $layoutSelectMock = $this->getMockBuilder('FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect')->setMethods(array('getPageTemplateConfiguration'))->getMock(); + $instance->injectLayoutSelect($layoutSelectMock); } if (true === $expectsMessage) { /** @var ConfigurationService|\PHPUnit_Framework_MockObject_MockObject $configurationService */ @@ -165,11 +175,86 @@ public function getControllerActionFromRecordTestValues() { return array( array(array('doktype' => PageControllerInterface::DOKTYPE_RAW), '', false, 'raw'), - array(array('doktype' => 0, 'tx_fed_page_controller_action' => ''), 'tx_fed_page_flexform', true, 'default'), - array(array('doktype' => 0, 'tx_fed_page_controller_action' => 'fluidpages->action'), 'tx_fed_page_flexform', false, 'action'), + array(array('doktype' => 0, 'backend_layout' => 'fluidpages__fluidpages', 'tx_fed_page_controller_action' => ''), 'tx_fed_page_flexform', true, 'default'), + array(array('doktype' => 0, 'backend_layout' => 'fluidpages__fluidpages', 'tx_fed_page_controller_action' => 'fluidpages->action'), 'tx_fed_page_flexform', false, 'action'), ); } + /** + * @dataProvider getControllerActionReferenceFromRecordTestValues + * + * @param array $record Page row + * @param mixed $calcRow Row data calculated by pageService->getPageTemplateConfiguration + * @param mixed $expected Expected return value + */ + public function testGetControllerActionReferenceFromRecord(array $record, $calcRow, $expected) + { + $instance = new PageProvider(); + /** @var LayoutSelect $layoutSelectMock */ + $layoutSelectMock = $this->getMockBuilder('FluidTYPO3\\Fluidpages\\UserFunction\\LayoutSelect')->setMethods(array('getPageTemplateConfiguration'))->getMock(); + $layoutSelectMock->expects($this->any()) + ->method('getPageTemplateConfiguration') + ->will($this->returnValue($calcRow)); + $instance->injectLayoutSelect($layoutSelectMock); + + $result = $instance->getControllerActionReferenceFromRecord($record); + $this->assertEquals($expected, $result); + } + + /** + * Data provider for testGetControllerActionReferenceFromRecord() + * + * @return array + */ + public function getControllerActionReferenceFromRecordTestValues() + { + return [ + 'no action' => [ + [ + 'tx_fed_page_controller_action' => '', + ], + null, + null + ], + 'normal action' => [ + [ + 'tx_fed_page_controller_action' => 'test1->test1', + 'backend_layout' => 'fluidpages__foo', + ], + [], + 'test1->test1' + ], + 'no action, result from pageService' => [ + [ + 'tx_fed_page_controller_action' => '', + 'backend_layout' => 'fluidpages__bar', + ], + [ + 'tx_fed_page_controller_action' => 'test1->test1', + ], + 'test1->test1' + ], + 'no backend layout' => [ + [ + 'tx_fed_page_controller_action' => 'test1->test1', + 'backend_layout' => '', + ], + null, + null + ], + 'no backend layout, result from pageService' => [ + [ + 'tx_fed_page_controller_action' => 'test1->test1', + 'backend_layout' => '', + ], + [ + 'tx_fed_page_controller_action' => 'test2->test2' + ], + 'test2->test2' + ], + ]; + } + public function testGetFlexFormValuesReturnsCollectedDataWhenEncounteringNullForm() { $tree = array( diff --git a/Tests/Unit/Service/PageServiceTest.php b/Tests/Unit/Service/PageServiceTest.php index b11f99b2..0d434957 100644 --- a/Tests/Unit/Service/PageServiceTest.php +++ b/Tests/Unit/Service/PageServiceTest.php @@ -40,47 +40,6 @@ public function getPageFlexFormSourceWithZeroUidReturnsNull() $this->assertNull($this->getPageService()->getPageFlexFormSource(0)); } - /** - * @test - */ - public function getPageTemplateConfigurationWithZeroUidReturnsNull() - { - $this->assertNull($this->getPageService()->getPageTemplateConfiguration(0)); - } - - /** - * @dataProvider getPageTemplateConfigurationTestValues - * @param array $records - * @param array|NULL $expected - */ - public function testGetPageTemplateConfiguration(array $records, $expected) - { - /** @var WorkspacesAwareRecordService|\PHPUnit_Framework_MockObject_MockObject $service */ - $service = $this->getMockBuilder('FluidTYPO3\\Flux\\Service\\WorkspacesAwareRecordService')->setMethods(array('getSingle'))->getMock(); - foreach ($records as $index => $record) { - $service->expects($this->at($index))->method('getSingle')->willReturn($record); - } - $instance = new PageService(); - $instance->injectWorkspacesAwareRecordService($service); - $result = $instance->getPageTemplateConfiguration(1); - $this->assertEquals($expected, $result); - } - - /** - * @return array - */ - public function getPageTemplateConfigurationTestValues() - { - $m = 'tx_fed_page_controller_action'; - $s = 'tx_fed_page_controller_action_sub'; - return array( - array(array(array()), null), - array(array(array($m => '', $s => '')), null), - array(array(array($m => 'test1->test1', $s => 'test2->test2')), array($m => 'test1->test1', $s => 'test2->test2')), - array(array(array($m => ''), array($s => 'test2->test2')), array($m => 'test2->test2', $s => 'test2->test2')) - ); - } - /** * @return void */ diff --git a/Tests/Unit/UserFunction/LayoutSelectTest.php b/Tests/Unit/UserFunction/LayoutSelectTest.php new file mode 100644 index 00000000..e99c3567 --- /dev/null +++ b/Tests/Unit/UserFunction/LayoutSelectTest.php @@ -0,0 +1,168 @@ +assertNull($this->getLayoutSelect()->getPageTemplateConfiguration(0)); + } + + /** + * @dataProvider getPageTemplateConfigurationTestValues + * @param array $records + * @param array|NULL $expected + */ + public function testGetPageTemplateConfiguration(array $records, $expected) + { + /** @var WorkspacesAwareRecordService|\PHPUnit_Framework_MockObject_MockObject $service */ + $service = $this->getMockBuilder('FluidTYPO3\\Flux\\Service\\WorkspacesAwareRecordService')->setMethods(array('getSingle'))->getMock(); + foreach ($records as $index => $record) { + //automatically set page uid + $record['uid'] = $index + 1; + $record['pid'] = $index + 2; + $service->expects($this->at($index))->method('getSingle')->willReturn($record); + } + $instance = new LayoutSelect(); + $instance->setWorkspacesAwareRecordService($service); + $result = $instance->getPageTemplateConfiguration(1); + $this->assertEquals($expected, $result); + } + + /** + * @return array + */ + public function getPageTemplateConfigurationTestValues() + { + $b = 'backend_layout'; + $bs = 'backend_layout_next_level'; + $a = 'tx_fed_page_controller_action'; + $as = 'tx_fed_page_controller_action_sub'; + $bfp = 'fluidpages__fluidpages'; + return [ + 'no data at all' => [ + [[]], + null + ], + 'empty actions' => [ + [ + [$a => '', $as => '', $b => $bfp, $bs => $bfp] + ], + null + ], + 'controller action on page itself' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => $bfp, $bs => $bfp] + ], + [$a => 'test->self', $as => 'test->selfsub'] + ], + 'sub controller action on parent page' => [ + [ + //pages are listed in reverse order, root level last + [$a => '', $b => $bfp, $bs => $bfp], + [$as => 'test->selfsub', $b => $bfp, $bs => $bfp] + ], + [$a => 'test->selfsub', $as => 'test->selfsub'] + ], + 'no backend layout configured' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => '', $bs => ''], + ], + null + ], + 'backend layout configured only for parent page' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => '' , $bs => ''], + [$a => 'test->root', $as => 'test->rootsub', $b => $bfp, $bs => ''], + ], + null + ], + 'backend layout configured on parent page' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => '', $bs => ''], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => $bfp], + ], + [$a => 'test->self', $as => 'test->selfsub'], + ], + 'backend layout configured on parent page #2' => [ + [ + [$a => '' , $as => '' , $b => '', $bs => ''], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => $bfp], + ], + [$a => 'test->rootsub', $as => 'test->rootsub'], + ], + 'different backend layout in between' => [ + [ + [$a => '' , $as => '' , $b => '', $bs => ''], + [$a => '' , $as => '' , $b => '', $bs => 'templavoila'], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => $bfp], + ], + null + ], + 'self backend layout, but different sub backend layout in between' => [ + [ + [$a => '' , $as => '' , $b => $bfp, $bs => ''], + [$a => '' , $as => '' , $b => '', $bs => 'templavoila'], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => $bfp], + ], + [$a => 'test->rootsub', $as => null] + ], + 'action and backend layout on different levels: action higher' => [ + [ + [$a => '' , $as => '' , $b => '', $bs => ''], + [$a => '' , $as => '' , $b => '', $bs => $bfp], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => ''], + ], + [$a => 'test->rootsub', $as => 'test->rootsub'] + ], + 'action and backend layout on different levels: backend layout higher' => [ + [ + [$a => '' , $as => '' , $b => '', $bs => ''], + [$a => 'test->mid', $as => 'test->midsub', $b => '', $bs => ''], + [$a => '' , $as => '' , $b => '', $bs => $bfp], + ], + [$a => 'test->midsub', $as => 'test->midsub'] + ], + 'only sub backend layout, but both actions, no parents' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => '', $bs => $bfp], + ], + [$a => null, $as => 'test->selfsub'] + ], + 'only sub backend layout, but both actions, parents' => [ + [ + [$a => 'test->self', $as => 'test->selfsub', $b => '', $bs => $bfp], + [$a => 'test->root', $as => 'test->rootsub', $b => '', $bs => ''], + ], + [$a => null, $as => 'test->selfsub'] + ], + ]; + } +}