From 528b90fea50622784dfdd2d651a6486e960b7460 Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Thu, 16 Dec 2021 17:55:44 +0100 Subject: [PATCH] [TASK] Allow ServerRequestInterface in ext:fluid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes StandaloneView and RenderingContext to accept instances of ServerRequestInterface or no Request at all - in contrast to extbase Request only. This is possible with extbase Request implementing ServerRequestInterface since v11. The patch changes a couple of ViewHelpers like the often used TranslateViewHelper: It can run without triggering extbase magic, which especially avoids the performance wise awful ConfigurationManager. Further patches will refactor backend views to leverage this. Internal method RenderingContext->getUriBuilder() is obsoleted and removed along the way. This is a powerful change since it drops the last hard dependency to extbase in fluid and allows views without extbase being involved at all. Change-Id: I3b447b6f70e9ae6f94b981478cd8c4f43a86e9d4 Resolves: #96473 Related: #94428 Releases: main Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72758 Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: core-ci <typo3@b13.com> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> --- phpstan.neon | 4 - .../Core/Rendering/RenderingContext.php | 108 +++++-------- .../Classes/View/AbstractTemplateView.php | 2 +- .../fluid/Classes/View/StandaloneView.php | 20 ++- .../Be/AbstractBackendViewHelper.php | 11 +- .../ViewHelpers/Be/Buttons/CshViewHelper.php | 42 +++-- .../ViewHelpers/Be/Labels/CshViewHelper.php | 60 +++---- .../Be/Menus/ActionMenuItemViewHelper.php | 46 +++--- .../Be/Menus/ActionMenuViewHelper.php | 31 +--- .../ViewHelpers/Be/PageRendererViewHelper.php | 70 +++++---- .../ViewHelpers/FlashMessagesViewHelper.php | 35 ++--- .../Form/AbstractFormFieldViewHelper.php | 74 +++------ .../Form/AbstractFormViewHelper.php | 37 ++--- .../Classes/ViewHelpers/FormViewHelper.php | 146 +++++++----------- .../ViewHelpers/Format/BytesViewHelper.php | 17 +- .../ViewHelpers/Link/ActionViewHelper.php | 29 ++-- .../ViewHelpers/Link/PageViewHelper.php | 39 +++-- .../ViewHelpers/TranslateViewHelper.php | 77 +++++---- .../ViewHelpers/Uri/ActionViewHelper.php | 43 +++--- .../ViewHelpers/Uri/PageViewHelper.php | 33 ++-- .../ViewHelpers/Uri/ResourceViewHelper.php | 16 -- .../PageRendererViewHelperTest.php | 98 ++++++++++++ .../ViewHelpers/TranslateViewHelperTest.php | 135 ++++++++++++---- .../Uri/ResourceViewHelperTest.php | 73 ++++++--- 24 files changed, 682 insertions(+), 564 deletions(-) create mode 100644 typo3/sysext/fluid/Tests/Functional/ViewHelpers/PageRendererViewHelperTest.php diff --git a/phpstan.neon b/phpstan.neon index 715ce57bf1a8..99cd5985df30 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -94,10 +94,6 @@ parameters: message: "#^Unsafe usage of new static\\(\\)\\.$#" count: 1 path: typo3/sysext/workspaces/Classes/Domain/Record/WorkspaceRecord.php - - - message: "#^Call to an undefined static method TYPO3Fluid\\\\Fluid\\\\Core\\\\Rendering\\\\RenderingContext\\:\\:getParserConfiguration\\(\\)\\.$#" - count: 1 - path: typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php # ignored errors for level 1 - diff --git a/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php b/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php index 014ec72d4754..3537a5e8635e 100644 --- a/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php +++ b/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,9 +17,9 @@ namespace TYPO3\CMS\Fluid\Core\Rendering; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Mvc\Request; -use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolver; use TYPO3\CMS\Fluid\View\TemplatePaths; use TYPO3Fluid\Fluid\Core\Cache\FluidCacheInterface; @@ -29,15 +31,9 @@ use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider; use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInvoker; use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer; -/** - * Class RenderingContext - */ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext { - /** - * @var Request - */ - protected $request; + protected ?ServerRequestInterface $request = null; /** * @var string @@ -58,130 +54,98 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext array $templateProcessors, array $expressionNodeTypes ) { - // Reproduced partial initialisation from parent::__construct; minus the custom implementations we attach below. + // Partially cloning parent::__construct() but with custom implementations. $this->setTemplateParser(new TemplateParser()); $this->setTemplateCompiler(new TemplateCompiler()); $this->setViewHelperInvoker(new ViewHelperInvoker()); $this->setViewHelperVariableContainer(new ViewHelperVariableContainer()); $this->setVariableProvider(new StandardVariableProvider()); - $this->setTemplateProcessors($templateProcessors); - $this->setExpressionNodeTypes($expressionNodeTypes); $this->setTemplatePaths(GeneralUtility::makeInstance(TemplatePaths::class)); $this->setViewHelperResolver($viewHelperResolver); - $this->setCache($cache); } /** - * Alternative to buildParserConfiguration, called only in Fluid 3.0 - * - * @return Configuration - */ - public function getParserConfiguration(): Configuration - { - $parserConfiguration = parent::getParserConfiguration(); - $this->addInterceptorsToParserConfiguration($GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['interceptors'], $parserConfiguration); - return $parserConfiguration; - } - - /** - * Build parser configuration + * Build parser configuration. Adds custom fluid interceptors from configuration. * * @return Configuration * @throws \InvalidArgumentException if a class not implementing InterceptorInterface was registered */ - public function buildParserConfiguration() + public function buildParserConfiguration(): Configuration { $parserConfiguration = parent::buildParserConfiguration(); - $this->addInterceptorsToParserConfiguration($GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['interceptors'], $parserConfiguration); - return $parserConfiguration; - } - - protected function addInterceptorsToParserConfiguration(iterable $interceptors, Configuration $parserConfiguration): void - { - foreach ($interceptors as $className) { + foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['interceptors'] as $className) { $interceptor = GeneralUtility::makeInstance($className); if (!$interceptor instanceof InterceptorInterface) { - throw new \InvalidArgumentException('Interceptor "' . $className . '" needs to implement ' . InterceptorInterface::class . '.', 1462869795); + throw new \InvalidArgumentException( + 'Interceptor "' . $className . '" needs to implement ' . InterceptorInterface::class . '.', + 1462869795 + ); } $parserConfiguration->addInterceptor($interceptor); } + return $parserConfiguration; } /** * @param string $action */ - public function setControllerAction($action) + public function setControllerAction($action): void { $dotPosition = strpos($action, '.'); if ($dotPosition !== false) { $action = substr($action, 0, $dotPosition); } $this->controllerAction = $action; - if ($this->request) { + if ($this->request instanceof RequestInterface) { + // @todo: Avoid altogether?! $this->request->setControllerActionName(lcfirst($action)); } } /** * @param string $controllerName - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException */ - public function setControllerName($controllerName) + public function setControllerName($controllerName): void { $this->controllerName = $controllerName; - if ($this->request instanceof Request) { + if ($this->request instanceof RequestInterface) { + // @todo: Avoid altogether?! $this->request->setControllerName($controllerName); } } - /** - * @return string - */ - public function getControllerName() + public function getControllerName(): string { - return $this->request instanceof Request ? $this->request->getControllerName() : $this->controllerName; + // @todo: Why fallback to request here? This is not consistent! + return $this->request instanceof RequestInterface ? $this->request->getControllerName() : $this->controllerName; } - /** - * @return string - */ - public function getControllerAction() + public function getControllerAction(): string { - return $this->request instanceof Request ? $this->request->getControllerActionName() : $this->controllerAction; + // @todo: Why fallback to request here? This is not consistent! + return $this->request instanceof RequestInterface ? $this->request->getControllerActionName() : $this->controllerAction; } /** - * @param Request $request - * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException - * @internal this might change to use a PSR-7 compliant request + * It is currently allowed to setRequest(null) to unset a + * request object created by factories. Some tests use this + * to make sure no extbase request is set. This may change. */ - public function setRequest(Request $request): void + public function setRequest(?ServerRequestInterface $request): void { $this->request = $request; - $this->setControllerAction($request->getControllerActionName()); - $this->setControllerName($request->getControllerName()); + if ($request instanceof RequestInterface) { + // Set magic if this is an extbase request + $this->setControllerAction($request->getControllerActionName()); + $this->setControllerName($request->getControllerName()); + } } - /** - * @return Request - * @internal this might change to use a PSR-7 compliant request - */ - public function getRequest(): Request + public function getRequest(): ?ServerRequestInterface { return $this->request; } - - /** - * @return UriBuilder - * @internal this is subject to change - */ - public function getUriBuilder(): UriBuilder - { - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); - $uriBuilder->setRequest($this->request); - return $uriBuilder; - } } diff --git a/typo3/sysext/fluid/Classes/View/AbstractTemplateView.php b/typo3/sysext/fluid/Classes/View/AbstractTemplateView.php index 9d22282b5600..7b74e292da7a 100644 --- a/typo3/sysext/fluid/Classes/View/AbstractTemplateView.php +++ b/typo3/sysext/fluid/Classes/View/AbstractTemplateView.php @@ -29,7 +29,7 @@ use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException; abstract class AbstractTemplateView extends Typo3FluidAbstractTemplateView { /** - * @param RenderingContextInterface $context + * @param RenderingContextInterface|null $context * @internal */ public function __construct(RenderingContextInterface $context = null) diff --git a/typo3/sysext/fluid/Classes/View/StandaloneView.php b/typo3/sysext/fluid/Classes/View/StandaloneView.php index 3ba5c1e82f5c..08a232521be7 100644 --- a/typo3/sysext/fluid/Classes/View/StandaloneView.php +++ b/typo3/sysext/fluid/Classes/View/StandaloneView.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Fluid\View; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext; @@ -30,6 +31,10 @@ class StandaloneView extends AbstractTemplateView public function __construct() { $renderingContext = GeneralUtility::makeInstance(RenderingContextFactory::class)->create(); + // @todo: This is very unfortunate. This creates an extbase request by default. Standalone + // usage is typically *not* extbase context. Controllers that want to get rid of this + // have to ->setRequest($myServerRequestInterface), or even ->setRequest(null) after + // object construction to get rid of an extbase request again. $renderingContext->setRequest(GeneralUtility::makeInstance(Request::class)); parent::__construct($renderingContext); } @@ -55,6 +60,7 @@ class StandaloneView extends AbstractTemplateView * * @return string $format * @throws \RuntimeException + * @todo: deprecate?! */ public function getFormat() { @@ -64,14 +70,24 @@ class StandaloneView extends AbstractTemplateView throw new \RuntimeException('The rendering context must be of type ' . RenderingContext::class, 1482251887); } + /** + * @internal Currently used especially in functional tests. May change. + */ + public function setRequest(?ServerRequestInterface $request = null): void + { + if ($this->baseRenderingContext instanceof RenderingContext) { + $this->baseRenderingContext->setRequest($request); + } + } + /** * Returns the current request object * - * @return \TYPO3\CMS\Extbase\Mvc\Request * @throws \RuntimeException * @internal + * @todo: deprecate?! */ - public function getRequest() + public function getRequest(): ?ServerRequestInterface { if ($this->baseRenderingContext instanceof RenderingContext) { return $this->baseRenderingContext->getRequest(); diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/AbstractBackendViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/AbstractBackendViewHelper.php index 7ba04d180402..21dfdf4fd23f 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/AbstractBackendViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/AbstractBackendViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -29,10 +31,8 @@ abstract class AbstractBackendViewHelper extends AbstractViewHelper /** * Gets instance of template if exists or create a new one. * Saves instance in viewHelperVariableContainer - * - * @return ModuleTemplate */ - public function getModuleTemplate() + public function getModuleTemplate(): ModuleTemplate { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if ($viewHelperVariableContainer->exists(self::class, 'ModuleTemplate')) { @@ -47,10 +47,8 @@ abstract class AbstractBackendViewHelper extends AbstractViewHelper /** * Gets instance of PageRenderer if exists or create a new one. * Saves instance in viewHelperVariableContainer - * - * @return PageRenderer */ - public function getPageRenderer() + public function getPageRenderer(): PageRenderer { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if ($viewHelperVariableContainer->exists(self::class, 'PageRenderer')) { @@ -59,7 +57,6 @@ abstract class AbstractBackendViewHelper extends AbstractViewHelper $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); $viewHelperVariableContainer->add(self::class, 'PageRenderer', $pageRenderer); } - return $pageRenderer; } } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Buttons/CshViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Buttons/CshViewHelper.php index 1058c0ffa1d6..4aadee71869e 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Buttons/CshViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Buttons/CshViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -16,6 +18,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Be\Buttons; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; @@ -62,12 +65,7 @@ final class CshViewHelper extends AbstractBackendViewHelper */ protected $escapeOutput = false; - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('table', 'string', 'Table name (\'_MOD_\'+module name). If not set, the current module name will be used'); @@ -75,35 +73,31 @@ final class CshViewHelper extends AbstractBackendViewHelper $this->registerArgument('wrap', 'string', 'Markup to wrap around the CSH, split by "|"', false, ''); } - /** - * Render context sensitive help (CSH) for the given table - * - * @return string the rendered CSH icon - */ - public function render() + public function render(): string { - return static::renderStatic( - $this->arguments, - $this->buildRenderChildrenClosure(), - $this->renderingContext - ); + return self::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext); } /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string + * @throws \RuntimeException */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { $table = $arguments['table']; $field = $arguments['field']; $wrap = $arguments['wrap']; if ($table === null) { - $currentRequest = $renderingContext->getRequest(); - $moduleName = $currentRequest->getPluginName(); + $request = $renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + // Throw if not an extbase request + throw new \RuntimeException( + 'ViewHelper f:be.buttons.csh needs an extbase Request object to resolve module name magically.' + . ' When not in extbase context, attribute "table" is required to be set to something like "_MOD_my_module_name"', + 1639740545 + ); + } + $moduleName = $request->getPluginName(); $table = '_MOD_' . $moduleName; } $content = (string)$renderChildrenClosure(); diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Labels/CshViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Labels/CshViewHelper.php index 24ea02a59ca4..dfcbac76ebcc 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Labels/CshViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Labels/CshViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -17,6 +19,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Be\Labels; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendViewHelper; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; @@ -55,21 +58,7 @@ final class CshViewHelper extends AbstractBackendViewHelper */ protected $escapeOutput = false; - /** - * Returns the Language Service - * @return LanguageService - */ - protected static function getLanguageService() - { - return $GLOBALS['LANG']; - } - - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('table', 'string', 'Table name (\'_MOD_\'+module name). If not set, the current module name will be used'); @@ -77,42 +66,39 @@ final class CshViewHelper extends AbstractBackendViewHelper $this->registerArgument('label', 'string', 'Language label which is wrapped with the CSH', false, ''); } - /** - * Render context sensitive help (CSH) for the given table - * - * @return string the rendered CSH icon - */ - public function render() + public function render(): string { - return static::renderStatic( - $this->arguments, - $this->buildRenderChildrenClosure(), - $this->renderingContext - ); + return self::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string - */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { $table = $arguments['table']; $field = $arguments['field']; $label = $arguments['label']; if ($table === null) { - $currentRequest = $renderingContext->getRequest(); - $moduleName = $currentRequest->getPluginName(); + $request = $renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + // Throw if not an extbase request + throw new \RuntimeException( + 'ViewHelper f:be.labels.csh needs an extbase Request object to resolve module name magically.' + . ' When not in extbase context, attribute "table" is required to be set to something like "_MOD_my_module_name"', + 1639759760 + ); + } + $moduleName = $request->getPluginName(); $table = '_MOD_' . $moduleName; } if (strpos($label, 'LLL:') === 0) { $label = self::getLanguageService()->sL($label); } - // Double encode can be set to true, once the typo3fluid/fluid fix is released and required - $label = '<label>' . htmlspecialchars($label, ENT_QUOTES, '', false) . '</label>'; + $label = '<label>' . htmlspecialchars($label, ENT_QUOTES, '', true) . '</label>'; return BackendUtility::wrapInHelp($table, $field, $label); } + + protected static function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php index 40daa1b6bfd1..25b32070e5a6 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -17,12 +19,14 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Be\Menus; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /** * ViewHelper which returns an option tag. * This ViewHelper only works in conjunction with :php:`\TYPO3\CMS\Fluid\ViewHelpers\Be\Menus\ActionMenuViewHelper`. + * This ViewHelper is tailored to be used only in extbase context. * * .. note:: * This ViewHelper is experimental! @@ -38,7 +42,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * <f:be.menus.actionMenuItem label="List Posts" controller="Post" action="index" arguments="{blog: blog}" /> * </f:be.menus.actionMenu> * - * Selectbox with the options "Overview", "Create new Blog" and "List Posts". + * Select box with the options "Overview", "Create new Blog" and "List Posts". * * Localized:: * @@ -47,7 +51,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * <f:be.menus.actionMenuItem label="{f:translate(key='create_blog')}" controller="Blog" action="new" /> * </f:be.menus.actionMenu> * - * Localized selectbox. + * Localized select box. */ final class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper { @@ -56,12 +60,7 @@ final class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'option'; - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('label', 'string', 'label of the option tag', true); @@ -70,13 +69,7 @@ final class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper $this->registerArgument('arguments', 'array', 'additional controller arguments to be passed to the action when this ActionMenuItem is selected', false, []); } - /** - * Renders an ActionMenu option tag - * - * @return string the rendered option tag - * @see \TYPO3\CMS\Fluid\ViewHelpers\Be\Menus\ActionMenuViewHelper - */ - public function render() + public function render(): string { $label = $this->arguments['label']; $controller = $this->arguments['controller']; @@ -84,7 +77,15 @@ final class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper $arguments = $this->arguments['arguments']; $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); - $uriBuilder->setRequest($this->renderingContext->getRequest()); + $request = $this->renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + // Throw if not an extbase request + throw new \RuntimeException( + 'ViewHelper f:be.menus.actionMenuItem needs an extbase Request object to create URIs.', + 1639741792 + ); + } + $uriBuilder->setRequest($request); $uri = $uriBuilder->reset()->uriFor($action, $arguments, $controller); $this->tag->addAttribute('value', $uri); @@ -93,21 +94,18 @@ final class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper $this->evaluateSelectItemState($controller, $action, $arguments); } - $this->tag->setContent( - // Double encode can be set to true, once the typo3fluid/fluid fix is released and required - htmlspecialchars($label, ENT_QUOTES, '', false) - ); + $this->tag->setContent(htmlspecialchars($label, ENT_QUOTES, '', true)); return $this->tag->render(); } protected function evaluateSelectItemState(string $controller, string $action, array $arguments): void { - $currentRequest = $this->renderingContext->getRequest(); + $request = $this->renderingContext->getRequest(); $flatRequestArguments = ArrayUtility::flattenPlain( array_merge([ - 'controller' => $currentRequest->getControllerName(), - 'action' => $currentRequest->getControllerActionName(), - ], $currentRequest->getArguments()) + 'controller' => $request->getControllerName(), + 'action' => $request->getControllerActionName(), + ], $request->getArguments()) ); $flatViewHelperArguments = ArrayUtility::flattenPlain( array_merge(['controller' => $controller, 'action' => $action], $arguments) diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php index 270efe7396c8..05f0e5efd8ab 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -40,7 +42,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * <f:be.menus.actionMenuItem label="List Posts" controller="Post" action="index" arguments="{blog: blog}" /> * </f:be.menus.actionMenu> * - * Selectbox with the options "Overview", "Create new Blog" and "List Posts". + * Select box with the options "Overview", "Create new Blog" and "List Posts". * * Localized:: * @@ -49,7 +51,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * <f:be.menus.actionMenuItem label="{f:translate(key:'create_blog')}" controller="Blog" action="new" /> * </f:be.menus.actionMenu> * - * Localized selectbox. + * Localized select box. */ final class ActionMenuViewHelper extends AbstractTagBasedViewHelper { @@ -58,30 +60,13 @@ final class ActionMenuViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'select'; - /** - * An array of \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode - * - * @var array - */ - protected $childNodes = []; - - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('defaultController', 'string', 'The default controller to be used'); } - /** - * Render FunctionMenu - * - * @return string - */ - public function render() + public function render(): string { $options = ''; foreach ($this->childNodes as $childNode) { @@ -102,12 +87,10 @@ final class ActionMenuViewHelper extends AbstractTagBasedViewHelper * @param string $argumentsName * @param string $closureName * @param string $initializationPhpCode - * @param ViewHelperNode $node - * @param TemplateCompiler $compiler */ public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler) { - // @TODO: replace with a true compiling method to make compilable! + // @todo: replace with a true compiling method to make compilable! $compiler->disable(); return null; } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/PageRendererViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/PageRendererViewHelper.php index 3dbd66153905..0485ce36e013 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/PageRendererViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/PageRendererViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,8 +17,10 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Be; +use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -30,26 +34,23 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; * * All options:: * - * <f:be.pageRenderer pageTitle="foo" - * includeCssFiles="{0: '{f:uri.resource(path:\'Css/Styles.css\')}'}" - * includeJsFiles="{0: '{f:uri.resource(path:\'JavaScript/Library1.js\')}', 1: '{f:uri.resource(path:\'JavaScript/Library2.js\')}'}" - * addJsInlineLabels="{0: 'label1', 1: 'label2'}" /> + * <f:be.pageRenderer + * pageTitle="foo" + * includeCssFiles="{0: 'EXT:my_ext/Resources/Public/Css/Stylesheet.css'}" + * includeJsFiles="{0: 'EXT:my_ext/Resources/Public/JavaScript/Library1.js', 1: 'EXT:my_ext/Resources/Public/JavaScript/Library2.js'}" + * addJsInlineLabels="{'my_ext.label1': 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:label1'}" + * includesRequireJsModules="{0: 'EXT:my_ext/Resources/Public/JavaScript/RequireJsModule.js'}" + * addInlineSettings="{'some.setting.key': 'some.setting.value'}" + * /> * - * Custom CSS file :file:`EXT:your_extension/Resources/Public/Css/styles.css` and - * JavaScript files :file:`EXT:your_extension/Resources/Public/JavaScript/Library1.js` and - * :file:`EXT:your_extension/Resources/Public/JavaScript/Library2.js` - * will be loaded, plus some inline labels for usage in JS code. + * This will load the specified css, js files and requireJs modules, adds a custom js + * inline setting, and adds a resolved label to be used in js. */ final class PageRendererViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('pageTitle', 'string', 'title tag of the module. Not required by default, as BE modules are shown in a frame', false, ''); $this->registerArgument('includeCssFiles', 'array', 'List of custom CSS file to be loaded'); @@ -59,14 +60,9 @@ final class PageRendererViewHelper extends AbstractViewHelper $this->registerArgument('addInlineSettings', 'array', 'Adds Javascript Inline Setting'); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): void { - $pageRenderer = static::getPageRenderer(); + $pageRenderer = self::getPageRenderer(); $pageTitle = $arguments['pageTitle']; $includeCssFiles = $arguments['includeCssFiles']; $includeJsFiles = $arguments['includeJsFiles']; @@ -79,32 +75,43 @@ final class PageRendererViewHelper extends AbstractViewHelper } // Include custom CSS and JS files - if (is_array($includeCssFiles) && count($includeCssFiles) > 0) { + if (is_array($includeCssFiles)) { foreach ($includeCssFiles as $addCssFile) { $pageRenderer->addCssFile($addCssFile); } } - if (is_array($includeJsFiles) && count($includeJsFiles) > 0) { + if (is_array($includeJsFiles)) { foreach ($includeJsFiles as $addJsFile) { $pageRenderer->addJsFile($addJsFile); } } - if (is_array($includeRequireJsModules) && count($includeRequireJsModules) > 0) { + if (is_array($includeRequireJsModules)) { foreach ($includeRequireJsModules as $addRequireJsFile) { $pageRenderer->loadRequireJsModule($addRequireJsFile); } } - if (is_array($addInlineSettings) && count($addInlineSettings) > 0) { + if (is_array($addInlineSettings)) { $pageRenderer->addInlineSettingArray(null, $addInlineSettings); } // Add inline language labels if (is_array($addJsInlineLabels) && count($addJsInlineLabels) > 0) { - $extensionKey = $renderingContext->getRequest()->getControllerExtensionKey(); - foreach ($addJsInlineLabels as $key) { - $label = LocalizationUtility::translate($key, $extensionKey); - $pageRenderer->addInlineLanguageLabel($key, $label); + $request = $renderingContext->getRequest(); + if ($request instanceof RequestInterface) { + // Extbase request resolves extension key and allows overriding labels using TypoScript configuration. + $extensionKey = $request->getControllerExtensionKey(); + foreach ($addJsInlineLabels as $key) { + $label = LocalizationUtility::translate($key, $extensionKey); + $pageRenderer->addInlineLanguageLabel($key, $label); + } + } else { + // No extbase request, labels should follow "LLL:EXT:some_ext/Resources/Private/someFile.xlf:key" + // syntax, and are not overridden by TypoScript extbase module / plugin configuration. + foreach ($addJsInlineLabels as &$labelKey) { + $labelKey = self::getLanguageService()->sL($labelKey); + } + $pageRenderer->addInlineLanguageLabelArray($addJsInlineLabels); } } } @@ -113,4 +120,9 @@ final class PageRendererViewHelper extends AbstractViewHelper { return GeneralUtility::makeInstance(PageRenderer::class); } + + protected static function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/FlashMessagesViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/FlashMessagesViewHelper.php index efcf1f49969d..36d69762b088 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/FlashMessagesViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/FlashMessagesViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -18,6 +20,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers; use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver; use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Service\ExtensionService; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -105,10 +108,7 @@ final class FlashMessagesViewHelper extends AbstractViewHelper */ protected $escapeOutput = false; - /** - * Initialize arguments - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('queueIdentifier', 'string', 'Flash-message queue to use'); $this->registerArgument('as', 'string', 'The name of the current flashMessage variable for rendering inside'); @@ -123,37 +123,36 @@ final class FlashMessagesViewHelper extends AbstractViewHelper * (e.g. for a controller action). * Custom caching using the Caching Framework can be used in this case. * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext * @return mixed */ public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) { $as = $arguments['as']; - $queueIdentifier = $arguments['queueIdentifier'] ?? null; + $queueIdentifier = $arguments['queueIdentifier']; if ($queueIdentifier === null) { + $request = $renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + // Throw if not an extbase request + throw new \RuntimeException( + 'ViewHelper f:flashMessages needs an extbase Request object to resolve the Queue identifier magically.' + . ' When not in extbase context, set attribute "queueIdentifier".', + 1639821269 + ); + } $extensionService = GeneralUtility::makeInstance(ExtensionService::class); - $pluginNamespace = $extensionService->getPluginNamespace( - $renderingContext->getRequest()->getControllerExtensionName(), - $renderingContext->getRequest()->getPluginName() - ); + $pluginNamespace = $extensionService->getPluginNamespace($request->getControllerExtensionName(), $request->getPluginName()); $queueIdentifier = 'extbase.flashmessages.' . $pluginNamespace; } - $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class) - ->getMessageQueueByIdentifier($queueIdentifier); - + $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier($queueIdentifier); $flashMessages = $flashMessageQueue->getAllMessagesAndFlush(); if ($flashMessages === null || count($flashMessages) === 0) { return ''; } if ($as === null) { - return GeneralUtility::makeInstance(FlashMessageRendererResolver::class) - ->resolve() - ->render($flashMessages); + return GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve()->render($flashMessages); } $templateVariableContainer = $renderingContext->getVariableProvider(); $templateVariableContainer->add($as, $flashMessages); diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php index da97347bcb3a..2f060ce3f1a3 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormFieldViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -17,6 +19,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Form; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Error\Result; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Reflection\ObjectAccess; use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper; @@ -25,31 +28,20 @@ use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper; * * If you set the "property" attribute to the name of the property to resolve from the object, this class will * automatically set the name and value of a form element. + * + * Note this set of ViewHelpers is tailored to be used only in extbase context. */ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper { - /** - * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface - */ - protected $configurationManager; - - /** - * @var bool - */ - protected $respectSubmittedDataValue = false; + protected ConfigurationManagerInterface $configurationManager; + protected bool $respectSubmittedDataValue = false; - /** - * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager - */ - public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager) + public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void { $this->configurationManager = $configurationManager; } - /** - * Initialize arguments. - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('name', 'string', 'Name of input tag'); @@ -63,20 +55,16 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Getting the current configuration for respectSubmittedDataValue. - * - * @return bool */ - public function getRespectSubmittedDataValue() + public function getRespectSubmittedDataValue(): bool { return $this->respectSubmittedDataValue; } /** * Define respectSubmittedDataValue to enable or disable the usage of the submitted values in the viewhelper. - * - * @param bool $respectSubmittedDataValue */ - public function setRespectSubmittedDataValue($respectSubmittedDataValue) + public function setRespectSubmittedDataValue(bool $respectSubmittedDataValue): void { $this->respectSubmittedDataValue = $respectSubmittedDataValue; } @@ -84,12 +72,9 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Get the name of this form element. * Either returns arguments['name'], or the correct name for Object Access. - * * In case property is something like bla.blubb (hierarchical), then [bla][blubb] is generated. - * - * @return string Name */ - protected function getName() + protected function getName(): string { $name = $this->getNameWithoutPrefix(); return $this->prefixFieldName($name); @@ -98,19 +83,17 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Shortcut for retrieving the request from the controller context * - * @return \TYPO3\CMS\Extbase\Mvc\Request + * @return RequestInterface The extbase (!) request. All these VH's are extbase-only. */ - protected function getRequest() + protected function getRequest(): RequestInterface { return $this->renderingContext->getRequest(); } /** * Get the name of this form element, without prefix. - * - * @return string name */ - protected function getNameWithoutPrefix() + protected function getNameWithoutPrefix(): string { if ($this->isObjectAccessorMode()) { $formObjectName = $this->renderingContext->getViewHelperVariableContainer()->get( @@ -214,12 +197,10 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Checks if a property mapping error has occurred in the last request. - * - * @return bool TRUE if a mapping error occurred, FALSE otherwise */ - protected function hasMappingErrorOccurred() + protected function hasMappingErrorOccurred(): bool { - return $this->renderingContext->getRequest()->getOriginalRequest() !== null; + return $this->getRequest()->getOriginalRequest() !== null; } /** @@ -232,7 +213,7 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper { $propertyPath = rtrim(preg_replace('/(\\]\\[|\\[|\\])/', '.', $this->getNameWithoutPrefix()) ?? '', '.'); $value = ObjectAccess::getPropertyPath( - $this->renderingContext->getRequest()->getOriginalRequest()->getArguments(), + $this->getRequest()->getOriginalRequest()->getArguments(), $propertyPath ); return $value; @@ -242,7 +223,7 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper * Add additional identity properties in case the current property is hierarchical (of the form "bla.blubb"). * Then, [bla][__identity] has to be generated as well. */ - protected function addAdditionalIdentityPropertiesIfNeeded() + protected function addAdditionalIdentityPropertiesIfNeeded(): void { if (!$this->isObjectAccessorMode()) { return; @@ -315,11 +296,10 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper } /** - * Internal method which checks if we should evaluate a domain object or just output arguments['name'] and arguments['value'] - * - * @return bool TRUE if we should evaluate the domain object, FALSE otherwise. + * Internal method which checks if we should evaluate a domain object or just output arguments['name'] + * and arguments['value']. Returns true if domoin object should be evaluated. */ - protected function isObjectAccessorMode() + protected function isObjectAccessorMode(): bool { return $this->hasArgument('property') && $this->renderingContext->getViewHelperVariableContainer()->exists( FormViewHelper::class, @@ -330,7 +310,7 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Add a CSS class if this ViewHelper has errors */ - protected function setErrorClassAttribute() + protected function setErrorClassAttribute(): void { if ($this->hasArgument('class')) { $cssClass = $this->arguments['class'] . ' '; @@ -351,10 +331,8 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Get errors for the property and form name of this ViewHelper - * - * @return \TYPO3\CMS\Extbase\Error\Result Array of errors */ - protected function getMappingResultsForProperty() + protected function getMappingResultsForProperty(): Result { if (!$this->isObjectAccessorMode()) { return new Result(); @@ -370,10 +348,8 @@ abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper /** * Renders a hidden field with the same name as the element, to make sure the empty value is submitted * in case nothing is selected. This is needed for checkbox and multiple select fields - * - * @return string the hidden field. */ - protected function renderHiddenFieldForEmptyValue() + protected function renderHiddenFieldForEmptyValue(): string { $hiddenFieldNames = []; $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormViewHelper.php index bb3a5b9ab570..037e3789116a 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Form/AbstractFormViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -26,31 +28,24 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * * If you set the "property" attribute to the name of the property to resolve from the object, this class will * automatically set the name and value of a form element. + * + * Note this set of ViewHelpers is tailored to be used only in extbase context. */ abstract class AbstractFormViewHelper extends AbstractTagBasedViewHelper { - /** - * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface - */ - protected $persistenceManager; + protected PersistenceManagerInterface $persistenceManager; - /** - * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager - */ - public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager) + public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager): void { $this->persistenceManager = $persistenceManager; } /** * Prefixes / namespaces the given name with the form field prefix - * - * @param string $fieldName field name to be prefixed - * @return string namespaced field name */ - protected function prefixFieldName($fieldName) + protected function prefixFieldName(string $fieldName): string { - if ($fieldName === null || $fieldName === '') { + if ($fieldName === '') { return ''; } $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); @@ -72,12 +67,12 @@ abstract class AbstractFormViewHelper extends AbstractTagBasedViewHelper /** * Renders a hidden form field containing the technical identity of the given object. * - * @param object $object Object to create the identity field for - * @param string $name Name - * @return string A hidden field containing the Identity (UID in TYPO3 Flow, uid in Extbase) of the given object or NULL if the object is unknown to the persistence framework + * @param object|null $object Object to create the identity field for + * @param string|null $name Name + * @return string A hidden field containing the Identity (uid) of the given object * @see \TYPO3\CMS\Extbase\Mvc\Controller\Argument::setValue() */ - protected function renderHiddenIdentityField($object, $name) + protected function renderHiddenIdentityField(?object $object, ?string $name): string { if ($object instanceof LazyLoadingProxy) { $object = $object->_loadRealInstance(); @@ -87,13 +82,13 @@ abstract class AbstractFormViewHelper extends AbstractTagBasedViewHelper || ($object->_isNew() && !$object->_isClone())) { return ''; } - // Intentionally NOT using PersistenceManager::getIdentifierByObject here!! + // Intentionally NOT using PersistenceManager::getIdentifierByObject here. // Using that one breaks re-submission of data in forms in case of an error. $identifier = $object->getUid(); if ($identifier === null) { return LF . '<!-- Object of type ' . get_class($object) . ' is without identity -->' . LF; } - $name = $this->prefixFieldName($name) . '[__identity]'; + $name = $this->prefixFieldName($name ?? '') . '[__identity]'; $this->registerFieldNameForFormTokenGeneration($name); return LF . '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars((string)$identifier) . '" />' . LF; @@ -101,10 +96,8 @@ abstract class AbstractFormViewHelper extends AbstractTagBasedViewHelper /** * Register a field name for inclusion in the HMAC / Form Token generation - * - * @param string $fieldName name of the field to register */ - protected function registerFieldNameForFormTokenGeneration($fieldName) + protected function registerFieldNameForFormTokenGeneration(string $fieldName): void { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if ($viewHelperVariableContainer->exists(FormViewHelper::class, 'formFieldNames')) { diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/FormViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/FormViewHelper.php index 730f4e633cea..1bfd376e0dd6 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/FormViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/FormViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,15 +17,17 @@ namespace TYPO3\CMS\Fluid\ViewHelpers; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService; use TYPO3\CMS\Extbase\Mvc\RequestInterface; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3\CMS\Extbase\Security\Cryptography\HashService; use TYPO3\CMS\Extbase\Service\ExtensionService; use TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormViewHelper; use TYPO3\CMS\Fluid\ViewHelpers\Form\CheckboxViewHelper; /** - * Form ViewHelper. Generates a :html:`<form>` Tag. + * Form ViewHelper. Generates a :html:`<form>` Tag. Tailored for extbase plugins, uses extbase Request. * * Basic usage * =========== @@ -63,57 +67,32 @@ class FormViewHelper extends AbstractFormViewHelper */ protected $tagName = 'form'; - /** - * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService - */ - protected $hashService; - - /** - * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService - */ - protected $mvcPropertyMappingConfigurationService; + protected HashService $hashService; + protected MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService; + protected ExtensionService $extensionService; /** - * @var \TYPO3\CMS\Extbase\Service\ExtensionService - */ - protected $extensionService; - - /** - * We need the arguments of the formActionUri on requesthash calculation + * We need the arguments of the formActionUri on request hash calculation * therefore we will store them in here right after calling uriBuilder - * - * @var array */ - protected $formActionUriArguments; + protected array $formActionUriArguments = []; - /** - * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService - */ - public function injectHashService(HashService $hashService) + public function injectHashService(HashService $hashService): void { $this->hashService = $hashService; } - /** - * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService - */ - public function injectMvcPropertyMappingConfigurationService(MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService) + public function injectMvcPropertyMappingConfigurationService(MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService): void { $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService; } - /** - * @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService - */ - public function injectExtensionService(ExtensionService $extensionService) + public function injectExtensionService(ExtensionService $extensionService): void { $this->extensionService = $extensionService; } - /** - * Initialize arguments. - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('action', 'string', 'Target action'); @@ -145,13 +124,16 @@ class FormViewHelper extends AbstractFormViewHelper $this->registerUniversalTagAttributes(); } - /** - * Render the form. - * - * @return string rendered form - */ - public function render() + public function render(): string { + $request = $this->renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException( + 'ViewHelper f:form can be used only in extbase context and needs a request implementing extbase RequestInterface.', + 1639821904 + ); + } + $this->setFormActionUri(); if (isset($this->arguments['method']) && strtolower($this->arguments['method']) === 'get') { $this->tag->addAttribute('method', 'get'); @@ -196,14 +178,15 @@ class FormViewHelper extends AbstractFormViewHelper /** * Sets the "action" attribute of the form tag */ - protected function setFormActionUri() + protected function setFormActionUri(): void { if ($this->hasArgument('actionUri')) { $formActionUri = $this->arguments['actionUri']; } else { - $uriBuilder = $this->renderingContext->getUriBuilder(); + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $uriBuilder ->reset() + ->setRequest($this->renderingContext->getRequest()) ->setTargetPageType($this->arguments['pageType'] ?? 0) ->setNoCache($this->arguments['noCache'] ?? false) ->setSection($this->arguments['section'] ?? '') @@ -237,11 +220,11 @@ class FormViewHelper extends AbstractFormViewHelper * * @return string HTML-string for the additional identity properties */ - protected function renderAdditionalIdentityFields() + protected function renderAdditionalIdentityFields(): string { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); - if ($viewHelperVariableContainer->exists(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties')) { - $additionalIdentityProperties = $viewHelperVariableContainer->get(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties'); + if ($viewHelperVariableContainer->exists(FormViewHelper::class, 'additionalIdentityProperties')) { + $additionalIdentityProperties = $viewHelperVariableContainer->get(FormViewHelper::class, 'additionalIdentityProperties'); $output = ''; foreach ($additionalIdentityProperties as $identity) { $output .= LF . $identity; @@ -258,7 +241,7 @@ class FormViewHelper extends AbstractFormViewHelper * @return string Hidden fields with referrer information * @todo filter out referrer information that is equal to the target (e.g. same packageKey) */ - protected function renderHiddenReferrerFields() + protected function renderHiddenReferrerFields(): string { /** @var RequestInterface $request */ $request = $this->renderingContext->getRequest(); @@ -284,22 +267,22 @@ class FormViewHelper extends AbstractFormViewHelper /** * Adds the form object name to the ViewHelperVariableContainer if "objectName" argument or "name" attribute is specified. */ - protected function addFormObjectNameToViewHelperVariableContainer() + protected function addFormObjectNameToViewHelperVariableContainer(): void { $formObjectName = $this->getFormObjectName(); if ($formObjectName !== null) { - $this->renderingContext->getViewHelperVariableContainer()->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName', $formObjectName); + $this->renderingContext->getViewHelperVariableContainer()->add(FormViewHelper::class, 'formObjectName', $formObjectName); } } /** * Removes the form name from the ViewHelperVariableContainer. */ - protected function removeFormObjectNameFromViewHelperVariableContainer() + protected function removeFormObjectNameFromViewHelperVariableContainer(): void { $formObjectName = $this->getFormObjectName(); if ($formObjectName !== null) { - $this->renderingContext->getViewHelperVariableContainer()->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName'); + $this->renderingContext->getViewHelperVariableContainer()->remove(FormViewHelper::class, 'formObjectName'); } } @@ -310,7 +293,7 @@ class FormViewHelper extends AbstractFormViewHelper * * @return string specified Form name or NULL if neither $objectName nor $name arguments have been specified */ - protected function getFormObjectName() + protected function getFormObjectName(): ?string { $formObjectName = null; if ($this->hasArgument('objectName')) { @@ -324,42 +307,37 @@ class FormViewHelper extends AbstractFormViewHelper /** * Adds the object that is bound to this form to the ViewHelperVariableContainer if the formObject attribute is specified. */ - protected function addFormObjectToViewHelperVariableContainer() + protected function addFormObjectToViewHelperVariableContainer(): void { if ($this->hasArgument('object')) { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); - $viewHelperVariableContainer->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject', $this->arguments['object']); - $viewHelperVariableContainer->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties', []); + $viewHelperVariableContainer->add(FormViewHelper::class, 'formObject', $this->arguments['object']); + $viewHelperVariableContainer->add(FormViewHelper::class, 'additionalIdentityProperties', []); } } /** * Removes the form object from the ViewHelperVariableContainer. */ - protected function removeFormObjectFromViewHelperVariableContainer() + protected function removeFormObjectFromViewHelperVariableContainer(): void { if ($this->hasArgument('object')) { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); - $viewHelperVariableContainer->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject'); - $viewHelperVariableContainer->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties'); + $viewHelperVariableContainer->remove(FormViewHelper::class, 'formObject'); + $viewHelperVariableContainer->remove(FormViewHelper::class, 'additionalIdentityProperties'); } } /** - * Adds the field name prefix to the ViewHelperVariableContainer + * Adds the field name prefix to the ViewHelperVariableContainer. */ - protected function addFieldNamePrefixToViewHelperVariableContainer() + protected function addFieldNamePrefixToViewHelperVariableContainer(): void { $fieldNamePrefix = $this->getFieldNamePrefix(); - $this->renderingContext->getViewHelperVariableContainer()->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'fieldNamePrefix', $fieldNamePrefix); + $this->renderingContext->getViewHelperVariableContainer()->add(FormViewHelper::class, 'fieldNamePrefix', $fieldNamePrefix); } - /** - * Get the field name prefix - * - * @return string - */ - protected function getFieldNamePrefix() + protected function getFieldNamePrefix(): string { if ($this->hasArgument('fieldNamePrefix')) { return $this->arguments['fieldNamePrefix']; @@ -368,39 +346,37 @@ class FormViewHelper extends AbstractFormViewHelper } /** - * Removes field name prefix from the ViewHelperVariableContainer + * Removes field name prefix from the ViewHelperVariableContainer. */ - protected function removeFieldNamePrefixFromViewHelperVariableContainer() + protected function removeFieldNamePrefixFromViewHelperVariableContainer(): void { - $this->renderingContext->getViewHelperVariableContainer()->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'fieldNamePrefix'); + $this->renderingContext->getViewHelperVariableContainer()->remove(FormViewHelper::class, 'fieldNamePrefix'); } /** - * Adds a container for form field names to the ViewHelperVariableContainer + * Adds a container for form field names to the ViewHelperVariableContainer. */ - protected function addFormFieldNamesToViewHelperVariableContainer() + protected function addFormFieldNamesToViewHelperVariableContainer(): void { - $this->renderingContext->getViewHelperVariableContainer()->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formFieldNames', []); + $this->renderingContext->getViewHelperVariableContainer()->add(FormViewHelper::class, 'formFieldNames', []); } /** - * Removes the container for form field names from the ViewHelperVariableContainer + * Removes the container for form field names from the ViewHelperVariableContainer. */ - protected function removeFormFieldNamesFromViewHelperVariableContainer() + protected function removeFormFieldNamesFromViewHelperVariableContainer(): void { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); - $viewHelperVariableContainer->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formFieldNames'); - if ($viewHelperVariableContainer->exists(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'renderedHiddenFields')) { - $viewHelperVariableContainer->remove(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'renderedHiddenFields'); + $viewHelperVariableContainer->remove(FormViewHelper::class, 'formFieldNames'); + if ($viewHelperVariableContainer->exists(FormViewHelper::class, 'renderedHiddenFields')) { + $viewHelperVariableContainer->remove(FormViewHelper::class, 'renderedHiddenFields'); } } /** * Retrieves the default field name prefix for this form - * - * @return string default field name prefix */ - protected function getDefaultFieldNamePrefix() + protected function getDefaultFieldNamePrefix(): string { $request = $this->renderingContext->getRequest(); if ($this->hasArgument('extensionName')) { @@ -422,7 +398,7 @@ class FormViewHelper extends AbstractFormViewHelper /** * Remove Checkbox field names from ViewHelper variable container, to start from scratch when a new form starts. */ - protected function removeCheckboxFieldNamesFromViewHelperVariableContainer() + protected function removeCheckboxFieldNamesFromViewHelperVariableContainer(): void { $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if ($viewHelperVariableContainer->exists(CheckboxViewHelper::class, 'checkboxFieldNames')) { @@ -432,12 +408,10 @@ class FormViewHelper extends AbstractFormViewHelper /** * Render the request hash field - * - * @return string The hmac field */ - protected function renderTrustedPropertiesField() + protected function renderTrustedPropertiesField(): string { - $formFieldNames = $this->renderingContext->getViewHelperVariableContainer()->get(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formFieldNames'); + $formFieldNames = $this->renderingContext->getViewHelperVariableContainer()->get(FormViewHelper::class, 'formFieldNames'); $requestHash = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken($formFieldNames, $this->getFieldNamePrefix()); return '<input type="hidden" name="' . htmlspecialchars($this->prefixFieldName('__trustedProperties')) . '" value="' . htmlspecialchars($requestHash) . '" />'; } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Format/BytesViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Format/BytesViewHelper.php index cf466f071caf..c56762dae043 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Format/BytesViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Format/BytesViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -70,10 +72,7 @@ final class BytesViewHelper extends AbstractViewHelper */ protected $escapeChildren = false; - /** - * Initialize ViewHelper arguments - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('value', 'int', 'The incoming data to convert, or NULL if VH children should be used'); $this->registerArgument('decimals', 'int', 'The number of digits after the decimal point', false, 0); @@ -83,15 +82,9 @@ final class BytesViewHelper extends AbstractViewHelper } /** - * Render the supplied byte count as a human readable string. - * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param \TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface $renderingContext - * - * @return string Formatted byte count + * Render the supplied byte count as a human-readable string. */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { if ($arguments['units'] !== null) { $units = $arguments['units']; diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Link/ActionViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Link/ActionViewHelper.php index 6da72b5f6ea4..a025cc357cb7 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Link/ActionViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Link/ActionViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,12 +17,14 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Link; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /** - * A ViewHelper for creating links to extbase actions. + * A ViewHelper for creating links to extbase actions. Tailored for extbase plugins, uses extbase Request and extbase UriBuilder. * * Examples * ======== @@ -42,10 +46,7 @@ final class ActionViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'a'; - /** - * Arguments initialization - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerUniversalTagAttributes(); @@ -70,11 +71,16 @@ final class ActionViewHelper extends AbstractTagBasedViewHelper $this->registerArgument('arguments', 'array', 'Arguments for the controller action, associative array'); } - /** - * @return string Rendered link - */ - public function render() + public function render(): string { + $request = $this->renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException( + 'ViewHelper f:link.action can be used only in extbase context and needs a request implementing extbase RequestInterface.', + 1639818540 + ); + } + $action = $this->arguments['action']; $controller = $this->arguments['controller']; $extensionName = $this->arguments['extensionName']; @@ -90,10 +96,11 @@ final class ActionViewHelper extends AbstractTagBasedViewHelper $addQueryString = (bool)$this->arguments['addQueryString']; $argumentsToBeExcludedFromQueryString = (array)$this->arguments['argumentsToBeExcludedFromQueryString']; $parameters = $this->arguments['arguments']; - /** @var UriBuilder $uriBuilder */ - $uriBuilder = $this->renderingContext->getUriBuilder(); + + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $uriBuilder ->reset() + ->setRequest($request) ->setTargetPageType($pageType) ->setNoCache($noCache) ->setSection($section) diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Link/PageViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Link/PageViewHelper.php index ae38b6405f8b..a80c9f093a51 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Link/PageViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Link/PageViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,12 +17,14 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Link; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /** - * A ViewHelper for creating links to TYPO3 pages. + * A ViewHelper for creating links to TYPO3 pages. Tailored for extbase plugins, uses extbase Request and extbase UriBuilder. * * Examples * ======== @@ -71,10 +75,7 @@ final class PageViewHelper extends AbstractTagBasedViewHelper */ protected $tagName = 'a'; - /** - * Arguments initialization - */ - public function initializeArguments() + public function initializeArguments(): void { parent::initializeArguments(); $this->registerUniversalTagAttributes(); @@ -83,7 +84,7 @@ final class PageViewHelper extends AbstractTagBasedViewHelper $this->registerArgument('pageUid', 'int', 'Target page. See TypoLink destination'); $this->registerArgument('pageType', 'int', 'Type of the target page. See typolink.parameter'); $this->registerArgument('noCache', 'bool', 'Set this to disable caching for the target page. You should not need this.'); - $this->registerArgument('language', 'string', 'link to a specific language - defaults to the current language, use a language ID or "current" to enforce a specific language', false, null); + $this->registerArgument('language', 'string', 'link to a specific language - defaults to the current language, use a language ID or "current" to enforce a specific language', false); $this->registerArgument('section', 'string', 'The anchor to be added to the URI'); $this->registerArgument('linkAccessRestrictedPages', 'bool', 'If set, links pointing to access restricted pages will still link to the page even though the page cannot be accessed.'); $this->registerArgument('additionalParams', 'array', 'Additional query parameters that won\'t be prefixed like $arguments (overrule $arguments)'); @@ -92,24 +93,30 @@ final class PageViewHelper extends AbstractTagBasedViewHelper $this->registerArgument('argumentsToBeExcludedFromQueryString', 'array', 'Arguments to be removed from the URI. Only active if $addQueryString = TRUE'); } - /** - * @return string Rendered page URI - */ - public function render() + public function render(): string { + $request = $this->renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException( + 'ViewHelper f:link.page can be used only in extbase context and needs a request implementing extbase RequestInterface.', + 1639819269 + ); + } + $pageUid = isset($this->arguments['pageUid']) ? (int)$this->arguments['pageUid'] : null; $pageType = isset($this->arguments['pageType']) ? (int)$this->arguments['pageType'] : 0; - $noCache = isset($this->arguments['noCache']) ? (bool)$this->arguments['noCache'] : false; + $noCache = isset($this->arguments['noCache']) && (bool)$this->arguments['noCache']; $section = isset($this->arguments['section']) ? (string)$this->arguments['section'] : ''; $language = $this->arguments['language'] ?? null; - $linkAccessRestrictedPages = isset($this->arguments['linkAccessRestrictedPages']) ? (bool)$this->arguments['linkAccessRestrictedPages'] : false; + $linkAccessRestrictedPages = isset($this->arguments['linkAccessRestrictedPages']) && (bool)$this->arguments['linkAccessRestrictedPages']; $additionalParams = isset($this->arguments['additionalParams']) ? (array)$this->arguments['additionalParams'] : []; - $absolute = isset($this->arguments['absolute']) ? (bool)$this->arguments['absolute'] : false; - $addQueryString = isset($this->arguments['addQueryString']) ? (bool)$this->arguments['addQueryString'] : false; + $absolute = isset($this->arguments['absolute']) && (bool)$this->arguments['absolute']; + $addQueryString = isset($this->arguments['addQueryString']) && (bool)$this->arguments['addQueryString']; $argumentsToBeExcludedFromQueryString = isset($this->arguments['argumentsToBeExcludedFromQueryString']) ? (array)$this->arguments['argumentsToBeExcludedFromQueryString'] : []; - /** @var UriBuilder $uriBuilder */ - $uriBuilder = $this->renderingContext->getUriBuilder(); + + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $uriBuilder->reset() + ->setRequest($request) ->setTargetPageType($pageType) ->setNoCache($noCache) ->setSection($section) diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php index d7011963f626..6fcc97629716 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/TranslateViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,6 +17,9 @@ namespace TYPO3\CMS\Fluid\ViewHelpers; +use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -106,32 +111,24 @@ final class TranslateViewHelper extends AbstractViewHelper */ protected $escapeChildren = false; - /** - * Initialize arguments. - * - * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('key', 'string', 'Translation Key'); $this->registerArgument('id', 'string', 'Translation ID. Same as key.'); $this->registerArgument('default', 'string', 'If the given locallang key could not be found, this value is used. If this argument is not set, child nodes will be used to render the default'); - $this->registerArgument('arguments', 'array', 'Arguments to be replaced in the resulting string'); + $this->registerArgument('arguments', 'array', 'Arguments to be replaced in the resulting string', false, []); $this->registerArgument('extensionName', 'string', 'UpperCamelCased extension key (for example BlogExample)'); - $this->registerArgument('languageKey', 'string', 'Language key ("dk" for example) or "default" to use for this translation. If this argument is empty, we use the current language'); - $this->registerArgument('alternativeLanguageKeys', 'array', 'Alternative language keys if no translation does exist'); + $this->registerArgument('languageKey', 'string', 'Language key ("dk" for example) or "default" to use. If empty, use current language. Ignored in non-extbase context.'); + $this->registerArgument('alternativeLanguageKeys', 'array', 'Alternative language keys if no translation does exist. Ignored in non-extbase context.'); } /** * Return array element by key. * - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext * @throws Exception - * @return string + * @throws \RuntimeException */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { $key = $arguments['key']; $id = $arguments['id']; @@ -149,10 +146,44 @@ final class TranslateViewHelper extends AbstractViewHelper } $request = $renderingContext->getRequest(); + + if (!$request instanceof RequestInterface) { + // Straight resolving via core LanguageService in non-extbase context + if (!str_starts_with($id, 'LLL:EXT:') && empty($default)) { + // Resolve "short key" without LLL:EXT: syntax given, if an extension name is given. + // @todo: We could consider to deprecate this case. It is mostly implemented for a more + // smooth transition when (backend) controllers no longer feed an extbase request. + if (empty($extensionName)) { + throw new \RuntimeException( + 'ViewHelper f:translate in non-extbase context needs attribute "extensionName" to resolve' + . ' key="' . $id . '" without path. Either set attribute "extensionName" together with the short' + . ' key "yourKey" to result in a lookup "LLL:EXT:your_extension/Resources/Private/Language/locallang.xlf:yourKey",' + . ' or (better) use a full LLL reference like key="LLL:EXT:your_extension/Resources/Private/Language/yourFile.xlf:yourKey"', + 1639828178 + ); + } + $id = 'LLL:EXT:' . GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '/Resources/Private/Language/locallang.xlf:' . $id; + } + $value = self::getLanguageService()->sL($id); + if (empty($value)) { + $value = $default ?? $renderChildrenClosure() ?? ''; + } + if (!empty($translateArguments)) { + $value = vsprintf($value, $translateArguments); + } + return $value; + } + $extensionName = $extensionName ?? $request->getControllerExtensionName(); try { - $value = static::translate($id, $extensionName, $translateArguments, $arguments['languageKey'], $arguments['alternativeLanguageKeys']); + // Trigger full extbase magic: "<f:translate key="key1" />" will look up + // "LLL:EXT:current_extension/Resources/Private/Language/locallang.xlf:key1" AND + // overloads from _LOCAL_LANG extbase TypoScript settings if specified. + // Not this triggers TypoScript parsing via extbase ConfigurationManager + // and should be avoided in backend context! + $value = LocalizationUtility::translate($id, $extensionName, $translateArguments, $arguments['languageKey'], $arguments['alternativeLanguageKeys']); } catch (\InvalidArgumentException $e) { + // @todo: Switch to more specific Exceptions here - for instance those thrown when a package was not found, see #95957 $value = null; } if ($value === null) { @@ -161,22 +192,12 @@ final class TranslateViewHelper extends AbstractViewHelper $value = vsprintf($value, $translateArguments); } } + $value = $value ?? ''; return $value; } - /** - * Wrapper call to static LocalizationUtility - * - * @param string $id Translation Key - * @param string $extensionName UpperCamelCased extension key (for example BlogExample) - * @param array $arguments Arguments to be replaced in the resulting string - * @param string $languageKey Language key to use for this translation - * @param string[] $alternativeLanguageKeys Alternative language keys if no translation does exist - * - * @return string|null - */ - protected static function translate($id, $extensionName, $arguments, $languageKey, $alternativeLanguageKeys) + protected static function getLanguageService(): LanguageService { - return LocalizationUtility::translate($id, $extensionName, $arguments, $languageKey, $alternativeLanguageKeys); + return $GLOBALS['LANG']; } } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ActionViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ActionViewHelper.php index 7ee727f76c4c..85cc95d5c536 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ActionViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ActionViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,12 +17,15 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Uri; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; /** - * A ViewHelper for creating URIs to extbase actions. + * A ViewHelper for creating URIs to extbase actions. Tailored for extbase plugins, uses extbase Request and extbase UriBuilder. * * Examples * ======== @@ -37,10 +42,7 @@ final class ActionViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; - /** - * Initialize arguments - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('action', 'string', 'Target action'); $this->registerArgument('arguments', 'array', 'Arguments', false, []); @@ -49,7 +51,7 @@ final class ActionViewHelper extends AbstractViewHelper $this->registerArgument('pluginName', 'string', 'Target plugin. If empty, the current plugin name is used'); $this->registerArgument('pageUid', 'int', 'Target page. See TypoLink destination'); $this->registerArgument('pageType', 'int', 'Type of the target page. See typolink.parameter', false, 0); - $this->registerArgument('noCache', 'bool', 'Set this to disable caching for the target page. You should not need this.', false, null); + $this->registerArgument('noCache', 'bool', 'Set this to disable caching for the target page. You should not need this.', false); $this->registerArgument('section', 'string', 'The anchor to be added to the URI', false, ''); $this->registerArgument('format', 'string', 'The requested format, e.g. ".html', false, ''); $this->registerArgument('linkAccessRestrictedPages', 'bool', 'If set, links pointing to access restricted pages will still link to the page even though the page cannot be accessed.', false, false); @@ -59,14 +61,16 @@ final class ActionViewHelper extends AbstractViewHelper $this->registerArgument('argumentsToBeExcludedFromQueryString', 'array', 'arguments to be removed from the URI. Only active if $addQueryString = TRUE', false, []); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string - */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { + $request = $renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException( + 'ViewHelper f:uri.action can be used only in extbase context and needs a request implementing extbase RequestInterface.', + 1639819692 + ); + } + /** @var int $pageUid */ $pageUid = $arguments['pageUid'] ?? 0; /** @var int $pageType */ @@ -98,45 +102,36 @@ final class ActionViewHelper extends AbstractViewHelper /** @var array|null $arguments */ $arguments = $arguments['arguments'] ?? []; - $uriBuilder = $renderingContext->getUriBuilder(); - $uriBuilder->reset(); + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + $uriBuilder->reset()->setRequest($request); if ($pageUid > 0) { $uriBuilder->setTargetPageUid($pageUid); } - if ($pageType > 0) { $uriBuilder->setTargetPageType($pageType); } - if ($noCache === true) { $uriBuilder->setNoCache($noCache); } - if (is_string($section)) { $uriBuilder->setSection($section); } - if (is_string($format)) { $uriBuilder->setFormat($format); } - if (is_array($additionalParams)) { $uriBuilder->setArguments($additionalParams); } - if ($absolute === true) { $uriBuilder->setCreateAbsoluteUri($absolute); } - if ($addQueryString === true) { $uriBuilder->setAddQueryString($addQueryString); } - if (is_array($argumentsToBeExcludedFromQueryString)) { $uriBuilder->setArgumentsToBeExcludedFromQueryString($argumentsToBeExcludedFromQueryString); } - if ($linkAccessRestrictedPages === true) { $uriBuilder->setLinkAccessRestrictedPages($linkAccessRestrictedPages); } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/PageViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/PageViewHelper.php index 9aea8c02bba1..7c18d3ccb950 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/PageViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/PageViewHelper.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -15,13 +17,16 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Uri; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; /** - * A ViewHelper for creating URIs to TYPO3 pages. + * A ViewHelper for creating URIs to TYPO3 pages. Tailored for extbase plugins, uses extbase Request and extbase UriBuilder. * * Examples * ======== @@ -63,16 +68,13 @@ final class PageViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; - /** - * Initialize arguments - */ - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('pageUid', 'int', 'target PID'); $this->registerArgument('additionalParams', 'array', 'query parameters to be attached to the resulting URI', false, []); $this->registerArgument('pageType', 'int', 'type of the target page. See typolink.parameter', false, 0); $this->registerArgument('noCache', 'bool', 'set this to disable caching for the target page. You should not need this.', false, false); - $this->registerArgument('language', 'string', 'link to a specific language - defaults to the current language, use a language ID or "current" to enforce a specific language', false, null); + $this->registerArgument('language', 'string', 'link to a specific language - defaults to the current language, use a language ID or "current" to enforce a specific language', false); $this->registerArgument('section', 'string', 'the anchor to be added to the URI', false, ''); $this->registerArgument('linkAccessRestrictedPages', 'bool', 'If set, links pointing to access restricted pages will still link to the page even though the page cannot be accessed.', false, false); $this->registerArgument('absolute', 'bool', 'If set, the URI of the rendered link is absolute', false, false); @@ -80,14 +82,16 @@ final class PageViewHelper extends AbstractViewHelper $this->registerArgument('argumentsToBeExcludedFromQueryString', 'array', 'arguments to be removed from the URI. Only active if $addQueryString = TRUE', false, []); } - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string Rendered page URI - */ - public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext) + public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { + $request = $renderingContext->getRequest(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException( + 'ViewHelper f:uri.page can be used only in extbase context and needs a request implementing extbase RequestInterface.', + 1639820200 + ); + } + $pageUid = $arguments['pageUid']; $additionalParams = $arguments['additionalParams']; $pageType = $arguments['pageType']; @@ -99,9 +103,10 @@ final class PageViewHelper extends AbstractViewHelper $addQueryString = $arguments['addQueryString']; $argumentsToBeExcludedFromQueryString = $arguments['argumentsToBeExcludedFromQueryString']; - $uriBuilder = $renderingContext->getUriBuilder(); + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $uri = $uriBuilder ->reset() + ->setRequest($request) ->setTargetPageType($pageType) ->setNoCache($noCache) ->setSection($section) diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ResourceViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ResourceViewHelper.php index d4116a6a74aa..ab08650d15e1 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ResourceViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Uri/ResourceViewHelper.php @@ -95,16 +95,11 @@ final class ResourceViewHelper extends AbstractViewHelper if ($arguments['absolute']) { $uri = GeneralUtility::locationHeaderUrl($uri); } - return $uri; } /** * Resolves the extension path, either directly when possible, or from extension name and request - * - * @param array $arguments - * @param RenderingContextInterface $renderingContext - * @return string */ private static function resolveExtensionPath(array $arguments, RenderingContextInterface $renderingContext): string { @@ -112,7 +107,6 @@ final class ResourceViewHelper extends AbstractViewHelper if (PathUtility::isExtensionPath($path)) { return $path; } - return sprintf( 'EXT:%s/Resources/Public/%s', self::resolveExtensionKey($arguments, $renderingContext), @@ -122,10 +116,6 @@ final class ResourceViewHelper extends AbstractViewHelper /** * Resolves extension key either from given extension name argument or from request - * - * @param array $arguments - * @param RenderingContextInterface $renderingContext - * @return string */ private static function resolveExtensionKey(array $arguments, RenderingContextInterface $renderingContext): string { @@ -133,16 +123,11 @@ final class ResourceViewHelper extends AbstractViewHelper if ($extensionName === null) { return self::resolveValidatedRequest($arguments, $renderingContext)->getControllerExtensionKey(); } - return GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName); } /** * Resolves and validates the request from rendering context - * - * @param array $arguments - * @param RenderingContextInterface $renderingContext - * @return RequestInterface */ private static function resolveValidatedRequest(array $arguments, RenderingContextInterface $renderingContext): RequestInterface { @@ -179,7 +164,6 @@ final class ResourceViewHelper extends AbstractViewHelper 1640097205 ); } - return $request; } } diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/PageRendererViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/PageRendererViewHelperTest.php new file mode 100644 index 000000000000..032c6a133ab0 --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/PageRendererViewHelperTest.php @@ -0,0 +1,98 @@ +<?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\Fluid\Tests\Functional\ViewHelpers; + +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; +use TYPO3\CMS\Extbase\Mvc\Request; +use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class PageRendererViewHelperTest extends FunctionalTestCase +{ + public function renderDataProvider(): array + { + return [ + 'renderSetsPageTitle' => [ + '<f:be.pageRenderer pageTitle="foo" />', + '<title>foo</title>', + ], + 'renderIncludesCssFile' => [ + '<f:be.pageRenderer includeCssFiles="{0: \'EXT:backend/Resources/Public/Css/backend.css\'}" />', + 'rel="stylesheet" href="typo3/sysext/backend/Resources/Public/Css/backend.css', + ], + 'renderIncludesJsFile' => [ + '<f:be.pageRenderer includeJsFiles="{0: \'EXT:backend/Resources/Public/JavaScript/backend.js\'}" />', + '<script src="typo3/sysext/backend/Resources/Public/JavaScript/backend.js', + ], + 'renderIncludesRequireJsModules' => [ + '<f:be.pageRenderer includeRequireJsModules="{0: \'EXT:backend/Resources/Public/JavaScript/iDoNotExist.js\'}" />', + '"name":"EXT:backend\/Resources\/Public\/JavaScript\/iDoNotExist.js"', + ], + 'renderIncludesInlineSettings' => [ + '<f:be.pageRenderer addInlineSettings="{\'foo\': \'bar\'}" />', + '"TYPO3":{"settings":{"foo":"bar"', + ], + 'renderResolvesLabelUsingExtSyntax' => [ + '<f:be.pageRenderer addJsInlineLabels="{\'login.header\': \'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:login.header\'}" />', + '"lang":{"login.header":"Login"}', + ], + ]; + } + + /** + * @test + * @dataProvider renderDataProvider + */ + public function render(string $template, string $expected): void + { + $view = new StandaloneView(); + // Make sure rendering context has no (extbase) request + $view->getRenderingContext()->setRequest(null); + $view->setTemplateSource($template); + $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create('default'); + $view->render(); + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + // PageRenderer depends on request to determine FE vs. BE + $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest())->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + self::assertStringContainsString($expected, $pageRenderer->render()); + } + + /** + * @test + */ + public function renderResolvesLabelWithExtbaseRequest(): void + { + $view = new StandaloneView(); + $view->setTemplateSource('<f:be.pageRenderer addJsInlineLabels="{0: \'login.header\'}" />'); + $extbaseRequestParameters = new ExtbaseRequestParameters(); + $extbaseRequestParameters->setControllerExtensionName('Backend'); + $extbaseRequest = (new Request())->withAttribute('extbase', $extbaseRequestParameters); + $view->getRenderingContext()->setRequest($extbaseRequest); + $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create('default'); + $view->render(); + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + // PageRenderer depends on request to determine FE vs. BE + $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest())->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + self::assertStringContainsString('"lang":{"login.header":"Login"}', $pageRenderer->render()); + } +} diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php index 63a9c5b5cc8c..199bac997db0 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TranslateViewHelperTest.php @@ -17,12 +17,18 @@ declare(strict_types=1); namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers; +use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; +use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Fluid\View\StandaloneView; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; use TYPO3Fluid\Fluid\Core\ViewHelper\Exception; class TranslateViewHelperTest extends FunctionalTestCase { + protected $coreExtensionsToLoad = ['indexed_search']; + /** * @test */ @@ -30,7 +36,6 @@ class TranslateViewHelperTest extends FunctionalTestCase { $this->expectException(Exception::class); $this->expectExceptionCode(1351584844); - $view = new StandaloneView(); $view->setTemplateSource('<f:translate />'); $view->render(); @@ -39,52 +44,130 @@ class TranslateViewHelperTest extends FunctionalTestCase /** * @test */ - public function renderReturnsStringForGivenKey(): void + public function renderThrowsExceptionInNonExtbaseContextWithoutExtensionName(): void { + $this->expectException(\RuntimeException::class); + $this->expectExceptionCode(1639828178); $view = new StandaloneView(); - $view->setTemplateSource('<f:translate key="foo">hello world</f:translate>'); - self::assertSame('hello world', $view->render()); + $view->getRenderingContext()->setRequest(null); + $view->setTemplateSource('<f:translate key="key1" />'); + $view->render(); } - /** - * @test - */ - public function renderReturnsStringForGivenId(): void + public function renderReturnsStringInNonExtbaseContextDataProvider(): array { - $view = new StandaloneView(); - $view->setTemplateSource('<f:translate id="foo">hello world</f:translate>'); - self::assertSame('hello world', $view->render()); + return [ + 'fallback to default attribute for not existing label' => [ + '<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:iDoNotExist" default="myDefault" />', + 'myDefault', + ], + 'fallback to child for not existing label' => [ + '<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:iDoNotExist">myDefault</f:translate>', + 'myDefault', + ], + 'id and underscored extensionName given' => [ + '<f:translate key="form.legend" extensionName="indexed_search" />', + 'Search form', + ], + 'key and underscored extensionName given' => [ + '<f:translate key="form.legend" extensionName="indexed_search" />', + 'Search form', + ], + 'id and CamelCased extensionName given' => [ + '<f:translate key="form.legend" extensionName="IndexedSearch" />', + 'Search form', + ], + 'key and CamelCased extensionName given' => [ + '<f:translate key="form.legend" extensionName="IndexedSearch" />', + 'Search form', + ], + 'full LLL syntax for not existing label' => [ + '<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:iDoNotExist" />', + '', + ], + 'full LLL syntax for existing label' => [ + '<f:translate key="LLL:EXT:indexed_search/Resources/Private/Language/locallang.xlf:form.legend" />', + 'Search form', + ], + 'empty string on invalid extension' => [ + '<f:translate key="LLL:EXT:i_am_invalid/Resources/Private/Language/locallang.xlf:dummy" />', + '', + ], + ]; } /** - * @test + * @dataProvider renderReturnsStringInNonExtbaseContextDataProvider */ - public function renderReturnsDefaultIfNoTranslationIsFound(): void + public function renderReturnsStringInNonExtbaseContext(string $template, string $expected): void { + $this->setUpBackendUserFromFixture(1); $view = new StandaloneView(); - $view->setTemplateSource('<f:translate id="foo" default="default" />'); - self::assertSame('default', $view->render()); + $view->setTemplateSource($template); + $view->getRenderingContext()->setRequest(null); + $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create('default'); + self::assertSame($expected, $view->render()); } - /** - * @test - */ - public function renderReturnsTranslatedKey(): void + public function renderReturnsStringInExtbaseContextDataProvider(): array { - $this->setUpBackendUserFromFixture(1); - $view = new StandaloneView(); - $view->setTemplateSource('<f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack" />'); - self::assertSame('Go back', $view->render()); + return [ + 'key given for not existing label, fallback to child' => [ + '<f:translate key="foo">hello world</f:translate>', + 'hello world', + ], + 'id given for not existing label, fallback to child' => [ + '<f:translate id="foo">hello world</f:translate>', + 'hello world', + ], + 'fallback to default attribute for not existing label' => [ + '<f:translate key="foo" default="myDefault" />', + 'myDefault', + ], + 'id given with existing label' => [ + '<f:translate id="login.header" />', + 'Login', + ], + 'key given with existing label' => [ + '<f:translate key="login.header" />', + 'Login', + ], + 'id and extensionName given' => [ + '<f:translate key="validator.string.notvalid" extensionName="extbase" />', + 'A valid string is expected.', + ], + 'key and extensionName given' => [ + '<f:translate key="validator.string.notvalid" extensionName="extbase" />', + 'A valid string is expected.', + ], + 'full LLL syntax for not existing label' => [ + '<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:iDoNotExist" />', + '', + ], + 'full LLL syntax for existing label' => [ + '<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang.xlf:login.header" />', + 'Login', + ], + 'empty string on invalid extension' => [ + '<f:translate key="LLL:EXT:i_am_invalid/Resources/Private/Language/locallang.xlf:dummy" />', + '', + ], + ]; } /** * @test + * @dataProvider renderReturnsStringInExtbaseContextDataProvider */ - public function renderReturnsNullOnInvalidExtension(): void + public function renderReturnsStringInExtbaseContext(string $template, string $expected): void { $this->setUpBackendUserFromFixture(1); $view = new StandaloneView(); - $view->setTemplateSource('<f:translate key="LLL:EXT:invalid/Resources/Private/Language/locallang.xlf:dummy" />'); - self::assertNull($view->render()); + $view->setTemplateSource($template); + $extbaseRequestParameters = new ExtbaseRequestParameters(); + $extbaseRequestParameters->setControllerExtensionName('backend'); + $extbaseRequest = (new Request())->withAttribute('extbase', $extbaseRequestParameters); + $view->getRenderingContext()->setRequest($extbaseRequest); + self::assertSame($expected, $view->render()); } } diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/ResourceViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/ResourceViewHelperTest.php index 0d1099a8e4af..66598bcb87c6 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/ResourceViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/ResourceViewHelperTest.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers\Uri; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Fluid\View\StandaloneView; @@ -29,28 +30,78 @@ class ResourceViewHelperTest extends FunctionalTestCase */ protected $initializeDatabase = false; + /** + * @test + */ + public function renderingFailsWithNonExtSyntaxWithoutExtensionNameWithPsr7Request() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionCode(1639672666); + $view = new StandaloneView(); + $view->getRenderingContext()->setRequest(new ServerRequest()); + $view->setTemplateSource('<f:uri.resource path="Icons/Extension.svg" />'); + $view->render(); + } + + /** + * @test + */ + public function renderingFailsWhenExtensionNameNotSetInExtbaseRequest(): void + { + $view = new StandaloneView(); + $view->setTemplateSource('<f:uri.resource path="Icons/Extension.svg" />'); + $this->expectException(\RuntimeException::class); + $this->expectExceptionCode(1640097205); + $view->render(); + } + + public function renderWithoutRequestDataProvider(): \Generator + { + yield 'render returns URI using UpperCamelCase extensionName' => [ + '<f:uri.resource path="Icons/Extension.svg" extensionName="Core" />', + 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', + ]; + yield 'render returns URI using extension key as extensionName' => [ + '<f:uri.resource path="Icons/Extension.svg" extensionName="core" />', + 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', + ]; + yield 'render returns URI using EXT: syntax' => [ + '<f:uri.resource path="EXT:core/Resources/Public/Icons/Extension.svg" />', + 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', + ]; + } + + /** + * @test + * @dataProvider renderWithoutRequestDataProvider + */ + public function render(string $template, string $expected): void + { + $view = new StandaloneView(); + // Make sure rendering context has no (extbase) request + $view->getRenderingContext()->setRequest(null); + $view->setTemplateSource($template); + self::assertEquals($expected, $view->render()); + } + public function renderWithExtbaseRequestDataProvider(): \Generator { yield 'render returns URI using extensionName from Extbase Request' => [ '<f:uri.resource path="Icons/Extension.svg" />', 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', ]; - yield 'render gracefully trims leading slashes from path' => [ '<f:uri.resource path="/Icons/Extension.svg" />', 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', ]; - yield 'render returns URI using UpperCamelCase extensionName' => [ '<f:uri.resource path="Icons/Extension.svg" extensionName="Core" />', 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', ]; - yield 'render returns URI using extension key as extensionName' => [ '<f:uri.resource path="Icons/Extension.svg" extensionName="core" />', 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', ]; - yield 'render returns URI using EXT: syntax' => [ '<f:uri.resource path="EXT:core/Resources/Public/Icons/Extension.svg" />', 'typo3/sysext/core/Resources/Public/Icons/Extension.svg', @@ -69,19 +120,5 @@ class ResourceViewHelperTest extends FunctionalTestCase $extbaseRequestParameters->setControllerExtensionName('Core'); $extbaseRequest = (new Request())->withAttribute('extbase', $extbaseRequestParameters); $view->getRenderingContext()->setRequest($extbaseRequest); - self::assertEquals($expected, $view->render()); - } - - /** - * @test - */ - public function renderingFailsWhenExtensionNameNotSetInRequest(): void - { - $view = new StandaloneView(); - $view->setTemplateSource('<f:uri.resource path="Icons/Extension.svg" />'); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionCode(1640097205); - $view->render(); } } -- GitLab