first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,418 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
/**
* An abstract base class for Controllers
*/
abstract class AbstractController implements ControllerInterface
{
/**
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
*/
protected $signalSlotDispatcher;
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
*/
protected $uriBuilder;
/**
* @var string Key of the extension this controller belongs to
*/
protected $extensionName;
/**
* Contains the settings of the current extension
*
* @var array
*/
protected $settings;
/**
* The current request.
*
* @var \TYPO3\CMS\Extbase\Mvc\RequestInterface
*/
protected $request;
/**
* The response which will be returned by this action controller
*
* @var \TYPO3\CMS\Extbase\Mvc\ResponseInterface
*/
protected $response;
/**
* @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver
*/
protected $validatorResolver;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments Arguments passed to the controller
*/
protected $arguments;
/**
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
*/
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
{
$this->signalSlotDispatcher = $signalSlotDispatcher;
}
/**
* @param \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver
*/
public function injectValidatorResolver(\TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver)
{
$this->validatorResolver = $validatorResolver;
}
/**
* An array of supported request types. By default only web requests are supported.
* Modify or replace this array if your specific controller supports certain
* (additional) request types.
*
* @var array
*/
protected $supportedRequestTypes = [\TYPO3\CMS\Extbase\Mvc\Request::class];
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
*/
protected $controllerContext;
/**
* @return ControllerContext
*/
public function getControllerContext()
{
return $this->controllerContext;
}
/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* Constructs the controller.
*/
public function __construct()
{
$className = static::class;
$classNameParts = explode('\\', $className, 4);
// Skip vendor and product name for core classes
if (strpos($className, 'TYPO3\\CMS\\') === 0) {
$this->extensionName = $classNameParts[2];
} else {
$this->extensionName = $classNameParts[1];
}
}
/**
* @param ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
$this->settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS);
}
/**
* Injects the object manager
*
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
$this->arguments = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\Arguments::class);
}
/**
* Creates a Message object and adds it to the FlashMessageQueue.
*
* @param string $messageBody The message
* @param string $messageTitle Optional message title
* @param int $severity Optional severity, must be one of \TYPO3\CMS\Core\Messaging\FlashMessage constants
* @param bool $storeInSession Optional, defines whether the message should be stored in the session (default) or not
* @throws \InvalidArgumentException if the message body is no string
* @see \TYPO3\CMS\Core\Messaging\FlashMessage
*/
public function addFlashMessage($messageBody, $messageTitle = '', $severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::OK, $storeInSession = true)
{
if (!is_string($messageBody)) {
throw new \InvalidArgumentException('The message body must be of type string, "' . gettype($messageBody) . '" given.', 1243258395);
}
/* @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
$flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\Messaging\FlashMessage::class,
(string)$messageBody,
(string)$messageTitle,
$severity,
$storeInSession
);
$this->controllerContext->getFlashMessageQueue()->enqueue($flashMessage);
}
/**
* Checks if the current request type is supported by the controller.
*
* If your controller only supports certain request types, either
* replace / modify the supportedRequestTypes property or override this
* method.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
* @return bool TRUE if this request type is supported, otherwise FALSE
*/
public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request)
{
foreach ($this->supportedRequestTypes as $supportedRequestType) {
if ($request instanceof $supportedRequestType) {
return true;
}
}
return false;
}
/**
* Processes a general request. The result can be returned by altering the given response.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
* @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
* @throws UnsupportedRequestTypeException if the controller doesn't support the current request type
*/
public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
{
if (!$this->canProcessRequest($request)) {
throw new UnsupportedRequestTypeException(static::class . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701132);
}
if ($response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response && $request instanceof WebRequest) {
$response->setRequest($request);
}
$this->request = $request;
$this->request->setDispatched(true);
$this->response = $response;
$this->uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
$this->uriBuilder->setRequest($request);
$this->initializeControllerArgumentsBaseValidators();
$this->mapRequestArgumentsToControllerArguments();
$this->controllerContext = $this->buildControllerContext();
}
/**
* Initialize the controller context
*
* @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext ControllerContext to be passed to the view
*/
protected function buildControllerContext()
{
/** @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext */
$controllerContext = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext::class);
$controllerContext->setRequest($this->request);
$controllerContext->setResponse($this->response);
if ($this->arguments !== null) {
$controllerContext->setArguments($this->arguments);
}
$controllerContext->setUriBuilder($this->uriBuilder);
return $controllerContext;
}
/**
* Forwards the request to another action and / or controller.
*
* Request is directly transferred to the other action / controller
* without the need for a new request.
*
* @param string $actionName Name of the action to forward to
* @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
* @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
* @param array|null $arguments Arguments to pass to the target action
* @throws StopActionException
* @see redirect()
*/
public function forward($actionName, $controllerName = null, $extensionName = null, array $arguments = null)
{
$this->request->setDispatched(false);
if ($this->request instanceof WebRequest) {
$this->request->setControllerActionName($actionName);
if ($controllerName !== null) {
$this->request->setControllerName($controllerName);
}
if ($extensionName !== null) {
$this->request->setControllerExtensionName($extensionName);
}
}
if ($arguments !== null) {
$this->request->setArguments($arguments);
}
throw new StopActionException('forward', 1476045801);
}
/**
* Redirects the request to another action and / or controller.
*
* Redirect will be sent to the client which then performs another request to the new URI.
*
* NOTE: This method only supports web requests and will thrown an exception
* if used with other request types.
*
* @param string $actionName Name of the action to forward to
* @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
* @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
* @param array|null $arguments Arguments to pass to the target action
* @param int|null $pageUid Target page uid. If NULL, the current page uid is used
* @param int $delay (optional) The delay in seconds. Default is no delay.
* @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
* @throws UnsupportedRequestTypeException If the request is not a web request
* @throws StopActionException
* @see forward()
*/
protected function redirect($actionName, $controllerName = null, $extensionName = null, array $arguments = null, $pageUid = null, $delay = 0, $statusCode = 303)
{
if (!$this->request instanceof WebRequest) {
throw new UnsupportedRequestTypeException('redirect() only supports web requests.', 1220539734);
}
if ($controllerName === null) {
$controllerName = $this->request->getControllerName();
}
$this->uriBuilder->reset()->setTargetPageUid($pageUid)->setCreateAbsoluteUri(true);
if (\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SSL')) {
$this->uriBuilder->setAbsoluteUriScheme('https');
}
$uri = $this->uriBuilder->uriFor($actionName, $arguments, $controllerName, $extensionName);
$this->redirectToUri($uri, $delay, $statusCode);
}
/**
* Redirects the web request to another uri.
*
* NOTE: This method only supports web requests and will thrown an exception if used with other request types.
*
* @param mixed $uri A string representation of a URI
* @param int $delay (optional) The delay in seconds. Default is no delay.
* @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
* @throws UnsupportedRequestTypeException If the request is not a web request
* @throws StopActionException
*/
protected function redirectToUri($uri, $delay = 0, $statusCode = 303)
{
if (!$this->request instanceof WebRequest) {
throw new UnsupportedRequestTypeException('redirect() only supports web requests.', 1220539735);
}
$this->objectManager->get(\TYPO3\CMS\Extbase\Service\CacheService::class)->clearCachesOfRegisteredPageIds();
$uri = $this->addBaseUriIfNecessary($uri);
$escapedUri = htmlentities($uri, ENT_QUOTES, 'utf-8');
$this->response->setContent('<html><head><meta http-equiv="refresh" content="' . (int)$delay . ';url=' . $escapedUri . '"/></head></html>');
if ($this->response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
$this->response->setStatus($statusCode);
$this->response->setHeader('Location', (string)$uri);
}
// Avoid caching the plugin when we issue a redirect response
// This means that even when an action is configured as cachable
// we avoid the plugin to be cached, but keep the page cache untouched
$contentObject = $this->configurationManager->getContentObject();
if ($contentObject->getUserObjectType() === \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::OBJECTTYPE_USER) {
$contentObject->convertToUserIntObject();
}
throw new StopActionException('redirectToUri', 1476045828);
}
/**
* Adds the base uri if not already in place.
*
* @param string $uri The URI
* @return string
*/
protected function addBaseUriIfNecessary($uri)
{
return \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl((string)$uri);
}
/**
* Sends the specified HTTP status immediately.
*
* NOTE: This method only supports web requests and will thrown an exception if used with other request types.
*
* @param int $statusCode The HTTP status code
* @param string $statusMessage A custom HTTP status message
* @param string $content Body content which further explains the status
* @throws UnsupportedRequestTypeException If the request is not a web request
* @throws StopActionException
*/
public function throwStatus($statusCode, $statusMessage = null, $content = null)
{
if (!$this->request instanceof WebRequest) {
throw new UnsupportedRequestTypeException('throwStatus() only supports web requests.', 1220539739);
}
if ($this->response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
$this->response->setStatus($statusCode, $statusMessage);
if ($content === null) {
$content = $this->response->getStatus();
}
}
$this->response->setContent($content);
throw new StopActionException('throwStatus', 1476045871);
}
/**
* Collects the base validators which were defined for the data type of each
* controller argument and adds them to the argument's validator chain.
*/
public function initializeControllerArgumentsBaseValidators()
{
/** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
foreach ($this->arguments as $argument) {
$validator = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
if ($validator !== null) {
$argument->setValidator($validator);
}
}
}
/**
* Maps arguments delivered by the request object to the local controller arguments.
*
* @throws Exception\RequiredArgumentMissingException
*/
protected function mapRequestArgumentsToControllerArguments()
{
/** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
foreach ($this->arguments as $argument) {
$argumentName = $argument->getName();
if ($this->request->hasArgument($argumentName)) {
$argument->setValue($this->request->getArgument($argumentName));
} elseif ($argument->isRequired()) {
throw new \TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException('Required argument "' . $argumentName . '" is not set for ' . $this->request->getControllerObjectName() . '->' . $this->request->getControllerActionName() . '.', 1298012500);
}
}
}
}
@@ -0,0 +1,635 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
use TYPO3Fluid\Fluid\View\TemplateView;
/**
* A multi action controller. This is by far the most common base class for Controllers.
*/
class ActionController extends AbstractController
{
/**
* @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
*/
protected $reflectionService;
/**
* @var \TYPO3\CMS\Extbase\Service\CacheService
*/
protected $cacheService;
/**
* The current view, as resolved by resolveView()
*
* @var ViewInterface
*/
protected $view;
/**
* @var string
*/
protected $namespacesViewObjectNamePattern = '@vendor\@extension\View\@controller\@action@format';
/**
* A list of formats and object names of the views which should render them.
*
* Example:
*
* array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
*
* @var array
*/
protected $viewFormatToObjectNameMap = [];
/**
* The default view object to use if none of the resolved views can render
* a response for the current request.
*
* @var string
*/
protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
/**
* Name of the action method
*
* @var string
*/
protected $actionMethodName = 'indexAction';
/**
* Name of the special error action method which is called in case of errors
*
* @var string
*/
protected $errorMethodName = 'errorAction';
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
*/
protected $mvcPropertyMappingConfigurationService;
/**
* The current request.
*
* @var \TYPO3\CMS\Extbase\Mvc\Request
*/
protected $request;
/**
* The response which will be returned by this action controller
*
* @var \TYPO3\CMS\Extbase\Mvc\Response
*/
protected $response;
/**
* @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
*/
public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}
/**
* @param \TYPO3\CMS\Extbase\Service\CacheService $cacheService
*/
public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
{
$this->cacheService = $cacheService;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService
*/
public function injectMvcPropertyMappingConfigurationService(\TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService)
{
$this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
}
/**
* Handles a request. The result output is returned by altering the given response.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
* @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
*
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
*/
public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
{
if (!$this->canProcessRequest($request)) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(static::class . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701131);
}
if ($response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response && $request instanceof WebRequest) {
$response->setRequest($request);
}
$this->request = $request;
$this->request->setDispatched(true);
$this->response = $response;
$this->uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
$this->uriBuilder->setRequest($request);
$this->actionMethodName = $this->resolveActionMethodName();
$this->initializeActionMethodArguments();
$this->initializeActionMethodValidators();
$this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
$this->initializeAction();
$actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
if (method_exists($this, $actionInitializationMethodName)) {
call_user_func([$this, $actionInitializationMethodName]);
}
$this->mapRequestArgumentsToControllerArguments();
$this->controllerContext = $this->buildControllerContext();
$this->view = $this->resolveView();
if ($this->view !== null) {
$this->initializeView($this->view);
}
$this->callActionMethod();
$this->renderAssetsForRequest($request);
}
/**
* Method which initializes assets that should be attached to the response
* for the given $request, which contains parameters that an override can
* use to determine which assets to add via PageRenderer.
*
* This default implementation will attempt to render the sections "HeaderAssets"
* and "FooterAssets" from the template that is being rendered, inserting the
* rendered content into either page header or footer, as appropriate. Both
* sections are optional and can be used one or both in combination.
*
* You can add assets with this method without worrying about duplicates, if
* for example you do this in a plugin that gets used multiple time on a page.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
*/
protected function renderAssetsForRequest($request)
{
if (!$this->view instanceof TemplateView) {
// Only TemplateView (from Fluid engine, so this includes all TYPO3 Views based
// on TYPO3's AbstractTemplateView) supports renderSection(). The method is not
// declared on ViewInterface - so we must assert a specific class. We silently skip
// asset processing if the View doesn't match, so we don't risk breaking custom Views.
return;
}
$pageRenderer = $this->objectManager->get(PageRenderer::class);
$variables = ['request' => $request, 'arguments' => $this->arguments];
$headerAssets = $this->view->renderSection('HeaderAssets', $variables, true);
$footerAssets = $this->view->renderSection('FooterAssets', $variables, true);
if (!empty(trim($headerAssets))) {
$pageRenderer->addHeaderData($headerAssets);
}
if (!empty(trim($footerAssets))) {
$pageRenderer->addFooterData($footerAssets);
}
}
/**
* Implementation of the arguments initilization in the action controller:
* Automatically registers arguments of the current action
*
* Don't override this method - use initializeAction() instead.
*
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
* @see initializeArguments()
*/
protected function initializeActionMethodArguments()
{
$methodParameters = $this->reflectionService
->getClassSchema(static::class)
->getMethod($this->actionMethodName)['params'] ?? [];
foreach ($methodParameters as $parameterName => $parameterInfo) {
$dataType = null;
if (isset($parameterInfo['type'])) {
$dataType = $parameterInfo['type'];
} elseif ($parameterInfo['array']) {
$dataType = 'array';
}
if ($dataType === null) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . static::class . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
}
$defaultValue = $parameterInfo['hasDefaultValue'] === true ? $parameterInfo['defaultValue'] : null;
$this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === false, $defaultValue);
}
}
/**
* Adds the needed validators to the Arguments:
*
* - Validators checking the data type from the @param annotation
* - Custom validators specified with validate annotations.
* - Model-based validators (validate annotations in the model)
* - Custom model validator classes
*/
protected function initializeActionMethodValidators()
{
if ($this->arguments->count() === 0) {
return;
}
$classSchema = $this->reflectionService->getClassSchema(static::class);
$ignoreValidationAnnotations = array_unique(array_flip(
$classSchema->getMethod($this->actionMethodName)['tags']['ignorevalidation'] ?? []
));
/** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
foreach ($this->arguments as $argument) {
if (isset($ignoreValidationAnnotations[$argument->getName()])) {
continue;
}
$validator = $this->objectManager->get(ConjunctionValidator::class);
$validatorDefinitions = $classSchema->getMethod($this->actionMethodName)['params'][$argument->getName()]['validators'] ?? [];
foreach ($validatorDefinitions as $validatorDefinition) {
/** @var ValidatorInterface $validatorInstance */
$validatorInstance = $this->objectManager->get(
$validatorDefinition['className'],
$validatorDefinition['options']
);
$validator->addValidator(
$validatorInstance
);
}
$baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
if ($baseValidatorConjunction->count() > 0) {
$validator->addValidator($baseValidatorConjunction);
}
$argument->setValidator($validator);
}
}
/**
* Resolves and checks the current action method name
*
* @return string Method name of the current action
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException if the action specified in the request object does not exist (and if there's no default action either).
*/
protected function resolveActionMethodName()
{
$actionMethodName = $this->request->getControllerActionName() . 'Action';
if (!method_exists($this, $actionMethodName)) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . static::class . '".', 1186669086);
}
return $actionMethodName;
}
/**
* Calls the specified action method and passes the arguments.
*
* If the action returns a string, it is appended to the content in the
* response object. If the action doesn't return anything and a valid
* view exists, the view is rendered automatically.
*/
protected function callActionMethod()
{
$preparedArguments = [];
/** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
foreach ($this->arguments as $argument) {
$preparedArguments[] = $argument->getValue();
}
$validationResult = $this->arguments->validate();
if (!$validationResult->hasErrors()) {
$this->emitBeforeCallActionMethodSignal($preparedArguments);
$actionResult = $this->{$this->actionMethodName}(...$preparedArguments);
} else {
$actionResult = $this->{$this->errorMethodName}();
}
if ($actionResult === null && $this->view instanceof ViewInterface) {
$this->response->appendContent($this->view->render());
} elseif (is_string($actionResult) && $actionResult !== '') {
$this->response->appendContent($actionResult);
} elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
$this->response->appendContent((string)$actionResult);
}
}
/**
* Emits a signal before the current action is called
*
* @param array $preparedArguments
*/
protected function emitBeforeCallActionMethodSignal(array $preparedArguments)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeCallActionMethod', [static::class, $this->actionMethodName, $preparedArguments]);
}
/**
* Prepares a view for the current action.
* By default, this method tries to locate a view with a name matching the current action.
*
* @return ViewInterface
*/
protected function resolveView()
{
$viewObjectName = $this->resolveViewObjectName();
if ($viewObjectName !== false) {
/** @var ViewInterface $view */
$view = $this->objectManager->get($viewObjectName);
$this->setViewConfiguration($view);
if ($view->canRender($this->controllerContext) === false) {
unset($view);
}
}
if (!isset($view) && $this->defaultViewObjectName != '') {
/** @var ViewInterface $view */
$view = $this->objectManager->get($this->defaultViewObjectName);
$this->setViewConfiguration($view);
if ($view->canRender($this->controllerContext) === false) {
unset($view);
}
}
if (!isset($view)) {
$view = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\View\NotFoundView::class);
$view->assign('errorMessage', 'No template was found. View could not be resolved for action "'
. $this->request->getControllerActionName() . '" in class "' . $this->request->getControllerObjectName() . '"');
}
$view->setControllerContext($this->controllerContext);
if (method_exists($view, 'injectSettings')) {
$view->injectSettings($this->settings);
}
$view->initializeView();
// In TYPO3.Flow, solved through Object Lifecycle methods, we need to call it explicitly
$view->assign('settings', $this->settings);
// same with settings injection.
return $view;
}
/**
* @param ViewInterface $view
*/
protected function setViewConfiguration(ViewInterface $view)
{
// Template Path Override
$extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
);
// set TemplateRootPaths
$viewFunctionName = 'setTemplateRootPaths';
if (method_exists($view, $viewFunctionName)) {
$setting = 'templateRootPaths';
$parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
// no need to bother if there is nothing to set
if ($parameter) {
$view->$viewFunctionName($parameter);
}
}
// set LayoutRootPaths
$viewFunctionName = 'setLayoutRootPaths';
if (method_exists($view, $viewFunctionName)) {
$setting = 'layoutRootPaths';
$parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
// no need to bother if there is nothing to set
if ($parameter) {
$view->$viewFunctionName($parameter);
}
}
// set PartialRootPaths
$viewFunctionName = 'setPartialRootPaths';
if (method_exists($view, $viewFunctionName)) {
$setting = 'partialRootPaths';
$parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
// no need to bother if there is nothing to set
if ($parameter) {
$view->$viewFunctionName($parameter);
}
}
}
/**
* Handles the path resolving for *rootPath(s)
*
* numerical arrays get ordered by key ascending
*
* @param array $extbaseFrameworkConfiguration
* @param string $setting parameter name from TypoScript
*
* @return array
*/
protected function getViewProperty($extbaseFrameworkConfiguration, $setting)
{
$values = [];
if (
!empty($extbaseFrameworkConfiguration['view'][$setting])
&& is_array($extbaseFrameworkConfiguration['view'][$setting])
) {
$values = $extbaseFrameworkConfiguration['view'][$setting];
}
return $values;
}
/**
* Determines the fully qualified view object name.
*
* @return mixed The fully qualified view object name or FALSE if no matching view could be found.
*/
protected function resolveViewObjectName()
{
$vendorName = $this->request->getControllerVendorName();
if ($vendorName === null) {
return false;
}
$possibleViewName = str_replace(
[
'@vendor',
'@extension',
'@controller',
'@action'
],
[
$vendorName,
$this->request->getControllerExtensionName(),
$this->request->getControllerName(),
ucfirst($this->request->getControllerActionName())
],
$this->namespacesViewObjectNamePattern
);
$format = $this->request->getFormat();
$viewObjectName = str_replace('@format', ucfirst($format), $possibleViewName);
if (class_exists($viewObjectName) === false) {
$viewObjectName = str_replace('@format', '', $possibleViewName);
}
if (isset($this->viewFormatToObjectNameMap[$format]) && class_exists($viewObjectName) === false) {
$viewObjectName = $this->viewFormatToObjectNameMap[$format];
}
return class_exists($viewObjectName) ? $viewObjectName : false;
}
/**
* Initializes the view before invoking an action method.
*
* Override this method to solve assign variables common for all actions
* or prepare the view in another way before the action is called.
*
* @param ViewInterface $view The view to be initialized
*/
protected function initializeView(ViewInterface $view)
{
}
/**
* Initializes the controller before invoking an action method.
*
* Override this method to solve tasks which all actions have in
* common.
*/
protected function initializeAction()
{
}
/**
* A special action which is called if the originally intended action could
* not be called, for example if the arguments were not valid.
*
* The default implementation sets a flash message, request errors and forwards back
* to the originating action. This is suitable for most actions dealing with form input.
*
* We clear the page cache by default on an error as well, as we need to make sure the
* data is re-evaluated when the user changes something.
*
* @return string
*/
protected function errorAction()
{
$this->clearCacheOnError();
$this->addErrorFlashMessage();
$this->forwardToReferringRequest();
return $this->getFlattenedValidationErrorMessage();
}
/**
* Clear cache of current page on error. Needed because we want a re-evaluation of the data.
* Better would be just do delete the cache for the error action, but that is not possible right now.
*/
protected function clearCacheOnError()
{
$extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
if (isset($GLOBALS['TSFE'])) {
$pageUid = $GLOBALS['TSFE']->id;
$this->cacheService->clearPageCache([$pageUid]);
}
}
}
/**
* If an error occurred during this request, this adds a flash message describing the error to the flash
* message container.
*/
protected function addErrorFlashMessage()
{
$errorFlashMessage = $this->getErrorFlashMessage();
if ($errorFlashMessage !== false) {
$this->addFlashMessage($errorFlashMessage, '', FlashMessage::ERROR);
}
}
/**
* A template method for displaying custom error flash messages, or to
* display no flash message at all on errors. Override this to customize
* the flash message in your action controller.
*
* @return string The flash message or FALSE if no flash message should be set
*/
protected function getErrorFlashMessage()
{
return 'An error occurred while trying to call ' . static::class . '->' . $this->actionMethodName . '()';
}
/**
* If information on the request before the current request was sent, this method forwards back
* to the originating request. This effectively ends processing of the current request, so do not
* call this method before you have finished the necessary business logic!
*
* @throws StopActionException
*/
protected function forwardToReferringRequest()
{
$referringRequest = $this->request->getReferringRequest();
if ($referringRequest !== null) {
$originalRequest = clone $this->request;
$this->request->setOriginalRequest($originalRequest);
$this->request->setOriginalRequestMappingResults($this->arguments->validate());
$this->forward(
$referringRequest->getControllerActionName(),
$referringRequest->getControllerName(),
$referringRequest->getControllerExtensionName(),
$referringRequest->getArguments()
);
}
}
/**
* Returns a string with a basic error message about validation failure.
* We may add all validation error messages to a log file in the future,
* but for security reasons (@see #54074) we do not return these here.
*
* @return string
*/
protected function getFlattenedValidationErrorMessage()
{
$outputMessage = 'Validation failed while trying to call ' . static::class . '->' . $this->actionMethodName . '().' . PHP_EOL;
return $outputMessage;
}
/**
* Returns a map of action method names and their parameters.
*
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*
* @return array Array of method parameters by action name
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
public static function getActionMethodParameters($objectManager)
{
trigger_error(
'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
E_USER_DEPRECATED
);
$reflectionService = $objectManager->get(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
$result = [];
$className = get_called_class();
$methodNames = get_class_methods($className);
foreach ($methodNames as $methodName) {
if (strlen($methodName) > 6 && strpos($methodName, 'Action', strlen($methodName) - 6) !== false) {
$result[$methodName] = $reflectionService->getMethodParameters($className, $methodName);
}
}
return $result;
}
}
@@ -0,0 +1,348 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Extbase\Error\Result;
use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
/**
* A controller argument
*/
class Argument
{
/**
* @var \TYPO3\CMS\Extbase\Property\PropertyMapper
*/
protected $propertyMapper;
/**
* @var MvcPropertyMappingConfiguration
*/
protected $propertyMappingConfiguration;
/**
* Name of this argument
*
* @var string
*/
protected $name = '';
/**
* Short name of this argument
*
* @var string
*/
protected $shortName;
/**
* Data type of this argument's value
*
* @var string
*/
protected $dataType;
/**
* TRUE if this argument is required
*
* @var bool
*/
protected $isRequired = false;
/**
* Actual value of this argument
*
* @var mixed
*/
protected $value;
/**
* Default value. Used if argument is optional.
*
* @var mixed
*/
protected $defaultValue;
/**
* A custom validator, used supplementary to the base validation
*
* @var \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface
*/
protected $validator;
/**
* The validation results. This can be asked if the argument has errors.
*
* @var \TYPO3\CMS\Extbase\Error\Result
*/
protected $validationResults;
/**
* @var bool
*/
private $hasBeenValidated = false;
/**
* @param \TYPO3\CMS\Extbase\Property\PropertyMapper $propertyMapper
*/
public function injectPropertyMapper(\TYPO3\CMS\Extbase\Property\PropertyMapper $propertyMapper)
{
$this->propertyMapper = $propertyMapper;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfiguration $propertyMappingConfiguration
*/
public function injectPropertyMappingConfiguration(MvcPropertyMappingConfiguration $propertyMappingConfiguration)
{
$this->propertyMappingConfiguration = $propertyMappingConfiguration;
}
/**
* Constructs this controller argument
*
* @param string $name Name of this argument
* @param string $dataType The data type of this argument
* @throws \InvalidArgumentException if $name is not a string or empty
*/
public function __construct($name, $dataType)
{
if (!is_string($name)) {
throw new \InvalidArgumentException('$name must be of type string, ' . gettype($name) . ' given.', 1187951688);
}
if ($name === '') {
throw new \InvalidArgumentException('$name must be a non-empty string.', 1232551853);
}
$this->name = $name;
$this->dataType = TypeHandlingUtility::normalizeType($dataType);
$this->validationResults = new Result();
}
/**
* Returns the name of this argument
*
* @return string This argument's name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the short name of this argument.
*
* @param string $shortName A "short name" - a single character
* @throws \InvalidArgumentException if $shortName is not a character
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Argument $this
*/
public function setShortName($shortName)
{
if ($shortName !== null && (!is_string($shortName) || strlen($shortName) !== 1)) {
throw new \InvalidArgumentException('$shortName must be a single character or NULL', 1195824959);
}
$this->shortName = $shortName;
return $this;
}
/**
* Returns the short name of this argument
*
* @return string This argument's short name
*/
public function getShortName()
{
return $this->shortName;
}
/**
* Returns the data type of this argument's value
*
* @return string The data type
*/
public function getDataType()
{
return $this->dataType;
}
/**
* Marks this argument to be required
*
* @param bool $required TRUE if this argument should be required
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Argument $this
*/
public function setRequired($required)
{
$this->isRequired = (bool)$required;
return $this;
}
/**
* Returns TRUE if this argument is required
*
* @return bool TRUE if this argument is required
*/
public function isRequired()
{
return $this->isRequired;
}
/**
* Sets the default value of the argument
*
* @param mixed $defaultValue Default value
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Argument $this
*/
public function setDefaultValue($defaultValue)
{
$this->defaultValue = $defaultValue;
return $this;
}
/**
* Returns the default value of this argument
*
* @return mixed The default value
*/
public function getDefaultValue()
{
return $this->defaultValue;
}
/**
* Sets a custom validator which is used supplementary to the base validation
*
* @param \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $validator The actual validator object
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Argument Returns $this (used for fluent interface)
*/
public function setValidator(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $validator)
{
$this->validator = $validator;
return $this;
}
/**
* Returns the set validator
*
* @return \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface The set validator, NULL if none was set
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the value of this argument.
*
* @param mixed $rawValue The value of this argument
*
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Argument
* @throws \TYPO3\CMS\Extbase\Property\Exception
*/
public function setValue($rawValue)
{
if ($rawValue === null) {
$this->value = null;
return $this;
}
if (is_object($rawValue) && $rawValue instanceof $this->dataType) {
$this->value = $rawValue;
return $this;
}
try {
$this->value = $this->propertyMapper->convert($rawValue, $this->dataType, $this->propertyMappingConfiguration);
} catch (TargetNotFoundException $e) {
// for optional arguments no exeption is thrown.
if ($this->isRequired()) {
throw $e;
}
}
$this->validationResults->merge($this->propertyMapper->getMessages());
return $this;
}
/**
* Returns the value of this argument
*
* @return mixed The value of this argument - if none was set, NULL is returned
*/
public function getValue()
{
if ($this->value === null) {
return $this->defaultValue;
}
return $this->value;
}
/**
* Return the Property Mapping Configuration used for this argument; can be used by the initialize*action to modify the Property Mapping.
*
* @return \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfiguration
*/
public function getPropertyMappingConfiguration()
{
return $this->propertyMappingConfiguration;
}
/**
* @return bool TRUE if the argument is valid, FALSE otherwise
*/
public function isValid(): bool
{
return !$this->validate()->hasErrors();
}
/**
* @return \TYPO3\CMS\Extbase\Error\Result Validation errors which have occurred.
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
public function getValidationResults()
{
trigger_error(
'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
E_USER_DEPRECATED
);
return $this->validate();
}
/**
* Returns a string representation of this argument's value
*
* @return string
*/
public function __toString()
{
return (string)$this->value;
}
/**
* @return \TYPO3\CMS\Extbase\Error\Result
*/
public function validate(): \TYPO3\CMS\Extbase\Error\Result
{
if ($this->hasBeenValidated) {
return $this->validationResults;
}
if ($this->validator !== null) {
$validationMessages = $this->validator->validate($this->value);
$this->validationResults->merge($validationMessages);
}
$this->hasBeenValidated = true;
return $this->validationResults;
}
}
@@ -0,0 +1,307 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* 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!
*/
/**
* A composite of controller arguments
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class Arguments extends \ArrayObject
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var array Names of the arguments contained by this object
*/
protected $argumentNames = [];
/**
* @var array
*/
protected $argumentShortNames = [];
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Constructor. If this one is removed, reflection breaks.
*/
public function __construct()
{
parent::__construct();
}
/**
* Adds or replaces the argument specified by $value. The argument's name is taken from the
* argument object itself, therefore the $offset does not have any meaning in this context.
*
* @param mixed $offset Offset - not used here
* @param mixed $value The argument
* @throws \InvalidArgumentException if the argument is not a valid Controller Argument object
*/
public function offsetSet($offset, $value)
{
if (!$value instanceof Argument) {
throw new \InvalidArgumentException('Controller arguments must be valid TYPO3\\CMS\\Extbase\\Mvc\\Controller\\Argument objects.', 1187953786);
}
$argumentName = $value->getName();
parent::offsetSet($argumentName, $value);
$this->argumentNames[$argumentName] = true;
}
/**
* Sets an argument, aliased to offsetSet()
*
* @param mixed $value The value
* @throws \InvalidArgumentException if the argument is not a valid Controller Argument object
*/
public function append($value)
{
if (!$value instanceof Argument) {
throw new \InvalidArgumentException('Controller arguments must be valid TYPO3\\CMS\\Extbase\\Mvc\\Controller\\Argument objects.', 1187953787);
}
$this->offsetSet(null, $value);
}
/**
* Unsets an argument
*
* @param mixed $offset Offset
*/
public function offsetUnset($offset)
{
$translatedOffset = $this->translateToLongArgumentName($offset);
parent::offsetUnset($translatedOffset);
unset($this->argumentNames[$translatedOffset]);
if ($offset != $translatedOffset) {
unset($this->argumentShortNames[$offset]);
}
}
/**
* Returns whether the requested index exists
*
* @param mixed $offset Offset
* @return bool
*/
public function offsetExists($offset)
{
$translatedOffset = $this->translateToLongArgumentName($offset);
return parent::offsetExists($translatedOffset);
}
/**
* Returns the value at the specified index
*
* @param mixed $offset Offset
* @return Argument The requested argument object
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if the argument does not exist
*/
public function offsetGet($offset)
{
$translatedOffset = $this->translateToLongArgumentName($offset);
if ($translatedOffset === '') {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException('The argument "' . $offset . '" does not exist.', 1216909923);
}
return parent::offsetGet($translatedOffset);
}
/**
* Creates, adds and returns a new controller argument to this composite object.
* If an argument with the same name exists already, it will be replaced by the
* new argument object.
*
* @param string $name Name of the argument
* @param string $dataType Name of one of the built-in data types
* @param bool $isRequired TRUE if this argument should be marked as required
* @param mixed $defaultValue Default value of the argument. Only makes sense if $isRequired==FALSE
* @return Argument The new argument
*/
public function addNewArgument($name, $dataType = 'Text', $isRequired = false, $defaultValue = null)
{
/** @var Argument $argument */
$argument = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\Argument::class, $name, $dataType);
$argument->setRequired($isRequired);
$argument->setDefaultValue($defaultValue);
$this->addArgument($argument);
return $argument;
}
/**
* Adds the specified controller argument to this composite object.
* If an argument with the same name exists already, it will be replaced by the
* new argument object.
*
* Note that the argument will be cloned, not referenced.
*
* @param Argument $argument The argument to add
*/
public function addArgument(Argument $argument)
{
$this->offsetSet(null, $argument);
}
/**
* Returns an argument specified by name
*
* @param string $argumentName Name of the argument to retrieve
* @return Argument
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
*/
public function getArgument($argumentName)
{
if (!$this->offsetExists($argumentName)) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException('An argument "' . $argumentName . '" does not exist.', 1195815178);
}
return $this->offsetGet($argumentName);
}
/**
* Checks if an argument with the specified name exists
*
* @param string $argumentName Name of the argument to check for
* @return bool TRUE if such an argument exists, otherwise FALSE
* @see offsetExists()
*/
public function hasArgument($argumentName)
{
return $this->offsetExists($argumentName);
}
/**
* Returns the names of all arguments contained in this object
*
* @return array Argument names
*/
public function getArgumentNames()
{
return array_keys($this->argumentNames);
}
/**
* Returns the short names of all arguments contained in this object that have one.
*
* @return array Argument short names
*/
public function getArgumentShortNames()
{
$argumentShortNames = [];
/** @var Argument $argument */
foreach ($this as $argument) {
$argumentShortNames[$argument->getShortName()] = true;
}
return array_keys($argumentShortNames);
}
/**
* Magic setter method for the argument values. Each argument
* value can be set by just calling the setArgumentName() method.
*
* @param string $methodName Name of the method
* @param array $arguments Method arguments
* @throws \LogicException
*/
public function __call($methodName, array $arguments)
{
if (strpos($methodName, 'set') !== 0) {
throw new \LogicException('Unknown method "' . $methodName . '".', 1210858451);
}
$firstLowerCaseArgumentName = $this->translateToLongArgumentName(strtolower($methodName[3]) . substr($methodName, 4));
$firstUpperCaseArgumentName = $this->translateToLongArgumentName(ucfirst(substr($methodName, 3)));
if (in_array($firstLowerCaseArgumentName, $this->getArgumentNames())) {
$argument = parent::offsetGet($firstLowerCaseArgumentName);
$argument->setValue($arguments[0]);
} elseif (in_array($firstUpperCaseArgumentName, $this->getArgumentNames())) {
$argument = parent::offsetGet($firstUpperCaseArgumentName);
$argument->setValue($arguments[0]);
}
}
/**
* Translates a short argument name to its corresponding long name. If the
* specified argument name is a real argument name already, it will be returned again.
*
* If an argument with the specified name or short name does not exist, an empty
* string is returned.
*
* @param string $argumentName argument name
* @return string long argument name or empty string
*/
protected function translateToLongArgumentName($argumentName)
{
if (in_array($argumentName, $this->getArgumentNames())) {
return $argumentName;
}
/** @var Argument $argument */
foreach ($this as $argument) {
if ($argumentName === $argument->getShortName()) {
return $argument->getName();
}
}
return '';
}
/**
* Remove all arguments and resets this object
*/
public function removeAll()
{
foreach ($this->argumentNames as $argumentName => $booleanValue) {
parent::offsetUnset($argumentName);
}
$this->argumentNames = [];
}
/**
* Get all property mapping / validation errors
*
* @return \TYPO3\CMS\Extbase\Error\Result
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
public function getValidationResults()
{
trigger_error(
'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
E_USER_DEPRECATED
);
return $this->validate();
}
/**
* @return \TYPO3\CMS\Extbase\Error\Result
*/
public function validate(): \TYPO3\CMS\Extbase\Error\Result
{
$results = new \TYPO3\CMS\Extbase\Error\Result();
/** @var Argument $argument */
foreach ($this as $argument) {
$argumentValidationResults = $argument->validate();
if ($argumentValidationResults === null) {
continue;
}
$results->forProperty($argument->getName())->merge($argumentValidationResults);
}
return $results;
}
}
@@ -0,0 +1,341 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition;
use TYPO3\CMS\Extbase\Mvc\Cli\ConsoleOutput;
use TYPO3\CMS\Extbase\Mvc\Cli\Request;
use TYPO3\CMS\Extbase\Mvc\Cli\Response;
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
use TYPO3\CMS\Extbase\Mvc\ResponseInterface;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
/**
* A controller which processes requests from the command line
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class CommandController implements CommandControllerInterface
{
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* @var Arguments
*/
protected $arguments;
/**
* Name of the command method
*
* @var string
*/
protected $commandMethodName = '';
/**
* Whether the command needs admin access to perform its job
*
* @var bool
*/
protected $requestAdminPermissions = false;
/**
* @var ReflectionService
*/
protected $reflectionService;
/**
* @var ObjectManagerInterface
*/
protected $objectManager;
/**
* @var ConsoleOutput
*/
protected $output;
public function __construct()
{
trigger_error('Extbase Command Controllers will be removed in TYPO3 v10.0. Migrate to symfony/console commands instead.', E_USER_DEPRECATED);
}
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
*/
public function injectReflectionService(ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}
/**
* Checks if the current request type is supported by the controller.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
* @return bool TRUE if this request type is supported, otherwise FALSE
*/
public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request)
{
return $request instanceof Request;
}
/**
* Processes a command line request.
*
* @param RequestInterface $request The request object
* @param ResponseInterface $response The response, modified by this handler
* @throws UnsupportedRequestTypeException if the controller doesn't support the current request type
*/
public function processRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$this->canProcessRequest($request)) {
throw new UnsupportedRequestTypeException(sprintf('%s only supports command line requests – requests of type "%s" given.', static::class, get_class($request)), 1300787096);
}
$this->request = $request;
$this->request->setDispatched(true);
$this->response = $response;
$this->commandMethodName = $this->resolveCommandMethodName();
$this->output = $this->objectManager->get(ConsoleOutput::class);
$this->arguments = $this->objectManager->get(Arguments::class);
$this->initializeCommandMethodArguments();
$this->mapRequestArgumentsToControllerArguments();
$this->initializeBackendAuthentication();
$this->callCommandMethod();
}
/**
* Resolves and checks the current command method name
*
* Note: The resulting command method name might not have the correct case, which isn't a problem because PHP is
* case insensitive regarding method names.
*
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException
* @return string Method name of the current command
* @throws NoSuchCommandException
*/
protected function resolveCommandMethodName()
{
$commandMethodName = $this->request->getControllerCommandName() . 'Command';
if (!is_callable([$this, $commandMethodName])) {
throw new NoSuchCommandException(sprintf('A command method "%s()" does not exist in controller "%s".', $commandMethodName, static::class), 1300902143);
}
return $commandMethodName;
}
/**
* Initializes the arguments array of this controller by creating an empty argument object for each of the
* method arguments found in the designated command method.
*
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
* @throws InvalidArgumentTypeException
*/
protected function initializeCommandMethodArguments()
{
$methodParameters = $this->reflectionService
->getClassSchema(static::class)
->getMethod($this->commandMethodName)['params'] ?? [];
foreach ($methodParameters as $parameterName => $parameterInfo) {
$dataType = null;
if (isset($parameterInfo['type'])) {
$dataType = $parameterInfo['type'];
} elseif ($parameterInfo['array']) {
$dataType = 'array';
}
if ($dataType === null) {
throw new InvalidArgumentTypeException(sprintf('The argument type for parameter $%s of method %s->%s() could not be detected.', $parameterName, static::class, $this->commandMethodName), 1306755296);
}
$defaultValue = ($parameterInfo['defaultValue'] ?? null);
$this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === false, $defaultValue);
}
}
/**
* Maps arguments delivered by the request object to the local controller arguments.
*/
protected function mapRequestArgumentsToControllerArguments()
{
/** @var Argument $argument */
foreach ($this->arguments as $argument) {
$argumentName = $argument->getName();
if ($this->request->hasArgument($argumentName)) {
$argument->setValue($this->request->getArgument($argumentName));
continue;
}
if (!$argument->isRequired()) {
continue;
}
$argumentValue = null;
$commandArgumentDefinition = $this->objectManager->get(CommandArgumentDefinition::class, $argumentName, true, null);
while ($argumentValue === null) {
$argumentValue = $this->output->ask(sprintf('<comment>Please specify the required argument "%s":</comment> ', $commandArgumentDefinition->getDashedName()));
}
$argument->setValue($argumentValue);
}
}
/**
* Initializes and ensures authenticated backend access
*/
protected function initializeBackendAuthentication()
{
$backendUserAuthentication = $this->getBackendUserAuthentication();
if ($backendUserAuthentication !== null) {
$backendUserAuthentication->backendCheckLogin();
}
}
/**
* Forwards the request to another command and / or CommandController.
*
* Request is directly transferred to the other command / controller
* without the need for a new request.
*
* @param string $commandName
* @param string $controllerObjectName
* @param array $arguments
* @throws StopActionException
*/
protected function forward($commandName, $controllerObjectName = null, array $arguments = [])
{
$this->request->setDispatched(false);
$this->request->setControllerCommandName($commandName);
if ($controllerObjectName !== null) {
$this->request->setControllerObjectName($controllerObjectName);
}
$this->request->setArguments($arguments);
$this->arguments->removeAll();
throw new StopActionException('forward', 1476107661);
}
/**
* Calls the specified command method and passes the arguments.
*
* If the command returns a string, it is appended to the content in the
* response object. If the command doesn't return anything and a valid
* view exists, the view is rendered automatically.
*/
protected function callCommandMethod()
{
$preparedArguments = [];
/** @var Argument $argument */
foreach ($this->arguments as $argument) {
$preparedArguments[] = $argument->getValue();
}
$commandResult = call_user_func_array([$this, $this->commandMethodName], $preparedArguments);
if (is_string($commandResult) && $commandResult !== '') {
$this->response->appendContent($commandResult);
} elseif (is_object($commandResult) && method_exists($commandResult, '__toString')) {
$this->response->appendContent((string)$commandResult);
}
}
/**
* Outputs specified text to the console window
* You can specify arguments that will be passed to the text via sprintf
*
* @see http://www.php.net/sprintf
* @param string $text Text to output
* @param array $arguments Optional arguments to use for sprintf
*/
protected function output($text, array $arguments = [])
{
$this->output->output($text, $arguments);
}
/**
* Outputs specified text to the console window and appends a line break
*
* @param string $text Text to output
* @param array $arguments Optional arguments to use for sprintf
* @see output()
*/
protected function outputLine($text = '', array $arguments = [])
{
$this->output->outputLine($text, $arguments);
}
/**
* Formats the given text to fit into MAXIMUM_LINE_LENGTH and outputs it to the
* console window
*
* @param string $text Text to output
* @param array $arguments Optional arguments to use for sprintf
* @param int $leftPadding The number of spaces to use for indentation
* @see outputLine()
*/
protected function outputFormatted($text = '', array $arguments = [], $leftPadding = 0)
{
$this->output->outputFormatted($text, $arguments, $leftPadding);
}
/**
* Exits the CLI through the dispatcher
* An exit status code can be specified @see http://www.php.net/exit
*
* @param int $exitCode Exit code to return on exit
* @throws StopActionException
*/
protected function quit($exitCode = 0)
{
$this->response->setExitCode($exitCode);
throw new StopActionException('quit', 1476107681);
}
/**
* Sends the response and exits the CLI without any further code execution
* Should be used for commands that flush code caches.
*
* @param int $exitCode Exit code to return on exit
*/
protected function sendAndExit($exitCode = 0)
{
$this->response->send();
exit($exitCode);
}
/**
* Returns the global BackendUserAuthentication object.
*
* @return BackendUserAuthentication|null
*/
protected function getBackendUserAuthentication()
{
return $GLOBALS['BE_USER'] ?? null;
}
}
@@ -0,0 +1,24 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* 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!
*/
/**
* Interface for command controllers
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
interface CommandControllerInterface extends \TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface
{
}
@@ -0,0 +1,164 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* 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!
*/
/**
* The controller context contains information from the controller
*/
class ControllerContext
{
/**
* @var \TYPO3\CMS\Extbase\Mvc\Request
*/
protected $request;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Response
*/
protected $response;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments
*/
protected $arguments;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
*/
protected $uriBuilder;
/**
* @var string
*/
protected $flashMessageQueueDefaultIdentifier;
/**
* @var \TYPO3\CMS\Core\Messaging\FlashMessageService
*/
protected $flashMessageService;
/**
* @var \TYPO3\CMS\Extbase\Service\ExtensionService
*/
protected $extensionService;
/**
* @param \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService
*/
public function injectFlashMessageService(\TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService)
{
$this->flashMessageService = $flashMessageService;
}
/**
* @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
*/
public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
{
$this->extensionService = $extensionService;
}
/**
* Set the request of the controller
*
* @param \TYPO3\CMS\Extbase\Mvc\Request $request
*/
public function setRequest(\TYPO3\CMS\Extbase\Mvc\Request $request)
{
$this->request = $request;
}
/**
* Get the request of the controller
*
* @return \TYPO3\CMS\Extbase\Mvc\Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Set the response of the controller
*
* @param \TYPO3\CMS\Extbase\Mvc\Response $response
*/
public function setResponse(\TYPO3\CMS\Extbase\Mvc\Response $response)
{
$this->response = $response;
}
/**
* Get the response of the controller
*
* @return \TYPO3\CMS\Extbase\Mvc\Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Set the arguments of the controller
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\Arguments $arguments
*/
public function setArguments(\TYPO3\CMS\Extbase\Mvc\Controller\Arguments $arguments)
{
$this->arguments = $arguments;
}
/**
* Get the arguments of the controller
*
* @return \TYPO3\CMS\Extbase\Mvc\Controller\Arguments
*/
public function getArguments()
{
return $this->arguments;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder $uriBuilder
*/
public function setUriBuilder(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder $uriBuilder)
{
$this->uriBuilder = $uriBuilder;
}
/**
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
*/
public function getUriBuilder()
{
return $this->uriBuilder;
}
/**
* @param string $identifier Queue-identifier
* @return \TYPO3\CMS\Core\Messaging\FlashMessageQueue
*/
public function getFlashMessageQueue($identifier = null)
{
if ($identifier === null) {
if ($this->flashMessageQueueDefaultIdentifier === null) {
// cache the default-identifier for performance-reasons
$this->flashMessageQueueDefaultIdentifier = 'extbase.flashmessages.' . $this->extensionService->getPluginNamespace($this->request->getControllerExtensionName(), $this->request->getPluginName());
}
$identifier = $this->flashMessageQueueDefaultIdentifier;
}
return $this->flashMessageService->getMessageQueueByIdentifier($identifier);
}
}
@@ -0,0 +1,38 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* 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!
*/
/**
* Interface for controllers
*/
interface ControllerInterface
{
/**
* Checks if the current request type is supported by the controller.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
* @return bool TRUE if this request type is supported, otherwise FALSE
*/
public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request);
/**
* Processes a general request. The result can be returned by altering the given response.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
* @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by the controller
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException if the controller doesn't support the current request type
*/
public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response);
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller\Exception;
/*
* 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!
*/
/**
* An "Invalid Argument Name" exception
*/
class RequiredArgumentMissingException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,55 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* 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!
*/
/**
* The default property mapping configuration is available
* inside the Argument-object.
*/
class MvcPropertyMappingConfiguration extends \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration
{
/**
* Allow creation of a certain sub property
*
* @param string $propertyPath
*/
public function allowCreationForSubProperty($propertyPath)
{
$this->forProperty($propertyPath)->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
}
/**
* Allow modification for a given property path
*
* @param string $propertyPath
*/
public function allowModificationForSubProperty($propertyPath)
{
$this->forProperty($propertyPath)->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, true);
}
/**
* Set the target type for a certain property. Especially useful
* if there is an object which has a nested object which is abstract,
* and you want to instanciate a concrete object instead.
*
* @param string $propertyPath
* @param string $targetType
*/
public function setTargetTypeForSubProperty($propertyPath, $targetType)
{
$this->forProperty($propertyPath)->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_TARGET_TYPE, $targetType);
}
}
@@ -0,0 +1,178 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Controller;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Error\Http\BadRequestException;
use TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException;
use TYPO3\CMS\Extbase\Security\Exception\InvalidHashException;
/**
* This is a Service which can generate a request hash and check whether the currently given arguments
* fit to the request hash.
*
* It is used when forms are generated and submitted:
* After a form has been generated, the method "generateRequestHash" is called with the names of all form fields.
* It cleans up the array of form fields and creates another representation of it, which is then serialized and hashed.
*
* Both serialized form field list and the added hash form the request hash, which will be sent over the wire (as an argument __hmac).
*
* On the validation side, the validation happens in two steps:
* 1) Check if the request hash is consistent (the hash value fits to the serialized string)
* 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
*
* Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
*
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class MvcPropertyMappingConfigurationService implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* The hash service class to use
*
* @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
*/
protected $hashService;
/**
* @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
*/
public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService)
{
$this->hashService = $hashService;
}
/**
* Generate a request hash for a list of form fields
*
* @param array $formFieldNames Array of form fields
* @param string $fieldNamePrefix
*
* @return string trusted properties token
* @throws \TYPO3\CMS\EXTBASE\Security\Exception\InvalidArgumentForHashGenerationException
*/
public function generateTrustedPropertiesToken($formFieldNames, $fieldNamePrefix = '')
{
$formFieldArray = [];
foreach ($formFieldNames as $formField) {
$formFieldParts = explode('[', $formField);
$currentPosition = &$formFieldArray;
$formFieldPartsCount = count($formFieldParts);
for ($i = 0; $i < $formFieldPartsCount; $i++) {
$formFieldPart = $formFieldParts[$i];
$formFieldPart = rtrim($formFieldPart, ']');
if (!is_array($currentPosition)) {
throw new \TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is declared as array, but it collides with a previous form field of the same name which declared the field as string. This is an inconsistency you need to fix inside your Fluid form. (String overridden by Array)', 1255072196);
}
if ($i === $formFieldPartsCount - 1) {
if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
throw new \TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is declared as string, but it collides with a previous form field of the same name which declared the field as array. This is an inconsistency you need to fix inside your Fluid form. (Array overridden by String)', 1255072587);
}
// Last iteration - add a string
if ($formFieldPart === '') {
$currentPosition[] = 1;
} else {
$currentPosition[$formFieldPart] = 1;
}
} else {
if ($formFieldPart === '') {
throw new \TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is invalid. Reason: "[]" used not as last argument, but somewhere in the middle (like foo[][bar]).', 1255072832);
}
if (!isset($currentPosition[$formFieldPart])) {
$currentPosition[$formFieldPart] = [];
}
$currentPosition = &$currentPosition[$formFieldPart];
}
}
}
if ($fieldNamePrefix !== '') {
$formFieldArray = ($formFieldArray[$fieldNamePrefix] ?? []);
}
return $this->serializeAndHashFormFieldArray($formFieldArray);
}
/**
* Serialize and hash the form field array
*
* @param array $formFieldArray form field array to be serialized and hashed
*
* @return string Hash
*/
protected function serializeAndHashFormFieldArray(array $formFieldArray)
{
$serializedFormFieldArray = serialize($formFieldArray);
return $this->hashService->appendHmac($serializedFormFieldArray);
}
/**
* Initialize the property mapping configuration in $controllerArguments if
* the trusted properties are set inside the request.
*
* @param \TYPO3\CMS\Extbase\Mvc\Request $request
* @param \TYPO3\CMS\Extbase\Mvc\Controller\Arguments $controllerArguments
* @throws BadRequestException
*/
public function initializePropertyMappingConfigurationFromRequest(\TYPO3\CMS\Extbase\Mvc\Request $request, \TYPO3\CMS\Extbase\Mvc\Controller\Arguments $controllerArguments)
{
$trustedPropertiesToken = $request->getInternalArgument('__trustedProperties');
if (!is_string($trustedPropertiesToken)) {
return;
}
try {
$serializedTrustedProperties = $this->hashService->validateAndStripHmac($trustedPropertiesToken);
} catch (InvalidHashException | InvalidArgumentForHashGenerationException $e) {
throw new BadRequestException('The HMAC of the form could not be validated.', 1581862822);
}
$trustedProperties = unserialize($serializedTrustedProperties, ['allowed_classes' => false]);
foreach ($trustedProperties as $propertyName => $propertyConfiguration) {
if (!$controllerArguments->hasArgument($propertyName)) {
continue;
}
$propertyMappingConfiguration = $controllerArguments->getArgument($propertyName)->getPropertyMappingConfiguration();
$this->modifyPropertyMappingConfiguration($propertyConfiguration, $propertyMappingConfiguration);
}
}
/**
* Modify the passed $propertyMappingConfiguration according to the $propertyConfiguration which
* has been generated by Fluid. In detail, if the $propertyConfiguration contains
* an __identity field, we allow modification of objects; else we allow creation.
*
* All other properties are specified as allowed properties.
*
* @param array $propertyConfiguration
* @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration
*/
protected function modifyPropertyMappingConfiguration($propertyConfiguration, \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration)
{
if (!is_array($propertyConfiguration)) {
return;
}
if (isset($propertyConfiguration['__identity'])) {
$propertyMappingConfiguration->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, true);
unset($propertyConfiguration['__identity']);
} else {
$propertyMappingConfiguration->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
}
foreach ($propertyConfiguration as $innerKey => $innerValue) {
if (is_array($innerValue)) {
$this->modifyPropertyMappingConfiguration($innerValue, $propertyMappingConfiguration->forProperty($innerKey));
}
$propertyMappingConfiguration->allowProperties($innerKey);
}
}
}