| Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-form/Classes/Domain/Finishers/ |
| Current File : /var/www/surf/TYPO3/vendor/typo3/cms-form/Classes/Domain/Finishers/AbstractFinisher.php |
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Finishers;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
use TYPO3\CMS\Form\Service\TranslationService;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Finisher base class.
*
* Scope: frontend
* **This class is meant to be sub classed by developers**
*/
abstract class AbstractFinisher implements FinisherInterface
{
/**
* @var string
*/
protected $finisherIdentifier = '';
/**
* @var string
*/
protected $shortFinisherIdentifier = '';
/**
* The options which have been set from the outside. Instead of directly
* accessing them, you should rather use parseOption().
*
* @var array
*/
protected $options = [];
/**
* These are the default options of the finisher.
* Override them in your concrete implementation.
* Default options should not be changed from "outside"
*
* @var array
*/
protected $defaultOptions = [];
/**
* @var \TYPO3\CMS\Form\Domain\Finishers\FinisherContext
*/
protected $finisherContext;
/**
* @param string $finisherIdentifier The identifier for this finisher
*/
public function setFinisherIdentifier(string $finisherIdentifier): void
{
$this->finisherIdentifier = $finisherIdentifier;
$this->shortFinisherIdentifier = preg_replace('/Finisher$/', '', $this->finisherIdentifier) ?? '';
}
public function getFinisherIdentifier(): string
{
return $this->finisherIdentifier;
}
/**
* @param array $options configuration options in the format ['option1' => 'value1', 'option2' => 'value2', ...]
*/
public function setOptions(array $options)
{
$this->options = $options;
}
/**
* Sets a single finisher option (@see setOptions())
*
* @param string $optionName name of the option to be set
* @param mixed $optionValue value of the option
*/
public function setOption(string $optionName, $optionValue)
{
$this->options[$optionName] = $optionValue;
}
/**
* Executes the finisher
*
* @param FinisherContext $finisherContext The Finisher context that contains the current Form Runtime and Response
* @return string|null
*/
final public function execute(FinisherContext $finisherContext)
{
$this->finisherContext = $finisherContext;
if (!$this->isEnabled()) {
return null;
}
return $this->executeInternal();
}
/**
* This method is called in the concrete finisher whenever self::execute() is called.
*
* Override and fill with your own implementation!
*
* @return string|null
*/
abstract protected function executeInternal();
/**
* Read the option called $optionName from $this->options, and parse {...}
* as object accessors.
*
* Then translate the value.
*
* If $optionName was not found, the corresponding default option is returned (from $this->defaultOptions)
*
* @param string $optionName
* @return string|array|int|null
*/
protected function parseOption(string $optionName)
{
if ($optionName === 'translation') {
return null;
}
try {
$optionValue = ArrayUtility::getValueByPath($this->options, $optionName, '.');
} catch (MissingArrayPathException $exception) {
$optionValue = null;
}
try {
$defaultValue = ArrayUtility::getValueByPath($this->defaultOptions, $optionName, '.');
} catch (MissingArrayPathException $exception) {
$defaultValue = null;
}
if ($optionValue === null && $defaultValue !== null) {
$optionValue = $defaultValue;
}
if ($optionValue === null) {
return null;
}
if (!is_string($optionValue) && !is_array($optionValue)) {
return $optionValue;
}
$formRuntime = $this->finisherContext->getFormRuntime();
$optionValue = $this->substituteRuntimeReferences($optionValue, $formRuntime);
if (is_string($optionValue)) {
$translationOptions = isset($this->options['translation']) && \is_array($this->options['translation'])
? $this->options['translation']
: [];
$optionValue = $this->translateFinisherOption(
$optionValue,
$formRuntime,
$optionName,
$optionValue,
$translationOptions
);
$optionValue = $this->substituteRuntimeReferences($optionValue, $formRuntime);
}
if (empty($optionValue)) {
if ($defaultValue !== null) {
$optionValue = $defaultValue;
}
}
return $optionValue;
}
/**
* Wraps TranslationService::translateFinisherOption to recursively
* invoke all array items of resolved form state values or nested
* finisher option configuration settings.
*
* @param string|array $subject
* @param FormRuntime $formRuntime
* @param string|array $optionValue
* @return array|string
*/
protected function translateFinisherOption(
$subject,
FormRuntime $formRuntime,
string $optionName,
$optionValue,
array $translationOptions
) {
if (is_array($subject)) {
foreach ($subject as $key => $value) {
$subject[$key] = $this->translateFinisherOption(
$value,
$formRuntime,
$optionName . '.' . $value,
$value,
$translationOptions
);
}
return $subject;
}
return GeneralUtility::makeInstance(TranslationService::class)->translateFinisherOption(
$formRuntime,
$this->finisherIdentifier,
$optionName,
$optionValue,
$translationOptions
);
}
/**
* You can encapsulate an option value with {}.
* This enables you to access every gettable property from the
* TYPO3\CMS\Form\Domain\Runtime\FormRuntime.
*
* For example: {formState.formValues.<elementIdentifier>}
* or {<elementIdentifier>}
*
* Both examples are equal to "$formRuntime->getFormState()->getFormValues()[<elementIdentifier>]"
* There is a special option value '{__currentTimestamp}'.
* This will be replaced with the current timestamp.
*
* @param string|array $needle
* @param FormRuntime $formRuntime
* @return mixed
*/
protected function substituteRuntimeReferences($needle, FormRuntime $formRuntime)
{
// neither array nor string, directly return
if (!is_array($needle) && !is_string($needle)) {
return $needle;
}
// resolve (recursively) all array items
if (is_array($needle)) {
$substitutedNeedle = [];
foreach ($needle as $key => $item) {
$key = $this->substituteRuntimeReferences($key, $formRuntime);
$item = $this->substituteRuntimeReferences($item, $formRuntime);
$substitutedNeedle[$key] = $item;
}
return $substitutedNeedle;
}
// substitute one(!) variable in string which either could result
// again in a string or an array representing multiple values
if (preg_match('/^{([^}]+)}$/', $needle, $matches)) {
return $this->resolveRuntimeReference(
$matches[1],
$formRuntime
);
}
// in case string contains more than just one variable or just a static
// value that does not need to be substituted at all, candidates are:
// * "prefix{variable}suffix
// * "{variable-1},{variable-2}"
// * "some static value"
// * mixed cases of the above
return preg_replace_callback(
'/{([^}]+)}/',
function ($matches) use ($formRuntime) {
$value = $this->resolveRuntimeReference(
$matches[1],
$formRuntime
);
// substitute each match by returning the resolved value
if (!is_array($value)) {
return $value;
}
// now the resolve value is an array that shall substitute
// a variable in a string that probably is not the only one
// or is wrapped with other static string content (see above)
// ... which is just not possible
throw new FinisherException(
'Cannot convert array to string',
1519239265
);
},
$needle
);
}
/**
* Resolving property by name from submitted form data.
*
* @return int|string|array
*/
protected function resolveRuntimeReference(string $property, FormRuntime $formRuntime)
{
if ($property === '__currentTimestamp') {
return time();
}
// try to resolve the path '{...}' within the FormRuntime
$value = ObjectAccess::getPropertyPath($formRuntime, $property);
if (is_object($value)) {
$element = $formRuntime->getFormDefinition()->getElementByIdentifier($property);
if (!$element instanceof StringableFormElementInterface) {
throw new FinisherException(
sprintf('Cannot convert object value of "%s" to string', $property),
1574362327
);
}
$value = $element->valueToString($value);
}
if ($value === null) {
// try to resolve the path '{...}' within the FinisherVariableProvider
$value = ObjectAccess::getPropertyPath(
$this->finisherContext->getFinisherVariableProvider(),
$property
);
}
if ($value !== null) {
return $value;
}
// in case no value could be resolved
return '{' . $property . '}';
}
/**
* Returns whether this finisher is enabled
*/
public function isEnabled(): bool
{
return !isset($this->options['renderingOptions']['enabled']) || (bool)$this->parseOption('renderingOptions.enabled') === true;
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}