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