TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing;
|
||||
|
||||
/**
|
||||
* Helper for array processing
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessing
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $expression;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
public function __construct(string $identifier, string $expression, callable $processor)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
$this->expression = $expression;
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getExpression(): string
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getProcessor(): callable
|
||||
{
|
||||
return $this->processor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\ArrayProcessorException;
|
||||
|
||||
/**
|
||||
* Helper for array processing
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = ArrayUtility::flattenPlain($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayProcessing[] $processings
|
||||
*/
|
||||
public function forEach(...$processings): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$processings = $this->getValidProcessings($processings);
|
||||
foreach ($this->data as $key => $value) {
|
||||
foreach ($processings as $processing) {
|
||||
// explicitly escaping non-escaped '#' which is used
|
||||
// as PCRE delimiter in the following processing
|
||||
$expression = preg_replace(
|
||||
'/(?<!\\\\)#/',
|
||||
'\\#',
|
||||
$processing->getExpression()
|
||||
);
|
||||
|
||||
if (preg_match('#' . $expression . '#', $key, $matches)) {
|
||||
$identifier = $processing->getIdentifier();
|
||||
$processor = $processing->getProcessor();
|
||||
$result[$identifier] = $result[$identifier] ?? [];
|
||||
$result[$identifier][$key] = $processor($key, $value, $matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayProcessing[]
|
||||
* @throws ArrayProcessorException
|
||||
*/
|
||||
protected function getValidProcessings(array $allProcessings): array
|
||||
{
|
||||
$validProcessings = [];
|
||||
$identifiers = [];
|
||||
foreach ($allProcessings as $processing) {
|
||||
if ($processing instanceof ArrayProcessing) {
|
||||
if (in_array($processing->getIdentifier(), $identifiers, true)) {
|
||||
throw new ArrayProcessorException(
|
||||
'ArrayProcessing identifier must be unique.',
|
||||
1528638085
|
||||
);
|
||||
}
|
||||
$identifiers[] = $processing->getIdentifier();
|
||||
$validProcessings[] = $processing;
|
||||
}
|
||||
}
|
||||
return $validProcessings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Http\ApplicationType;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface as ExtbaseConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PrototypeNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AdditionalElementPropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\ExtractorDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\IsCreatableFormElementExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\MultiValuePropertiesExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\PredefinedDefaultsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\PropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\SelectOptionsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\IsCreatablePropertyCollectionElementExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\MultiValuePropertiesExtractor as CollectionMultiValuePropertiesExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\PredefinedDefaultsExtractor as CollectionPredefinedDefaultsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\PropertyPathsExtractor as CollectionPropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\SelectOptionsExtractor as CollectionSelectOptionsExtractor;
|
||||
use TYPO3\CMS\Form\Event\AfterFormDefinitionValidationConfigurationIsBuiltEvent;
|
||||
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface as ExtFormConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
|
||||
/**
|
||||
* Helper for configuration settings
|
||||
* Scope: frontend / backend
|
||||
*
|
||||
* @todo: Get rid of ConfigurationManagerInterface by handing over $formSettings to
|
||||
* methods instead to make the indirect dependency to Request explicit in consuming classes.
|
||||
* @todo: Declare readonly when ConfigurationService is no longer injected lazy, or wait
|
||||
* for PHP 8.3 minimum for symfony to allow both lazy and readonly dependencies.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
class ConfigurationService
|
||||
{
|
||||
public function __construct(
|
||||
protected ExtbaseConfigurationManagerInterface $extbaseConfigurationManager,
|
||||
protected ExtFormConfigurationManagerInterface $extFormConfigurationManager,
|
||||
protected TranslationService $translationService,
|
||||
#[Autowire(service: 'cache.assets')]
|
||||
protected FrontendInterface $assetsCache,
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
protected FrontendInterface $runtimeCache,
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the prototype configuration
|
||||
*
|
||||
* @param string $prototypeName name of the prototype to get the configuration for
|
||||
* @return array the prototype configuration
|
||||
* @throws PrototypeNotFoundException if prototype with the name $prototypeName was not found
|
||||
*/
|
||||
public function getPrototypeConfiguration(string $prototypeName): array
|
||||
{
|
||||
$formSettings = $this->getFormSettings();
|
||||
if (!isset($formSettings['prototypes'][$prototypeName])) {
|
||||
throw new PrototypeNotFoundException(sprintf('The Prototype "%s" was not found.', $prototypeName), 1475924277);
|
||||
}
|
||||
return $formSettings['prototypes'][$prototypeName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all prototype names which are defined within "formManager.selectablePrototypesConfiguration.*.identifier"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getSelectablePrototypeNamesDefinedInFormEditorSetup(): array
|
||||
{
|
||||
$formSettings = $this->getFormSettings();
|
||||
$returnValue = GeneralUtility::makeInstance(
|
||||
ArrayProcessor::class,
|
||||
$formSettings['formManager']['selectablePrototypesConfiguration'] ?? []
|
||||
)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'selectablePrototypeNames',
|
||||
'^([\d]+)\.identifier$',
|
||||
static function ($_, $value) {
|
||||
return $value;
|
||||
}
|
||||
)
|
||||
);
|
||||
return array_values($returnValue['selectablePrototypeNames'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form element property is defined in the form setup.
|
||||
* If a form element property is defined in the form setup then it
|
||||
* means that the form element property can be written by the form editor.
|
||||
* A form element property can be written if the property path is defined within
|
||||
* the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.*.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.additionalElementPropertyPaths
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.additionalElementPropertyPaths
|
||||
* If a form editor property "templateName" is
|
||||
* "Inspector-PropertyGridEditor" or "Inspector-MultiSelectEditor" or "Inspector-ValidationErrorMessageEditor"
|
||||
* it means that the form editor property "propertyPath" is interpreted as a so called "multiValueProperty".
|
||||
* A "multiValueProperty" can contain any subproperties relative to the value from "propertyPath" which are valid.
|
||||
* If "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-PropertyGridEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.propertyPath = options.xxx"
|
||||
* then (for example) "options.xxx.yyy" is a valid property path to write.
|
||||
* If you use a custom form editor "inspector editor" implementation which does not define the writable
|
||||
* property paths by one of the above described inspector editor properties (e.g "propertyPath") within
|
||||
* the form setup, you must provide the writable property paths via the
|
||||
* AfterFormDefinitionValidationConfigurationIsBuiltEvent PSR-14 event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementPropertyDefinedInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$subConfig = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()] ?? [];
|
||||
return $this->isPropertyDefinedInFormEditorSetup($dto->getPropertyPath(), $subConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form elements finisher|validator property is defined in the form setup.
|
||||
* If a form elements finisher|validator property is defined in the form setup then it
|
||||
* means that the form elements finisher|validator property can be written by the form editor.
|
||||
* A form elements finisher|validator property can be written if the property path is defined within
|
||||
* the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.*.propertyPath
|
||||
* If a form elements finisher|validator property "templateName" is
|
||||
* "Inspector-PropertyGridEditor" or "Inspector-MultiSelectEditor" or "Inspector-ValidationErrorMessageEditor"
|
||||
* it means that the form editor property "propertyPath" is interpreted as a so called "multiValueProperty".
|
||||
* A "multiValueProperty" can contain any subproperties relative to the value from "propertyPath" which are valid.
|
||||
* If "formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.templateName = Inspector-PropertyGridEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.propertyPath = options.xxx"
|
||||
* that (for example) "options.xxx.yyy" is a valid property path to write.
|
||||
* If you use a custom form elements finisher|validator editor implementation which does not define the writable
|
||||
* property paths by one of the above described inspector editor properties (e.g "propertyPath") within
|
||||
* the form setup, you must provide the writable property paths via the
|
||||
* AfterFormDefinitionValidationConfigurationIsBuiltEvent PSR-14 event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionPropertyDefinedInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$subConfig = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()] ?? [];
|
||||
return $this->isPropertyDefinedInFormEditorSetup($dto->getPropertyPath(), $subConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a form element editor has a property called "selectOptions"
|
||||
* (e.g. editors with templateName "Inspector-SingleSelectEditor" or "Inspector-MultiSelectEditor")
|
||||
* then only the defined values within the selectOptions are allowed to be written
|
||||
* by the form editor.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValueFormElementProperty($dto);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['selectOptions'][$propertyPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "selectOptions" value for a form element property from the form setup.
|
||||
*
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getAllowedValuesForFormElementPropertyFromFormEditorSetup(
|
||||
ValidationDto $dto,
|
||||
bool $translated = true
|
||||
): array {
|
||||
if (!$this->formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No selectOptions found for form element type "%s" and property path "%s"',
|
||||
$dto->getFormElementType(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1614264312
|
||||
);
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$property = $translated ? 'selectOptions' : 'untranslatedSelectOptions';
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValueFormElementProperty($dto);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()][$property][$propertyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* If a form elements finisher|validator editor has a property called "selectOptions"
|
||||
* (e.g. editors with templateName "Inspector-SingleSelectEditor" or "Inspector-MultiSelectEditor")
|
||||
* then only the defined values within the selectOptions are allowed to be written
|
||||
* by the form editor.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValuePropertyCollectionElement($dto);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['selectOptions'][$propertyPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "selectOptions" value for a form elements finisher|validator property from the form setup.
|
||||
*
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup(
|
||||
ValidationDto $dto,
|
||||
bool $translated = true
|
||||
): array {
|
||||
if (!$this->propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No selectOptions found for property collection "%s" and identifier "%s" and property path "%s"',
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1614264313
|
||||
);
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$property = $translated ? 'selectOptions' : 'untranslatedSelectOptions';
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValuePropertyCollectionElement($dto);
|
||||
return $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()][$property][$propertyPath];
|
||||
}
|
||||
|
||||
protected function getBasePropertyPathFromMultiValueFormElementProperty(ValidationDto $dto): string
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$propertyPath = $dto->getPropertyPath();
|
||||
$multiValueProperties = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['multiValueProperties'] ?? [];
|
||||
foreach ($multiValueProperties as $multiValueProperty) {
|
||||
if (str_starts_with($propertyPath, $multiValueProperty)) {
|
||||
$propertyPath = $multiValueProperty;
|
||||
}
|
||||
}
|
||||
return $propertyPath;
|
||||
}
|
||||
|
||||
protected function getBasePropertyPathFromMultiValuePropertyCollectionElement(ValidationDto $dto): string
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$propertyPath = $dto->getPropertyPath();
|
||||
$multiValueProperties = $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['multiValueProperties'] ?? [];
|
||||
foreach ($multiValueProperties as $multiValueProperty) {
|
||||
if (str_starts_with($propertyPath, $multiValueProperty)) {
|
||||
$propertyPath = $multiValueProperty;
|
||||
}
|
||||
}
|
||||
return $propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form element property is defined in "predefinedDefaults" in the form setup.
|
||||
* If a form element property is defined in the "predefinedDefaults" in the form setup then it
|
||||
* means that the form element property can be written by the form editor.
|
||||
* A form element default property is defined within the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['predefinedDefaults'][$dto->getPropertyPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "predefinedDefaults" value for a form element property from the form setup.
|
||||
* A form element default property is defined within the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getFormElementPredefinedDefaultValueFromFormEditorSetup(ValidationDto $dto, bool $translated = true): mixed
|
||||
{
|
||||
if (!$this->isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No predefinedDefaults found for form element type "%s" and property path "%s"',
|
||||
$dto->getFormElementType(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1528578401
|
||||
);
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$property = $translated ? 'predefinedDefaults' : 'untranslatedPredefinedDefaults';
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()][$property][$dto->getPropertyPath()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form elements finisher|validator property is defined in "predefinedDefaults" in the form setup.
|
||||
* If a form elements finisher|validator property is defined in "predefinedDefaults" in the form setup then it
|
||||
* means that the form elements finisher|validator property can be written by the form editor.
|
||||
* A form elements finisher|validator default property is defined within the following form editor properties:
|
||||
* * <validatorsDefinition|finishersDefinition>.<index>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['predefinedDefaults'][$dto->getPropertyPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "predefinedDefaults" value for a form elements finisher|validator property from the form setup.
|
||||
* A form elements finisher|validator default property is defined within the following form editor properties:
|
||||
* * <validatorsDefinition|finishersDefinition>.<index>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup(ValidationDto $dto, bool $translated = true): mixed
|
||||
{
|
||||
if (!$this->isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No predefinedDefaults found for property collection "%s" and identifier "%s" and property path "%s"',
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1528578402
|
||||
);
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$property = $translated ? 'predefinedDefaults' : 'untranslatedPredefinedDefaults';
|
||||
return $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()][$property][$dto->getPropertyPath()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form element is creatable through the form editor.
|
||||
* A form element is creatable if the following properties are set:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.group
|
||||
* * formElementsDefinition.<formElementType>.formEditor.groupSorting
|
||||
* And the value from "formElementsDefinition.<formElementType>.formEditor.group" is
|
||||
* one of the keys within "formEditor.formElementGroups"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementTypeCreatableByFormEditor(ValidationDto $dto): bool
|
||||
{
|
||||
if ($dto->getFormElementType() === 'Form') {
|
||||
return true;
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['creatable'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form elements finisher|validator is creatable through the form editor.
|
||||
* A form elements finisher|validator is creatable if the following conditions are true:
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-FinishersEditor"
|
||||
* or
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-ValidatorsEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.selectOptions.<index>.value = <finisherIdentifier|validatorIdentifier>"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionElementIdentifierCreatableByFormEditor(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['creatable'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form elements type is defined within the form setup.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementTypeDefinedInFormSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($dto->getPrototypeName());
|
||||
return ArrayUtility::isValidPath(
|
||||
$prototypeConfiguration,
|
||||
'formElementsDefinition.' . $dto->getFormElementType(),
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getAllBackendTranslationsForTranslationKeys(array $keys, string $prototypeName): array
|
||||
{
|
||||
$translations = [];
|
||||
foreach ($keys as $key) {
|
||||
if (!is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
$translations[$key] = $this->getAllBackendTranslationsForTranslationKey($key, $prototypeName);
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
|
||||
public function getAllBackendTranslationsForTranslationKey(string $key, string $prototypeName): array
|
||||
{
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($prototypeName);
|
||||
return $this->translationService->translateToAllBackendLanguages(
|
||||
$key,
|
||||
[],
|
||||
$prototypeConfiguration['formEditor']['translationFiles'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
protected function getFormSettings(): array
|
||||
{
|
||||
// @todo: This is needed for extFormConfigurationManager to apply stdWrap on TS configuration.
|
||||
// Find a way to get rid of this.
|
||||
$isFrontend = false;
|
||||
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
|
||||
if ($request instanceof ServerRequestInterface) {
|
||||
$isFrontend = ApplicationType::fromRequest($request)->isFrontend();
|
||||
}
|
||||
// @todo: Note this code relies on the fact that the request has been set to ExtbaseConfigurationManagerInterface already.
|
||||
$typoScriptSettings = $this->extbaseConfigurationManager->getConfiguration(ExtbaseConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'form');
|
||||
return $this->extFormConfigurationManager->getYamlConfiguration($typoScriptSettings, $isFrontend, $isFrontend ? $request : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the form editor configurations which are needed to check if a
|
||||
* form definition property can be written or not.
|
||||
*/
|
||||
protected function buildFormDefinitionValidationConfigurationFromFormEditorSetup(string $prototypeName): array
|
||||
{
|
||||
$cacheKey = implode('_', ['buildFormDefinitionValidationConfigurationFromFormEditorSetup', $prototypeName]);
|
||||
$configuration = $this->getCacheEntry($cacheKey);
|
||||
if ($configuration === null) {
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($prototypeName);
|
||||
$extractorDto = GeneralUtility::makeInstance(ExtractorDto::class, $prototypeConfiguration);
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $prototypeConfiguration)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.(propertyPath|.*\.propertyPath)$',
|
||||
GeneralUtility::makeInstance(PropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementAdditionalElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.additionalElementPropertyPaths\.([\d]+)',
|
||||
GeneralUtility::makeInstance(AdditionalElementPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementRelativeMultiValueProperties',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(MultiValuePropertiesExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementSelectOptions',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.selectOptions\.([\d]+)\.value$',
|
||||
GeneralUtility::makeInstance(SelectOptionsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementPredefinedDefaults',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.predefinedDefaults\.(.+)$',
|
||||
GeneralUtility::makeInstance(PredefinedDefaultsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementCreatable',
|
||||
'^formElementsDefinition\.(.*)\.formEditor.group$',
|
||||
GeneralUtility::makeInstance(IsCreatableFormElementExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionCreatable',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(IsCreatablePropertyCollectionElementExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.(propertyPath|.*\.propertyPath)$',
|
||||
GeneralUtility::makeInstance(CollectionPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionAdditionalElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.additionalElementPropertyPaths\.([\d]+)',
|
||||
GeneralUtility::makeInstance(AdditionalElementPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionRelativeMultiValueProperties',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(CollectionMultiValuePropertiesExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionSelectOptions',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.selectOptions\.([\d]+)\.value$',
|
||||
GeneralUtility::makeInstance(CollectionSelectOptionsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionPredefinedDefaults',
|
||||
'^(validatorsDefinition|finishersDefinition)\.(.*)\.formEditor\.predefinedDefaults\.(.+)$',
|
||||
GeneralUtility::makeInstance(CollectionPredefinedDefaultsExtractor::class, $extractorDto)
|
||||
)
|
||||
);
|
||||
$configuration = $extractorDto->getResult();
|
||||
$configuration = $this->translateValues($prototypeConfiguration, $configuration);
|
||||
$configuration = $this->eventDispatcher
|
||||
->dispatch(new AfterFormDefinitionValidationConfigurationIsBuiltEvent($prototypeName, $configuration))
|
||||
->getConfiguration();
|
||||
$this->setCacheEntry($cacheKey, $configuration);
|
||||
}
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
protected function isPropertyDefinedInFormEditorSetup(string $propertyPath, array $subConfig): bool
|
||||
{
|
||||
if (empty($subConfig)) {
|
||||
return false;
|
||||
}
|
||||
if (in_array($propertyPath, $subConfig['propertyPaths'] ?? [], true)
|
||||
|| in_array($propertyPath, $subConfig['additionalElementPropertyPaths'] ?? [], true)
|
||||
|| in_array($propertyPath, $subConfig['additionalPropertyPaths'] ?? [], true)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
foreach ($subConfig['multiValueProperties'] ?? [] as $relativeMultiValueProperty) {
|
||||
if (str_starts_with($propertyPath, $relativeMultiValueProperty)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function translateValues(array $prototypeConfiguration, array $configuration): array
|
||||
{
|
||||
if (isset($configuration['formElements'])) {
|
||||
$configuration['formElements'] = $this->translatePredefinedDefaults(
|
||||
$prototypeConfiguration,
|
||||
$configuration['formElements']
|
||||
);
|
||||
$configuration['formElements'] = $this->translateSelectOptions(
|
||||
$prototypeConfiguration,
|
||||
$configuration['formElements']
|
||||
);
|
||||
}
|
||||
foreach ($configuration['collections'] ?? [] as $name => $collections) {
|
||||
$configuration['collections'][$name] = $this->translatePredefinedDefaults($prototypeConfiguration, $collections);
|
||||
$configuration['collections'][$name] = $this->translateSelectOptions($prototypeConfiguration, $configuration['collections'][$name]);
|
||||
}
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
protected function translatePredefinedDefaults(array $prototypeConfiguration, array $formElements): array
|
||||
{
|
||||
foreach ($formElements as $name => $formElement) {
|
||||
if (!isset($formElement['predefinedDefaults'])) {
|
||||
continue;
|
||||
}
|
||||
$formElement['untranslatedPredefinedDefaults'] = $formElement['predefinedDefaults'];
|
||||
$formElement['predefinedDefaults'] = $this->translationService->translateValuesRecursive(
|
||||
$formElement['predefinedDefaults'],
|
||||
$prototypeConfiguration['formEditor']['translationFiles'] ?? []
|
||||
);
|
||||
$formElements[$name] = $formElement;
|
||||
}
|
||||
return $formElements;
|
||||
}
|
||||
|
||||
protected function translateSelectOptions(array $prototypeConfiguration, array $formElements): array
|
||||
{
|
||||
foreach ($formElements as $name => $formElement) {
|
||||
if (empty($formElement['selectOptions']) || !is_array($formElement['selectOptions'])) {
|
||||
continue;
|
||||
}
|
||||
$formElement['untranslatedSelectOptions'] = $formElement['selectOptions'];
|
||||
$formElement['selectOptions'] = $this->translationService->translateValuesRecursive(
|
||||
$formElement['selectOptions'],
|
||||
$prototypeConfiguration['formEditor']['translationFiles'] ?? []
|
||||
);
|
||||
$formElements[$name] = $formElement;
|
||||
}
|
||||
return $formElements;
|
||||
}
|
||||
|
||||
protected function getCacheEntry(string $cacheKey): mixed
|
||||
{
|
||||
$cacheKey = 'form_' . $cacheKey;
|
||||
if ($this->runtimeCache->has($cacheKey)) {
|
||||
return $this->runtimeCache->get($cacheKey);
|
||||
}
|
||||
if ($this->assetsCache->has($cacheKey)) {
|
||||
return $this->assetsCache->get($cacheKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function setCacheEntry(string $cacheKey, mixed $value): void
|
||||
{
|
||||
$cacheKey = 'form_' . $cacheKey;
|
||||
$this->runtimeCache->set($cacheKey, $value);
|
||||
$this->assetsCache->set($cacheKey, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessorException extends Exception {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a form setup property was not found.
|
||||
*/
|
||||
class PropertyException extends Exception {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a form prototype for a given name was not found.
|
||||
*/
|
||||
class PrototypeNotFoundException extends Exception {}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @var ProcessorDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
public function __construct(ProcessorDto $converterDto)
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
|
||||
/**
|
||||
* Generate a FlexForm element for a finisher option
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FinisherOptionGenerator extends AbstractProcessor
|
||||
{
|
||||
/**
|
||||
* @param string $_ unused in this context
|
||||
* @param mixed $__ unused in this context
|
||||
* @param array $matches the expression matches from the ArrayProcessor - for example matches of ^(.*)\.config\.type$
|
||||
*/
|
||||
public function __invoke(string $_, $__, array $matches)
|
||||
{
|
||||
[, $optionKey] = $matches;
|
||||
|
||||
$finisherIdentifier = $this->converterDto->getFinisherIdentifier();
|
||||
$finisherDefinitionFromSetup = $this->converterDto->getFinisherDefinitionFromSetup();
|
||||
$finisherDefinitionFromFormDefinition = $this->converterDto->getFinisherDefinitionFromFormDefinition();
|
||||
|
||||
try {
|
||||
$elementConfiguration = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromSetup['FormEngine']['elements'],
|
||||
$optionKey,
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
// use the option value from the ext:form setup from the current finisher as default value
|
||||
try {
|
||||
$optionValue = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromSetup,
|
||||
sprintf('options.%s', $optionKey),
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
$optionValue = null;
|
||||
}
|
||||
|
||||
// use the option value from the form definition from the current finisher (if exists) as default value
|
||||
try {
|
||||
$optionValue = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromFormDefinition,
|
||||
sprintf('options.%s', $optionKey),
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
}
|
||||
|
||||
if (isset($elementConfiguration['config'])) {
|
||||
$elementConfiguration['config']['default'] = $optionValue;
|
||||
}
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$elementConfiguration['label'] = (string)($elementConfiguration['label'] ?? '');
|
||||
if (empty($optionValue)) {
|
||||
$optionValue = $languageService->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:empty');
|
||||
} elseif (is_array($optionValue)) {
|
||||
$optionValue = implode(',', $optionValue);
|
||||
}
|
||||
$elementConfiguration['label'] .= sprintf(' (%s: "%s")', $languageService->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:default'), $optionValue);
|
||||
|
||||
$sheetElements = $this->converterDto->getResult();
|
||||
$sheetElements['settings.finishers.' . $finisherIdentifier . '.' . $optionKey] = $elementConfiguration;
|
||||
|
||||
$this->converterDto->setResult($sheetElements);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/**
|
||||
* Data container for finisher FlexForm processing
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ProcessorDto
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $finisherIdentifier;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinitionFromSetup;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinitionFromFormDefinition;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $result = [];
|
||||
|
||||
public function __construct(
|
||||
string $finisherIdentifier,
|
||||
array $finisherDefinitionFromSetup,
|
||||
array $finisherDefinitionFromFormDefinition
|
||||
) {
|
||||
$this->finisherIdentifier = $finisherIdentifier;
|
||||
$this->finisherDefinitionFromSetup = $finisherDefinitionFromSetup;
|
||||
$this->finisherDefinitionFromFormDefinition = $finisherDefinitionFromFormDefinition;
|
||||
}
|
||||
|
||||
public function getFinisherIdentifier(): string
|
||||
{
|
||||
return $this->finisherIdentifier;
|
||||
}
|
||||
|
||||
public function getFinisherDefinitionFromSetup(): array
|
||||
{
|
||||
return $this->finisherDefinitionFromSetup;
|
||||
}
|
||||
|
||||
public function getFinisherDefinitionFromFormDefinition(): array
|
||||
{
|
||||
return $this->finisherDefinitionFromFormDefinition;
|
||||
}
|
||||
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public function setResult(array $result): ProcessorDto
|
||||
{
|
||||
$this->result = $result;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/**
|
||||
* Interface for FlexForm processors
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
public function __construct(ProcessorDto $converterDto);
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value, array $matches);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractConverter implements ConverterInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionToken;
|
||||
|
||||
/**
|
||||
* @var ConverterDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
public function __construct(ConverterDto $converterDto, string $sessionToken = '')
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
$this->sessionToken = $sessionToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataConverter extends AbstractConverter
|
||||
{
|
||||
/**
|
||||
* Add a new value "_orig_<propertyName>" as a sibling of the property key.
|
||||
* "_orig_<propertyName>" is an array which contains the property value
|
||||
* and a hmac hash for the property value.
|
||||
* "_orig_<propertyName>" will be used to validate the form definition on saving.
|
||||
* @see \TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService::validateFormDefinitionProperties()
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$renderablePathParts = explode('.', $key);
|
||||
array_pop($renderablePathParts);
|
||||
|
||||
if (count($renderablePathParts) > 1) {
|
||||
$renderablePath = implode('.', $renderablePathParts);
|
||||
$currentFormElement = ArrayUtility::getValueByPath($formDefinition, $renderablePath, '.');
|
||||
} else {
|
||||
$currentFormElement = $formDefinition;
|
||||
}
|
||||
|
||||
$propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
|
||||
$propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
|
||||
unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
|
||||
|
||||
$this->converterDto
|
||||
->setRenderablePathParts($renderablePathParts)
|
||||
->setFormElementIdentifier($value);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentFormElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'^.*',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataToFormElementPropertyConverter::class,
|
||||
$this->converterDto,
|
||||
$this->sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->converterDto->setPropertyCollectionName($propertyCollectionName);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionIndex => $propertyCollectionElement) {
|
||||
$this->converterDto
|
||||
->setPropertyCollectionIndex((int)$propertyCollectionIndex)
|
||||
->setPropertyCollectionElementIdentifier($propertyCollectionElement['identifier']);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $propertyCollectionElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'^(?!(.*\._label|.*\._value)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataToPropertyCollectionElementConverter::class,
|
||||
$this->converterDto,
|
||||
$this->sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataToFormElementPropertyConverter extends AbstractConverter
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($propertyPathParts);
|
||||
$propertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
$hmacValuePath = implode('.', array_merge($this->converterDto->getRenderablePathParts(), $propertyPathParts));
|
||||
$hmacValue = [
|
||||
'value' => $value,
|
||||
'hmac' => $hashService->hmac(serialize([$this->converterDto->getFormElementIdentifier(), $key, $value]), $this->sessionToken),
|
||||
];
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $hmacValuePath, $hmacValue, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataToPropertyCollectionElementConverter extends AbstractConverter
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($propertyPathParts);
|
||||
$propertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
|
||||
$hmacValuePath = implode('.', array_merge(
|
||||
$this->converterDto->getRenderablePathParts(),
|
||||
[$this->converterDto->getPropertyCollectionName(), $this->converterDto->getPropertyCollectionIndex()],
|
||||
$propertyPathParts
|
||||
));
|
||||
|
||||
$hashService = GeneralUtility::makeInstance(HashService::class);
|
||||
$hmacValue = [
|
||||
'value' => $value,
|
||||
'hmac' => $hashService->hmac(
|
||||
serialize([
|
||||
$this->converterDto->getFormElementIdentifier(),
|
||||
$this->converterDto->getPropertyCollectionName(),
|
||||
$this->converterDto->getPropertyCollectionElementIdentifier(),
|
||||
$key,
|
||||
$value,
|
||||
]),
|
||||
$this->sessionToken
|
||||
),
|
||||
];
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $hmacValuePath, $hmacValue, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConverterDto
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $formDefinition = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $renderablePathParts = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $propertyCollectionIndex = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionElementIdentifier = '';
|
||||
|
||||
public function __construct(array $formDefinition)
|
||||
{
|
||||
$this->formDefinition = $formDefinition;
|
||||
}
|
||||
|
||||
public function getFormDefinition(): array
|
||||
{
|
||||
return $this->formDefinition;
|
||||
}
|
||||
|
||||
public function setFormDefinition(array $formDefinition): ConverterDto
|
||||
{
|
||||
$this->formDefinition = $formDefinition;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRenderablePathParts(): array
|
||||
{
|
||||
return $this->renderablePathParts;
|
||||
}
|
||||
|
||||
public function setRenderablePathParts(array $renderablePathParts): ConverterDto
|
||||
{
|
||||
$this->renderablePathParts = $renderablePathParts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormElementIdentifier(): string
|
||||
{
|
||||
return $this->formElementIdentifier;
|
||||
}
|
||||
|
||||
public function setFormElementIdentifier(string $formElementIdentifier): ConverterDto
|
||||
{
|
||||
$this->formElementIdentifier = $formElementIdentifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPropertyCollectionIndex(): int
|
||||
{
|
||||
return $this->propertyCollectionIndex;
|
||||
}
|
||||
|
||||
public function setPropertyCollectionIndex(int $propertyCollectionIndex): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionIndex = $propertyCollectionIndex;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPropertyCollectionName(): string
|
||||
{
|
||||
return $this->propertyCollectionName;
|
||||
}
|
||||
|
||||
public function setPropertyCollectionName(string $propertyCollectionName): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionName = $propertyCollectionName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPropertyCollectionElementIdentifier(): string
|
||||
{
|
||||
return $this->propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
public function setPropertyCollectionElementIdentifier(string $propertyCollectionElementIdentifier): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionElementIdentifier = $propertyCollectionElementIdentifier;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ConverterInterface
|
||||
{
|
||||
public function __construct(ConverterDto $converterDto, string $sessionToken = '');
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value);
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
|
||||
/**
|
||||
* Apply FlexForm finisher option overrides
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FinisherOptionsFlexFormOverridesConverter
|
||||
{
|
||||
/**
|
||||
* @var FlexFormFinisherOverridesConverterDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
public function __construct(FlexFormFinisherOverridesConverterDto $converterDto)
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for overriding finisher options with flexform settings
|
||||
* Flexform settings "win": When a setting is set in the form
|
||||
* definition and in flexform the one in flexform will overwrite the
|
||||
* one defined in the form definition.
|
||||
*
|
||||
* Here we adjust the parsed configuration and apply the overrides.
|
||||
*
|
||||
* @param string $_ unused in this context
|
||||
* @param mixed $__ unused in this context
|
||||
* @param array $matches the expression matches from the ArrayProcessor - for example matches of ^(.*)\.config\.type$
|
||||
*/
|
||||
public function __invoke(string $_, $__, array $matches): void
|
||||
{
|
||||
[, $optionKey] = $matches;
|
||||
$prototypeFinisherDefinition = $this->converterDto->getPrototypeFinisherDefinition();
|
||||
$finisherDefinition = $this->converterDto->getFinisherDefinition();
|
||||
$finisherIdentifier = $this->converterDto->getFinisherIdentifier();
|
||||
$flexFormSheetSettings = $this->converterDto->getFlexFormSheetSettings();
|
||||
|
||||
try {
|
||||
$value = ArrayUtility::getValueByPath(
|
||||
$flexFormSheetSettings['finishers'][$finisherIdentifier],
|
||||
$optionKey,
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldConfiguration = $prototypeFinisherDefinition['FormEngine']['elements'][$optionKey] ?? [];
|
||||
|
||||
if ($fieldConfiguration['section'] ?? false) {
|
||||
if (!is_array($value) || $value === []) {
|
||||
// Do not process empty values for sections
|
||||
return;
|
||||
}
|
||||
|
||||
$processedOptionValue = [];
|
||||
|
||||
foreach ($value as $optionListValue) {
|
||||
$key = $optionListValue[$fieldConfiguration['sectionItemKey']];
|
||||
$value = $optionListValue[$fieldConfiguration['sectionItemValue']];
|
||||
$processedOptionValue[$key] = $value;
|
||||
}
|
||||
|
||||
$value = $processedOptionValue;
|
||||
}
|
||||
|
||||
$optionPath = 'options.' . $optionKey;
|
||||
|
||||
// Skip additional translation for finisher options that were changed via flexform
|
||||
if ($this->optionValueHasChanged($finisherDefinition, $optionPath, $value)) {
|
||||
$finisherDefinition['options']['translation']['propertiesExcludedFromTranslation'][] = $optionKey;
|
||||
}
|
||||
|
||||
$finisherDefinition = ArrayUtility::setValueByPath($finisherDefinition, $optionPath, $value, '.');
|
||||
|
||||
$this->converterDto->setFinisherDefinition($finisherDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if finisher option value differs from finisher definition.
|
||||
*
|
||||
* Compares the given finisher option value with the corresponding value in the
|
||||
* finisher definition. Returns `true` if both values are equal, `false` otherwise.
|
||||
*
|
||||
* @param array<string, mixed> $finisherDefinition
|
||||
*/
|
||||
protected function optionValueHasChanged(array $finisherDefinition, string $optionPath, mixed $value): bool
|
||||
{
|
||||
try {
|
||||
return $value !== ArrayUtility::getValueByPath($finisherDefinition, $optionPath, '.');
|
||||
} catch (MissingArrayPathException) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FinisherTranslationLanguageConverter extends AbstractConverter
|
||||
{
|
||||
/**
|
||||
* If "finishers.x.options.translation.language" is empty then set the value to "default" and remove
|
||||
* the hmac.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
if (!empty($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $key, 'default', '.');
|
||||
|
||||
$hmacPropertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($hmacPropertyPathParts);
|
||||
$hmacPropertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
$hmacValuePath = implode('.', $hmacPropertyPathParts);
|
||||
|
||||
if (ArrayUtility::isValidPath($formDefinition, $hmacValuePath, '.')) {
|
||||
$formDefinition = ArrayUtility::removeByPath($formDefinition, $hmacValuePath, '.');
|
||||
}
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FlexFormFinisherOverridesConverterDto
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $prototypeFinisherDefinition = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinition = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $finisherIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $flexFormSheetSettings = [];
|
||||
|
||||
public function __construct(
|
||||
array $prototypeFinisherDefinition,
|
||||
array $finisherDefinition,
|
||||
string $finisherIdentifier,
|
||||
array $flexFormSheetSettings
|
||||
) {
|
||||
$this->prototypeFinisherDefinition = $prototypeFinisherDefinition;
|
||||
$this->finisherDefinition = $finisherDefinition;
|
||||
$this->finisherIdentifier = $finisherIdentifier;
|
||||
$this->flexFormSheetSettings = $flexFormSheetSettings;
|
||||
}
|
||||
|
||||
public function getPrototypeFinisherDefinition(): array
|
||||
{
|
||||
return $this->prototypeFinisherDefinition;
|
||||
}
|
||||
|
||||
public function getFinisherDefinition(): array
|
||||
{
|
||||
return $this->finisherDefinition;
|
||||
}
|
||||
|
||||
public function setFinisherDefinition(array $finisherDefinition): FlexFormFinisherOverridesConverterDto
|
||||
{
|
||||
$this->finisherDefinition = $finisherDefinition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFinisherIdentifier(): string
|
||||
{
|
||||
return $this->finisherIdentifier;
|
||||
}
|
||||
|
||||
public function getFlexFormSheetSettings(): array
|
||||
{
|
||||
return $this->flexFormSheetSettings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RemoveHmacDataConverter extends AbstractConverter
|
||||
{
|
||||
/**
|
||||
* Remove the hmac data ("_orig_<propertyName>") for the corresponding property.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
array_pop($propertyPathParts);
|
||||
$propertyPath = implode('.', $propertyPathParts);
|
||||
$formDefinition = ArrayUtility::removeByPath($formDefinition, $propertyPath, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractValidator implements ValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $currentElement;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionToken;
|
||||
|
||||
/**
|
||||
* @var ValidationDto
|
||||
*/
|
||||
protected $validationDto;
|
||||
|
||||
public function __construct(array $currentElement, string $sessionToken, ValidationDto $validationDto)
|
||||
{
|
||||
$this->currentElement = $currentElement;
|
||||
$this->sessionToken = $sessionToken;
|
||||
$this->validationDto = $validationDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the path in which the hmac value is expected based on the property path.
|
||||
*/
|
||||
protected function buildHmacDataPath(string $propertyPath): string
|
||||
{
|
||||
$pathParts = explode('.', $propertyPath);
|
||||
$lastPathSegment = array_pop($pathParts);
|
||||
$pathParts[] = '_orig_' . $lastPathSegment;
|
||||
|
||||
return implode('.', $pathParts);
|
||||
}
|
||||
|
||||
protected function getFormDefinitionValidationService(): FormDefinitionValidationService
|
||||
{
|
||||
return GeneralUtility::makeInstance(FormDefinitionValidationService::class);
|
||||
}
|
||||
|
||||
protected function getConfigurationService(): ConfigurationService
|
||||
{
|
||||
return GeneralUtility::makeInstance(ConfigurationService::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class CollectionBasedValidator extends AbstractValidator
|
||||
{
|
||||
/**
|
||||
* Throws an exception if value from a property collection property
|
||||
* does not match its hmac hash or if there is no hmac hash
|
||||
* available for the value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
array $currentElement,
|
||||
$value,
|
||||
string $sessionToken,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$hmacDataPath = $this->buildHmacDataPath($dto->getPropertyPath());
|
||||
if (ArrayUtility::isValidPath($currentElement, $hmacDataPath, '.')) {
|
||||
$hmacData = ArrayUtility::getValueByPath($currentElement, $hmacDataPath, '.');
|
||||
|
||||
$hmacContent = [
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath(),
|
||||
];
|
||||
|
||||
if (!$this->getFormDefinitionValidationService()->isPropertyValueEqualToHistoricalValue($hmacContent, $value, $hmacData, $sessionToken)) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s" / "%s.%s") is not equal to the historical value "%s" #1528591586';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$hmacData['value'] ?? ''
|
||||
),
|
||||
1528591586
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = 'No hmac found for property "%s" (form element "%s" / "%s.%s") #1528591585';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier()
|
||||
),
|
||||
1528591585
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CreatableFormElementPropertiesValidator extends ElementBasedValidator
|
||||
{
|
||||
/**
|
||||
* Checks if the form element property is defined within the form editor setup
|
||||
* or if the property is defined within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
* If the form element property is defined within the form editor setup
|
||||
* and there is no valid hmac hash for the value
|
||||
* and is the form element property configured to only allow a limited set of values,
|
||||
* check the current (submitted) value against the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value)
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
|
||||
if ($this->getConfigurationService()->isFormElementPropertyDefinedInFormEditorSetup($dto)) {
|
||||
if ($this->getConfigurationService()->formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
$this->validateFormElementValue($value, $dto);
|
||||
}
|
||||
} elseif (
|
||||
$this->getConfigurationService()->isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
|
||||
&& !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
|
||||
) {
|
||||
$this->validateFormElementPredefinedDefaultValue($value, $dto);
|
||||
} else {
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a form element property
|
||||
* does not match the default value from the form editor setup.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validateFormElementPredefinedDefaultValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
// If the form element is newly created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
|
||||
// to check the integrity (at this time we don't have a hmac for the $value to check the integrity)
|
||||
$predefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto);
|
||||
if ($value !== $predefinedDefaultValue) {
|
||||
$throwException = true;
|
||||
|
||||
if (is_string($predefinedDefaultValue)) {
|
||||
// Last chance:
|
||||
// Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
|
||||
// compare the (already translated) $value (from the form definition) against the possible
|
||||
// translations from $predefinedDefaultValue.
|
||||
// Usecase:
|
||||
// * backend language is EN
|
||||
// * open the form editor and add a ContentElement form element
|
||||
// * switch to another browser tab and change the backend language to DE
|
||||
// * clear the cache
|
||||
// * go back to the form editor and click the save button
|
||||
// Out of scope:
|
||||
// * the same scenario as above + delete the previous chosen backend language within the maintenance tool
|
||||
$untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto, false);
|
||||
$translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
|
||||
$untranslatedPredefinedDefaultValue,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
if (in_array($value, $translations, true)) {
|
||||
$throwException = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($throwException) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s") is not equal to the default value "%s" #1528588035';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$predefinedDefaultValue
|
||||
),
|
||||
1528588035
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a form element property
|
||||
* does not match the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validateFormElementValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$allowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto);
|
||||
|
||||
if (!in_array($value, $allowedValues, true)) {
|
||||
$untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto, false);
|
||||
// Compare the $value against the untranslated set of allowed values
|
||||
if (in_array($value, $untranslatedAllowedValues, true)) {
|
||||
// All good, $value is within the untranslated set of allowed values
|
||||
return;
|
||||
}
|
||||
// Get all translations (from all backend languages) for the untranslated! $allowedValues and
|
||||
// compare the (already translated) $value (from the form definition) against all possible
|
||||
// translations for $untranslatedAllowedValues.
|
||||
$allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
|
||||
$untranslatedAllowedValues,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
foreach ($allPossibleAllowedValuesTranslations as $translations) {
|
||||
if (in_array($value, $translations, true)) {
|
||||
// All good, $value is within the set of translated allowed values
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance:
|
||||
// If $value is not configured within the form setup as an allowed value
|
||||
// but was written within the form definition by hand (and therefore contains a hmac),
|
||||
// check if $value is manipulated.
|
||||
// If $value has no hmac or if the hmac exists but is not valid,
|
||||
// then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
|
||||
// throw an exception.
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CreatablePropertyCollectionElementPropertiesValidator extends CollectionBasedValidator
|
||||
{
|
||||
/**
|
||||
* Checks if the property collection element property is defined
|
||||
* within the form editor setup or if the property is defined within
|
||||
* the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
* If the property collection element property is defined within the form editor setup
|
||||
* and there is no valid hmac hash for the value
|
||||
* and is the form property collection element property configured to only allow a limited set of values,
|
||||
* check the current (submitted) value against the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value)
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
|
||||
if ($this->getConfigurationService()->isPropertyCollectionPropertyDefinedInFormEditorSetup($dto)) {
|
||||
if ($this->getConfigurationService()->propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
$this->validatePropertyCollectionPropertyValue($value, $dto);
|
||||
}
|
||||
} elseif (
|
||||
$this->getConfigurationService()->isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
|
||||
&& !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
|
||||
) {
|
||||
$this->validatePropertyCollectionElementPredefinedDefaultValue($value, $dto);
|
||||
} else {
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a property collection property
|
||||
* does not match the default value from the form editor setup.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validatePropertyCollectionElementPredefinedDefaultValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
// If the property collection element is newly created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
|
||||
// to check the integrity (at this time we don't have a hmac on the value to check the integrity)
|
||||
$predefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto);
|
||||
if ($value !== $predefinedDefaultValue) {
|
||||
$throwException = true;
|
||||
|
||||
if (is_string($predefinedDefaultValue)) {
|
||||
// Last chance:
|
||||
// Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
|
||||
// compare the (already translated) $value (from the form definition) against the possible
|
||||
// translations from $predefinedDefaultValue.
|
||||
$untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto, false);
|
||||
$translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
|
||||
$untranslatedPredefinedDefaultValue,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
if (in_array($value, $translations, true)) {
|
||||
$throwException = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($throwException) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s" / "%s.%s") is not equal to the default value "%s" #1528591502';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$predefinedDefaultValue
|
||||
),
|
||||
1528591502
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a property collection property
|
||||
* does not match the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validatePropertyCollectionPropertyValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$allowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto);
|
||||
|
||||
if (!in_array($value, $allowedValues, true)) {
|
||||
$untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto, false);
|
||||
// Compare the $value against the untranslated set of allowed values
|
||||
if (in_array($value, $untranslatedAllowedValues, true)) {
|
||||
// All good, $value is within the untranslated set of allowed values
|
||||
return;
|
||||
}
|
||||
// Get all translations (from all backend languages) for the untranslated! $allowedValues and
|
||||
// compare the (already translated) $value (from the form definition) against all possible
|
||||
// translations for $untranslatedAllowedValues.
|
||||
$allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
|
||||
$untranslatedAllowedValues,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
foreach ($allPossibleAllowedValuesTranslations as $translations) {
|
||||
if (in_array($value, $translations, true)) {
|
||||
// All good, $value is within the set of translated allowed values
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance:
|
||||
// If $value is not configured within the form setup as an allowed value
|
||||
// but was written within the form definition by hand (and therefore contains a hmac),
|
||||
// check if $value is manipulated.
|
||||
// If $value has no hmac or if the hmac exists but is not valid,
|
||||
// then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
|
||||
// throw an exception.
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class ElementBasedValidator extends AbstractValidator
|
||||
{
|
||||
/**
|
||||
* Throws an exception if value from a form element property
|
||||
* does not match its hmac hash or if there is no hmac hash
|
||||
* available for the value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validateFormElementPropertyValueByHmacData(
|
||||
array $currentElement,
|
||||
$value,
|
||||
string $sessionToken,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$hmacDataPath = $this->buildHmacDataPath($dto->getPropertyPath());
|
||||
if (ArrayUtility::isValidPath($currentElement, $hmacDataPath, '.')) {
|
||||
$hmacData = ArrayUtility::getValueByPath($currentElement, $hmacDataPath, '.');
|
||||
|
||||
$hmacContent = [$dto->getFormElementIdentifier(), $dto->getPropertyPath()];
|
||||
if (!$this->getFormDefinitionValidationService()->isPropertyValueEqualToHistoricalValue($hmacContent, $value, $hmacData, $sessionToken)) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s") is not equal to the historical value "%s" #1528588036';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$hmacData['value'] ?? ''
|
||||
),
|
||||
1528588036
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = 'No hmac found for property "%s" (form element "%s") #1528588037';
|
||||
throw new PropertyException(
|
||||
sprintf($message, $dto->getPropertyPath(), $dto->getFormElementIdentifier()),
|
||||
1528588037
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FormElementHmacDataValidator extends ElementBasedValidator
|
||||
{
|
||||
/**
|
||||
* Checks if the form element property value matches to its hmac hash.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyCollectionElementHmacDataValidator extends CollectionBasedValidator
|
||||
{
|
||||
/**
|
||||
* Checks if the property collection element values matches to its hmac hash.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key)->withPropertyCollectionElementIdentifier(
|
||||
$this->currentElement['identifier']
|
||||
);
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
class ValidationDto
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prototypeName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementIdentifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionElementIdentifier;
|
||||
|
||||
public function __construct(
|
||||
?string $prototypeName = null,
|
||||
?string $formElementType = null,
|
||||
?string $formElementIdentifier = null,
|
||||
?string $propertyPath = null,
|
||||
?string $propertyCollectionName = null,
|
||||
?string $propertyCollectionElementIdentifier = null
|
||||
) {
|
||||
$this->prototypeName = $prototypeName;
|
||||
$this->formElementType = $formElementType;
|
||||
$this->formElementIdentifier = $formElementIdentifier;
|
||||
$this->propertyPath = $propertyPath;
|
||||
$this->propertyCollectionName = $propertyCollectionName;
|
||||
$this->propertyCollectionElementIdentifier = $propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
public function getPrototypeName(): string
|
||||
{
|
||||
return $this->prototypeName;
|
||||
}
|
||||
|
||||
public function getFormElementType(): string
|
||||
{
|
||||
return $this->formElementType;
|
||||
}
|
||||
|
||||
public function getFormElementIdentifier(): string
|
||||
{
|
||||
return $this->formElementIdentifier;
|
||||
}
|
||||
|
||||
public function getPropertyPath(): string
|
||||
{
|
||||
return $this->propertyPath;
|
||||
}
|
||||
|
||||
public function getPropertyCollectionName(): string
|
||||
{
|
||||
return $this->propertyCollectionName;
|
||||
}
|
||||
|
||||
public function getPropertyCollectionElementIdentifier(): string
|
||||
{
|
||||
return $this->propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
public function hasPrototypeName(): bool
|
||||
{
|
||||
return !empty($this->prototypeName);
|
||||
}
|
||||
|
||||
public function hasFormElementType(): bool
|
||||
{
|
||||
return !empty($this->formElementType);
|
||||
}
|
||||
|
||||
public function hasFormElementIdentifier(): bool
|
||||
{
|
||||
return !empty($this->formElementIdentifier);
|
||||
}
|
||||
|
||||
public function hasPropertyPath(): bool
|
||||
{
|
||||
return !empty($this->propertyPath);
|
||||
}
|
||||
|
||||
public function hasPropertyCollectionName(): bool
|
||||
{
|
||||
return !empty($this->propertyCollectionName);
|
||||
}
|
||||
|
||||
public function hasPropertyCollectionElementIdentifier(): bool
|
||||
{
|
||||
return !empty($this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withPrototypeName(string $prototypeName): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withFormElementType(string $formElementType): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withFormElementIdentifier(string $formElementIdentifier): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withPropertyPath(string $propertyPath): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withPropertyCollectionName(string $propertyCollectionName): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
public function withPropertyCollectionElementIdentifier(string $propertyCollectionElementIdentifier): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $propertyCollectionElementIdentifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ValidatorInterface
|
||||
{
|
||||
public function __construct(array $currentElement, string $sessionToken, ValidationDto $validationDto);
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value);
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\Html\SanitizerBuilderFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\AddHmacDataConverter;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\ConverterDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FinisherTranslationLanguageConverter;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\RemoveHmacDataConverter;
|
||||
use TYPO3\CMS\Form\Service\RichTextConfigurationService;
|
||||
use TYPO3\HtmlSanitizer\Sanitizer;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class FormDefinitionConversionService
|
||||
{
|
||||
public function __construct(
|
||||
private RichTextConfigurationService $richTextConfigurationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Add a new value "_orig_<propertyName>" for each scalar property value
|
||||
* within the form definition as a sibling of the property key.
|
||||
* "_orig_<propertyName>" is an array which contains the property value
|
||||
* and a hmac hash for the property value.
|
||||
* "_orig_<propertyName>" will be used to validate the form definition on saving.
|
||||
* @see \TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService::validateFormDefinitionProperties()
|
||||
*/
|
||||
public function addHmacData(array $formDefinition, string $formPersistenceIdentifier): array
|
||||
{
|
||||
// Extend the hmac hashing key with a "per form editor session" unique key.
|
||||
$sessionToken = $this->generateSessionToken();
|
||||
$this->persistSessionToken($sessionToken, $formPersistenceIdentifier);
|
||||
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'(^identifier$|renderables\.([\d]+)\.identifier$)',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataConverter::class,
|
||||
$converterDto,
|
||||
$sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$result = $converterDto->getFormDefinition();
|
||||
|
||||
// Embed the form persistence identifier so the TypeConverter can
|
||||
// look up the correct per-form session token when saving.
|
||||
$result['_formPersistenceIdentifier'] = $formPersistenceIdentifier;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the "_orig_<propertyName>" values and the
|
||||
* "_formPersistenceIdentifier" marker from the form definition.
|
||||
*/
|
||||
public function removeHmacData(array $formDefinition): array
|
||||
{
|
||||
unset($formDefinition['_formPersistenceIdentifier']);
|
||||
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'removeHmacData',
|
||||
'(_orig_.*|.*\._orig_.*)\.hmac',
|
||||
GeneralUtility::makeInstance(
|
||||
RemoveHmacDataConverter::class,
|
||||
$converterDto
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $converterDto->getFormDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate various finisher options
|
||||
*/
|
||||
public function migrateFinisherConfiguration(array $formDefinition): array
|
||||
{
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'migrateFinisherLanguageSettings',
|
||||
'^finishers\.([\d]+)\.options.translation.language$',
|
||||
GeneralUtility::makeInstance(
|
||||
FinisherTranslationLanguageConverter::class,
|
||||
$converterDto
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $converterDto->getFormDefinition();
|
||||
}
|
||||
|
||||
protected function persistSessionToken(string $sessionToken, string $formPersistenceIdentifier): void
|
||||
{
|
||||
$tokens = $this->getBackendUser()->getSessionData('extFormProtectionSessionTokens') ?? [];
|
||||
if (!is_array($tokens)) {
|
||||
$tokens = [];
|
||||
}
|
||||
$tokens[$formPersistenceIdentifier] = $sessionToken;
|
||||
$this->getBackendUser()->setAndSaveSessionData('extFormProtectionSessionTokens', $tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the session token for a specific form persistence identifier.
|
||||
*/
|
||||
public function retrieveSessionToken(string $formPersistenceIdentifier): string
|
||||
{
|
||||
$tokens = $this->getBackendUser()->getSessionData('extFormProtectionSessionTokens');
|
||||
if (is_array($tokens) && isset($tokens[$formPersistenceIdentifier]) && is_string($tokens[$formPersistenceIdentifier])) {
|
||||
return $tokens[$formPersistenceIdentifier];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function sanitizeHtml(array $rawFormDefinitionArray, array $rtePropertyPaths = [], string $defaultBuild = 'default'): array
|
||||
{
|
||||
return $this->sanitizeValuesRecursively($rawFormDefinitionArray, $rtePropertyPaths, $defaultBuild);
|
||||
}
|
||||
|
||||
public function transformRteContentForPersistence(array $formDefinition, array $rtePropertyPaths): array
|
||||
{
|
||||
if ($rtePropertyPaths === []) {
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
return $this->transformRteContentRecursively($formDefinition, $rtePropertyPaths, $this->richTextConfigurationService, 'persistence');
|
||||
}
|
||||
|
||||
public function transformRteContentForRichTextEditor(array $formDefinition, array $rtePropertyPaths): array
|
||||
{
|
||||
if ($rtePropertyPaths === []) {
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
return $this->transformRteContentRecursively($formDefinition, $rtePropertyPaths, $this->richTextConfigurationService, 'rte');
|
||||
}
|
||||
|
||||
protected function transformRteContentRecursively(
|
||||
array $formDefinition,
|
||||
array $rtePropertyPaths,
|
||||
RichTextConfigurationService $richTextConfigurationService,
|
||||
string $direction = 'persistence'
|
||||
): array {
|
||||
// Get the element type (e.g., 'Checkbox', 'StaticText', 'Form')
|
||||
$elementType = $formDefinition['type'] ?? null;
|
||||
|
||||
// Transform properties for this specific element type
|
||||
if ($elementType !== null && isset($rtePropertyPaths[$elementType])) {
|
||||
foreach ($rtePropertyPaths[$elementType] as $propertyPath => $presetName) {
|
||||
$value = $this->getValueByPath($formDefinition, $propertyPath);
|
||||
if (is_string($value) && $value !== '') {
|
||||
$transformedValue = $direction === 'persistence'
|
||||
? $richTextConfigurationService->transformTextForPersistence($value, $presetName)
|
||||
: $richTextConfigurationService->transformTextForRichTextEditor($value, $presetName);
|
||||
$formDefinition = $this->setValueByPath($formDefinition, $propertyPath, $transformedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into renderables (form elements on pages)
|
||||
if (is_array($formDefinition['renderables'] ?? null)) {
|
||||
foreach ($formDefinition['renderables'] as $key => $renderable) {
|
||||
if (is_array($renderable)) {
|
||||
$formDefinition['renderables'][$key] = $this->transformRteContentRecursively(
|
||||
$renderable,
|
||||
$rtePropertyPaths,
|
||||
$richTextConfigurationService,
|
||||
$direction
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform finisher options
|
||||
if (is_array($formDefinition['finishers'] ?? null)) {
|
||||
$finisherRtePaths = $rtePropertyPaths['_finishers'] ?? [];
|
||||
foreach ($formDefinition['finishers'] as $key => $finisher) {
|
||||
if (!is_array($finisher)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$finisherIdentifier = $finisher['identifier'] ?? null;
|
||||
if ($finisherIdentifier === null || !isset($finisherRtePaths[$finisherIdentifier])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($finisherRtePaths[$finisherIdentifier] as $propertyPath => $presetName) {
|
||||
// Property path in finisher config is like 'options.message'
|
||||
$value = $this->getValueByPath($finisher, $propertyPath);
|
||||
if (is_string($value) && $value !== '') {
|
||||
$transformedValue = $direction === 'persistence'
|
||||
? $richTextConfigurationService->transformTextForPersistence($value, $presetName)
|
||||
: $richTextConfigurationService->transformTextForRichTextEditor($value, $presetName);
|
||||
$finisher = $this->setValueByPath($finisher, $propertyPath, $transformedValue);
|
||||
$formDefinition['finishers'][$key] = $finisher;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
protected function getValueByPath(array $array, string $path): mixed
|
||||
{
|
||||
$keys = explode('.', $path);
|
||||
$current = $array;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!is_array($current) || !array_key_exists($key, $current)) {
|
||||
return null;
|
||||
}
|
||||
$current = $current[$key];
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
protected function setValueByPath(array $array, string $path, mixed $value): array
|
||||
{
|
||||
$keys = explode('.', $path);
|
||||
$current = &$array;
|
||||
|
||||
foreach ($keys as $i => $key) {
|
||||
if ($i === count($keys) - 1) {
|
||||
$current[$key] = $value;
|
||||
} else {
|
||||
if (!isset($current[$key]) || !is_array($current[$key])) {
|
||||
$current[$key] = [];
|
||||
}
|
||||
$current = &$current[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract RTE-enabled property paths from prototype configuration.
|
||||
*
|
||||
* Scans the form editor configuration for all form element types and finishers
|
||||
* to find editors with enableRichtext=true and returns their property paths
|
||||
* along with the RTE preset name, organized by element type.
|
||||
*
|
||||
* @param array $prototypeConfiguration The prototype configuration array
|
||||
* @return array Map of element types to their RTE property paths
|
||||
* Format: [
|
||||
* 'Checkbox' => ['label' => 'form-label'],
|
||||
* 'StaticText' => ['properties.text' => 'form-content'],
|
||||
* '_finishers' => ['Confirmation' => ['options.message' => 'form-label']]
|
||||
* ]
|
||||
*/
|
||||
public function extractRtePropertyPaths(array $prototypeConfiguration): array
|
||||
{
|
||||
$rtePropertyPaths = [];
|
||||
|
||||
// Extract from form elements definition
|
||||
$formElementsDefinition = $prototypeConfiguration['formElementsDefinition'] ?? [];
|
||||
foreach ($formElementsDefinition as $formElementType => $elementConfig) {
|
||||
$editors = $elementConfig['formEditor']['editors'] ?? [];
|
||||
foreach ($editors as $editor) {
|
||||
if ($this->isRteEditor($editor)) {
|
||||
$propertyPath = $editor['propertyPath'] ?? '';
|
||||
$presetName = $editor['richtextConfiguration'] ?? 'form-label';
|
||||
if ($propertyPath !== '') {
|
||||
$rtePropertyPaths[$formElementType][$propertyPath] = $presetName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract from finisher property collections on the Form element
|
||||
// Finisher editors are defined in:
|
||||
// formElementsDefinition.Form.formEditor.propertyCollections.finishers.<index>.editors
|
||||
$finisherCollections = $formElementsDefinition['Form']['formEditor']['propertyCollections']['finishers'] ?? [];
|
||||
foreach ($finisherCollections as $finisherCollection) {
|
||||
$finisherIdentifier = $finisherCollection['identifier'] ?? '';
|
||||
if ($finisherIdentifier === '') {
|
||||
continue;
|
||||
}
|
||||
$editors = $finisherCollection['editors'] ?? [];
|
||||
foreach ($editors as $editor) {
|
||||
if ($this->isRteEditor($editor)) {
|
||||
$propertyPath = $editor['propertyPath'] ?? '';
|
||||
$presetName = $editor['richtextConfiguration'] ?? 'form-label';
|
||||
if ($propertyPath !== '') {
|
||||
$rtePropertyPaths['_finishers'][$finisherIdentifier][$propertyPath] = $presetName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rtePropertyPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an editor configuration represents an RTE-enabled textarea.
|
||||
*/
|
||||
protected function isRteEditor(array $editor): bool
|
||||
{
|
||||
return ($editor['templateName'] ?? '') === 'Inspector-TextareaEditor'
|
||||
&& ($editor['enableRichtext'] ?? false) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sanitizes values in form definition.
|
||||
*
|
||||
* For RTE-enabled fields: Uses HtmlSanitizer with the preset configured in the RTE configuration
|
||||
* For all other string fields: Uses strip_tags to remove ALL HTML
|
||||
*
|
||||
* @param array $array The array to sanitize
|
||||
* @param array $rtePropertyPaths Map of element types to their RTE property paths with preset names
|
||||
* @param string $defaultBuild Default sanitizer build name for RTE fields without specific preset
|
||||
* @param string|null $currentElementType The current element type being processed
|
||||
* @param string $currentPath The current property path being processed
|
||||
*/
|
||||
protected function sanitizeValuesRecursively(
|
||||
array $array,
|
||||
array $rtePropertyPaths = [],
|
||||
string $defaultBuild = 'default',
|
||||
?string $currentElementType = null,
|
||||
string $currentPath = ''
|
||||
): array {
|
||||
$result = $array;
|
||||
|
||||
// Detect element type from current array (only at element root level)
|
||||
$elementType = $result['type'] ?? $currentElementType;
|
||||
|
||||
// Get RTE property paths for this element type (with their preset names)
|
||||
$elementRtePaths = [];
|
||||
if ($elementType !== null && isset($rtePropertyPaths[$elementType])) {
|
||||
$elementRtePaths = $rtePropertyPaths[$elementType];
|
||||
}
|
||||
|
||||
foreach ($result as $key => $value) {
|
||||
// Build the full property path
|
||||
$propertyPath = $currentPath === '' ? $key : $currentPath . '.' . $key;
|
||||
|
||||
if ($key === 'renderables' && is_array($value)) {
|
||||
// For renderables, process each child element with fresh context
|
||||
foreach ($value as $childKey => $childValue) {
|
||||
if (is_array($childValue)) {
|
||||
$result[$key][$childKey] = $this->sanitizeValuesRecursively(
|
||||
$childValue,
|
||||
$rtePropertyPaths,
|
||||
$defaultBuild
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($key === 'finishers' && is_array($value)) {
|
||||
// Handle finishers separately
|
||||
$finisherRtePaths = $rtePropertyPaths['_finishers'] ?? [];
|
||||
foreach ($value as $finisherKey => $finisher) {
|
||||
if (is_array($finisher)) {
|
||||
$finisherIdentifier = $finisher['identifier'] ?? null;
|
||||
$finisherRteFields = [];
|
||||
if ($finisherIdentifier !== null && isset($finisherRtePaths[$finisherIdentifier])) {
|
||||
$finisherRteFields = $finisherRtePaths[$finisherIdentifier];
|
||||
}
|
||||
$result[$key][$finisherKey] = $this->sanitizeFinisherRecursively(
|
||||
$finisher,
|
||||
$finisherRteFields,
|
||||
$defaultBuild
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif (is_array($value)) {
|
||||
// Recurse into nested arrays, keeping the element type and building path
|
||||
$result[$key] = $this->sanitizeValuesRecursively(
|
||||
$value,
|
||||
$rtePropertyPaths,
|
||||
$defaultBuild,
|
||||
$elementType,
|
||||
$propertyPath
|
||||
);
|
||||
} elseif (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) {
|
||||
$stringValue = (string)$value;
|
||||
|
||||
// Check if this property path is an RTE field for the current element type
|
||||
if (isset($elementRtePaths[$propertyPath])) {
|
||||
// RTE field: use HtmlSanitizer with the configured preset
|
||||
// This ensures sanitization even for form definitions from external sources
|
||||
$presetBuild = $this->resolveSanitizerBuildFromPreset($elementRtePaths[$propertyPath]);
|
||||
$result[$key] = $this->sanitizeWithBuild($stringValue, $presetBuild ?? $defaultBuild);
|
||||
} else {
|
||||
// Non-RTE field: strip ALL HTML tags for security
|
||||
$result[$key] = strip_tags($stringValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sanitize finisher values.
|
||||
*
|
||||
* @param array $finisher The finisher configuration
|
||||
* @param array $rteFields Map of RTE field paths to their preset names
|
||||
* @param string $defaultBuild Default sanitizer build name
|
||||
* @param string $currentPath Current property path
|
||||
*/
|
||||
protected function sanitizeFinisherRecursively(
|
||||
array $finisher,
|
||||
array $rteFields,
|
||||
string $defaultBuild = 'default',
|
||||
string $currentPath = ''
|
||||
): array {
|
||||
foreach ($finisher as $key => $value) {
|
||||
$fullPath = $currentPath === '' ? $key : $currentPath . '.' . $key;
|
||||
|
||||
if (is_array($value)) {
|
||||
$finisher[$key] = $this->sanitizeFinisherRecursively($value, $rteFields, $defaultBuild, $fullPath);
|
||||
} elseif (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) {
|
||||
$stringValue = (string)$value;
|
||||
|
||||
if (isset($rteFields[$fullPath])) {
|
||||
// RTE field: use HtmlSanitizer with the configured preset
|
||||
$presetBuild = $this->resolveSanitizerBuildFromPreset($rteFields[$fullPath]);
|
||||
$finisher[$key] = $this->sanitizeWithBuild($stringValue, $presetBuild ?? $defaultBuild);
|
||||
} else {
|
||||
// Non-RTE field: strip ALL HTML tags for security
|
||||
$finisher[$key] = strip_tags($stringValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $finisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the sanitizer build name from an RTE preset configuration.
|
||||
*
|
||||
* @param string $presetName The RTE preset name (e.g., 'form-label', 'form-content')
|
||||
* @return string|null The sanitizer build name, or null if not configured
|
||||
*/
|
||||
protected function resolveSanitizerBuildFromPreset(string $presetName): ?string
|
||||
{
|
||||
$processingConfig = $this->richTextConfigurationService->resolveProcessingConfiguration($presetName);
|
||||
return $processingConfig['HTMLparser_db.']['htmlSanitize.']['build'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTML content with the specified sanitizer build.
|
||||
*
|
||||
* @param string $content The HTML content to sanitize
|
||||
* @param string $build The sanitizer build name or class name
|
||||
* @return string The sanitized content
|
||||
*/
|
||||
protected function sanitizeWithBuild(string $content, string $build): string
|
||||
{
|
||||
return $this->createSanitizer($build)->sanitize($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sanitizer instance for the given build configuration.
|
||||
*
|
||||
* Supports both preset names (e.g., 'default') and class names implementing BuilderInterface.
|
||||
*
|
||||
* @param string $build The sanitizer build name or class name
|
||||
* @return Sanitizer The sanitizer instance
|
||||
*/
|
||||
protected function createSanitizer(string $build): Sanitizer
|
||||
{
|
||||
if (class_exists($build) && is_a($build, \TYPO3\HtmlSanitizer\Builder\BuilderInterface::class, true)) {
|
||||
$builder = GeneralUtility::makeInstance($build);
|
||||
} else {
|
||||
$factory = GeneralUtility::makeInstance(SanitizerBuilderFactory::class);
|
||||
$builder = $factory->build($build);
|
||||
}
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the random token which is used in the hash for the form tokens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSessionToken(): string
|
||||
{
|
||||
return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(64);
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatableFormElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatablePropertyCollectionElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\FormElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\PropertyCollectionElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class FormDefinitionValidationService
|
||||
{
|
||||
public function __construct(
|
||||
protected HashService $hashService,
|
||||
protected ConfigurationService $configurationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate the form definition properties using the form setup.
|
||||
* Pseudo workflow:
|
||||
* Is the form element type creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is the form element property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form element property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form element property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form element property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finishers|validators)
|
||||
* is the form elements finisher|validator creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is the form elements finisher|validator property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form elements finisher|validator property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form elements finisher|validator property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validateFormDefinitionProperties(
|
||||
array $currentFormElement,
|
||||
string $prototypeName,
|
||||
string $sessionToken
|
||||
): void {
|
||||
$renderables = $currentFormElement['renderables'] ?? [];
|
||||
$propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
|
||||
$propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
|
||||
unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
|
||||
|
||||
$validationDto = GeneralUtility::makeInstance(
|
||||
ValidationDto::class,
|
||||
$prototypeName,
|
||||
$currentFormElement['type'],
|
||||
$currentFormElement['identifier'],
|
||||
null,
|
||||
$propertyCollectionName
|
||||
);
|
||||
|
||||
if ($this->configurationService->isFormElementTypeCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatableFormElement(
|
||||
$currentFormElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$validationDto = $validationDto->withPropertyCollectionElementIdentifier(
|
||||
$propertyCollectionElement['identifier']
|
||||
);
|
||||
if ($this->configurationService->isPropertyCollectionElementIdentifierCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
} else {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->validateAllFormElementPropertyValuesByHmac($currentFormElement, $sessionToken, $validationDto);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renderables as $renderable) {
|
||||
$this->validateFormDefinitionProperties($renderable, $prototypeName, $sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a property value is equals to the historical value
|
||||
* and FALSE if not.
|
||||
* "Historical values" means values which are available within the form definition
|
||||
* while the form editor is loaded and the values which are available after a
|
||||
* successful validation of the form definition on a save operation.
|
||||
* The value must be equal to the historical value if the property key for the value
|
||||
* is not defined within the form setup.
|
||||
* This means that the property can not be changed by the form editor but we want to keep the value
|
||||
* in its original state.
|
||||
* If this is not the case (return value is FALSE), an exception must be thrown.
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function isPropertyValueEqualToHistoricalValue(
|
||||
array $hmacContent,
|
||||
mixed $propertyValue,
|
||||
array $hmacData,
|
||||
string $sessionToken
|
||||
): bool {
|
||||
$this->checkHmacDataIntegrity($hmacData, $hmacContent, $sessionToken);
|
||||
$hmacContent[] = $propertyValue;
|
||||
$expectedHash = $this->hashService->hmac(serialize($hmacContent), $sessionToken);
|
||||
return hash_equals($expectedHash, $hmacData['hmac']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the historical value and the hmac hash to ensure the integrity
|
||||
* of the data.
|
||||
* An exception will be thrown if the value is modified.
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function checkHmacDataIntegrity(array $hmacData, array $hmacContent, string $sessionToken)
|
||||
{
|
||||
$hmac = $hmacData['hmac'] ?? null;
|
||||
if (empty($hmac)) {
|
||||
throw new PropertyException('Hmac must not be empty. #1528538222', 1528538222);
|
||||
}
|
||||
$hmacContent[] = $hmacData['value'] ?? '';
|
||||
$expectedHash = $this->hashService->hmac(serialize($hmacContent), $sessionToken);
|
||||
if (!hash_equals($expectedHash, $hmac)) {
|
||||
throw new PropertyException('Unauthorized modification of historical data. #1528538252', 1528538252);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*/
|
||||
protected function validateAllFormElementPropertyValuesByHmac(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
FormElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*/
|
||||
protected function validateAllPropertyCollectionElementValuesByHmac(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
PropertyCollectionElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is defined within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatableFormElement(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|type|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatableFormElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is defined within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatablePropertyCollectionElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExtractorDto
|
||||
*/
|
||||
protected $extractorDto;
|
||||
|
||||
public function __construct(ExtractorDto $extractorDto)
|
||||
{
|
||||
$this->extractorDto = $extractorDto;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AdditionalElementPropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType] = $matches;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['additionalElementPropertyPaths'][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ExtractorDto
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $prototypeConfiguration;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $result = [];
|
||||
|
||||
public function __construct(array $prototypeConfiguration)
|
||||
{
|
||||
$this->prototypeConfiguration = $prototypeConfiguration;
|
||||
}
|
||||
|
||||
public function getPrototypeConfiguration(): array
|
||||
{
|
||||
return $this->prototypeConfiguration;
|
||||
}
|
||||
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public function setResult(array $result): ExtractorDto
|
||||
{
|
||||
$this->result = $result;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ExtractorInterface
|
||||
{
|
||||
public function __construct(ExtractorDto $extractorDto);
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value, array $matches);
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class IsCreatableFormElementExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType] = $matches;
|
||||
|
||||
$formElementGroup = $value;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (!ArrayUtility::isValidPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
'formElementsDefinition.' . $formElementType . '.formEditor.groupSorting',
|
||||
'.'
|
||||
)) {
|
||||
$result['formElements'][$formElementType]['creatable'] = false;
|
||||
$this->extractorDto->setResult($result);
|
||||
return;
|
||||
}
|
||||
|
||||
$formElementGroups = array_keys(
|
||||
ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), 'formEditor.formElementGroups', '.')
|
||||
);
|
||||
|
||||
$result['formElements'][$formElementType]['creatable'] = in_array(
|
||||
$formElementGroup,
|
||||
$formElementGroups,
|
||||
true
|
||||
);
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MultiValuePropertiesExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-PropertyGridEditor'
|
||||
&& $value !== 'Inspector-MultiSelectEditor'
|
||||
&& $value !== 'Inspector-CountrySelectEditor'
|
||||
&& $value !== 'Inspector-ValidationErrorMessageEditor'
|
||||
&& $value !== 'Inspector-RequiredValidatorEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value === 'Inspector-RequiredValidatorEditor') {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'validationErrorMessage',
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $propertyPath, '.')) {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$propertyPath,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($value === 'Inspector-PropertyGridEditor') {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = 'defaultValue';
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PredefinedDefaultsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyPath] = $matches;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['predefinedDefaults'][$propertyPath] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
$formElementPropertyPaths = $this->getPropertyPaths($value, $matches);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result = array_merge_recursive($result, ['formElements' => $formElementPropertyPaths]);
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
|
||||
protected function getPropertyPaths(string $value, array $matches): array
|
||||
{
|
||||
$paths = [];
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
$paths[$formElementType]['propertyPaths'] = [];
|
||||
$templateNamePath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'templateName',
|
||||
]
|
||||
);
|
||||
$templateName = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$templateNamePath,
|
||||
'.'
|
||||
);
|
||||
|
||||
// Special processing of "Inspector-GridColumnViewPortConfigurationEditor" inspector editors.
|
||||
// Expand the property path which contains a "{@viewPortIdentifier}" placeholder
|
||||
// to X property paths which contain all available placeholder replacements.
|
||||
if ($templateName === 'Inspector-GridColumnViewPortConfigurationEditor') {
|
||||
$viewPortsPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'viewPorts',
|
||||
]
|
||||
);
|
||||
$viewPorts = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $viewPortsPath, '.');
|
||||
foreach ($viewPorts as $viewPort) {
|
||||
$viewPortIdentifier = $viewPort['viewPortIdentifier'];
|
||||
$propertyPath = str_replace('{@viewPortIdentifier}', $viewPortIdentifier, $value);
|
||||
$paths[$formElementType]['propertyPaths'][] = $propertyPath;
|
||||
}
|
||||
} else {
|
||||
$paths[$formElementType]['propertyPaths'][] = $value;
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SelectOptionsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
$templateName = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'templateName',
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
if ($templateName === 'Inspector-FinishersEditor') {
|
||||
$propertyPath = '_finishers';
|
||||
} elseif ($templateName === 'Inspector-ValidatorsEditor') {
|
||||
$propertyPath = '_validators';
|
||||
} else {
|
||||
if ($templateName === 'Inspector-RequiredValidatorEditor') {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'validationErrorMessage',
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$propertyPath = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$propertyPath,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['selectOptions'][$propertyPath][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class IsCreatablePropertyCollectionElementExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-FinishersEditor'
|
||||
&& $value !== 'Inspector-ValidatorsEditor'
|
||||
&& $value !== 'Inspector-RequiredValidatorEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propertyCollectionName = $value === 'Inspector-FinishersEditor' ? 'finishers' : 'validators';
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (
|
||||
$value === 'Inspector-FinishersEditor'
|
||||
|| $value === 'Inspector-ValidatorsEditor'
|
||||
) {
|
||||
$selectOptionsPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'selectOptions',
|
||||
]
|
||||
);
|
||||
if (!ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $selectOptionsPath, '.')) {
|
||||
return;
|
||||
}
|
||||
$selectOptions = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$selectOptionsPath,
|
||||
'.'
|
||||
);
|
||||
foreach ($selectOptions as $selectOption) {
|
||||
$validatorIdentifier = $selectOption['value'] ?? '';
|
||||
if (empty($validatorIdentifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$validatorIdentifier]['creatable'] = true;
|
||||
}
|
||||
} else {
|
||||
$validatorIdentifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'validatorIdentifier',
|
||||
]
|
||||
);
|
||||
if (!ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $validatorIdentifierPath, '.')) {
|
||||
return;
|
||||
}
|
||||
$validatorIdentifier = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$validatorIdentifierPath,
|
||||
'.'
|
||||
);
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$validatorIdentifier]['creatable'] = true;
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MultiValuePropertiesExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex, $propertyCollectionEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-PropertyGridEditor'
|
||||
&& $value !== 'Inspector-MultiSelectEditor'
|
||||
&& $value !== 'Inspector-CountrySelectEditor'
|
||||
&& $value !== 'Inspector-ValidationErrorMessageEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'editors',
|
||||
$propertyCollectionEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
$propertyValue = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $propertyPath, '.');
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (
|
||||
$value === 'Inspector-PropertyGridEditor'
|
||||
|| $value === 'Inspector-MultiSelectEditor'
|
||||
) {
|
||||
$identifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier',
|
||||
]
|
||||
);
|
||||
$identifier = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $identifierPath, '.');
|
||||
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['multiValueProperties'][] = $propertyValue;
|
||||
if ($value === 'Inspector-PropertyGridEditor') {
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['multiValueProperties'][] = 'defaultValue';
|
||||
}
|
||||
} else {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = $propertyValue;
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PredefinedDefaultsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $propertyCollectionName, $propertyCollectionElementIdentifier, $propertyPath] = $matches;
|
||||
$propertyCollectionName = str_replace('Definition', '', $propertyCollectionName);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['collections'][$propertyCollectionName][$propertyCollectionElementIdentifier]['predefinedDefaults'][$propertyPath] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex] = $matches;
|
||||
|
||||
$identifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier',
|
||||
]
|
||||
);
|
||||
$identifier = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $identifierPath, '.');
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['propertyPaths'][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SelectOptionsExtractor extends AbstractExtractor
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex, $propertyCollectionEditorIndex] = $matches;
|
||||
|
||||
$propertyPath = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'editors',
|
||||
$propertyCollectionEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
$propertyCollectionElementIdentifier = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier',
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
$propertyCollectionName = str_replace('Definition', '', $propertyCollectionName);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['collections'][$propertyCollectionName][$propertyCollectionElementIdentifier]['selectOptions'][$propertyPath][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Core\Http\ApplicationType;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface as ExtbaseConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Form\Domain\DTO\PersistenceManagerConfiguration;
|
||||
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface as ExtFormConfigurationManagerInterface;
|
||||
|
||||
/**
|
||||
* Service for accessing form storage configuration (persistenceManager settings)
|
||||
*
|
||||
* This service provides a clean interface to access form storage related settings
|
||||
* from the YAML configuration without coupling every component to the configuration
|
||||
* loading mechanism.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
final readonly class PersistenceConfigurationService
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(lazy: true)]
|
||||
private ExtbaseConfigurationManagerInterface $extbaseConfigurationManager,
|
||||
#[Autowire(lazy: ExtFormConfigurationManagerInterface::class)]
|
||||
private ExtFormConfigurationManagerInterface $extFormConfigurationManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get all form settings
|
||||
*/
|
||||
public function getFormSettings(): array
|
||||
{
|
||||
$isFrontend = $this->isFrontendRequest();
|
||||
$request = $this->getCurrentRequest();
|
||||
|
||||
$typoScriptSettings = $this->extbaseConfigurationManager->getConfiguration(
|
||||
ExtbaseConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
|
||||
'form'
|
||||
);
|
||||
|
||||
return $this->extFormConfigurationManager->getYamlConfiguration(
|
||||
$typoScriptSettings,
|
||||
$isFrontend,
|
||||
$isFrontend ? $request : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get persistence manager settings as a typed DTO
|
||||
*/
|
||||
public function getPersistenceManagerConfiguration(): PersistenceManagerConfiguration
|
||||
{
|
||||
$formSettings = $this->getFormSettings();
|
||||
return PersistenceManagerConfiguration::fromArray($formSettings['persistenceManager'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed extension paths from form configuration
|
||||
*
|
||||
* @return string[] Array of allowed extension paths (e.g., ["EXT:my_extension/Configuration/Forms/"])
|
||||
*/
|
||||
public function getAllowedExtensionPaths(): array
|
||||
{
|
||||
return $this->getPersistenceManagerConfiguration()->allowedExtensionPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed pages for form storage.
|
||||
*
|
||||
* Forms are always stored on pid 0 (root level).
|
||||
*
|
||||
* @return array<int, array{uid: int, title: string}> Array of allowed page IDs
|
||||
*/
|
||||
public function getAllowedPages(): array
|
||||
{
|
||||
return [
|
||||
0 => [
|
||||
'uid' => 0,
|
||||
'title' => 'Root',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if saving to extension paths is allowed
|
||||
*/
|
||||
public function isAllowedToSaveToExtensionPaths(): bool
|
||||
{
|
||||
return $this->getPersistenceManagerConfiguration()->allowSaveToExtensionPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if deleting from extension paths is allowed
|
||||
*/
|
||||
public function isAllowedToDeleteFromExtensionPaths(): bool
|
||||
{
|
||||
return $this->getPersistenceManagerConfiguration()->allowDeleteFromExtensionPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sort configuration for form listing
|
||||
*
|
||||
* @return array{sortByKeys: string[], sortAscending: bool}
|
||||
*/
|
||||
public function getSortConfiguration(): array
|
||||
{
|
||||
$configuration = $this->getPersistenceManagerConfiguration();
|
||||
|
||||
return [
|
||||
'sortByKeys' => $configuration->sortByKeys,
|
||||
'sortAscending' => $configuration->sortAscending,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current request is a frontend request
|
||||
*/
|
||||
private function isFrontendRequest(): bool
|
||||
{
|
||||
$request = $this->getCurrentRequest();
|
||||
|
||||
if ($request !== null) {
|
||||
return ApplicationType::fromRequest($request)->isFrontend();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current request from globals
|
||||
*/
|
||||
private function getCurrentRequest(): ?ServerRequestInterface
|
||||
{
|
||||
return $GLOBALS['TYPO3_REQUEST'] ?? null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user