From 19dc7fa901111bebb6b56dd2768dca8d38c311fd Mon Sep 17 00:00:00 2001 From: Alexander Schnitzler <git@alexanderschnitzler.de> Date: Mon, 30 Mar 2020 17:32:32 +0200 Subject: [PATCH] [BUGFIX] Re-enable dynamic resolving of view objects With https://review.typo3.org/c/Packages/TYPO3.CMS/+/59514/ all possibilities have been taken to define which view class to use based on the current plugin environment parameters like controller name, action name and format. Since this is used a lot in user land code, this functionality has been re-enabled for version 10. But still, the main flaw of the original code, i.e. checking for possibly defined classes, is avoided. Instead, users can implement a deterministic view resolver which ensures view objects that implement the ViewInterface. Releases: master Resolves: #90892 Change-Id: Ide2919a6d86b2904087d3d7aa8dfece1abee4658 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64017 Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de> Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Daniel Goerz <daniel.goerz@posteo.de> Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de> Reviewed-by: Daniel Goerz <daniel.goerz@posteo.de> --- .../Mvc/Controller/ActionController.php | 36 ++++++++++-- .../Classes/Mvc/View/GenericViewResolver.php | 55 +++++++++++++++++++ .../Mvc/View/ViewResolverInterface.php | 16 ++++++ .../extbase/Configuration/Services.yaml | 3 + .../Mvc/Controller/ActionControllerTest.php | 26 +++++++++ .../Fixture/Controller/TestController.php | 5 ++ 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php create mode 100644 typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php index e8da5211583e..ec6f3867f34a 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php +++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php @@ -23,7 +23,9 @@ use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent; use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; +use TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; +use TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface; use TYPO3\CMS\Extbase\Mvc\Web\ReferringRequest; use TYPO3\CMS\Extbase\Security\Cryptography\HashService; use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; @@ -50,6 +52,11 @@ class ActionController implements ControllerInterface */ protected $hashService; + /** + * @var ViewResolverInterface + */ + private $viewResolver; + /** * The current view, as resolved by resolveView() * @@ -103,6 +110,15 @@ class ActionController implements ControllerInterface */ protected $response; + /** + * @param ViewResolverInterface $viewResolver + * @internal + */ + public function injectViewResolver(ViewResolverInterface $viewResolver) + { + $this->viewResolver = $viewResolver; + } + /** * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService */ @@ -371,10 +387,22 @@ class ActionController implements ControllerInterface */ protected function resolveView() { - $view = null; - if ($this->defaultViewObjectName != '') { - /** @var ViewInterface $view */ - $view = $this->objectManager->get($this->defaultViewObjectName); + if ($this->viewResolver instanceof GenericViewResolver) { + /* + * This setter is not part of the ViewResolverInterface as it's only necessary to set + * the default view class from this point when using the generic view resolver which + * must respect the possibly overridden property defaultViewObjectName. + */ + $this->viewResolver->setDefaultViewClass($this->defaultViewObjectName); + } + + $view = $this->viewResolver->resolve( + $this->request->getControllerObjectName(), + $this->request->getControllerActionName(), + $this->request->getFormat() + ); + + if ($view instanceof ViewInterface) { $this->setViewConfiguration($view); if ($view->canRender($this->controllerContext) === false) { $view = null; diff --git a/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php b/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php new file mode 100644 index 000000000000..766c85c4393b --- /dev/null +++ b/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +namespace TYPO3\CMS\Extbase\Mvc\View; + +/* + * 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\Extbase\Object\ObjectManager; + +/** + * @internal only to be used within Extbase, not part of TYPO3 Core API. + */ +class GenericViewResolver implements ViewResolverInterface +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var string + */ + private $defaultViewClass; + + public function __construct(ObjectManager $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @param string $defaultViewClass + * @internal + */ + public function setDefaultViewClass(string $defaultViewClass): void + { + $this->defaultViewClass = $defaultViewClass; + } + + public function resolve(string $controllerObjectName, string $actionName, string $format): ViewInterface + { + return $this->objectManager->get($this->defaultViewClass); + } +} diff --git a/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php b/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php new file mode 100644 index 000000000000..27ee2f9c3349 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +namespace TYPO3\CMS\Extbase\Mvc\View; + +/** + * @internal only to be used within Extbase, not part of TYPO3 Core API. + * + * It's safe to use this interface in TYPO3 10 LTS as it will not be changed or removed in that + * version but this interface is likely to be removed and/or changed in version 11. + */ +interface ViewResolverInterface +{ + public function resolve(string $controllerObjectName, string $actionName, string $format): ViewInterface; +} diff --git a/typo3/sysext/extbase/Configuration/Services.yaml b/typo3/sysext/extbase/Configuration/Services.yaml index e393761fbb7b..d0c5dfda66bc 100644 --- a/typo3/sysext/extbase/Configuration/Services.yaml +++ b/typo3/sysext/extbase/Configuration/Services.yaml @@ -83,3 +83,6 @@ services: identifier: 'legacy-slot' method: 'emitAfterPersistObjectSignal' event: TYPO3\CMS\Extbase\Event\Persistence\EntityPersistedEvent + + TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver: ~ + TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface: '@TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver' diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php index e8fabffe2505..e68796e94346 100644 --- a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\View\JsonView; use TYPO3\CMS\Extbase\Mvc\Web\Request; use TYPO3\CMS\Extbase\Mvc\Web\Response; use TYPO3\CMS\Extbase\Object\ObjectManager; @@ -118,4 +119,29 @@ class ActionControllerTest extends \TYPO3\TestingFramework\Core\Functional\Funct $validators->rewind(); self::assertInstanceOf(NotEmptyValidator::class, $validators->current()); } + + /** + * @test + */ + public function resolveViewRespectsDefaultViewObjectName() + { + // Test setup + $reflectionClass = new \ReflectionClass($this->controller); + $reflectionMethod = $reflectionClass->getProperty('defaultViewObjectName'); + $reflectionMethod->setAccessible(true); + $reflectionMethod->setValue($this->controller, JsonView::class); + + $this->request->setControllerActionName('qux'); + + // Test run + $this->controller->processRequest($this->request, $this->response); + + // Assertions + $reflectionMethod = $reflectionClass->getProperty('view'); + $reflectionMethod->setAccessible(true); + $reflectionMethod->getValue($this->controller); + + $view = $reflectionMethod->getValue($this->controller); + self::assertInstanceOf(JsonView::class, $view); + } } diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php index 32f9ec608e86..6f2b22aead7c 100644 --- a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php +++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php @@ -71,4 +71,9 @@ class TestController extends ActionController // return string so we don't need to mock a view return ''; } + + public function quxAction() + { + return ''; + } } -- GitLab