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,289 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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\Reflection\ClassSchema;
/**
* Represents a Command
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class Command
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var string
*/
protected $controllerClassName;
/**
* @var string
*/
protected $controllerCommandName;
/**
* @var string
*/
protected $commandIdentifier;
/**
* Name of the extension to which this command belongs
*
* @var string
*/
protected $extensionName;
/**
* @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
*/
protected $reflectionService;
/**
* @var ClassSchema
*/
protected $classSchema;
/**
* @var string
*/
protected $controllerCommandMethod;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
*/
public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}
/**
* Constructor
*
* @param string $controllerClassName Class name of the controller providing the command
* @param string $controllerCommandName Command name, i.e. the method name of the command, without the "Command" suffix
* @throws \InvalidArgumentException
*/
public function __construct($controllerClassName, $controllerCommandName)
{
$this->controllerClassName = $controllerClassName;
$this->controllerCommandName = $controllerCommandName;
$this->controllerCommandMethod = $this->controllerCommandName . 'Command';
$classNameParts = explode('\\', $controllerClassName);
if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && isset($classNameParts[1]) && $classNameParts[1] === 'CMS') {
$classNameParts[0] .= '\\' . $classNameParts[1];
unset($classNameParts[1]);
$classNameParts = array_values($classNameParts);
}
$numberOfClassNameParts = count($classNameParts);
if ($numberOfClassNameParts < 3) {
throw new \InvalidArgumentException(
'Controller class names must at least consist of three parts: vendor, extension name and path.',
1438782187
);
}
if (strpos($classNameParts[$numberOfClassNameParts - 1], 'CommandController') === false) {
throw new \InvalidArgumentException(
'Invalid controller class name "' . $controllerClassName . '". Class name must end with "CommandController".',
1305100019
);
}
$this->extensionName = $classNameParts[1];
$extensionKey = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($this->extensionName);
$this->commandIdentifier = strtolower($extensionKey . ':' . substr($classNameParts[$numberOfClassNameParts - 1], 0, -17) . ':' . $controllerCommandName);
}
public function initializeObject()
{
$this->classSchema = $this->reflectionService->getClassSchema($this->controllerClassName);
}
/**
* @return string
*/
public function getControllerClassName()
{
return $this->controllerClassName;
}
/**
* @return string
*/
public function getControllerCommandName()
{
return $this->controllerCommandName;
}
/**
* Returns the command identifier for this command
*
* @return string The command identifier for this command, following the pattern extensionname:controllername:commandname
*/
public function getCommandIdentifier()
{
return $this->commandIdentifier;
}
/**
* Returns the name of the extension to which this command belongs
*
* @return string
*/
public function getExtensionName()
{
return $this->extensionName;
}
/**
* Returns a short description of this command
*
* @return string A short description
*/
public function getShortDescription()
{
$lines = explode(LF, $this->classSchema->getMethod($this->controllerCommandMethod)['description']);
return !empty($lines) ? trim($lines[0]) : '<no description available>';
}
/**
* Returns a longer description of this command
* This is the complete method description except for the first line which can be retrieved via getShortDescription()
* If The command description only consists of one line, an empty string is returned
*
* @return string A longer description of this command
*/
public function getDescription()
{
$lines = explode(LF, $this->classSchema->getMethod($this->controllerCommandMethod)['description']);
array_shift($lines);
$descriptionLines = [];
foreach ($lines as $line) {
$trimmedLine = trim($line);
if ($descriptionLines !== [] || $trimmedLine !== '') {
$descriptionLines[] = $trimmedLine;
}
}
return implode(LF, $descriptionLines);
}
/**
* Returns TRUE if this command expects required and/or optional arguments, otherwise FALSE
*
* @return bool
*/
public function hasArguments()
{
return !empty($this->classSchema->getMethod($this->controllerCommandMethod)['params']);
}
/**
* Returns an array of \TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition that contains
* information about required/optional arguments of this command.
* If the command does not expect any arguments, an empty array is returned
*
* @return array<\TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition>
*/
public function getArgumentDefinitions()
{
if (!$this->hasArguments()) {
return [];
}
$commandArgumentDefinitions = [];
$commandParameters = $this->classSchema->getMethod($this->controllerCommandMethod)['params'];
$commandParameterTags = $this->classSchema->getMethod($this->controllerCommandMethod)['tags']['param'];
$i = 0;
foreach ($commandParameters as $commandParameterName => $commandParameterDefinition) {
$explodedAnnotation = preg_split('/\s+/', $commandParameterTags[$i], 3);
$description = !empty($explodedAnnotation[2]) ? $explodedAnnotation[2] : '';
$required = $commandParameterDefinition['optional'] !== true;
$commandArgumentDefinitions[] = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition::class, $commandParameterName, $required, $description);
$i++;
}
return $commandArgumentDefinitions;
}
/**
* Tells if this command is internal and thus should not be exposed through help texts, user documentation etc.
* Internall commands are still accessible through the regular command line interface, but should not be used
* by users.
*
* @return bool
*/
public function isInternal()
{
return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['internal']);
}
/**
* Tells if this command is meant to be used on CLI only.
*
* @return bool
*/
public function isCliOnly()
{
return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['cli']);
}
/**
* Tells if this command flushes all caches and thus needs special attention in the interactive shell.
*
* Note that neither this method nor the @flushesCaches annotation is currently part of the official API.
*
* @return bool
*
* @deprecated will be removed in TYPO3 v10.0.
*/
public function isFlushingCaches()
{
trigger_error(
'Method isFlushingCaches() will be removed in TYPO3 v10.0. Do not call from other extension.',
E_USER_DEPRECATED
);
return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['flushesCaches']);
}
/**
* Returns an array of command identifiers which were specified in the "@see"
* annotation of a command method.
*
* @return array
*/
public function getRelatedCommandIdentifiers()
{
if (!isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['see'])) {
return [];
}
$relatedCommandIdentifiers = [];
foreach ($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['see'] as $tagValue) {
if (preg_match('/^[\\w\\d\\.]+:[\\w\\d]+:[\\w\\d]+$/', $tagValue) === 1) {
$relatedCommandIdentifiers[] = $tagValue;
}
}
return $relatedCommandIdentifiers;
}
}
@@ -0,0 +1,88 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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!
*/
/**
* Represents a CommandArgumentDefinition
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class CommandArgumentDefinition
{
/**
* @var string
*/
protected $name = '';
/**
* @var bool
*/
protected $required = false;
/**
* @var string
*/
protected $description = '';
/**
* Constructor
*
* @param string $name name of the command argument (= parameter name)
* @param bool $required defines whether this argument is required or optional
* @param string $description description of the argument
*/
public function __construct($name, $required, $description)
{
$this->name = $name;
$this->required = $required;
$this->description = $description;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the lowercased name with dashes as word separator
*
* @return string
*/
public function getDashedName()
{
$dashedName = ucfirst($this->name);
$dashedName = preg_replace('/([A-Z][a-z0-9]+)/', '$1-', $dashedName);
return '--' . strtolower(substr($dashedName, 0, -1));
}
/**
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* @return string
*/
public function isRequired()
{
return $this->required;
}
}
@@ -0,0 +1,175 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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 helper for CLI commands
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class CommandManager implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var array<\TYPO3\CMS\Extbase\Mvc\Cli\Command>
*/
protected $availableCommands;
/**
* @var array
*/
protected $shortCommandIdentifiers;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Returns an array of all commands
*
* @return Command[]
*/
public function getAvailableCommands()
{
if ($this->availableCommands === null) {
$this->availableCommands = [];
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'] ?? [] as $className) {
if (!class_exists($className)) {
continue;
}
foreach (get_class_methods($className) as $methodName) {
if (substr($methodName, -7, 7) === 'Command') {
$this->availableCommands[] = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\Command::class, $className, substr($methodName, 0, -7));
}
}
}
}
return $this->availableCommands;
}
/**
* Returns a Command that matches the given identifier.
* If no Command could be found a CommandNotFoundException is thrown
* If more than one Command matches an AmbiguousCommandIdentifierException is thrown that contains the matched Commands
*
* @param string $commandIdentifier command identifier in the format foo:bar:baz
* @return Command
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException if no matching command is available
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\AmbiguousCommandIdentifierException if more than one Command matches the identifier (the exception contains the matched commands)
*/
public function getCommandByIdentifier($commandIdentifier)
{
$commandIdentifier = strtolower(trim($commandIdentifier));
if ($commandIdentifier === 'help') {
$commandIdentifier = 'extbase:help:help';
}
$matchedCommands = [];
$availableCommands = $this->getAvailableCommands();
foreach ($availableCommands as $command) {
if ($this->commandMatchesIdentifier($command, $commandIdentifier)) {
$matchedCommands[] = $command;
}
}
if (empty($matchedCommands)) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException('No command could be found that matches the command identifier "' . $commandIdentifier . '".', 1310556663);
}
if (count($matchedCommands) > 1) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\AmbiguousCommandIdentifierException('More than one command matches the command identifier "' . $commandIdentifier . '"', 1310557169, null, $matchedCommands);
}
return current($matchedCommands);
}
/**
* Returns the shortest, non-ambiguous command identifier for the given command
*
* @param Command $command The command
* @return string The shortest possible command identifier
*/
public function getShortestIdentifierForCommand(Command $command)
{
if ($command->getCommandIdentifier() === 'extbase:help:help') {
return 'help';
}
$shortCommandIdentifiers = $this->getShortCommandIdentifiers();
if (!isset($shortCommandIdentifiers[$command->getCommandIdentifier()])) {
$command->getCommandIdentifier();
}
return $shortCommandIdentifiers[$command->getCommandIdentifier()];
}
/**
* Returns an array that contains all available command identifiers and their shortest non-ambiguous alias
*
* @return array in the format array('full.command:identifier1' => 'alias1', 'full.command:identifier2' => 'alias2')
*/
protected function getShortCommandIdentifiers()
{
if ($this->shortCommandIdentifiers === null) {
$commandsByCommandName = [];
foreach ($this->getAvailableCommands() as $availableCommand) {
list($extensionKey, $controllerName, $commandName) = explode(':', $availableCommand->getCommandIdentifier());
if (!isset($commandsByCommandName[$commandName])) {
$commandsByCommandName[$commandName] = [];
}
if (!isset($commandsByCommandName[$commandName][$controllerName])) {
$commandsByCommandName[$commandName][$controllerName] = [];
}
$commandsByCommandName[$commandName][$controllerName][] = $extensionKey;
}
foreach ($this->getAvailableCommands() as $availableCommand) {
list($extensionKey, $controllerName, $commandName) = explode(':', $availableCommand->getCommandIdentifier());
if (count($commandsByCommandName[$commandName][$controllerName]) > 1) {
$this->shortCommandIdentifiers[$availableCommand->getCommandIdentifier()] = sprintf('%s:%s:%s', $extensionKey, $controllerName, $commandName);
} else {
$this->shortCommandIdentifiers[$availableCommand->getCommandIdentifier()] = sprintf('%s:%s', $controllerName, $commandName);
}
}
}
return $this->shortCommandIdentifiers;
}
/**
* Returns TRUE if the specified command identifier matches the identifier of the specified command.
* This is the case, if the identifiers are the same or if at least the last two command parts match (case sensitive).
*
* @param Command $command
* @param string $commandIdentifier command identifier in the format foo:bar:baz (all lower case)
* @return bool TRUE if the specified command identifier matches this commands identifier
*/
protected function commandMatchesIdentifier(Command $command, $commandIdentifier)
{
$commandIdentifierParts = explode(':', $command->getCommandIdentifier());
$searchedCommandIdentifierParts = explode(':', $commandIdentifier);
$extensionKey = array_shift($commandIdentifierParts);
if (count($searchedCommandIdentifierParts) === 3) {
$searchedExtensionKey = array_shift($searchedCommandIdentifierParts);
if ($searchedExtensionKey !== $extensionKey) {
return false;
}
}
if (count($searchedCommandIdentifierParts) !== 2) {
return false;
}
return $searchedCommandIdentifierParts === $commandIdentifierParts;
}
}
@@ -0,0 +1,364 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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 Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
/**
* A wrapper for Symfony ConsoleOutput and related helpers
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class ConsoleOutput
{
/**
* @var ArgvInput
*/
protected $input;
/**
* @var SymfonyConsoleOutput
*/
protected $output;
/**
* @var QuestionHelper
*/
protected $questionHelper;
/**
* @var ProgressBar
*/
protected $progressBar;
/**
* @var Table
*/
protected $table;
/**
* Creates and initializes the Symfony I/O instances
*/
public function __construct()
{
$this->output = new SymfonyConsoleOutput();
$this->output->getFormatter()->setStyle('b', new OutputFormatterStyle(null, null, ['bold']));
$this->output->getFormatter()->setStyle('i', new OutputFormatterStyle('black', 'white'));
$this->output->getFormatter()->setStyle('u', new OutputFormatterStyle(null, null, ['underscore']));
$this->output->getFormatter()->setStyle('em', new OutputFormatterStyle(null, null, ['reverse']));
$this->output->getFormatter()->setStyle('strike', new OutputFormatterStyle(null, null, ['conceal']));
}
/**
* Returns the desired maximum line length for console output.
*
* @return int
*/
public function getMaximumLineLength()
{
return 79;
}
/**
* 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
*/
public function output($text, array $arguments = [])
{
if ($arguments !== []) {
$text = vsprintf($text, $arguments);
}
$this->output->write($text);
}
/**
* 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()
* @see outputLines()
*/
public function outputLine($text = '', array $arguments = [])
{
$this->output($text . PHP_EOL, $arguments);
}
/**
* Formats the given text to fit into the 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()
*/
public function outputFormatted($text = '', array $arguments = [], $leftPadding = 0)
{
$lines = explode(PHP_EOL, $text);
foreach ($lines as $line) {
$formattedText = str_repeat(' ', $leftPadding) . wordwrap($line, $this->getMaximumLineLength() - $leftPadding, PHP_EOL . str_repeat(' ', $leftPadding), true);
$this->outputLine($formattedText, $arguments);
}
}
/**
* Renders a table like output of the given $rows
*
* @param array $rows
* @param array $headers
*/
public function outputTable($rows, $headers = null)
{
$table = $this->getTable();
if ($headers !== null) {
$table->setHeaders($headers);
}
$table->setRows($rows);
$table->render();
}
/**
* Asks the user to select a value
*
* @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
* @param array $choices List of choices to pick from
* @param bool $default The default answer if the user enters nothing
* @param bool $multiSelect If TRUE the result will be an array with the selected options. Multiple options can be given separated by commas
* @param int|null $attempts Max number of times to ask before giving up (null by default, which means infinite)
* @return int|string|array The selected value or values (the key of the choices array)
* @throws \InvalidArgumentException
*/
public function select($question, $choices, $default = null, $multiSelect = false, $attempts = null)
{
$question = (new ChoiceQuestion($question, $choices, $default))
->setMultiselect($multiSelect)
->setMaxAttempts($attempts)
->setErrorMessage('Value "%s" is invalid');
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Asks a question to the user
*
* @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
* @param string $default The default answer if none is given by the user
* @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
* @return string The user answer
* @throws \RuntimeException If there is no data to read in the input stream
*/
public function ask($question, $default = null, array $autocomplete = null)
{
$question = (new Question($question, $default))
->setAutocompleterValues($autocomplete);
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Asks a confirmation to the user.
*
* The question will be asked until the user answers by nothing, yes, or no.
*
* @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
* @param bool $default The default answer if the user enters nothing
* @return bool true if the user has confirmed, false otherwise
*/
public function askConfirmation($question, $default = true)
{
$question = new ConfirmationQuestion($question, $default);
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Asks a question to the user, the response is hidden
*
* @param string|array $question The question. If an array each array item is turned into one line of a multi-line question
* @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
* @return string The answer
* @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
*/
public function askHiddenResponse($question, $fallback = true)
{
$question = (new Question($question))
->setHidden(true)
->setHiddenFallback($fallback);
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Asks for a value and validates the response
*
* The validator receives the data to validate. It must return the
* validated data when the data is valid and throw an exception
* otherwise.
*
* @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
* @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
* @param int|null $attempts Max number of times to ask before giving up (null by default, which means infinite)
* @param string $default The default answer if none is given by the user
* @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
* @return mixed
* @throws \Exception When any of the validators return an error
*/
public function askAndValidate($question, $validator, $attempts = null, $default = null, array $autocomplete = null)
{
$question = (new Question($question, $default))
->setValidator($validator)
->setMaxAttempts($attempts)
->setAutocompleterValues($autocomplete);
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Asks for a value, hide and validates the response
*
* The validator receives the data to validate. It must return the
* validated data when the data is valid and throw an exception
* otherwise.
*
* @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
* @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
* @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
* @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
* @return string The response
* @throws \Exception When any of the validators return an error
* @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
*/
public function askHiddenResponseAndValidate($question, $validator, $attempts = false, $fallback = true)
{
$question = (new Question($question))
->setValidator($validator)
->setMaxAttempts($attempts)
->setHidden(true)
->setHiddenFallback($fallback);
return $this->getQuestionHelper()->ask($this->getInput(), $this->output, $question);
}
/**
* Starts the progress output
*
* @param int $max Maximum steps. If NULL an indeterminate progress bar is rendered
*/
public function progressStart($max = null)
{
$this->getProgressBar()->start($max);
}
/**
* Advances the progress output X steps
*
* @param int $step Number of steps to advance
* @throws \LogicException
*/
public function progressAdvance($step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* Sets the current progress
*
* @param int $current The current progress
* @throws \LogicException
*/
public function progressSet($current)
{
$this->getProgressBar()->setProgress($current);
}
/**
* Finishes the progress output
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
}
/**
* @return ArgvInput
* @throws \RuntimeException
*/
protected function getInput()
{
if ($this->input === null) {
if (!isset($_SERVER['argv'])) {
throw new \RuntimeException('Cannot initialize ArgvInput object without CLI context.', 1456914444);
}
$this->input = new ArgvInput();
}
return $this->input;
}
/**
* Returns or initializes the symfony/console QuestionHelper
*
* @return QuestionHelper
*/
protected function getQuestionHelper()
{
if ($this->questionHelper === null) {
$this->questionHelper = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$this->questionHelper->setHelperSet($helperSet);
}
return $this->questionHelper;
}
/**
* Returns or initializes the symfony/console ProgressBar
*
* @return ProgressBar
*/
protected function getProgressBar()
{
if ($this->progressBar === null) {
$this->progressBar = new ProgressBar($this->output);
}
return $this->progressBar;
}
/**
* Returns or initializes the symfony/console Table
*
* @return Table
*/
protected function getTable()
{
if ($this->table === null) {
$this->table = new Table($this->output);
}
return $this->table;
}
}
@@ -0,0 +1,282 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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!
*/
/**
* Represents a CLI request.
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class Request implements \TYPO3\CMS\Extbase\Mvc\RequestInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var string
*/
protected $controllerObjectName;
/**
* @var string
*/
protected $controllerCommandName = 'default';
/**
* @var string Name of the extension which is supposed to handle this request.
*/
protected $controllerExtensionName;
/**
* The arguments for this request
*
* @var array
*/
protected $arguments = [];
/**
* @var array
*/
protected $exceedingArguments = [];
/**
* If this request has been changed and needs to be dispatched again
*
* @var bool
*/
protected $dispatched = false;
/**
* @var array
*/
protected $commandLineArguments;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Cli\Command | NULL
*/
protected $command;
/**
* @var string
*/
protected $callingScript;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param string $callingScript
*/
public function setCallingScript($callingScript)
{
$this->callingScript = $callingScript;
}
/**
* @return string
*/
public function getCallingScript()
{
return $this->callingScript;
}
/**
* Sets the dispatched flag
*
* @param bool $flag If this request has been dispatched
*/
public function setDispatched($flag)
{
$this->dispatched = (bool)$flag;
}
/**
* If this request has been dispatched and addressed by the responsible
* controller and the response is ready to be sent.
*
* The dispatcher will try to dispatch the request again if it has not been
* addressed yet.
*
* @return bool TRUE if this request has been disptached successfully
*/
public function isDispatched()
{
return $this->dispatched;
}
/**
* Sets the object name of the controller
*
* @param string $controllerObjectName The fully qualified controller object name
*/
public function setControllerObjectName($controllerObjectName)
{
$nameParts = \TYPO3\CMS\Core\Utility\ClassNamingUtility::explodeObjectControllerName($controllerObjectName);
$this->controllerExtensionName = $nameParts['extensionName'];
$this->controllerObjectName = $controllerObjectName;
$this->command = null;
}
/**
* Returns the object name of the controller
*
* @return string The controller's object name
*/
public function getControllerObjectName()
{
return $this->controllerObjectName;
}
/**
* Returns the extension name of the specified controller.
*
* @return string The extension name
*/
public function getControllerExtensionName()
{
return $this->controllerExtensionName;
}
/**
* Sets the name of the command contained in this request.
*
* Note that the command name must start with a lower case letter and is case sensitive.
*
* @param string $commandName Name of the command to execute by the controller
*/
public function setControllerCommandName($commandName)
{
$this->controllerCommandName = $commandName;
$this->command = null;
}
/**
* Returns the name of the command the controller is supposed to execute.
*
* @return string Command name
*/
public function getControllerCommandName()
{
return $this->controllerCommandName;
}
/**
* Returns the command object for this request
*
* @return \TYPO3\CMS\Extbase\Mvc\Cli\Command
*/
public function getCommand()
{
if ($this->command === null) {
$this->command = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\Command::class, $this->controllerObjectName, $this->controllerCommandName);
}
return $this->command;
}
/**
* Sets the value of the specified argument
*
* @param string $argumentName Name of the argument to set
* @param mixed $value The new value
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException
*/
public function setArgument($argumentName, $value)
{
if (!is_string($argumentName) || $argumentName === '') {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException('Invalid argument name.', 1300893885);
}
$this->arguments[$argumentName] = $value;
}
/**
* Sets the whole arguments ArrayObject and therefore replaces any arguments
* which existed before.
*
* @param array $arguments An array of argument names and their values
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
}
/**
* Returns the value of the specified argument
*
* @param string $argumentName Name of the argument
* @return string Value of the argument
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist
*/
public function getArgument($argumentName)
{
if (!isset($this->arguments[$argumentName])) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException('An argument "' . $argumentName . '" does not exist for this request.', 1300893886);
}
return $this->arguments[$argumentName];
}
/**
* Checks if an argument of the given name exists (is set)
*
* @param string $argumentName Name of the argument to check
* @return bool TRUE if the argument is set, otherwise FALSE
*/
public function hasArgument($argumentName)
{
return isset($this->arguments[$argumentName]);
}
/**
* Returns an ArrayObject of arguments and their values
*
* @return array Array of arguments and their values (which may be arguments and values as well)
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Sets the exceeding arguments
*
* @param array $exceedingArguments Numeric array of exceeding arguments
*/
public function setExceedingArguments(array $exceedingArguments)
{
$this->exceedingArguments = $exceedingArguments;
}
/**
* Returns additional unnamed arguments (if any) which have been passed through the command line after all
* required arguments (if any) have been specified.
*
* For a command method with the signature ($argument1, $argument2) and for the command line
* typo3/sysext/core/bin/typo3 acme:foo --argument1 Foo --argument2 Bar baz quux
* this method would return array(0 => 'baz', 1 => 'quux')
*
* @return array Numeric array of exceeding argument values
*/
public function getExceedingArguments()
{
return $this->exceedingArguments;
}
}
@@ -0,0 +1,240 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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!
*/
/**
* Builds a CLI request object from the raw command call
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class RequestBuilder implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
*/
protected $reflectionService;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Cli\CommandManager
*/
protected $commandManager;
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
*/
public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
{
$this->reflectionService = $reflectionService;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Cli\CommandManager $commandManager
*/
public function injectCommandManager(\TYPO3\CMS\Extbase\Mvc\Cli\CommandManager $commandManager)
{
$this->commandManager = $commandManager;
}
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* Builds a CLI request object from a command line.
*
* The given command line may be a string (e.g. "myextension:foo do-that-thing --force") or
* an array consisting of the individual parts. The array must not include the script
* name (like in $argv) but start with command right away.
*
* @param mixed $commandLine The command line, either as a string or as an array
* @param string $callingScript The calling script (usually ./typo3/sysext/core/bin/typo3)
* @return \TYPO3\CMS\Extbase\Mvc\Cli\Request The CLI request as an object
*/
public function build($commandLine = '', $callingScript = './typo3/sysext/core/bin/typo3')
{
$request = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\Request::class);
$request->setCallingScript($callingScript);
$request->setControllerObjectName(\TYPO3\CMS\Extbase\Command\HelpCommandController::class);
$rawCommandLineArguments = is_array($commandLine) ? $commandLine : explode(' ', $commandLine);
if (empty($rawCommandLineArguments)) {
$request->setControllerCommandName('helpStub');
return $request;
}
$commandIdentifier = trim(array_shift($rawCommandLineArguments));
try {
$command = $this->commandManager->getCommandByIdentifier($commandIdentifier);
$this->configurationManager->setConfiguration(['extensionName' => $command->getExtensionName()]);
} catch (\TYPO3\CMS\Extbase\Mvc\Exception\CommandException $exception) {
$request->setArgument('exception', $exception);
$request->setControllerCommandName('error');
return $request;
}
$controllerObjectName = $command->getControllerClassName();
$controllerCommandName = $command->getControllerCommandName();
$request->setControllerObjectName($controllerObjectName);
$request->setControllerCommandName($controllerCommandName);
list($commandLineArguments, $exceedingCommandLineArguments) = $this->parseRawCommandLineArguments($rawCommandLineArguments, $controllerObjectName, $controllerCommandName);
$request->setArguments($commandLineArguments);
$request->setExceedingArguments($exceedingCommandLineArguments);
return $request;
}
/**
* Takes an array of unparsed command line arguments and options and converts it separated
* by named arguments, options and unnamed arguments.
*
* @param array $rawCommandLineArguments The unparsed command parts (such as "--foo") as an array
* @param string $controllerObjectName Object name of the designated command controller
* @param string $controllerCommandName Command name of the recognized command (ie. method name without "Command" suffix)
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException
* @return array All and exceeding command line arguments
*/
protected function parseRawCommandLineArguments(array $rawCommandLineArguments, $controllerObjectName, $controllerCommandName)
{
$commandLineArguments = [];
$exceedingArguments = [];
$commandMethodName = $controllerCommandName . 'Command';
$commandMethodParameters = $this->reflectionService
->getClassSchema($controllerObjectName)
->getMethod($commandMethodName)['params'] ?? [];
$requiredArguments = [];
$optionalArguments = [];
$argumentNames = [];
foreach ($commandMethodParameters as $parameterName => $parameterInfo) {
$argumentNames[] = $parameterName;
if ($parameterInfo['optional'] === false) {
$requiredArguments[strtolower($parameterName)] = ['parameterName' => $parameterName, 'type' => $parameterInfo['type']];
} else {
$optionalArguments[strtolower($parameterName)] = ['parameterName' => $parameterName, 'type' => $parameterInfo['type']];
}
}
$decidedToUseNamedArguments = false;
$decidedToUseUnnamedArguments = false;
$argumentIndex = 0;
while (!empty($rawCommandLineArguments)) {
$rawArgument = array_shift($rawCommandLineArguments);
if ($rawArgument[0] === '-') {
if ($rawArgument[1] === '-') {
$rawArgument = substr($rawArgument, 2);
} else {
$rawArgument = substr($rawArgument, 1);
}
$argumentName = $this->extractArgumentNameFromCommandLinePart($rawArgument);
if (isset($optionalArguments[$argumentName])) {
$argumentValue = $this->getValueOfCurrentCommandLineOption($rawArgument, $rawCommandLineArguments, $optionalArguments[$argumentName]['type']);
$commandLineArguments[$optionalArguments[$argumentName]['parameterName']] = $argumentValue;
} elseif (isset($requiredArguments[$argumentName])) {
if ($decidedToUseUnnamedArguments) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException(sprintf('Unexpected named argument "%s". If you use unnamed arguments, all required arguments must be passed without a name.', $argumentName), 1309971821);
}
$decidedToUseNamedArguments = true;
$argumentValue = $this->getValueOfCurrentCommandLineOption($rawArgument, $rawCommandLineArguments, $requiredArguments[$argumentName]['type']);
$commandLineArguments[$requiredArguments[$argumentName]['parameterName']] = $argumentValue;
unset($requiredArguments[$argumentName]);
}
} else {
if (!empty($requiredArguments)) {
if ($decidedToUseNamedArguments) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException(sprintf('Unexpected unnamed argument "%s". If you use named arguments, all required arguments must be passed named.', $rawArgument), 1309971820);
}
$argument = array_shift($requiredArguments);
$commandLineArguments[$argument['parameterName']] = $rawArgument;
$decidedToUseUnnamedArguments = true;
} else {
if ($argumentIndex < count($argumentNames)) {
$commandLineArguments[$argumentNames[$argumentIndex]] = $rawArgument;
} else {
$exceedingArguments[] = $rawArgument;
}
}
}
$argumentIndex++;
}
return [$commandLineArguments, $exceedingArguments];
}
/**
* Extracts the option or argument name from the name / value pair of a command line.
*
* @param string $commandLinePart Part of the command line, e.g. "my-important-option=SomeInterestingValue
* @return string The lowercased argument name, e.g. "myimportantoption
*/
protected function extractArgumentNameFromCommandLinePart($commandLinePart)
{
$nameAndValue = explode('=', $commandLinePart, 2);
return strtolower(str_replace('-', '', $nameAndValue[0]));
}
/**
* Returns the value of the first argument of the given input array. Shifts the parsed argument off the array.
*
* @param string $currentArgument The current argument
* @param array &$rawCommandLineArguments Array of the remaining command line arguments
* @param string $expectedArgumentType The expected type of the current argument, because booleans get special attention
* @return string The value of the first argument
*/
protected function getValueOfCurrentCommandLineOption($currentArgument, array &$rawCommandLineArguments, $expectedArgumentType)
{
if (!isset($rawCommandLineArguments[0]) && strpos($currentArgument, '=') === false || isset($rawCommandLineArguments[0]) && $rawCommandLineArguments[0][0] === '-' && strpos($currentArgument, '=') === false) {
return true;
}
if (strpos($currentArgument, '=') === false) {
$possibleValue = trim(array_shift($rawCommandLineArguments));
if (strpos($possibleValue, '=') === false) {
if ($expectedArgumentType !== 'boolean') {
return $possibleValue;
}
if (in_array($possibleValue, ['on', '1', 'y', 'yes', 'true', 'TRUE'], true)) {
return true;
}
if (in_array($possibleValue, ['off', '0', 'n', 'no', 'false', 'FALSE'], true)) {
return false;
}
array_unshift($rawCommandLineArguments, $possibleValue);
return true;
}
$currentArgument .= $possibleValue;
}
$splitArgument = explode('=', $currentArgument, 2);
while ((!isset($splitArgument[1]) || trim($splitArgument[1]) === '') && !empty($rawCommandLineArguments)) {
$currentArgument .= array_shift($rawCommandLineArguments);
$splitArgument = explode('=', $currentArgument);
}
$value = $splitArgument[1] ?? '';
return $value;
}
}
@@ -0,0 +1,106 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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\Core\Environment;
/**
* The generic command line interface request handler for the MVC framework.
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class RequestHandler implements \TYPO3\CMS\Extbase\Mvc\RequestHandlerInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Dispatcher
*/
protected $dispatcher;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Cli\RequestBuilder
*/
protected $requestBuilder;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Dispatcher $dispatcher
*/
public function injectDispatcher(\TYPO3\CMS\Extbase\Mvc\Dispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Cli\RequestBuilder $requestBuilder
*/
public function injectRequestBuilder(\TYPO3\CMS\Extbase\Mvc\Cli\RequestBuilder $requestBuilder)
{
$this->requestBuilder = $requestBuilder;
}
/**
* Handles the request
*
* @return \TYPO3\CMS\Extbase\Mvc\ResponseInterface
*/
public function handleRequest()
{
$commandLine = $_SERVER['argv'] ?? [];
$callingScript = array_shift($commandLine);
if ($callingScript !== $_SERVER['_']) {
$callingScript = $_SERVER['_'] . ' ' . $callingScript;
}
$request = $this->requestBuilder->build($commandLine, $callingScript);
/** @var \TYPO3\CMS\Extbase\Mvc\Cli\Response $response */
$response = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\Response::class);
$this->dispatcher->dispatch($request, $response);
$response->send();
return $response;
}
/**
* This request handler can handle any command line request.
*
* @return bool If the request is a command line request, TRUE otherwise FALSE
*/
public function canHandleRequest()
{
return Environment::isCli();
}
/**
* Returns the priority - how eager the handler is to actually handle the
* request.
*
* @return int The priority of the request handler.
*/
public function getPriority()
{
return 100;
}
}
@@ -0,0 +1,62 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Cli;
/*
* 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 CLI specific response implementation
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class Response extends \TYPO3\CMS\Extbase\Mvc\Response
{
/**
* @var int
*/
private $exitCode = 0;
/**
* Sets the numerical exit code which should be returned when exiting this application.
*
* @param int $exitCode
* @throws \InvalidArgumentException
*/
public function setExitCode($exitCode)
{
if (!is_int($exitCode)) {
throw new \InvalidArgumentException(sprintf('Tried to set invalid exit code. The value must be integer, %s given.', gettype($exitCode)), 1312222064);
}
$this->exitCode = $exitCode;
}
/**
* Rets the numerical exit code which should be returned when exiting this application.
*
* @return int
*/
public function getExitCode()
{
return $this->exitCode;
}
/**
* Renders and sends the whole web response
*/
public function send()
{
if ($this->content !== null) {
echo $this->shutdown();
}
}
}
@@ -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);
}
}
}
@@ -0,0 +1,111 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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!
*/
/**
* Dispatches requests to the controller which was specified by the request and
* returns the response the controller generated.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface A reference to the object manager
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
*/
protected $signalSlotDispatcher;
/**
* @var array
*/
protected $settings = [];
/**
* @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
*/
public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
{
$this->signalSlotDispatcher = $signalSlotDispatcher;
}
/**
* Constructs the global dispatcher
*
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager A reference to the object manager
*/
public function __construct(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Dispatches a request to a controller and initializes the security framework.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request to dispatch
* @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, to be modified by the controller
* @throws Exception\InfiniteLoopException
*/
public function dispatch(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
{
$dispatchLoopCount = 0;
while (!$request->isDispatched()) {
if ($dispatchLoopCount++ > 99) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InfiniteLoopException('Could not ultimately dispatch the request after ' . $dispatchLoopCount . ' iterations. Most probably, a @' . \TYPO3\CMS\Extbase\Annotation\IgnoreValidation::class . ' annotation is missing on re-displaying a form with validation errors.', 1217839467);
}
$controller = $this->resolveController($request);
try {
$controller->processRequest($request, $response);
} catch (\TYPO3\CMS\Extbase\Mvc\Exception\StopActionException $ignoredException) {
}
}
$this->emitAfterRequestDispatchSignal($request, $response);
}
/**
* Emits a signal after a request was dispatched
*
* @param RequestInterface $request
* @param ResponseInterface $response
*/
protected function emitAfterRequestDispatchSignal(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
{
$this->signalSlotDispatcher->dispatch(__CLASS__, 'afterRequestDispatch', [$request, $response]);
}
/**
* Finds and instanciates a controller that matches the current request.
* If no controller can be found, an instance of NotFoundControllerInterface is returned.
*
* @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request to dispatch
* @throws Exception\InvalidControllerException
* @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface
*/
protected function resolveController(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request)
{
$controllerObjectName = $request->getControllerObjectName();
$controller = $this->objectManager->get($controllerObjectName);
if (!$controller instanceof \TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerException(
'Invalid controller "' . $request->getControllerObjectName() . '". The controller must implement the TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ControllerInterface.',
1476109646
);
}
return $controller;
}
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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 generic MVC exception
*/
class Exception extends \TYPO3\CMS\Extbase\Exception
{
}
@@ -0,0 +1,51 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 "Ambiguous command identifier" exception
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class AmbiguousCommandIdentifierException extends \TYPO3\CMS\Extbase\Mvc\Exception\CommandException
{
/**
* @var array<\TYPO3\CMS\Extbase\Mvc\Cli\Command>
*/
protected $matchingCommands = [];
/**
* Overwrites parent constructor to be able to inject matching commands.
*
* @param string $message
* @param int $code
* @param \Exception|null $previousException
* @param array $matchingCommands <\TYPO3\CMS\Extbase\Mvc\Cli\Command> $matchingCommands Commands that matched the command identifier
* @see Exception
*/
public function __construct($message = '', $code = 0, \Exception $previousException = null, array $matchingCommands)
{
$this->matchingCommands = $matchingCommands;
parent::__construct($message, $code, $previousException);
}
/**
* @return array<\TYPO3\CMS\Extbase\Mvc\Cli\Command>
*/
public function getMatchingCommands()
{
return $this->matchingCommands;
}
}
@@ -0,0 +1,23 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* Base command exception
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
*/
class CommandException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 "Infinite Loop" exception
*/
class InfiniteLoopException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 action name" exception
*/
class InvalidActionNameException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 InvalidArgumentMixingException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 InvalidArgumentNameException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Type" exception
*/
class InvalidArgumentTypeException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Value" exception
*/
class InvalidArgumentValueException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Controller" exception
*/
class InvalidControllerException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Controller Name" exception
*/
class InvalidControllerNameException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Extension Name" exception
*/
class InvalidExtensionNameException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 Request Method" exception
*/
class InvalidRequestMethodException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* A "No Such Action" exception
*/
class NoSuchActionException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* A "No Such Argument" exception
*/
class NoSuchArgumentException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* A "No Such Command" exception
*/
class NoSuchCommandException extends \TYPO3\CMS\Extbase\Mvc\Exception\CommandException
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* A "No Such Controller" exception
*/
class NoSuchControllerException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,27 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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!
*/
/**
* This exception is thrown by a controller to stop the execution of the current
* action and return the control to the dispatcher. The dispatcher catches this
* exception and - depending on the "dispatched" status of the request - either
* continues dispatching the request or returns control to the request handler.
*
* See the Action Controller's forward() and redirectToUri() methods for more information.
*/
class StopActionException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\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 "Unsupported Request Type" exception
*/
class UnsupportedRequestTypeException extends \TYPO3\CMS\Extbase\Mvc\Exception
{
}
@@ -0,0 +1,518 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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\Utility\ClassNamingUtility;
/**
* Represents a generic request.
*/
class Request implements RequestInterface
{
const PATTERN_MATCH_FORMAT = '/^[a-z0-9]{1,5}$/';
/**
* Pattern after which the namespaced controller object name is built
*
* @var string
*/
protected $namespacedControllerObjectNamePattern = '@vendor\@extension\@subpackage\Controller\@controllerController';
/**
* @var string Key of the plugin which identifies the plugin. It must be a string containing [a-z0-9]
*/
protected $pluginName = '';
/**
* @var string Name of the extension which is supposed to handle this request. This is the extension name converted to UpperCamelCase
*/
protected $controllerExtensionName;
/**
* @var string vendor prefix
*/
protected $controllerVendorName;
/**
* Subpackage key of the controller which is supposed to handle this request.
*
* @var string
*/
protected $controllerSubpackageKey;
/**
* @var string Object name of the controller which is supposed to handle this request.
*/
protected $controllerName = 'Standard';
/**
* @var string Name of the action the controller is supposed to take.
*/
protected $controllerActionName = 'index';
/**
* @var array The arguments for this request
*/
protected $arguments = [];
/**
* Framework-internal arguments for this request, such as __referrer.
* All framework-internal arguments start with double underscore (__),
* and are only used from within the framework. Not for user consumption.
* Internal Arguments can be objects, in contrast to public arguments
*
* @var array
*/
protected $internalArguments = [];
/**
* @var string The requested representation format
*/
protected $format = 'txt';
/**
* @var bool If this request has been changed and needs to be dispatched again
*/
protected $dispatched = false;
/**
* If this request is a forward because of an error, the original request gets filled.
*
* @var \TYPO3\CMS\Extbase\Mvc\Request
*/
protected $originalRequest;
/**
* If the request is a forward because of an error, these mapping results get filled here.
*
* @var \TYPO3\CMS\Extbase\Error\Result
*/
protected $originalRequestMappingResults;
/**
* Sets the dispatched flag
*
* @param bool $flag If this request has been dispatched
*/
public function setDispatched($flag)
{
$this->dispatched = (bool)$flag;
}
/**
* If this request has been dispatched and addressed by the responsible
* controller and the response is ready to be sent.
*
* The dispatcher will try to dispatch the request again if it has not been
* addressed yet.
*
* @return bool TRUE if this request has been disptached successfully
*/
public function isDispatched()
{
return $this->dispatched;
}
/**
* Returns the object name of the controller defined by the extension name and
* controller name
*
* @return string The controller's Object Name
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException if the controller does not exist
*/
public function getControllerObjectName()
{
$objectName = str_replace(
[
'@extension',
'@subpackage',
'@controller',
'@vendor',
'\\\\'
],
[
$this->controllerExtensionName,
$this->controllerSubpackageKey,
$this->controllerName,
$this->controllerVendorName,
'\\'
],
$this->namespacedControllerObjectNamePattern
);
// @todo implement getCaseSensitiveObjectName()
if ($objectName === false) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException('The controller object "' . $objectName . '" does not exist.', 1220884009);
}
return $objectName;
}
/**
* Explicitly sets the object name of the controller
*
* @param string $controllerObjectName The fully qualified controller object name
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerObjectName($controllerObjectName)
{
$nameParts = ClassNamingUtility::explodeObjectControllerName($controllerObjectName);
$this->controllerVendorName = $nameParts['vendorName'] ?? null;
$this->controllerExtensionName = $nameParts['extensionName'];
$this->controllerSubpackageKey = $nameParts['subpackageKey'] ?? null;
$this->controllerName = $nameParts['controllerName'];
}
/**
* Sets the plugin name.
*
* @param string|null $pluginName
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setPluginName($pluginName = null)
{
if ($pluginName !== null) {
$this->pluginName = $pluginName;
}
}
/**
* Returns the plugin key.
*
* @return string The plugin key
*/
public function getPluginName()
{
return $this->pluginName;
}
/**
* Sets the extension name of the controller.
*
* @param string $controllerExtensionName The extension name.
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException if the extension name is not valid
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerExtensionName($controllerExtensionName)
{
if ($controllerExtensionName !== null) {
$this->controllerExtensionName = $controllerExtensionName;
}
}
/**
* Returns the extension name of the specified controller.
*
* @return string The extension name
*/
public function getControllerExtensionName()
{
return $this->controllerExtensionName;
}
/**
* Returns the extension name of the specified controller.
*
* @return string The extension key
*/
public function getControllerExtensionKey()
{
return \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($this->controllerExtensionName);
}
/**
* Sets the subpackage key of the controller.
*
* @param string $subpackageKey The subpackage key.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerSubpackageKey($subpackageKey)
{
$this->controllerSubpackageKey = $subpackageKey;
}
/**
* Returns the subpackage key of the specified controller.
* If there is no subpackage key set, the method returns NULL
*
* @return string The subpackage key
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getControllerSubpackageKey()
{
return $this->controllerSubpackageKey;
}
/**
* Sets the name of the controller which is supposed to handle the request.
* Note: This is not the object name of the controller!
*
* @param string $controllerName Name of the controller
* @throws Exception\InvalidControllerNameException
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerName($controllerName)
{
if (!is_string($controllerName) && $controllerName !== null) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException('The controller name must be a valid string, ' . gettype($controllerName) . ' given.', 1187176358);
}
if ($controllerName !== null) {
$this->controllerName = $controllerName;
}
}
/**
* Returns the object name of the controller supposed to handle this request, if one
* was set already (if not, the name of the default controller is returned)
*
* @return string Object name of the controller
*/
public function getControllerName()
{
return $this->controllerName;
}
/**
* Sets the name of the action contained in this request.
*
* Note that the action name must start with a lower case letter and is case sensitive.
*
* @param string $actionName Name of the action to execute by the controller
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException if the action name is not valid
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerActionName($actionName)
{
if (!is_string($actionName) && $actionName !== null) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException('The action name must be a valid string, ' . gettype($actionName) . ' given (' . $actionName . ').', 1187176359);
}
if ($actionName[0] !== strtolower($actionName[0]) && $actionName !== null) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException('The action name must start with a lower case letter, "' . $actionName . '" does not match this criteria.', 1218473352);
}
if ($actionName !== null) {
$this->controllerActionName = $actionName;
}
}
/**
* Returns the name of the action the controller is supposed to execute.
*
* @return string Action name
*/
public function getControllerActionName()
{
$controllerObjectName = $this->getControllerObjectName();
if ($controllerObjectName !== '' && $this->controllerActionName === strtolower($this->controllerActionName)) {
$actionMethodName = $this->controllerActionName . 'Action';
$classMethods = get_class_methods($controllerObjectName);
if (is_array($classMethods)) {
foreach ($classMethods as $existingMethodName) {
if (strtolower($existingMethodName) === strtolower($actionMethodName)) {
$this->controllerActionName = substr($existingMethodName, 0, -6);
break;
}
}
}
}
return $this->controllerActionName;
}
/**
* Sets the value of the specified argument
*
* @param string $argumentName Name of the argument to set
* @param mixed $value The new value
* @throws Exception\InvalidArgumentNameException
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setArgument($argumentName, $value)
{
if (!is_string($argumentName) || $argumentName === '') {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException('Invalid argument name.', 1210858767);
}
if ($argumentName[0] === '_' && $argumentName[1] === '_') {
$this->internalArguments[$argumentName] = $value;
return;
}
if (!in_array($argumentName, ['@extension', '@subpackage', '@controller', '@action', '@format', '@vendor'], true)) {
$this->arguments[$argumentName] = $value;
}
}
/**
* sets the VendorName
*
* @param string $vendorName
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setControllerVendorName($vendorName)
{
$this->controllerVendorName = $vendorName;
}
/**
* get the VendorName
*
* @return string
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getControllerVendorName()
{
return $this->controllerVendorName;
}
/**
* Sets the whole arguments array and therefore replaces any arguments
* which existed before.
*
* @param array $arguments An array of argument names and their values
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setArguments(array $arguments)
{
$this->arguments = [];
foreach ($arguments as $argumentName => $argumentValue) {
$this->setArgument($argumentName, $argumentValue);
}
}
/**
* Returns an array of arguments and their values
*
* @return array Associative array of arguments and their values (which may be arguments and values as well)
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the value of the specified argument
*
* @param string $argumentName Name of the argument
*
* @return string|array Value of the argument
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist
*/
public function getArgument($argumentName)
{
if (!isset($this->arguments[$argumentName])) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException('An argument "' . $argumentName . '" does not exist for this request.', 1176558158);
}
return $this->arguments[$argumentName];
}
/**
* Checks if an argument of the given name exists (is set)
*
* @param string $argumentName Name of the argument to check
*
* @return bool TRUE if the argument is set, otherwise FALSE
*/
public function hasArgument($argumentName)
{
return isset($this->arguments[$argumentName]);
}
/**
* Sets the requested representation format
*
* @param string $format The desired format, something like "html", "xml", "png", "json" or the like. Can even be something like "rss.xml".
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setFormat($format)
{
$this->format = $format;
}
/**
* Returns the requested representation format
*
* @return string The desired format, something like "html", "xml", "png", "json" or the like.
*/
public function getFormat()
{
return $this->format;
}
/**
* Returns the original request. Filled only if a property mapping error occurred.
*
* @return \TYPO3\CMS\Extbase\Mvc\Request the original request.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getOriginalRequest()
{
return $this->originalRequest;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Request $originalRequest
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setOriginalRequest(\TYPO3\CMS\Extbase\Mvc\Request $originalRequest)
{
$this->originalRequest = $originalRequest;
}
/**
* Get the request mapping results for the original request.
*
* @return \TYPO3\CMS\Extbase\Error\Result
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getOriginalRequestMappingResults()
{
if ($this->originalRequestMappingResults === null) {
return new \TYPO3\CMS\Extbase\Error\Result();
}
return $this->originalRequestMappingResults;
}
/**
* @param \TYPO3\CMS\Extbase\Error\Result $originalRequestMappingResults
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setOriginalRequestMappingResults(\TYPO3\CMS\Extbase\Error\Result $originalRequestMappingResults)
{
$this->originalRequestMappingResults = $originalRequestMappingResults;
}
/**
* Get the internal arguments of the request, i.e. every argument starting
* with two underscores.
*
* @return array
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getInternalArguments()
{
return $this->internalArguments;
}
/**
* Returns the value of the specified argument
*
* @param string $argumentName Name of the argument
* @return string Value of the argument, or NULL if not set.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getInternalArgument($argumentName)
{
if (!isset($this->internalArguments[$argumentName])) {
return null;
}
return $this->internalArguments[$argumentName];
}
}
@@ -0,0 +1,44 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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 interface for a request handler
*/
interface RequestHandlerInterface
{
/**
* Handles a raw request and returns the respsonse.
*
* @return \TYPO3\CMS\Extbase\Mvc\ResponseInterface
*/
public function handleRequest();
/**
* Checks if the request handler can handle the current request.
*
* @return bool TRUE if it can handle the request, otherwise FALSE
*/
public function canHandleRequest();
/**
* Returns the priority - how eager the handler is to actually handle the
* request. An integer > 0 means "I want to handle this request" where
* "100" is default. "0" means "I am a fallback solution".
*
* @return int The priority of the request handler
*/
public function getPriority();
}
@@ -0,0 +1,87 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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!
*/
/**
* Analyzes the raw request and delivers a request handler which can handle it.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class RequestHandlerResolver
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* Analyzes the raw request and tries to find a request handler which can handle
* it. If none is found, an exception is thrown.
*
* @return \TYPO3\CMS\Extbase\Mvc\RequestHandlerInterface A request handler
* @throws \TYPO3\CMS\Extbase\Mvc\Exception
*/
public function resolveRequestHandler()
{
$availableRequestHandlerClassNames = $this->getRegisteredRequestHandlerClassNames();
$suitableRequestHandlers = [];
foreach ($availableRequestHandlerClassNames as $requestHandlerClassName) {
$requestHandler = $this->objectManager->get($requestHandlerClassName);
if ($requestHandler->canHandleRequest()) {
$priority = $requestHandler->getPriority();
if (isset($suitableRequestHandlers[$priority])) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception('More than one request handler with the same priority can handle the request, but only one handler may be active at a time!', 1176475350);
}
$suitableRequestHandlers[$priority] = $requestHandler;
}
}
if (empty($suitableRequestHandlers)) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception('No suitable request handler found.', 1205414233);
}
ksort($suitableRequestHandlers);
return array_pop($suitableRequestHandlers);
}
/**
* Returns a list of all registered request handlers.
*
* @return array
*/
public function getRegisteredRequestHandlerClassNames()
{
$settings = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
return is_array($settings['mvc']['requestHandlers']) ? $settings['mvc']['requestHandlers'] : [];
}
}
@@ -0,0 +1,88 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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!
*/
/**
* Contract for a request.
*/
interface RequestInterface
{
/**
* Sets the dispatched flag
*
* @param bool $flag If this request has been dispatched
*/
public function setDispatched($flag);
/**
* If this request has been dispatched and addressed by the responsible
* controller and the response is ready to be sent.
*
* The dispatcher will try to dispatch the request again if it has not been
* addressed yet.
*
* @return bool TRUE if this request has been disptached successfully
*/
public function isDispatched();
/**
* Returns the object name of the controller defined by the package key and
* controller name
*
* @return string The controller's Object Name
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchControllerException if the controller does not exist
*/
public function getControllerObjectName();
/**
* Sets the value of the specified argument
*
* @param string $argumentName Name of the argument to set
* @param mixed $value The new value
*/
public function setArgument($argumentName, $value);
/**
* Sets the whole arguments array and therefore replaces any arguments
* which existed before.
*
* @param array $arguments An array of argument names and their values
*/
public function setArguments(array $arguments);
/**
* Returns the value of the specified argument
*
* @param string $argumentName Name of the argument
* @return string Value of the argument
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist
*/
public function getArgument($argumentName);
/**
* Checks if an argument of the given name exists (is set)
*
* @param string $argumentName Name of the argument to check
* @return bool TRUE if the argument is set, otherwise FALSE
*/
public function hasArgument($argumentName);
/**
* Returns an array of arguments and their values
*
* @return array Array of arguments and their values (which may be arguments and values as well)
*/
public function getArguments();
}
@@ -0,0 +1,78 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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 generic and very basic response implementation
*/
class Response implements \TYPO3\CMS\Extbase\Mvc\ResponseInterface
{
/**
* @var string The response content
*/
protected $content;
/**
* Overrides and sets the content of the response
*
* @param string $content The response content
*/
public function setContent($content)
{
$this->content = $content;
}
/**
* Appends content to the already existing content.
*
* @param string $content More response content
*/
public function appendContent($content)
{
$this->content .= $content;
}
/**
* Returns the response content without sending it.
*
* @return string The response content
*/
public function getContent()
{
return $this->content;
}
/**
* Fetches the content, returns and clears it.
*
* @return string
*/
public function shutdown()
{
$content = $this->getContent();
$this->setContent('');
return $content;
}
/**
* Returns the content of the response.
*
* @return string
*/
public function __toString()
{
return $this->getContent();
}
}
@@ -0,0 +1,49 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc;
/*
* 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 generic and very basic response implementation
*/
interface ResponseInterface
{
/**
* Overrides and sets the content of the response
*
* @param string $content The response content
*/
public function setContent($content);
/**
* Appends content to the already existing content.
*
* @param string $content More response content
*/
public function appendContent($content);
/**
* Returns the response content without sending it.
*
* @return string The response content
*/
public function getContent();
/**
* Returns the response content without sending it.
*
* @return string The response content
*/
public function shutdown();
}
@@ -0,0 +1,95 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\View;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* An abstract View
*/
abstract class AbstractView implements \TYPO3\CMS\Extbase\Mvc\View\ViewInterface
{
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
*/
protected $controllerContext;
/**
* View variables and their values
*
* @var array
* @see assign()
*/
protected $variables = [];
/**
* Sets the current controller context
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
*/
public function setControllerContext(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext)
{
$this->controllerContext = $controllerContext;
}
/**
* Add a variable to $this->viewData.
* Can be chained, so $this->view->assign(..., ...)->assign(..., ...); is possible
*
* @param string $key Key of variable
* @param mixed $value Value of object
* @return \TYPO3\CMS\Extbase\Mvc\View\AbstractView an instance of $this, to enable chaining
*/
public function assign($key, $value)
{
$this->variables[$key] = $value;
return $this;
}
/**
* Add multiple variables to $this->viewData.
*
* @param array $values array in the format array(key1 => value1, key2 => value2).
* @return \TYPO3\CMS\Extbase\Mvc\View\AbstractView an instance of $this, to enable chaining
*/
public function assignMultiple(array $values)
{
foreach ($values as $key => $value) {
$this->assign($key, $value);
}
return $this;
}
/**
* Tells if the view implementation can render the view for the given context.
*
* By default we assume that the view implementation can handle all kinds of
* contexts. Override this method if that is not the case.
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
* @return bool TRUE if the view has something useful to display, otherwise FALSE
*/
public function canRender(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext)
{
return true;
}
/**
* Initializes this view.
*
* Override this method for initializing your concrete view implementation.
*/
public function initializeView()
{
}
}
@@ -0,0 +1,99 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\View;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* An empty view - a special case.
*/
class EmptyView implements \TYPO3\CMS\Extbase\Mvc\View\ViewInterface
{
/**
* Dummy method to satisfy the ViewInterface
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
* @internal
*/
public function setControllerContext(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext)
{
}
/**
* Dummy method to satisfy the ViewInterface
*
* @param string $key
* @param mixed $value
* @return \TYPO3\CMS\Extbase\Mvc\View\EmptyView instance of $this to allow chaining
*/
public function assign($key, $value)
{
return $this;
}
/**
* Dummy method to satisfy the ViewInterface
*
* @param array $values
* @return \TYPO3\CMS\Extbase\Mvc\View\EmptyView instance of $this to allow chaining
*/
public function assignMultiple(array $values)
{
return $this;
}
/**
* This view can be used in any case.
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
* @return bool TRUE
*/
public function canRender(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext)
{
return true;
}
/**
* Renders the empty view
*
* @return string An empty string
*/
public function render()
{
return '<!-- This is the output of the Empty View. An appropriate View was not found. -->';
}
/**
* A magic call method.
*
* Because this empty view is used as a Special Case in situations when no matching
* view is available, it must be able to handle method calls which originally were
* directed to another type of view. This magic method should prevent PHP from issuing
* a fatal error.
*
* @param string $methodName
* @param array $arguments
*/
public function __call($methodName, array $arguments)
{
}
/**
* Initializes this view.
*
* Override this method for initializing your concrete view implementation.
*/
public function initializeView()
{
}
}
@@ -0,0 +1,341 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Extbase\Mvc\View;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Extbase\Mvc\Web\Response as WebResponse;
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* A JSON view
*/
class JsonView extends AbstractView
{
/**
* Definition for the class name exposure configuration,
* that is, if the class name of an object should also be
* part of the output JSON, if configured.
*
* Setting this value, the object's class name is fully
* put out, including the namespace.
*/
const EXPOSE_CLASSNAME_FULLY_QUALIFIED = 1;
/**
* Puts out only the actual class name without namespace.
* See EXPOSE_CLASSNAME_FULL for the meaning of the constant at all.
*/
const EXPOSE_CLASSNAME_UNQUALIFIED = 2;
/**
* @var ReflectionService
*/
protected $reflectionService;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
*/
protected $controllerContext;
/**
* Only variables whose name is contained in this array will be rendered
*
* @var string[]
*/
protected $variablesToRender = ['value'];
/**
* The rendering configuration for this JSON view which
* determines which properties of each variable to render.
*
* The configuration array must have the following structure:
*
* Example 1:
*
* [
* 'variable1' => [
* '_only' => ['property1', 'property2', ...]
* ],
* 'variable2' => [
* '_exclude' => ['property3', 'property4, ...]
* ],
* 'variable3' => [
* '_exclude' => ['secretTitle'],
* '_descend' => [
* 'customer' => [
* '_only' => [array(]'firstName', 'lastName']
* ]
* ]
* ],
* 'somearrayvalue' => [
* '_descendAll' => [
* '_only' => ['property1']
* ]
* ]
* ]
*
* Of variable1 only property1 and property2 will be included.
* Of variable2 all properties except property3 and property4
* are used.
* Of variable3 all properties except secretTitle are included.
*
* If a property value is an array or object, it is not included
* by default. If, however, such a property is listed in a "_descend"
* section, the renderer will descend into this sub structure and
* include all its properties (of the next level).
*
* The configuration of each property in "_descend" has the same syntax
* like at the top level. Therefore - theoretically - infinitely nested
* structures can be configured.
*
* To export indexed arrays the "_descendAll" section can be used to
* include all array keys for the output. The configuration inside a
* "_descendAll" will be applied to each array element.
*
*
* Example 2: exposing object identifier
*
* [
* 'variableFoo' => [
* '_exclude' => ['secretTitle'],
* '_descend' => [
* 'customer' => [ // consider 'customer' being a persisted entity
* '_only' => ['firstName'],
* '_exposeObjectIdentifier' => TRUE,
* '_exposedObjectIdentifierKey' => 'guid'
* ]
* ]
* ]
* ]
*
* Note for entity objects you are able to expose the object's identifier
* also, just add an "_exposeObjectIdentifier" directive set to TRUE and
* an additional property '__identity' will appear keeping the persistence
* identifier. Renaming that property name instead of '__identity' is also
* possible with the directive "_exposedObjectIdentifierKey".
* Example 2 above would output (summarized):
* {"customer":{"firstName":"John","guid":"892693e4-b570-46fe-af71-1ad32918fb64"}}
*
*
* Example 3: exposing object's class name
*
* [
* 'variableFoo' => [
* '_exclude' => ['secretTitle'],
* '_descend' => [
* 'customer' => [ // consider 'customer' being an object
* '_only' => ['firstName'],
* '_exposeClassName' => \TYPO3\CMS\Extbase\Mvc\View\JsonView::EXPOSE_CLASSNAME_FULLY_QUALIFIED
* ]
* ]
* ]
* ]
*
* The ``_exposeClassName`` is similar to the objectIdentifier one, but the class name is added to the
* JSON object output, for example (summarized):
* {"customer":{"firstName":"John","__class":"Acme\Foo\Domain\Model\Customer"}}
*
* The other option is EXPOSE_CLASSNAME_UNQUALIFIED which only will give the last part of the class
* without the namespace, for example (summarized):
* {"customer":{"firstName":"John","__class":"Customer"}}
* This might be of interest to not provide information about the package or domain structure behind.
*
* @var array
*/
protected $configuration = [];
/**
* @var PersistenceManagerInterface
*/
protected $persistenceManager;
/**
* @param PersistenceManagerInterface $persistenceManager
* @internal
*/
public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager): void
{
$this->persistenceManager = $persistenceManager;
}
/**
* @param ReflectionService $reflectionService
* @internal
*/
public function injectReflectionService(ReflectionService $reflectionService): void
{
$this->reflectionService = $reflectionService;
}
/**
* Specifies which variables this JsonView should render
* By default only the variable 'value' will be rendered
*
* @param array $variablesToRender
*/
public function setVariablesToRender(array $variablesToRender): void
{
$this->variablesToRender = $variablesToRender;
}
/**
* @param array $configuration The rendering configuration for this JSON view
*/
public function setConfiguration(array $configuration): void
{
$this->configuration = $configuration;
}
/**
* Transforms the value view variable to a serializable
* array representation using a YAML view configuration and JSON encodes
* the result.
*
* @return string The JSON encoded variables
*/
public function render(): string
{
$response = $this->controllerContext->getResponse();
if ($response instanceof WebResponse) {
// @todo Ticket: #63643 This should be solved differently once request/response model is available for TSFE.
if (!empty($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
/** @var TypoScriptFrontendController $typoScriptFrontendController */
$typoScriptFrontendController = $GLOBALS['TSFE'];
if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
// If the charset header is *not* disabled in configuration,
// TypoScriptFrontendController will send the header later with the Content-Type which we set here.
$typoScriptFrontendController->setContentType('application/json');
} else {
// Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
// Content-Type headers optionally carry charset information at the same time.
// Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
$response->setHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
}
} else {
$response->setHeader('Content-Type', 'application/json');
}
}
$propertiesToRender = $this->renderArray();
return json_encode($propertiesToRender, JSON_UNESCAPED_UNICODE);
}
/**
* Loads the configuration and transforms the value to a serializable
* array.
*
* @return mixed
*/
protected function renderArray()
{
if (count($this->variablesToRender) === 1) {
$variableName = current($this->variablesToRender);
$valueToRender = $this->variables[$variableName] ?? null;
$configuration = $this->configuration[$variableName] ?? [];
} else {
$valueToRender = [];
foreach ($this->variablesToRender as $variableName) {
$valueToRender[$variableName] = $this->variables[$variableName] ?? null;
}
$configuration = $this->configuration;
}
return $this->transformValue($valueToRender, $configuration);
}
/**
* Transforms a value depending on type recursively using the
* supplied configuration.
*
* @param mixed $value The value to transform
* @param array $configuration Configuration for transforming the value
* @return mixed The transformed value
*/
protected function transformValue($value, array $configuration)
{
if (is_array($value) || $value instanceof \ArrayAccess) {
$array = [];
foreach ($value as $key => $element) {
if (isset($configuration['_descendAll']) && is_array($configuration['_descendAll'])) {
$array[$key] = $this->transformValue($element, $configuration['_descendAll']);
} else {
if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($key, $configuration['_only'], true)) {
continue;
}
if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($key, $configuration['_exclude'], true)) {
continue;
}
$array[$key] = $this->transformValue($element, $configuration[$key] ?? []);
}
}
return $array;
}
if (is_object($value)) {
return $this->transformObject($value, $configuration);
}
return $value;
}
/**
* Traverses the given object structure in order to transform it into an
* array structure.
*
* @param object $object Object to traverse
* @param array $configuration Configuration for transforming the given object or NULL
* @return array|string Object structure as an array or as a rendered string (for a DateTime instance)
*/
protected function transformObject(object $object, array $configuration)
{
if ($object instanceof \DateTime) {
return $object->format(\DateTime::ATOM);
}
$propertyNames = ObjectAccess::getGettablePropertyNames($object);
$propertiesToRender = [];
foreach ($propertyNames as $propertyName) {
if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($propertyName, $configuration['_only'], true)) {
continue;
}
if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($propertyName, $configuration['_exclude'], true)) {
continue;
}
$propertyValue = ObjectAccess::getProperty($object, $propertyName);
if (!is_array($propertyValue) && !is_object($propertyValue)) {
$propertiesToRender[$propertyName] = $propertyValue;
} elseif (isset($configuration['_descend']) && array_key_exists($propertyName, $configuration['_descend'])) {
$propertiesToRender[$propertyName] = $this->transformValue($propertyValue, $configuration['_descend'][$propertyName]);
}
}
if (isset($configuration['_exposeObjectIdentifier']) && $configuration['_exposeObjectIdentifier'] === true) {
if (isset($configuration['_exposedObjectIdentifierKey']) && strlen($configuration['_exposedObjectIdentifierKey']) > 0) {
$identityKey = $configuration['_exposedObjectIdentifierKey'];
} else {
$identityKey = '__identity';
}
$propertiesToRender[$identityKey] = $this->persistenceManager->getIdentifierByObject($object);
}
if (isset($configuration['_exposeClassName']) && ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED || $configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_UNQUALIFIED)) {
$className = get_class($object);
$classNameParts = explode('\\', $className);
$propertiesToRender['__class'] = ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED ? $className : array_pop($classNameParts));
}
return $propertiesToRender;
}
}
@@ -0,0 +1,74 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\View;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* The not found view - a special case.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class NotFoundView extends \TYPO3\CMS\Extbase\Mvc\View\AbstractView
{
/**
* @var array
*/
protected $variablesMarker = ['errorMessage' => 'ERROR_MESSAGE'];
/**
* Renders the not found view
*
* @return string The rendered view
* @throws \TYPO3\CMS\Extbase\Mvc\Exception if no request has been set
*/
public function render()
{
if (!is_object($this->controllerContext->getRequest())) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception('Can\'t render view without request object.', 1192450280);
}
$template = file_get_contents($this->getTemplatePathAndFilename());
if ($this->controllerContext->getRequest() instanceof \TYPO3\CMS\Extbase\Mvc\Web\Request) {
$template = str_replace('###BASEURI###', \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), $template);
}
foreach ($this->variablesMarker as $variableName => $marker) {
$variableValue = $this->variables[$variableName] ?? '';
$template = str_replace('###' . $marker . '###', $variableValue, $template);
}
return $template;
}
/**
* Retrieves path and filename of the not-found-template
*
* @return string path and filename of the not-found-template
*/
protected function getTemplatePathAndFilename()
{
return \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('extbase') . 'Resources/Private/MVC/NotFoundView_Template.html';
}
/**
* A magic call method.
*
* Because this not found view is used as a Special Case in situations when no matching
* view is available, it must be able to handle method calls which originally were
* directed to another type of view. This magic method should prevent PHP from issuing
* a fatal error.
*
* @param string $methodName
* @param array $arguments
*/
public function __call($methodName, array $arguments)
{
}
}
@@ -0,0 +1,67 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\View;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Interface of a view
*/
interface ViewInterface
{
/**
* Sets the current controller context
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
* @internal
*/
public function setControllerContext(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext);
/**
* Add a variable to the view data collection.
* Can be chained, so $this->view->assign(..., ...)->assign(..., ...); is possible
*
* @param string $key Key of variable
* @param mixed $value Value of object
* @return \TYPO3\CMS\Extbase\Mvc\View\ViewInterface an instance of $this, to enable chaining
*/
public function assign($key, $value);
/**
* Add multiple variables to the view data collection
*
* @param array $values array in the format array(key1 => value1, key2 => value2)
* @return \TYPO3\CMS\Extbase\Mvc\View\ViewInterface an instance of $this, to enable chaining
*/
public function assignMultiple(array $values);
/**
* Tells if the view implementation can render the view for the given context.
*
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
* @return bool TRUE if the view has something useful to display, otherwise FALSE
*/
public function canRender(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext);
/**
* Renders the view
*
* @return string The rendered view
*/
public function render();
/**
* Initializes this view.
*/
public function initializeView();
}
@@ -0,0 +1,95 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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 request handler which can handle web requests.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
abstract class AbstractRequestHandler implements \TYPO3\CMS\Extbase\Mvc\RequestHandlerInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Dispatcher
*/
protected $dispatcher;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder
*/
protected $requestBuilder;
/**
* @var \TYPO3\CMS\Extbase\Service\EnvironmentService
*/
protected $environmentService;
/**
* @param \TYPO3\CMS\Extbase\Mvc\Dispatcher $dispatcher
*/
public function injectDispatcher(\TYPO3\CMS\Extbase\Mvc\Dispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder $requestBuilder
*/
public function injectRequestBuilder(\TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder $requestBuilder)
{
$this->requestBuilder = $requestBuilder;
}
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
*/
public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
{
$this->environmentService = $environmentService;
}
/**
* This request handler can handle any web request.
*
* @return bool If the request is a web request, TRUE otherwise FALSE
*/
public function canHandleRequest()
{
return true;
}
/**
* Returns the priority - how eager the handler is to actually handle the
* request.
*
* @return int The priority of the request handler.
*/
public function getPriority()
{
return 100;
}
}
@@ -0,0 +1,48 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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\Core\Environment;
/**
* A request handler which can handle web requests invoked by the backend.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class BackendRequestHandler extends AbstractRequestHandler
{
/**
* Handles the web request. The response will automatically be sent to the client.
*
* @return \TYPO3\CMS\Extbase\Mvc\Web\Response
*/
public function handleRequest()
{
$request = $this->requestBuilder->build();
/** @var \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response */
$response = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Response::class);
$this->dispatcher->dispatch($request, $response);
return $response;
}
/**
* This request handler can handle a web request invoked by the backend.
*
* @return bool If we are in backend mode TRUE otherwise FALSE
*/
public function canHandleRequest()
{
return $this->environmentService->isEnvironmentInBackendMode() && !Environment::isCli();
}
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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\SingletonInterface;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
/**
* Enforces cHash argument if it is required for a given request
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class CacheHashEnforcer implements SingletonInterface
{
/**
* @var CacheHashCalculator
*/
protected $cacheHashCalculator;
/**
* @var TypoScriptFrontendController
*/
protected $typoScriptFrontendController;
/**
* CacheHashEnforcer constructor.
*
* @param CacheHashCalculator $cacheHashCalculator
* @param TypoScriptFrontendController|null $typoScriptFrontendController
*/
public function __construct(
CacheHashCalculator $cacheHashCalculator,
TypoScriptFrontendController $typoScriptFrontendController = null
) {
$this->cacheHashCalculator = $cacheHashCalculator;
$this->typoScriptFrontendController = $typoScriptFrontendController ?: $GLOBALS['TSFE'];
}
/**
* Checks if cHash is required for the current request and calls
* TypoScriptFrontendController::reqCHash() if so.
* This call will trigger a PageNotFoundException if arguments are required and cHash is not present.
*
* @param Request $request
* @param string $pluginNamespace
*/
public function enforceForRequest(Request $request, string $pluginNamespace)
{
$arguments = $request->getArguments();
if (is_array($arguments) && count($arguments) > 0) {
$parameters = [$pluginNamespace => $arguments];
$parameters['id'] = $this->typoScriptFrontendController->id;
$relevantParameters = $this->cacheHashCalculator->getRelevantParameters(
http_build_query($parameters, '', '&', PHP_QUERY_RFC3986)
);
if (count($relevantParameters) > 0) {
$this->typoScriptFrontendController->reqCHash();
}
}
}
}
@@ -0,0 +1,105 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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 request handler which can handle web requests invoked by the frontend.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class FrontendRequestHandler extends AbstractRequestHandler
{
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var \TYPO3\CMS\Extbase\Service\ExtensionService
*/
protected $extensionService;
/**
* @var \TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer
*/
protected $cacheHashEnforcer;
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
*/
public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
{
$this->extensionService = $extensionService;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer $cacheHashEnforcer
*/
public function injectCacheHashEnforcer(\TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer $cacheHashEnforcer)
{
$this->cacheHashEnforcer = $cacheHashEnforcer;
}
/**
* Handles the web request. The response will automatically be sent to the client.
*
* @return \TYPO3\CMS\Extbase\Mvc\ResponseInterface|null
*/
public function handleRequest()
{
$request = $this->requestBuilder->build();
if ($this->extensionService->isActionCacheable(null, null, $request->getControllerName(), $request->getControllerActionName())) {
$request->setIsCached(true);
} else {
$contentObject = $this->configurationManager->getContentObject();
if ($contentObject->getUserObjectType() === \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::OBJECTTYPE_USER) {
$contentObject->convertToUserIntObject();
// \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::convertToUserIntObject() will recreate the object, so we have to stop the request here
return null;
}
$request->setIsCached(false);
}
if ($this->configurationManager->isFeatureEnabled('requireCHashArgumentForActionArguments')) {
$pluginNamespace = $this->extensionService->getPluginNamespace(
$request->getControllerExtensionName(),
$request->getPluginName()
);
$this->cacheHashEnforcer->enforceForRequest($request, $pluginNamespace);
}
/** @var \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response */
$response = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Response::class);
$this->dispatcher->dispatch($request, $response);
return $response;
}
/**
* This request handler can handle any web request.
*
* @return bool If the request is a web request, TRUE otherwise FALSE
*/
public function canHandleRequest()
{
return $this->environmentService->isEnvironmentInFrontendMode();
}
}
@@ -0,0 +1,53 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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!
*/
/**
* Represents a referring web request.
*/
class ReferringRequest extends Request
{
/**
* Sets the value of the specified argument
*
* @param string $argumentName Name of the argument to set
* @param mixed $value The new value
*/
public function setArgument($argumentName, $value)
{
parent::setArgument($argumentName, $value);
switch ($argumentName) {
case '@extension':
$this->setControllerExtensionName($value);
break;
case '@subpackage':
$this->setControllerSubpackageKey($value);
break;
case '@controller':
$this->setControllerName($value);
break;
case '@action':
$this->setControllerActionName($value);
break;
case '@format':
$this->setFormat($value);
break;
case '@vendor':
$this->setControllerVendorName($value);
break;
}
}
}
@@ -0,0 +1,202 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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!
*/
/**
* Represents a web request.
*/
class Request extends \TYPO3\CMS\Extbase\Mvc\Request
{
/**
* @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
*/
protected $hashService;
/**
* @var string The requested representation format
*/
protected $format = 'html';
/**
* @var string Contains the request method
*/
protected $method = 'GET';
/**
* @var string
*/
protected $requestUri;
/**
* @var string The base URI for this request - ie. the host and path leading to the index.php
*/
protected $baseUri;
/**
* @var bool TRUE if the current request is cached, false otherwise.
*/
protected $isCached = false;
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var \TYPO3\CMS\Extbase\Service\EnvironmentService
*/
protected $environmentService;
/**
* @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService)
{
$this->hashService = $hashService;
}
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
{
$this->environmentService = $environmentService;
}
/**
* Sets the request method
*
* @param string $method Name of the request method
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidRequestMethodException if the request method is not supported
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setMethod($method)
{
if ($method === '' || strtoupper($method) !== $method) {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidRequestMethodException('The request method "' . $method . '" is not supported.', 1217778382);
}
$this->method = $method;
}
/**
* Returns the name of the request method
*
* @return string Name of the request method
*/
public function getMethod()
{
return $this->method;
}
/**
* Sets the request URI
*
* @param string $requestUri URI of this web request
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setRequestUri($requestUri)
{
$this->requestUri = $requestUri;
}
/**
* Returns the request URI
*
* @return string URI of this web request
*/
public function getRequestUri()
{
return $this->requestUri;
}
/**
* Sets the base URI for this request.
*
* @param string $baseUri New base URI
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setBaseUri($baseUri)
{
$this->baseUri = $baseUri;
}
/**
* Returns the base URI
*
* @return string Base URI of this web request
*/
public function getBaseUri()
{
if ($this->environmentService->isEnvironmentInBackendMode()) {
return $this->baseUri . TYPO3_mainDir;
}
return $this->baseUri;
}
/**
* Set if the current request is cached.
*
* @param bool $isCached
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setIsCached($isCached)
{
$this->isCached = (bool)$isCached;
}
/**
* Return whether the current request is a cached request or not.
*
* @return bool the caching status.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function isCached()
{
return $this->isCached;
}
/**
* Get a freshly built request object pointing to the Referrer.
*
* @return ReferringRequest the referring request, or null if no referrer found
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getReferringRequest()
{
if (isset($this->internalArguments['__referrer']['@request'])) {
$referrerArray = unserialize($this->hashService->validateAndStripHmac($this->internalArguments['__referrer']['@request']), ['allowed_classes' => false]);
$arguments = [];
if (isset($this->internalArguments['__referrer']['arguments'])) {
// This case is kept for compatibility in 7.6 and 6.2, but will be removed in 8
$arguments = unserialize(base64_decode($this->hashService->validateAndStripHmac($this->internalArguments['__referrer']['arguments'])));
}
$referringRequest = new ReferringRequest();
$referringRequest->setArguments(array_replace_recursive($arguments, $referrerArray));
return $referringRequest;
}
return null;
}
}
@@ -0,0 +1,344 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Exception as MvcException;
/**
* Builds a web request.
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
class RequestBuilder implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* This is the vendor name of the extension
*
* @var string
*/
protected $vendorName;
/**
* This is a unique key for a plugin (not the extension key!)
*
* @var string
*/
protected $pluginName = 'plugin';
/**
* The name of the extension (in UpperCamelCase)
*
* @var string
*/
protected $extensionName;
/**
* The default controller name
*
* @var string
*/
protected $defaultControllerName = '';
/**
* The default format of the response object
*
* @var string
*/
protected $defaultFormat = 'html';
/**
* The allowed actions of the controller. This actions can be called via $_GET and $_POST.
*
* @var array
*/
protected $allowedControllerActions = [];
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var \TYPO3\CMS\Extbase\Service\ExtensionService
*/
protected $extensionService;
/**
* @var \TYPO3\CMS\Extbase\Service\EnvironmentService
*/
protected $environmentService;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
*/
public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
{
$this->extensionService = $extensionService;
}
/**
* @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
*/
public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
{
$this->environmentService = $environmentService;
}
/**
* @throws MvcException
*/
protected function loadDefaultValues()
{
$configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
if (empty($configuration['extensionName'])) {
throw new MvcException('"extensionName" is not properly configured. Request can\'t be dispatched!', 1289843275);
}
if (empty($configuration['pluginName'])) {
throw new MvcException('"pluginName" is not properly configured. Request can\'t be dispatched!', 1289843277);
}
if (!empty($configuration['vendorName'])) {
$this->vendorName = $configuration['vendorName'];
} else {
$this->vendorName = null;
}
$this->extensionName = $configuration['extensionName'];
$this->pluginName = $configuration['pluginName'];
$this->defaultControllerName = (string)current(array_keys($configuration['controllerConfiguration']));
$this->allowedControllerActions = [];
foreach ($configuration['controllerConfiguration'] as $controllerName => $controllerActions) {
$this->allowedControllerActions[$controllerName] = $controllerActions['actions'] ?? null;
}
if (!empty($configuration['format'])) {
$this->defaultFormat = $configuration['format'];
}
}
/**
* Builds a web request object from the raw HTTP information and the configuration
*
* @return \TYPO3\CMS\Extbase\Mvc\Web\Request The web request as an object
*/
public function build()
{
$this->loadDefaultValues();
$pluginNamespace = $this->extensionService->getPluginNamespace($this->extensionName, $this->pluginName);
/** @var \TYPO3\CMS\Core\Http\ServerRequest $typo3Request */
$typo3Request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($typo3Request instanceof ServerRequestInterface) {
$queryArguments = $typo3Request->getAttribute('routing');
if ($queryArguments instanceof PageArguments) {
$getParameters = $queryArguments->get($pluginNamespace) ?? [];
} else {
$getParameters = $typo3Request->getQueryParams()[$pluginNamespace] ?? [];
}
$bodyParameters = $typo3Request->getParsedBody()[$pluginNamespace] ?? [];
$bodyParameters = is_array($bodyParameters) ? $bodyParameters : [];
$parameters = is_array($getParameters) ? $getParameters : [];
ArrayUtility::mergeRecursiveWithOverrule($parameters, $bodyParameters);
} else {
$parameters = \TYPO3\CMS\Core\Utility\GeneralUtility::_GPmerged($pluginNamespace);
}
$files = $this->untangleFilesArray($_FILES);
if (is_array($files[$pluginNamespace] ?? null)) {
$parameters = array_replace_recursive($parameters, $files[$pluginNamespace]);
}
$controllerName = $this->resolveControllerName($parameters);
$actionName = $this->resolveActionName($controllerName, $parameters);
/** @var \TYPO3\CMS\Extbase\Mvc\Web\Request $request */
$request = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Request::class);
if ($this->vendorName !== null) {
$request->setControllerVendorName($this->vendorName);
}
$request->setPluginName($this->pluginName);
$request->setControllerExtensionName($this->extensionName);
$request->setControllerName($controllerName);
$request->setControllerActionName($actionName);
// @todo Use Environment
$request->setRequestUri(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
$request->setBaseUri(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
$request->setMethod($this->environmentService->getServerRequestMethod());
if (isset($parameters['format']) && is_string($parameters['format']) && $parameters['format'] !== '') {
$request->setFormat(filter_var($parameters['format'], FILTER_SANITIZE_STRING));
} else {
$request->setFormat($this->defaultFormat);
}
foreach ($parameters as $argumentName => $argumentValue) {
$request->setArgument($argumentName, $argumentValue);
}
return $request;
}
/**
* Returns the current ControllerName extracted from given $parameters.
* If no controller is specified, the defaultControllerName will be returned.
* If that's not available, an exception is thrown.
*
* @param array $parameters
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException
* @throws MvcException if the controller could not be resolved
* @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
* @return string
*/
protected function resolveControllerName(array $parameters)
{
if (!isset($parameters['controller']) || $parameters['controller'] === '') {
if (empty($this->defaultControllerName)) {
throw new MvcException('The default controller for extension "' . $this->extensionName . '" and plugin "' . $this->pluginName . '" can not be determined. Please check for TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1316104317);
}
return $this->defaultControllerName;
}
$allowedControllerNames = array_keys($this->allowedControllerActions);
if (!in_array($parameters['controller'], $allowedControllerNames)) {
$configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
throw new \TYPO3\CMS\Core\Error\Http\PageNotFoundException('The requested resource was not found', 1313857897);
}
if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
return $this->defaultControllerName;
}
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException(
'The controller "' . $parameters['controller'] . '" is not allowed by plugin "' . $this->pluginName . '". Please check for TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.',
1313855173
);
}
return filter_var($parameters['controller'], FILTER_SANITIZE_STRING);
}
/**
* Returns the current actionName extracted from given $parameters.
* If no action is specified, the defaultActionName will be returned.
* If that's not available or the specified action is not defined in the current plugin, an exception is thrown.
*
* @param string $controllerName
* @param array $parameters
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException
* @throws MvcException
* @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
* @return string
*/
protected function resolveActionName($controllerName, array $parameters)
{
$defaultActionName = is_array($this->allowedControllerActions[$controllerName]) ? current($this->allowedControllerActions[$controllerName]) : '';
if (!isset($parameters['action']) || $parameters['action'] === '') {
if ($defaultActionName === '') {
throw new MvcException('The default action can not be determined for controller "' . $controllerName . '". Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1295479651);
}
return $defaultActionName;
}
$actionName = $parameters['action'];
$allowedActionNames = $this->allowedControllerActions[$controllerName];
if (!in_array($actionName, $allowedActionNames)) {
$configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
throw new \TYPO3\CMS\Core\Error\Http\PageNotFoundException('The requested resource was not found', 1313857898);
}
if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
return $defaultActionName;
}
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException('The action "' . $actionName . '" (controller "' . $controllerName . '") is not allowed by this plugin / module. Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php / TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configureModule() in your ext_tables.php.', 1313855175);
}
return filter_var($actionName, FILTER_SANITIZE_STRING);
}
/**
* Transforms the convoluted _FILES superglobal into a manageable form.
*
* @param array $convolutedFiles The _FILES superglobal
* @return array Untangled files
*/
protected function untangleFilesArray(array $convolutedFiles)
{
$untangledFiles = [];
$fieldPaths = [];
foreach ($convolutedFiles as $firstLevelFieldName => $fieldInformation) {
if (!is_array($fieldInformation['error'])) {
$fieldPaths[] = [$firstLevelFieldName];
} else {
$newFieldPaths = $this->calculateFieldPaths($fieldInformation['error'], $firstLevelFieldName);
array_walk($newFieldPaths, function (&$value, $key) {
$value = explode('/', $value);
});
$fieldPaths = array_merge($fieldPaths, $newFieldPaths);
}
}
foreach ($fieldPaths as $fieldPath) {
if (count($fieldPath) === 1) {
$fileInformation = $convolutedFiles[$fieldPath[0]];
} else {
$fileInformation = [];
foreach ($convolutedFiles[$fieldPath[0]] as $key => $subStructure) {
try {
$fileInformation[$key] = ArrayUtility::getValueByPath($subStructure, array_slice($fieldPath, 1));
} catch (MissingArrayPathException $e) {
// do nothing if the path is invalid
}
}
}
$untangledFiles = ArrayUtility::setValueByPath($untangledFiles, $fieldPath, $fileInformation);
}
return $untangledFiles;
}
/**
* Returns an array of all possibles "field paths" for the given array.
*
* @param array $structure The array to walk through
* @param string $firstLevelFieldName
* @return array An array of paths (as strings) in the format "key1/key2/key3" ...
*/
protected function calculateFieldPaths(array $structure, $firstLevelFieldName = null)
{
$fieldPaths = [];
if (is_array($structure)) {
foreach ($structure as $key => $subStructure) {
$fieldPath = ($firstLevelFieldName !== null ? $firstLevelFieldName . '/' : '') . $key;
if (is_array($subStructure)) {
foreach ($this->calculateFieldPaths($subStructure) as $subFieldPath) {
$fieldPaths[] = $fieldPath . '/' . $subFieldPath;
}
} else {
$fieldPaths[] = $fieldPath;
}
}
}
return $fieldPaths;
}
}
@@ -0,0 +1,341 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web;
/*
* 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\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* A web specific response implementation
*/
class Response extends \TYPO3\CMS\Extbase\Mvc\Response
{
/**
* The HTTP headers which will be sent in the response
*
* @var array
*/
protected $headers = [];
/**
* Additional header tags
*
* @var array
*/
protected $additionalHeaderData = [];
/**
* The HTTP status code
*
* @var int
*/
protected $statusCode;
/**
* The HTTP status message
*
* @var string
*/
protected $statusMessage = 'OK';
/**
* The Request which generated the Response
*
* @var \TYPO3\CMS\Extbase\Mvc\Web\Request
*/
protected $request;
/**
* The standardized and other important HTTP Status messages
*
* @var array
*/
protected $statusMessages = [
// INFORMATIONAL CODES
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
103 => 'Early Hints',
// SUCCESS CODES
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-status',
208 => 'Already Reported',
226 => 'IM Used',
// REDIRECTION CODES
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => 'Switch Proxy', // Deprecated
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
// CLIENT ERROR
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Unordered Collection',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
// SERVER ERROR
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
509 => 'Bandwidth Limit Exceeded',
511 => 'Network Authentication Required',
];
/**
* @var \TYPO3\CMS\Extbase\Service\EnvironmentService
*/
protected $environmentService;
/**
* @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
{
$this->environmentService = $environmentService;
}
/**
* Sets the HTTP status code and (optionally) a customized message.
*
* @param int $code The status code
* @param string $message If specified, this message is sent instead of the standard message
* @throws \InvalidArgumentException if the specified status code is not valid
*/
public function setStatus($code, $message = null)
{
if (!is_int($code)) {
throw new \InvalidArgumentException('The HTTP status code must be of type integer, ' . gettype($code) . ' given.', 1220526013);
}
if ($message === null && !isset($this->statusMessages[$code])) {
throw new \InvalidArgumentException('No message found for HTTP status code "' . $code . '".', 1220526014);
}
$this->statusCode = $code;
$this->statusMessage = $message ?? $this->statusMessages[$code];
}
/**
* Returns status code and status message.
*
* @return string The status code and status message, eg. "404 Not Found
*/
public function getStatus()
{
return $this->statusCode . ' ' . $this->statusMessage;
}
/**
* Returns the status code, if not set, uses the OK status code 200
*
* @return int
* @internal only use for backend module handling
*/
public function getStatusCode()
{
return $this->statusCode ?: 200;
}
/**
* Sets the specified HTTP header
*
* @param string $name Name of the header, for example "Location", "Content-Description" etc.
* @param mixed $value The value of the given header
* @param bool $replaceExistingHeader If a header with the same name should be replaced. Default is TRUE.
* @throws \InvalidArgumentException
*/
public function setHeader($name, $value, $replaceExistingHeader = true)
{
if (stripos($name, 'HTTP') === 0) {
throw new \InvalidArgumentException('The HTTP status header must be set via setStatus().', 1220541963);
}
if ($replaceExistingHeader === true || !isset($this->headers[$name])) {
$this->headers[$name] = [$value];
} else {
$this->headers[$name][] = $value;
}
}
/**
* Returns the HTTP headers - including the status header - of this web response
*
* @return string[] The HTTP headers
*/
public function getHeaders()
{
$preparedHeaders = [];
if ($this->statusCode !== null) {
$protocolVersion = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0';
$statusHeader = $protocolVersion . ' ' . $this->statusCode . ' ' . $this->statusMessage;
$preparedHeaders[] = $statusHeader;
}
foreach ($this->headers as $name => $values) {
foreach ($values as $value) {
$preparedHeaders[] = $name . ': ' . $value;
}
}
return $preparedHeaders;
}
/**
* Returns the HTTP headers grouped by name without the status header
*
* @return array all headers set for this request
* @internal only used within TYPO3 Core to convert to PSR-7 response headers
*/
public function getUnpreparedHeaders(): array
{
return $this->headers;
}
/**
* Sends the HTTP headers.
*
* If headers have already been sent, this method fails silently.
*/
public function sendHeaders()
{
if (headers_sent() === true) {
return;
}
foreach ($this->getHeaders() as $header) {
header($header);
}
}
/**
* Renders and sends the whole web response
*/
public function send()
{
$this->sendHeaders();
if ($this->content !== null) {
echo $this->getContent();
}
}
/**
* Adds an additional header data (something like
* '<script src="myext/Resources/JavaScript/my.js" type="text/javascript"></script>'
* )
*
* @TODO The workround and the $request member should be removed again, once the PageRender does support non-cached USER_INTs
* @param string $additionalHeaderData The value additional header
* @throws \InvalidArgumentException
*/
public function addAdditionalHeaderData($additionalHeaderData)
{
if (!is_string($additionalHeaderData)) {
throw new \InvalidArgumentException('The additiona header data must be of type String, ' . gettype($additionalHeaderData) . ' given.', 1237370877);
}
if ($this->request->isCached()) {
/** @var PageRenderer $pageRenderer */
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addHeaderData($additionalHeaderData);
} else {
$this->additionalHeaderData[] = $additionalHeaderData;
}
}
/**
* Returns the additional header data
*
* @return array The additional header data
*/
public function getAdditionalHeaderData()
{
return $this->additionalHeaderData;
}
/**
* @param \TYPO3\CMS\Extbase\Mvc\Web\Request $request
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setRequest(\TYPO3\CMS\Extbase\Mvc\Web\Request $request)
{
$this->request = $request;
}
/**
* @return \TYPO3\CMS\Extbase\Mvc\Web\Request
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getRequest()
{
return $this->request;
}
/**
* Sends additional headers and returns the content
*
* @return string|null
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function shutdown()
{
if (!empty($this->getAdditionalHeaderData())) {
$this->getTypoScriptFrontendController()->additionalHeaderData[] = implode(LF, $this->getAdditionalHeaderData());
}
$this->sendHeaders();
return parent::shutdown();
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}
@@ -0,0 +1,841 @@
<?php
namespace TYPO3\CMS\Extbase\Mvc\Web\Routing;
/*
* 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\Backend\Routing\Exception\ResourceNotFoundException;
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Extbase\Mvc\Request;
use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
/**
* An URI Builder
*/
class UriBuilder
{
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var \TYPO3\CMS\Extbase\Service\ExtensionService
*/
protected $extensionService;
/**
* An instance of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
*
* @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
*/
protected $contentObject;
/**
* @var Request
*/
protected $request;
/**
* @var array
*/
protected $arguments = [];
/**
* Arguments which have been used for building the last URI
*
* @var array
*/
protected $lastArguments = [];
/**
* @var string
*/
protected $section = '';
/**
* @var bool
*/
protected $createAbsoluteUri = false;
/**
* @var string
*/
protected $absoluteUriScheme;
/**
* @var bool
*/
protected $addQueryString = false;
/**
* @var string
*/
protected $addQueryStringMethod;
/**
* @var array
*/
protected $argumentsToBeExcludedFromQueryString = [];
/**
* @var bool
*/
protected $linkAccessRestrictedPages = false;
/**
* @var int
*/
protected $targetPageUid;
/**
* @var int
*/
protected $targetPageType = 0;
/**
* @var bool
*/
protected $noCache = false;
/**
* @var bool
*/
protected $useCacheHash = true;
/**
* @var string
*/
protected $format = '';
/**
* @var string
*/
protected $argumentPrefix;
/**
* @var \TYPO3\CMS\Extbase\Service\EnvironmentService
*/
protected $environmentService;
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
{
$this->configurationManager = $configurationManager;
}
/**
* @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
{
$this->extensionService = $extensionService;
}
/**
* @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
{
$this->environmentService = $environmentService;
}
/**
* Life-cycle method that is called by the DI container as soon as this object is completely built
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function initializeObject()
{
$this->contentObject = $this->configurationManager->getContentObject();
}
/**
* Sets the current request
*
* @param Request $request
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* @return Request
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getRequest()
{
return $this->request;
}
/**
* Additional query parameters.
* If you want to "prefix" arguments, you can pass in multidimensional arrays:
* array('prefix1' => array('foo' => 'bar')) gets "&prefix1[foo]=bar"
*
* @param array $arguments
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
return $this;
}
/**
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* If specified, adds a given HTML anchor to the URI (#...)
*
* @param string $section
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setSection($section)
{
$this->section = $section;
return $this;
}
/**
* @return string
*/
public function getSection()
{
return $this->section;
}
/**
* Specifies the format of the target (e.g. "html" or "xml")
*
* @param string $format
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setFormat($format)
{
$this->format = $format;
return $this;
}
/**
* @return string
*/
public function getFormat()
{
return $this->format;
}
/**
* If set, the URI is prepended with the current base URI. Defaults to FALSE.
*
* @param bool $createAbsoluteUri
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setCreateAbsoluteUri($createAbsoluteUri)
{
$this->createAbsoluteUri = $createAbsoluteUri;
return $this;
}
/**
* @return bool
*/
public function getCreateAbsoluteUri()
{
return $this->createAbsoluteUri;
}
/**
* @return string
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getAbsoluteUriScheme()
{
return $this->absoluteUriScheme;
}
/**
* Sets the scheme that should be used for absolute URIs in FE mode
*
* @param string $absoluteUriScheme the scheme to be used for absolute URIs
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setAbsoluteUriScheme($absoluteUriScheme)
{
$this->absoluteUriScheme = $absoluteUriScheme;
return $this;
}
/**
* If set, the current query parameters will be merged with $this->arguments. Defaults to FALSE.
*
* @param bool $addQueryString
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @see TSref/typolink.addQueryString
*/
public function setAddQueryString($addQueryString)
{
$this->addQueryString = (bool)$addQueryString;
return $this;
}
/**
* @return bool
*/
public function getAddQueryString()
{
return $this->addQueryString;
}
/**
* Sets the method to get the addQueryString parameters. Defaults undefined
* which results in using QUERY_STRING.
*
* @param string $addQueryStringMethod
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @see TSref/typolink.addQueryString.method
*/
public function setAddQueryStringMethod($addQueryStringMethod)
{
$this->addQueryStringMethod = $addQueryStringMethod;
return $this;
}
/**
* @return string
*/
public function getAddQueryStringMethod()
{
return (string)$this->addQueryStringMethod;
}
/**
* A list of arguments to be excluded from the query parameters
* Only active if addQueryString is set
*
* @param array $argumentsToBeExcludedFromQueryString
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @see TSref/typolink.addQueryString.exclude
* @see setAddQueryString()
*/
public function setArgumentsToBeExcludedFromQueryString(array $argumentsToBeExcludedFromQueryString)
{
$this->argumentsToBeExcludedFromQueryString = $argumentsToBeExcludedFromQueryString;
return $this;
}
/**
* @return array
*/
public function getArgumentsToBeExcludedFromQueryString()
{
return $this->argumentsToBeExcludedFromQueryString;
}
/**
* Specifies the prefix to be used for all arguments.
*
* @param string $argumentPrefix
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function setArgumentPrefix($argumentPrefix)
{
$this->argumentPrefix = (string)$argumentPrefix;
return $this;
}
/**
* @return string
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getArgumentPrefix()
{
return $this->argumentPrefix;
}
/**
* If set, URIs for pages without access permissions will be created
*
* @param bool $linkAccessRestrictedPages
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setLinkAccessRestrictedPages($linkAccessRestrictedPages)
{
$this->linkAccessRestrictedPages = (bool)$linkAccessRestrictedPages;
return $this;
}
/**
* @return bool
*/
public function getLinkAccessRestrictedPages()
{
return $this->linkAccessRestrictedPages;
}
/**
* Uid of the target page
*
* @param int $targetPageUid
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setTargetPageUid($targetPageUid)
{
$this->targetPageUid = $targetPageUid;
return $this;
}
/**
* returns $this->targetPageUid.
*
* @return int
*/
public function getTargetPageUid()
{
return $this->targetPageUid;
}
/**
* Sets the page type of the target URI. Defaults to 0
*
* @param int $targetPageType
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setTargetPageType($targetPageType)
{
$this->targetPageType = (int)$targetPageType;
return $this;
}
/**
* @return int
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getTargetPageType()
{
return $this->targetPageType;
}
/**
* by default FALSE; if TRUE, &no_cache=1 will be appended to the URI
* This overrules the useCacheHash setting
*
* @param bool $noCache
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setNoCache($noCache)
{
$this->noCache = (bool)$noCache;
return $this;
}
/**
* @return bool
*/
public function getNoCache()
{
return $this->noCache;
}
/**
* by default TRUE; if FALSE, no cHash parameter will be appended to the URI
* If noCache is set, this setting will be ignored.
*
* @param bool $useCacheHash
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function setUseCacheHash($useCacheHash)
{
$this->useCacheHash = (bool)$useCacheHash;
return $this;
}
/**
* @return bool
*/
public function getUseCacheHash()
{
return $this->useCacheHash;
}
/**
* Returns the arguments being used for the last URI being built.
* This is only set after build() / uriFor() has been called.
*
* @return array The last arguments
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function getLastArguments()
{
return $this->lastArguments;
}
/**
* Resets all UriBuilder options to their default value
*
* @return \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder the current UriBuilder to allow method chaining
*/
public function reset()
{
$this->arguments = [];
$this->section = '';
$this->format = '';
$this->createAbsoluteUri = false;
$this->addQueryString = false;
$this->addQueryStringMethod = null;
$this->argumentsToBeExcludedFromQueryString = [];
$this->linkAccessRestrictedPages = false;
$this->targetPageUid = null;
$this->targetPageType = 0;
$this->noCache = false;
$this->useCacheHash = true;
$this->argumentPrefix = null;
return $this;
}
/**
* Creates an URI used for linking to an Extbase action.
* Works in Frontend and Backend mode of TYPO3.
*
* @param string $actionName Name of the action to be called
* @param array $controllerArguments Additional query parameters. Will be "namespaced" and merged with $this->arguments.
* @param string $controllerName Name of the target controller. If not set, current ControllerName is used.
* @param string $extensionName Name of the target extension, without underscores. If not set, current ExtensionName is used.
* @param string $pluginName Name of the target plugin. If not set, current PluginName is used.
* @return string the rendered URI
* @see build()
*/
public function uriFor($actionName = null, $controllerArguments = [], $controllerName = null, $extensionName = null, $pluginName = null)
{
if ($actionName !== null) {
$controllerArguments['action'] = $actionName;
}
if ($controllerName !== null) {
$controllerArguments['controller'] = $controllerName;
} else {
$controllerArguments['controller'] = $this->request->getControllerName();
}
if ($extensionName === null) {
$extensionName = $this->request->getControllerExtensionName();
}
if ($pluginName === null && $this->environmentService->isEnvironmentInFrontendMode()) {
$pluginName = $this->extensionService->getPluginNameByAction($extensionName, $controllerArguments['controller'], $controllerArguments['action']);
}
if ($pluginName === null) {
$pluginName = $this->request->getPluginName();
}
if ($this->environmentService->isEnvironmentInFrontendMode() && $this->configurationManager->isFeatureEnabled('skipDefaultArguments')) {
$controllerArguments = $this->removeDefaultControllerAndAction($controllerArguments, $extensionName, $pluginName);
}
if ($this->targetPageUid === null && $this->environmentService->isEnvironmentInFrontendMode()) {
$this->targetPageUid = $this->extensionService->getTargetPidByPlugin($extensionName, $pluginName);
}
if ($this->format !== '') {
$controllerArguments['format'] = $this->format;
}
if ($this->argumentPrefix !== null) {
$prefixedControllerArguments = [$this->argumentPrefix => $controllerArguments];
} else {
$pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
$prefixedControllerArguments = [$pluginNamespace => $controllerArguments];
}
ArrayUtility::mergeRecursiveWithOverrule($this->arguments, $prefixedControllerArguments);
return $this->build();
}
/**
* This removes controller and/or action arguments from given controllerArguments
* if they are equal to the default controller/action of the target plugin.
* Note: This is only active in FE mode and if feature "skipDefaultArguments" is enabled
*
* @see \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::isFeatureEnabled()
* @param array $controllerArguments the current controller arguments to be modified
* @param string $extensionName target extension name
* @param string $pluginName target plugin name
* @return array
*/
protected function removeDefaultControllerAndAction(array $controllerArguments, $extensionName, $pluginName)
{
$defaultControllerName = $this->extensionService->getDefaultControllerNameByPlugin($extensionName, $pluginName);
if (isset($controllerArguments['action'])) {
$defaultActionName = $this->extensionService->getDefaultActionNameByPluginAndController($extensionName, $pluginName, $controllerArguments['controller']);
if ($controllerArguments['action'] === $defaultActionName) {
unset($controllerArguments['action']);
}
}
if ($controllerArguments['controller'] === $defaultControllerName) {
unset($controllerArguments['controller']);
}
return $controllerArguments;
}
/**
* Builds the URI
* Depending on the current context this calls buildBackendUri() or buildFrontendUri()
*
* @return string The URI
* @see buildBackendUri()
* @see buildFrontendUri()
*/
public function build()
{
if ($this->environmentService->isEnvironmentInBackendMode()) {
return $this->buildBackendUri();
}
return $this->buildFrontendUri();
}
/**
* Builds the URI, backend flavour
* The resulting URI is relative and starts with "index.php".
* The settings pageUid, pageType, noCache, useCacheHash & linkAccessRestrictedPages
* will be ignored in the backend.
*
* @return string The URI
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function buildBackendUri()
{
$arguments = [];
if ($this->addQueryString === true) {
if ($this->addQueryStringMethod) {
switch ($this->addQueryStringMethod) {
case 'GET':
$arguments = GeneralUtility::_GET();
break;
case 'POST':
$arguments = GeneralUtility::_POST();
break;
case 'GET,POST':
$arguments = array_replace_recursive(GeneralUtility::_GET(), GeneralUtility::_POST());
break;
case 'POST,GET':
$arguments = array_replace_recursive(GeneralUtility::_POST(), GeneralUtility::_GET());
break;
default:
// Explode GET vars recursively
parse_str(GeneralUtility::getIndpEnv('QUERY_STRING'), $arguments);
}
} else {
$arguments = GeneralUtility::_GET();
}
foreach ($this->argumentsToBeExcludedFromQueryString as $argumentToBeExcluded) {
$argumentArrayToBeExcluded = [];
parse_str($argumentToBeExcluded, $argumentArrayToBeExcluded);
$arguments = ArrayUtility::arrayDiffAssocRecursive($arguments, $argumentArrayToBeExcluded);
}
} else {
$id = GeneralUtility::_GP('id');
// backwards compatibility: check for M parameter
// @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
$route = GeneralUtility::_GP('route') ?: GeneralUtility::_GP('M');
if ($id !== null) {
$arguments['id'] = $id;
}
if ($route !== null) {
$arguments['route'] = $route;
}
}
ArrayUtility::mergeRecursiveWithOverrule($arguments, $this->arguments);
$arguments = $this->convertDomainObjectsToIdentityArrays($arguments);
$this->lastArguments = $arguments;
$routeName = $arguments['route'] ?? null;
unset($arguments['route'], $arguments['token']);
$backendUriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
try {
if ($this->request instanceof WebRequest && $this->createAbsoluteUri) {
$uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
} else {
$uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
}
} catch (ResourceNotFoundException $e) {
try {
if ($this->request instanceof WebRequest && $this->createAbsoluteUri) {
$uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
} else {
$uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
}
} catch (RouteNotFoundException $e) {
$uri = '';
}
}
if ($this->section !== '') {
$uri .= '#' . $this->section;
}
return $uri;
}
/**
* Builds the URI, frontend flavour
*
* @return string The URI
* @see buildTypolinkConfiguration()
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function buildFrontendUri()
{
$typolinkConfiguration = $this->buildTypolinkConfiguration();
if ($this->createAbsoluteUri === true) {
$typolinkConfiguration['forceAbsoluteUrl'] = true;
if ($this->absoluteUriScheme !== null) {
$typolinkConfiguration['forceAbsoluteUrl.']['scheme'] = $this->absoluteUriScheme;
}
}
$uri = $this->contentObject->typoLink_URL($typolinkConfiguration);
return $uri;
}
/**
* Builds a TypoLink configuration array from the current settings
*
* @return array typolink configuration array
* @see TSref/typolink
*/
protected function buildTypolinkConfiguration()
{
$typolinkConfiguration = [];
$typolinkConfiguration['parameter'] = $this->targetPageUid ?? $GLOBALS['TSFE']->id;
if ($this->targetPageType !== 0) {
$typolinkConfiguration['parameter'] .= ',' . $this->targetPageType;
} elseif ($this->format !== '') {
$targetPageType = $this->extensionService->getTargetPageTypeByFormat($this->request->getControllerExtensionName(), $this->format);
$typolinkConfiguration['parameter'] .= ',' . $targetPageType;
}
if (!empty($this->arguments)) {
$arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments);
$this->lastArguments = $arguments;
$typolinkConfiguration['additionalParams'] = HttpUtility::buildQueryString($arguments, '&');
}
if ($this->addQueryString === true) {
$typolinkConfiguration['addQueryString'] = 1;
if (!empty($this->argumentsToBeExcludedFromQueryString)) {
$typolinkConfiguration['addQueryString.'] = [
'exclude' => implode(',', $this->argumentsToBeExcludedFromQueryString)
];
}
if ($this->addQueryStringMethod) {
$typolinkConfiguration['addQueryString.']['method'] = $this->addQueryStringMethod;
}
}
if ($this->noCache === true) {
$typolinkConfiguration['no_cache'] = 1;
} elseif ($this->useCacheHash) {
$typolinkConfiguration['useCacheHash'] = 1;
}
if ($this->section !== '') {
$typolinkConfiguration['section'] = $this->section;
}
if ($this->linkAccessRestrictedPages === true) {
$typolinkConfiguration['linkAccessRestrictedPages'] = 1;
}
return $typolinkConfiguration;
}
/**
* Recursively iterates through the specified arguments and turns instances of type \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
* into an arrays containing the uid of the domain object.
*
* @param array $arguments The arguments to be iterated
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException
* @return array The modified arguments array
*/
protected function convertDomainObjectsToIdentityArrays(array $arguments)
{
foreach ($arguments as $argumentKey => $argumentValue) {
// if we have a LazyLoadingProxy here, make sure to get the real instance for further processing
if ($argumentValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
$argumentValue = $argumentValue->_loadRealInstance();
// also update the value in the arguments array, because the lazyLoaded object could be
// hidden and thus the $argumentValue would be NULL.
$arguments[$argumentKey] = $argumentValue;
}
if ($argumentValue instanceof \Iterator) {
$argumentValue = $this->convertIteratorToArray($argumentValue);
}
if ($argumentValue instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) {
if ($argumentValue->getUid() !== null) {
$arguments[$argumentKey] = $argumentValue->getUid();
} elseif ($argumentValue instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
$arguments[$argumentKey] = $this->convertTransientObjectToArray($argumentValue);
} else {
throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException('Could not serialize Domain Object ' . get_class($argumentValue) . '. It is neither an Entity with identity properties set, nor a Value Object.', 1260881688);
}
} elseif (is_array($argumentValue)) {
$arguments[$argumentKey] = $this->convertDomainObjectsToIdentityArrays($argumentValue);
}
}
return $arguments;
}
/**
* @param \Iterator $iterator
* @return array
*/
protected function convertIteratorToArray(\Iterator $iterator)
{
if (method_exists($iterator, 'toArray')) {
$array = $iterator->toArray();
} else {
$array = iterator_to_array($iterator);
}
return $array;
}
/**
* Converts a given object recursively into an array.
*
* @param \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject $object
* @return array
* @todo Refactore this into convertDomainObjectsToIdentityArrays()
* @internal only to be used within Extbase, not part of TYPO3 Core API.
*/
public function convertTransientObjectToArray(\TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject $object)
{
$result = [];
foreach ($object->_getProperties() as $propertyName => $propertyValue) {
if ($propertyValue instanceof \Iterator) {
$propertyValue = $this->convertIteratorToArray($propertyValue);
}
if ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) {
if ($propertyValue->getUid() !== null) {
$result[$propertyName] = $propertyValue->getUid();
} else {
$result[$propertyName] = $this->convertTransientObjectToArray($propertyValue);
}
} elseif (is_array($propertyValue)) {
$result[$propertyName] = $this->convertDomainObjectsToIdentityArrays($propertyValue);
} else {
$result[$propertyName] = $propertyValue;
}
}
return $result;
}
}