TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
use TYPO3\CMS\Backend\Dto\Settings\EditableSetting;
|
||||
|
||||
/**
|
||||
* @template T of SettingDefinition|EditableSetting
|
||||
* @internal
|
||||
*/
|
||||
final readonly class Category implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param list<T> $settings
|
||||
* @param list<Category<T>> $categories
|
||||
*/
|
||||
public function __construct(
|
||||
public string $key,
|
||||
public string $label,
|
||||
public ?string $description = null,
|
||||
public ?string $icon = null,
|
||||
public array $settings = [],
|
||||
public array $categories = [],
|
||||
) {}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return get_object_vars($this);
|
||||
}
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
class CategoryAccumulator
|
||||
{
|
||||
/**
|
||||
* Retrieve list of ordered sets, matched by
|
||||
* $setNames, including their dependencies (recursive)
|
||||
*
|
||||
* @param CategoryDefinition[] $categoryDefinitions
|
||||
* @param SettingDefinition[] $settingsDefinitions
|
||||
* @return list<Category>
|
||||
*/
|
||||
public function getCategories(iterable $categoryDefinitions, iterable $settingsDefinitions): array
|
||||
{
|
||||
$categories = [];
|
||||
foreach ($categoryDefinitions as $category) {
|
||||
$data = $category->toArray();
|
||||
$parent = $data['parent'] ?? null;
|
||||
unset($data['parent']);
|
||||
$categories[$category->key] = [
|
||||
'children' => [],
|
||||
'parent' => $parent,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
foreach ($categoryDefinitions as $category) {
|
||||
if ($category->parent === null) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($categories[$category->parent])) {
|
||||
throw new \RuntimeException('Missing parent category: ' . $category->parent, 1716291554);
|
||||
}
|
||||
$categories[$category->parent]['children'][] = $category->key;
|
||||
}
|
||||
|
||||
$categorizedSettings = [];
|
||||
foreach ($settingsDefinitions as $definition) {
|
||||
$category = $definition->category ?? '';
|
||||
$categorizedSettings[isset($categories[$category]) ? $category : 'other'][] = $definition;
|
||||
}
|
||||
|
||||
if (isset($categorizedSettings['other'])) {
|
||||
$categories['other'] = [
|
||||
'children' => [],
|
||||
'parent' => null,
|
||||
'data' => [
|
||||
'key' => 'other',
|
||||
'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_sitesettings.xlf:categories.other',
|
||||
'description' => '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$instances = [];
|
||||
foreach ($categories as $key => $category) {
|
||||
if ($category['parent'] === null) {
|
||||
$instances[] = $this->createInstance($categories, $key, $categorizedSettings);
|
||||
}
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
private function createInstance(array $categories, string $key, array $categorizedSettings): Category
|
||||
{
|
||||
try {
|
||||
return new Category(...[
|
||||
...$categories[$key]['data'],
|
||||
'settings' => $categorizedSettings[$key] ?? [],
|
||||
'categories' => array_map(fn($key) => $this->createInstance($categories, $key, $categorizedSettings), $categories[$key]['children']),
|
||||
]);
|
||||
} catch (\Error $e) {
|
||||
throw new \Exception('Invalid category definition: ' . json_encode($categories[$key]['data']), 1720528084, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class CategoryDefinition
|
||||
{
|
||||
public function __construct(
|
||||
public string $key,
|
||||
public string $label,
|
||||
public ?string $description = null,
|
||||
public ?string $icon = null,
|
||||
public ?string $parent = null,
|
||||
) {}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_filter(get_object_vars($this), fn(mixed $value) => $value !== null && $value !== []);
|
||||
}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal Only to be used by internal settings functionality
|
||||
*/
|
||||
final class InvalidSettingDefinitionException extends \RuntimeException {}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
final readonly class SettingDefinition implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param array<int|string, string|int|float|bool> $enum
|
||||
* @param list<string> $tags
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $key,
|
||||
public string $type,
|
||||
public string|int|float|bool|array|object|null $default,
|
||||
public string $label,
|
||||
public ?string $description = null,
|
||||
public bool $readonly = false,
|
||||
public array $enum = [],
|
||||
public ?string $category = null,
|
||||
public array $tags = [],
|
||||
public array $options = [],
|
||||
) {}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
...get_object_vars($this),
|
||||
'enum' => (object)$this->enum,
|
||||
'options' => (object)$this->options,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingDefinitionValidation
|
||||
{
|
||||
public function __construct(
|
||||
private SettingsTypeRegistry $settingsTypeRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws InvalidSettingDefinitionException
|
||||
*/
|
||||
public function validate(SettingDefinition $definition): void
|
||||
{
|
||||
if (!$this->settingsTypeRegistry->has($definition->type)) {
|
||||
throw new InvalidSettingDefinitionException('Invalid settings type "' . $definition->type . '" in setting "' . $definition->key . '"', 1732181103);
|
||||
}
|
||||
|
||||
$type = $this->settingsTypeRegistry->get($definition->type);
|
||||
|
||||
// Only validate options if type supports them
|
||||
if ($type instanceof SettingsTypeOptionAwareInterface) {
|
||||
$this->validateSettingsTypeOptions($definition, $type);
|
||||
}
|
||||
|
||||
// Validate default value
|
||||
if (!$type->validate($definition->default, $definition)) {
|
||||
throw new InvalidSettingDefinitionException('Invalid default value in setting "' . $definition->key . '"', 1732181102);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSettingsTypeOptions(
|
||||
SettingDefinition $definition,
|
||||
SettingsTypeInterface&SettingsTypeOptionAwareInterface $type
|
||||
): void {
|
||||
$supportedOptions = $type->getSupportedOptions();
|
||||
|
||||
$options = $definition->options;
|
||||
foreach ($supportedOptions as $optionName => $optionDefinition) {
|
||||
if (!array_key_exists($optionName, $options)) {
|
||||
if ($optionDefinition->required) {
|
||||
throw new InvalidSettingDefinitionException(
|
||||
'Required option "' . $optionName . '" missing for type "' . $definition->type . '" in setting "' . $definition->key . '"',
|
||||
1732181110
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $definition->options[$optionName];
|
||||
$isValid = match ($optionDefinition->type) {
|
||||
'string' => is_string($value),
|
||||
'int' => is_int($value),
|
||||
'number' => is_int($value) || is_float($value),
|
||||
'bool' => is_bool($value),
|
||||
'array' => is_array($value),
|
||||
default => throw new InvalidSettingDefinitionException('Unsupported settings type option: ' . $optionDefinition->type, 1734513842),
|
||||
};
|
||||
|
||||
if (!$isValid) {
|
||||
throw new InvalidSettingDefinitionException(
|
||||
'Invalid value for option "' . $optionName . '" in setting "' . $definition->key . '": expected ' . $optionDefinition->type . ', got ' . gettype($value),
|
||||
1732181109
|
||||
);
|
||||
}
|
||||
unset($options[$optionName]);
|
||||
}
|
||||
|
||||
if ($options !== []) {
|
||||
throw new InvalidSettingDefinitionException(
|
||||
'Unsupported options [' . implode(', ', array_keys($options)) . '] for type "' . $definition->type . '" in setting "' . $definition->key . '". Supported options: [' . implode(', ', array_keys($supportedOptions)) . ']',
|
||||
1732181108
|
||||
);
|
||||
}
|
||||
|
||||
if (!$type->validateOptions($definition)) {
|
||||
throw new InvalidSettingDefinitionException(
|
||||
'Setting definition options for type "' . $definition->type . '" in setting "' . $definition->key . '" could not be validated.',
|
||||
1752821671
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SettingNotFoundException extends Exception implements NotFoundExceptionInterface {}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingValue
|
||||
{
|
||||
public function __construct(
|
||||
public mixed $value,
|
||||
public string $key,
|
||||
public ?SettingDefinition $definition,
|
||||
) {}
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class Settings implements SettingsInterface
|
||||
{
|
||||
public function __construct(
|
||||
private array $settings,
|
||||
) {}
|
||||
|
||||
public function has(string $identifier): bool
|
||||
{
|
||||
return array_key_exists($identifier, $this->settings);
|
||||
}
|
||||
|
||||
public function get(string $identifier): mixed
|
||||
{
|
||||
if (!$this->has($identifier)) {
|
||||
throw new SettingNotFoundException('Setting does not exist', 1709555772);
|
||||
}
|
||||
return $this->settings[$identifier];
|
||||
}
|
||||
|
||||
public function getIdentifiers(): array
|
||||
{
|
||||
return array_keys($this->settings);
|
||||
}
|
||||
|
||||
public static function __set_state(array $state): static
|
||||
{
|
||||
return new static(...$state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingsDiff
|
||||
{
|
||||
/**
|
||||
* @param string[] $changes
|
||||
* @param string[] $deletions
|
||||
*/
|
||||
public function __construct(
|
||||
public array $settings,
|
||||
public array $changes,
|
||||
public array $deletions,
|
||||
) {}
|
||||
|
||||
public function asArray(): array
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a new settings tree for the given $targetSettings
|
||||
*
|
||||
* Settings that have the same value as their default value
|
||||
* are removed (tree is minified) if the list of default settings
|
||||
* via $defaultSettings.
|
||||
*
|
||||
* @param array $currentSettings Current settings
|
||||
* In case of site settings: config/sites/…/settings.yaml
|
||||
* @param SettingsInterface $targetSettings Target settings
|
||||
* (values as supplied via the settings editor)
|
||||
* @param SettingsInterface $defaultSettings Default settings, without local settings tree applied.
|
||||
* In case of site settings: Combination of all settings
|
||||
* defined in settings.definitions.yaml + setting.yaml
|
||||
* from all selected sets combined
|
||||
*/
|
||||
public static function create(
|
||||
array $currentSettings,
|
||||
SettingsInterface $targetSettings,
|
||||
?SettingsInterface $defaultSettings = null,
|
||||
): self {
|
||||
// Copy existing settings from current settings map/tree, to keep any settings
|
||||
// that have been present before (and are not defined in $defaultSettings)
|
||||
// Usecase for site settings:
|
||||
// Preserve "anonymous" v12-style site settings that have no definition in settings.definitions.yaml
|
||||
// and are stored as a tree instead of a map
|
||||
$settings = $currentSettings;
|
||||
|
||||
// Merge target settings into current settings
|
||||
$changes = [];
|
||||
$deletions = [];
|
||||
foreach ($targetSettings->getIdentifiers() as $key) {
|
||||
$value = $targetSettings->get($key);
|
||||
if ($defaultSettings !== null && $value === $defaultSettings->get($key)) {
|
||||
if (ArrayUtility::isValidPath($settings, $key, '.')) {
|
||||
$settings = self::removeByPathWithAncestors($settings, $key, '.');
|
||||
$deletions[] = $key;
|
||||
}
|
||||
if (array_key_exists($key, $settings)) {
|
||||
unset($settings[$key]);
|
||||
$deletions[] = $key;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove key from legacy tree
|
||||
if (str_contains($key, '.') && ArrayUtility::isValidPath($settings, $key, '.')) {
|
||||
$settings = self::removeByPathWithAncestors($settings, $key, '.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $settings)
|
||||
|| $value !== $settings[$key]
|
||||
) {
|
||||
$settings[$key] = $value;
|
||||
$changes[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return new self(
|
||||
$settings,
|
||||
$changes,
|
||||
$deletions
|
||||
);
|
||||
}
|
||||
|
||||
private static function removeByPathWithAncestors(array $array, string $path, string $delimiter): array
|
||||
{
|
||||
if ($path === '' || !ArrayUtility::isValidPath($array, $path, $delimiter)) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$array = ArrayUtility::removeByPath($array, $path, $delimiter);
|
||||
$parts = explode($delimiter, $path);
|
||||
array_pop($parts);
|
||||
$parentPath = implode($delimiter, $parts);
|
||||
|
||||
if ($parentPath !== '' && ArrayUtility::isValidPath($array, $parentPath, $delimiter)) {
|
||||
$parent = ArrayUtility::getValueByPath($array, $parentPath, $delimiter);
|
||||
if ($parent === []) {
|
||||
return self::removeByPathWithAncestors($array, $parentPath, $delimiter);
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingsFactory
|
||||
{
|
||||
public function __construct(
|
||||
private SettingsTypeRegistry $settingsTypeRegistry,
|
||||
) {}
|
||||
|
||||
public function resolveSettings(SettingsProviderInterface ...$providers): SettingsInterface
|
||||
{
|
||||
/** @var SettingDefinition[] $definitions */
|
||||
$definitions = [];
|
||||
/** @var SettingValue[] $settings */
|
||||
$settings = [];
|
||||
foreach ($providers as $provider) {
|
||||
foreach ($provider->getDefinitions() as $definition) {
|
||||
$definitions[$definition->key] = $definition;
|
||||
}
|
||||
$settings = [
|
||||
...$settings,
|
||||
...$provider->getProvidedSettings($definitions),
|
||||
];
|
||||
}
|
||||
|
||||
/** @var array<string, string|int|float|bool|array|null> $map */
|
||||
$map = [];
|
||||
foreach (array_reverse($settings) as $setting) {
|
||||
if (array_key_exists($setting->key, $map)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $setting->value;
|
||||
if ($setting->definition !== null && !$this->validateAndTransformValue($value, $setting->definition)) {
|
||||
continue;
|
||||
}
|
||||
$map[$setting->key] = $value;
|
||||
}
|
||||
|
||||
return new Settings($map);
|
||||
}
|
||||
|
||||
public function createSettingsFromFormData(array $settings, iterable $definitions): SettingsInterface
|
||||
{
|
||||
$definitionMap = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$definitionMap[$definition->key] = $definition;
|
||||
}
|
||||
foreach ($settings as $key => $value) {
|
||||
$definition = $definitionMap[$key] ?? null;
|
||||
if ($definition === null) {
|
||||
throw new \RuntimeException('Unexpected setting ' . $key . ' is not defined', 1724067004);
|
||||
}
|
||||
if ($definition->readonly) {
|
||||
unset($settings[$key]);
|
||||
continue;
|
||||
}
|
||||
// @todo We should collect invalid values and report in the UI instead of ignoring them
|
||||
if (!$this->validateAndTransformValue($value, $definition)) {
|
||||
$value = $definition->default;
|
||||
}
|
||||
$settings[$key] = $value;
|
||||
}
|
||||
|
||||
return new Settings($settings);
|
||||
}
|
||||
|
||||
private function validateAndTransformValue(mixed &$value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (!$this->settingsTypeRegistry->has($definition->type)) {
|
||||
throw new \RuntimeException('Setting type ' . $definition->type . ' is not defined.', 1712437727);
|
||||
}
|
||||
$type = $this->settingsTypeRegistry->get($definition->type);
|
||||
if (!$type->validate($value, $definition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $type->transformValue($value, $definition);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface SettingsInterface extends ContainerInterface
|
||||
{
|
||||
public function getIdentifiers(): array;
|
||||
|
||||
public static function __set_state(array $state): SettingsInterface;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* Settings provider implementation for providing settings definitions and values.
|
||||
*
|
||||
* This provider is used internally by the Settings API to manage setting definitions
|
||||
* and their corresponding values. It combines default values from definitions with
|
||||
* runtime values provided through the settings array.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingsProvider implements SettingsProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
private array $settings,
|
||||
private array $definitions = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getDefinitions(): array
|
||||
{
|
||||
return $this->definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SettingValue[]
|
||||
*/
|
||||
public function getProvidedSettings(array $globalDefinitions): array
|
||||
{
|
||||
/** @var SettingValue[] $settings */
|
||||
$settings = [];
|
||||
foreach ($this->definitions as $definition) {
|
||||
$settings[] = new SettingValue(
|
||||
value: $definition->default,
|
||||
key: $definition->key,
|
||||
definition: $definition,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->settings as $key => $value) {
|
||||
$definition = $globalDefinitions[$key] ?? null;
|
||||
if ($definition !== null) {
|
||||
$settings[] = new SettingValue(
|
||||
value: $value,
|
||||
key: $key,
|
||||
definition: $definition,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface SettingsProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return SettingDefinition[]
|
||||
*/
|
||||
public function getDefinitions(): array;
|
||||
|
||||
/**
|
||||
* @param SettingDefinition[] $currentDefinitions
|
||||
* @return SettingValue[]
|
||||
*/
|
||||
public function getProvidedSettings(array $currentDefinitions): array;
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[AutoconfigureTag('settings.type')]
|
||||
interface SettingsTypeInterface
|
||||
{
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool;
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): mixed;
|
||||
|
||||
public function getJavaScriptModule(): string;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingsTypeOption
|
||||
{
|
||||
public function __construct(
|
||||
public string $type,
|
||||
public string $description,
|
||||
public bool $required = false,
|
||||
) {}
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface SettingsTypeOptionAwareInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, SettingsTypeOption>
|
||||
*/
|
||||
public function getSupportedOptions(): array;
|
||||
|
||||
public function validateOptions(SettingDefinition $definition): bool;
|
||||
}
|
||||
@@ -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\Core\Settings;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SettingsTypeRegistry
|
||||
{
|
||||
public function __construct(
|
||||
#[AutowireLocator('settings.type')]
|
||||
private ServiceLocator $types
|
||||
) {}
|
||||
|
||||
public function has(string $type): bool
|
||||
{
|
||||
return $this->types->has($type);
|
||||
}
|
||||
|
||||
public function get(string $type): SettingsTypeInterface
|
||||
{
|
||||
return $this->types->get($type);
|
||||
}
|
||||
}
|
||||
@@ -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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
|
||||
#[AsTaggedItem(index: 'bool')]
|
||||
readonly class BoolType implements SettingsTypeInterface
|
||||
{
|
||||
/** @var array<int|string, bool> */
|
||||
private array $stringMap;
|
||||
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
$this->stringMap = [
|
||||
'0' => false,
|
||||
'1' => true,
|
||||
'false' => false,
|
||||
'true' => true,
|
||||
'off' => false,
|
||||
'on' => true,
|
||||
'no' => false,
|
||||
'yes' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return true;
|
||||
}
|
||||
if (($value === 0 || $value === 1)) {
|
||||
return true;
|
||||
}
|
||||
if (is_string($value) && isset($this->stringMap[$value])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_int($value)) {
|
||||
return (bool)$value;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return $this->stringMap[$value] ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/bool.js';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* @todo Extract color parsing into utility including a registry for
|
||||
* custom color spaces.
|
||||
*/
|
||||
#[AsTaggedItem(index: 'color')]
|
||||
readonly class ColorType implements SettingsTypeInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
$stringType = new StringType($this->logger);
|
||||
if (!$stringType->validate($value, $definition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $stringType->transformValue($value, $definition);
|
||||
return $this->doColorNormalization($value) !== null;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): string
|
||||
{
|
||||
$stringType = new StringType($this->logger);
|
||||
if (!$stringType->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
|
||||
$value = $stringType->transformValue($value, $definition);
|
||||
return $this->doColorNormalization($value) ?? $definition->default;
|
||||
}
|
||||
|
||||
private function doColorNormalization(string $value): ?string
|
||||
{
|
||||
if (str_starts_with($value, 'rgb(') && str_ends_with($value, ')')) {
|
||||
$values = GeneralUtility::trimExplode(',', substr(substr($value, 0, -1), 4));
|
||||
return $this->normalizeRgb($values);
|
||||
}
|
||||
if (str_starts_with($value, 'rgba(') && str_ends_with($value, ')')) {
|
||||
$values = GeneralUtility::trimExplode(',', substr(substr($value, 0, -1), 5));
|
||||
return $this->normalizeRgba($values);
|
||||
}
|
||||
|
||||
if (str_starts_with($value, '#')) {
|
||||
return $this->normalizeHex(substr($value, 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizeRgb(array $values): ?string
|
||||
{
|
||||
if (count($values) === 1) {
|
||||
$values = GeneralUtility::trimExplode('/', $values[0]);
|
||||
if (count($values) === 2) {
|
||||
return $this->normalizeRgba([...GeneralUtility::trimExplode(' ', $values[0]), $values[1]]);
|
||||
}
|
||||
$values = GeneralUtility::trimExplode(' ', $values[0]);
|
||||
}
|
||||
if (count($values) !== 3) {
|
||||
return null;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (int)$value;
|
||||
if ($value < 0 || $value > 255) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return 'rgb(' . implode(',', $values) . ')';
|
||||
}
|
||||
|
||||
private function normalizeRgba(array $values): ?string
|
||||
{
|
||||
if (count($values) !== 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$a = array_pop($values);
|
||||
if (!MathUtility::canBeInterpretedAsFloat($a)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((float)$a < 0 || (float)$a > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (int)$value;
|
||||
if ($value < 0 || $value > 255) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$values[] = $a;
|
||||
return 'rgba(' . implode(',', $values) . ')';
|
||||
}
|
||||
|
||||
private function normalizeHex(string $values): ?string
|
||||
{
|
||||
$len = strlen($values);
|
||||
if ($len !== 3 && $len !== 6 && $len !== 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[0-9a-f]+$/i', $values)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '#' . $values;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/color.js';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOption;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOptionAwareInterface;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
#[AsTaggedItem(index: 'int')]
|
||||
readonly class IntType implements SettingsTypeInterface, SettingsTypeOptionAwareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
// Normalize to integer if possible
|
||||
if (is_int($value)) {
|
||||
$intValue = $value;
|
||||
} elseif (is_string($value) && MathUtility::canBeInterpretedAsInteger($value)) {
|
||||
$intValue = (int)$value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check optional constraints
|
||||
if (array_key_exists('min', $definition->options)
|
||||
&& $intValue < $definition->options['min']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('max', $definition->options)
|
||||
&& $intValue > $definition->options['max']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('step', $definition->options)) {
|
||||
$stepBase = array_key_exists('min', $definition->options) ? $definition->options['min'] : 0;
|
||||
if (($stepBase - $value) % $definition->options['step'] !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): int
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
public function getSupportedOptions(): array
|
||||
{
|
||||
return [
|
||||
'min' => new SettingsTypeOption(
|
||||
type: 'int',
|
||||
description: 'Minimum value allowed',
|
||||
required: false,
|
||||
),
|
||||
'max' => new SettingsTypeOption(
|
||||
type: 'int',
|
||||
description: 'Maximum value allowed',
|
||||
required: false,
|
||||
),
|
||||
'step' => new SettingsTypeOption(
|
||||
type: 'int',
|
||||
description: 'Step size',
|
||||
required: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function validateOptions(SettingDefinition $definition): bool
|
||||
{
|
||||
$min = $definition->options['min'] ?? null;
|
||||
$max = $definition->options['max'] ?? null;
|
||||
$step = $definition->options['step'] ?? null;
|
||||
if ($min !== null && $max !== null && $min > $max) {
|
||||
return false;
|
||||
}
|
||||
if ($min !== null && $max !== null && $step !== null) {
|
||||
if ($max - $min < $step) {
|
||||
return false;
|
||||
}
|
||||
if (($max - $min) % $step !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/int.js';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOption;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOptionAwareInterface;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
#[AsTaggedItem(index: 'number')]
|
||||
readonly class NumberType implements SettingsTypeInterface, SettingsTypeOptionAwareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
// Normalize value
|
||||
if (is_int($value) || is_float($value)) {
|
||||
$numericValue = (float)$value;
|
||||
} elseif (is_string($value) && (
|
||||
MathUtility::canBeInterpretedAsInteger($value)
|
||||
|| MathUtility::canBeInterpretedAsFloat($value)
|
||||
)) {
|
||||
$numericValue = (float)$value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check optional constraints
|
||||
if (array_key_exists('min', $definition->options)
|
||||
&& $numericValue < $definition->options['min']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('max', $definition->options)
|
||||
&& $numericValue > $definition->options['max']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('step', $definition->options)) {
|
||||
$stepBase = array_key_exists('min', $definition->options) ? $definition->options['min'] : 0.0;
|
||||
$offset = ($stepBase - $numericValue) / $definition->options['step'];
|
||||
if ((string)(int)$offset !== (string)$offset) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): int|float
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return MathUtility::canBeInterpretedAsInteger($value)
|
||||
? (int)$value
|
||||
: (float)$value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getSupportedOptions(): array
|
||||
{
|
||||
return [
|
||||
'min' => new SettingsTypeOption(
|
||||
type: 'number',
|
||||
description: 'Minimum value allowed',
|
||||
required: false,
|
||||
),
|
||||
'max' => new SettingsTypeOption(
|
||||
type: 'number',
|
||||
description: 'Maximum value allowed',
|
||||
required: false,
|
||||
),
|
||||
'step' => new SettingsTypeOption(
|
||||
type: 'number',
|
||||
description: 'Step size',
|
||||
required: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function validateOptions(SettingDefinition $definition): bool
|
||||
{
|
||||
$min = $definition->options['min'] ?? null;
|
||||
$max = $definition->options['max'] ?? null;
|
||||
$step = $definition->options['step'] ?? null;
|
||||
if ($min !== null && $max !== null && $min > $max) {
|
||||
return false;
|
||||
}
|
||||
if ($min !== null && $max !== null && $step !== null) {
|
||||
if ($max - $min < $step) {
|
||||
return false;
|
||||
}
|
||||
$steps = ($max - $min) / $step;
|
||||
if ((string)(int)$steps !== (string)$steps) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/number.js';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
#[AsTaggedItem(index: 'page')]
|
||||
readonly class PageType implements SettingsTypeInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (is_int($value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($value) && MathUtility::canBeInterpretedAsInteger($value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): int
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/page.js';
|
||||
}
|
||||
}
|
||||
@@ -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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
|
||||
#[AsTaggedItem(index: 'stringlist')]
|
||||
readonly class StringListType implements SettingsTypeInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
$value = $this->decodeJsonAsFallback($value);
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
return $this->doValidate(new StringType($this->logger), $value, $definition);
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): array
|
||||
{
|
||||
$stringType = new StringType($this->logger);
|
||||
$value = $this->decodeJsonAsFallback($value);
|
||||
if (!is_array($value) || !$this->doValidate($stringType, $value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
|
||||
return array_map(static fn(mixed $v): string => $stringType->transformValue($v, $definition), $value);
|
||||
}
|
||||
|
||||
public function doValidate(StringType $stringType, array $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (!array_is_list($value)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($value as $v) {
|
||||
if (!$stringType->validate($v, $definition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/stringlist.js';
|
||||
}
|
||||
|
||||
private function decodeJsonAsFallback(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Otherwise, check if given string value is a json-encoded string
|
||||
if (is_string($value)) {
|
||||
try {
|
||||
// A json-encoded stringlist only needs 2-levels
|
||||
$value = json_decode($value, false, 2, JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOption;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOptionAwareInterface;
|
||||
|
||||
#[AsTaggedItem(index: 'string')]
|
||||
readonly class StringType implements SettingsTypeInterface, SettingsTypeOptionAwareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if (is_object($value) && !$value instanceof \Stringable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize value
|
||||
$stringValue = (string)$value;
|
||||
|
||||
// Check optional constraints
|
||||
if (array_key_exists('min', $definition->options)
|
||||
&& mb_strlen($stringValue) < (int)$definition->options['min']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('max', $definition->options)
|
||||
&& mb_strlen($stringValue) > (int)$definition->options['max']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): string
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Setting validation field, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return $definition->default;
|
||||
}
|
||||
if (is_bool($value)) {
|
||||
if ($value) {
|
||||
return 'true';
|
||||
}
|
||||
return 'false';
|
||||
}
|
||||
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
public function getSupportedOptions(): array
|
||||
{
|
||||
return [
|
||||
'min' => new SettingsTypeOption(
|
||||
type: 'int',
|
||||
description: 'Minimum character count allowed',
|
||||
required: false,
|
||||
),
|
||||
'max' => new SettingsTypeOption(
|
||||
type: 'int',
|
||||
description: 'Maximum character count allowed',
|
||||
required: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function validateOptions(SettingDefinition $definition): bool
|
||||
{
|
||||
$min = $definition->options['min'] ?? null;
|
||||
$max = $definition->options['max'] ?? null;
|
||||
if ($min !== null && $min < 0) {
|
||||
return false;
|
||||
}
|
||||
if ($max !== null && $max < 1) {
|
||||
return false;
|
||||
}
|
||||
if ($min !== null && $max !== null && $min > $max) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/string.js';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
|
||||
#[AsTaggedItem(index: 'text')]
|
||||
readonly class TextType extends StringType {}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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\Core\Settings\Type;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeInterface;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOption;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeOptionAwareInterface;
|
||||
|
||||
#[AsTaggedItem(index: 'url')]
|
||||
readonly class UrlType implements SettingsTypeInterface, SettingsTypeOptionAwareInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function validate(mixed $value, SettingDefinition $definition): bool
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return true;
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter_var($value, FILTER_VALIDATE_URL) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check optional constraints
|
||||
if (array_key_exists('pattern', $definition->options)
|
||||
&& preg_match('/' . str_replace('/', '\/', $definition->options['pattern']) . '/', $value) !== 1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function transformValue(mixed $value, SettingDefinition $definition): string
|
||||
{
|
||||
if (!$this->validate($value, $definition)) {
|
||||
$this->logger->warning('Invalid URL, reverting to default: {key}', ['key' => $definition->key]);
|
||||
return (string)$definition->default;
|
||||
}
|
||||
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
public function getSupportedOptions(): array
|
||||
{
|
||||
return [
|
||||
'pattern' => new SettingsTypeOption(
|
||||
type: 'string',
|
||||
description: 'Regular expression pattern for URL validation',
|
||||
required: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function validateOptions(SettingDefinition $definition): bool
|
||||
{
|
||||
if (array_key_exists('pattern', $definition->options)
|
||||
&& @preg_match('/' . str_replace('/', '\/', $definition->options['pattern']) . '/', '') === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getJavaScriptModule(): string
|
||||
{
|
||||
return '@typo3/backend/settings/type/url.js';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user