TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:15 +02:00
commit 6830982e7d
295 changed files with 31995 additions and 0 deletions
@@ -0,0 +1,145 @@
<?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\Extbase\Validation\Validator;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException;
/**
* An abstract composite validator consisting of other validators
*/
abstract class AbstractCompositeValidator implements ObjectValidatorInterface, \Countable
{
/**
* This contains the supported options, their default values and descriptions.
*
* @var array
*/
protected $supportedOptions = [];
protected array $options = [];
/**
* @var \SplObjectStorage<ValidatorInterface>
*/
protected \SplObjectStorage $validators;
protected ?ServerRequestInterface $request = null;
protected \SplObjectStorage $validatedInstancesContainer;
public function setOptions(array $options): void
{
$this->initializeDefaultOptions($options);
}
/**
* Adds a new validator to the composition.
*/
public function addValidator(ValidatorInterface $validator): void
{
$this->validators->offsetSet($validator);
}
public function getRequest(): ?ServerRequestInterface
{
return $this->request;
}
public function setRequest(?ServerRequestInterface $request): void
{
$this->request = $request;
}
/**
* Removes the specified validator.
*
* @throws NoSuchValidatorException
*/
public function removeValidator(ValidatorInterface $validator): void
{
if (!$this->validators->offsetExists($validator)) {
throw new NoSuchValidatorException('Cannot remove validator because its not in the conjunction.', 1207020177);
}
$this->validators->offsetUnset($validator);
}
/**
* Returns the number of validators contained in this composition.
*/
public function count(): int
{
return count($this->validators);
}
/**
* Returns the child validators of this Composite Validator
*
* @return \SplObjectStorage<ValidatorInterface>
*/
public function getValidators(): \SplObjectStorage
{
return $this->validators;
}
/**
* Returns the options for this validator
*/
public function getOptions(): array
{
return $this->options;
}
/**
* Allows to set a container to keep track of validated instances.
*/
public function setValidatedInstancesContainer(\SplObjectStorage $validatedInstancesContainer): void
{
$this->validatedInstancesContainer = $validatedInstancesContainer;
}
/**
* Initialize default options.
* @throws InvalidValidationOptionsException
*/
protected function initializeDefaultOptions(array $options): void
{
// check for options given but not supported
if (($unsupportedOptions = array_diff_key($options, $this->supportedOptions)) !== []) {
throw new InvalidValidationOptionsException('Unsupported validation option(s) found: ' . implode(', ', array_keys($unsupportedOptions)), 1339079804);
}
// check for required options being set
array_walk(
$this->supportedOptions,
static function (array $supportedOptionData, string $supportedOptionName, array $options): void {
if (isset($supportedOptionData[3]) && !array_key_exists($supportedOptionName, $options)) {
throw new InvalidValidationOptionsException('Required validation option not set: ' . $supportedOptionName, 1339163922);
}
},
$options
);
// merge with default values
$this->options = array_merge(
array_map(
static fn(array $value): mixed => $value[0],
$this->supportedOptions
),
$options
);
$this->validators = new \SplObjectStorage();
}
}
@@ -0,0 +1,178 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Error\Result;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
/**
* A generic object validator which allows for specifying property validators.
*/
abstract class AbstractGenericObjectValidator extends AbstractValidator implements ObjectValidatorInterface
{
/**
* @var array<string, \SplObjectStorage<ValidatorInterface>>
*/
protected array $propertyValidators = [];
/**
* @var \SplObjectStorage
*/
protected $validatedInstancesContainer;
/**
* Checks if the given value is valid according to the validator, and returns
* the Error Messages object which occurred.
*
* @param mixed $value The value that should be validated
*/
public function validate(mixed $value): Result
{
if (is_object($value) && $this->isValidatedAlready($value)) {
return $this->result;
}
$this->result = new Result();
if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) {
if (!is_object($value)) {
$this->addError('Object expected, %1$s given.', 1241099149, [gettype($value)]);
} elseif ($this->isValidatedAlready($value) === false) {
$this->markInstanceAsValidated($value);
$this->isValid($value);
}
}
return $this->result;
}
/**
* Load the property value to be used for validation.
* In case the object is a doctrine proxy, we need to load the real instance first.
*/
protected function getPropertyValue(object $object, string $propertyName): mixed
{
if (ObjectAccess::isPropertyGettable($object, $propertyName)) {
return ObjectAccess::getProperty($object, $propertyName);
}
throw new \RuntimeException(
sprintf(
'Could not get value of property "%s::%s", make sure the property is either public or has a getter get%3$s(), a hasser has%3$s() or an isser is%3$s().',
get_class($object),
$propertyName,
ucfirst($propertyName)
),
1546632293
);
}
/**
* Checks if the specified property of the given object is valid, and adds
* found errors to the $messages object.
*
* @param \Traversable<ValidatorInterface> $validators
*/
protected function checkProperty(mixed $value, \Traversable $validators, string $propertyName): void
{
/** @var Result|null $result */
$result = null;
foreach ($validators as $validator) {
if ($validator instanceof ObjectValidatorInterface) {
$validator->setValidatedInstancesContainer($this->validatedInstancesContainer);
}
$currentResult = $validator->validate($value);
if ($currentResult->hasMessages()) {
if ($result == null) {
$result = $currentResult;
} else {
$result->merge($currentResult);
}
}
}
if ($result != null) {
$this->result->forProperty($propertyName)->merge($result);
}
}
/**
* Checks if the given value is valid according to the property validators.
*/
protected function isValid(mixed $object): void
{
foreach ($this->propertyValidators as $propertyName => $validators) {
$propertyValue = $this->getPropertyValue($object, $propertyName);
$this->checkProperty($propertyValue, $validators, $propertyName);
}
}
/**
* Checks the given object can be validated by the validator implementation
*/
public function canValidate(mixed $object): bool
{
return is_object($object);
}
/**
* Adds the given validator for validation of the specified property.
*/
public function addPropertyValidator(string $propertyName, ValidatorInterface $validator): void
{
if (!isset($this->propertyValidators[$propertyName])) {
$this->propertyValidators[$propertyName] = new \SplObjectStorage();
}
$this->propertyValidators[$propertyName]->offsetSet($validator);
}
protected function isValidatedAlready(object $object): bool
{
if ($this->validatedInstancesContainer === null) {
$this->validatedInstancesContainer = new \SplObjectStorage();
}
if ($this->validatedInstancesContainer->offsetExists($object)) {
return true;
}
return false;
}
protected function markInstanceAsValidated(object $object): void
{
$this->validatedInstancesContainer->offsetSet($object);
}
/**
* Returns all property validators - or only validators of the specified property
*
* @return ($propertyName is null ? array<string, \SplObjectStorage<ValidatorInterface>> : \SplObjectStorage<ValidatorInterface>)
*/
public function getPropertyValidators(?string $propertyName = null): array|\SplObjectStorage
{
if ($propertyName !== null) {
return $this->propertyValidators[$propertyName] ?? [];
}
return $this->propertyValidators;
}
/**
* Allows to set a container to keep track of validated instances.
*/
public function setValidatedInstancesContainer(\SplObjectStorage $validatedInstancesContainer): void
{
$this->validatedInstancesContainer = $validatedInstancesContainer;
}
}
@@ -0,0 +1,231 @@
<?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\Extbase\Validation\Validator;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Type\File\FileInfo;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Error\Result;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Extbase\Validation\Error;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Abstract validator. Mother af most validators.
*/
abstract class AbstractValidator implements ValidatorInterface
{
/**
* Specifies whether this validator accepts empty values.
*
* If this is TRUE, the validators isValid() method is not called in case of an empty value
* Note: A value is considered empty if it is NULL or an empty string!
* By default, all validators except for NotEmpty and the Composite Validators accept empty values.
*
* @var bool
*/
protected $acceptsEmptyValues = true;
/**
* Contains an array of property names used for translation handling of error messages.
*/
protected array $translationOptions = ['message'];
/**
* This contains the supported options, their default values, types and descriptions.
*
* @var array
*/
protected $supportedOptions = [];
protected array $options = [];
protected Result $result;
protected ?ServerRequestInterface $request = null;
public function setOptions(array $options): void
{
$this->initializeDefaultOptions($options);
$this->initializeTranslationOptions($options);
}
public function setRequest(?ServerRequestInterface $request): void
{
$this->request = $request;
}
/**
* Checks if the given value is valid according to the validator, and returns
* the error messages object which occurred.
*
* @param mixed $value The value that should be validated
*/
public function validate(mixed $value): Result
{
$this->result = new Result();
if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) {
$this->isValid($value);
}
return $this->result;
}
/**
* Check if $value is valid. If it is not valid, needs to add an error to result.
*/
abstract protected function isValid(mixed $value): void;
/**
* Creates a new validation error object and adds it to $this->result
*
* @param string $message The error message
* @param int $code The error code (a unix timestamp)
* @param array $arguments Arguments to be replaced in message
* @param string $title title of the error
*/
protected function addError(string $message, int $code, array $arguments = [], string $title = ''): void
{
$this->result->addError(new Error($message, $code, $arguments, $title));
}
/**
* Creates a new validation error object for a property and adds it to the proper sub result of $this->result
*
* @param string|array $propertyPath The property path (string or array)
* @param string $message The error message
* @param int $code The error code (a unix timestamp)
* @param array $arguments Arguments to be replaced in message
* @param string $title Title of the error
*/
protected function addErrorForProperty(string|array $propertyPath, string $message, int $code, array $arguments = [], string $title = ''): void
{
$propertyPath = is_array($propertyPath) ? implode('.', $propertyPath) : $propertyPath;
$error = new Error($message, $code, $arguments, $title);
$this->result->forProperty($propertyPath)->addError($error);
}
/**
* Returns the options of this validator
*/
public function getOptions(): array
{
return $this->options;
}
public function getRequest(): ?ServerRequestInterface
{
return $this->request;
}
/**
* TRUE if the given $value is NULL or an empty string ('')
*/
final protected function isEmpty(mixed $value): bool
{
return $value === null || $value === '';
}
/**
* Translates an error message using LocalizationUtility::translate() method. If the translate key does not
* start with 'LLL:' and if no extension name is provided, the original translate key is returned.
*/
protected function translateErrorMessage(
string $translateKey,
string $extensionName = '',
array $arguments = []
): string {
if ($extensionName === '' && !str_starts_with($translateKey, 'LLL:')) {
return $translateKey;
}
return LocalizationUtility::translate(
$translateKey,
$extensionName,
$arguments
) ?? '';
}
/**
* Initialize default options.
* @throws InvalidValidationOptionsException
*/
protected function initializeDefaultOptions(array $options): void
{
// check for options given but not supported
if (($unsupportedOptions = array_diff_key($options, $this->supportedOptions)) !== []) {
throw new InvalidValidationOptionsException('Unsupported validation option(s) found: ' . implode(', ', array_keys($unsupportedOptions)), 1379981890);
}
// check for required options being set
array_walk(
$this->supportedOptions,
static function (array $supportedOptionData, string $supportedOptionName, array $options): void {
if (isset($supportedOptionData[3]) && $supportedOptionData[3] === true && !array_key_exists($supportedOptionName, $options)) {
throw new InvalidValidationOptionsException('Required validation option not set: ' . $supportedOptionName, 1379981891);
}
},
$options
);
// merge with default values
$this->options = array_merge(
array_map(
static fn(array $value): mixed => $value[0],
$this->supportedOptions
),
$options
);
}
/**
* Ensures that the provided value is either an instance of UploadedFile or an ObjectStorage containing only
* UploadedFile instances.
*/
protected function ensureFileUploadTypes(mixed $value): void
{
if ($value instanceof UploadedFile) {
return;
}
if ($value instanceof ObjectStorage) {
foreach ($value as $uploadedFile) {
if (!$uploadedFile instanceof UploadedFile) {
throw new \InvalidArgumentException('Value to validate must be an ObjectStorage of TYPO3\\CMS\\Core\\Http\\UploadedFile', 1722763902);
}
}
return;
}
throw new \InvalidArgumentException('Value to validate must be a TYPO3\\CMS\\Core\\Http\\UploadedFile', 1712057926);
}
protected function getFileInfo(string $filePath): FileInfo
{
return GeneralUtility::makeInstance(FileInfo::class, $filePath);
}
/**
* Initializes all registered translation options with custom translation options from the given options array
*/
protected function initializeTranslationOptions(array $options): void
{
foreach ($this->translationOptions as $translationOption) {
if (property_exists($this, $translationOption)) {
$this->$translationOption = $options[$translationOption] ?? $this->$translationOption;
}
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for alphanumeric strings
*/
final class AlphanumericValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.alphanumeric.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* The given $value is valid if it is an alphanumeric string, which is defined as [\pL\d]*.
*/
public function isValid(mixed $value): void
{
if (!is_string($value) || preg_match('/^[\pL\d]*$/u', $value) !== 1) {
$this->addError($this->translateErrorMessage($this->message), 1221551320);
}
}
}
@@ -0,0 +1,84 @@
<?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\Extbase\Validation\Validator;
/**
* Validator for boolean values
*/
final class BooleanValidator extends AbstractValidator
{
protected string $notTrueMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.boolean.nottrue';
protected string $notFalseMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.boolean.notfalse';
protected array $translationOptions = ['notTrueMessage', 'notFalseMessage'];
/**
* @var array
*/
protected $supportedOptions = [
// The default is set to NULL here, because we need to be backward compatible here, because this
// BooleanValidator is called automatically on boolean action arguments. If we would set it to TRUE,
// every FALSE value for an action argument would break.
// @todo with next patches: deprecate this BooleanValidator and introduce a BooleanValueValidator, like
// in Flow, which won't be called on boolean action arguments.
'is' => [null, 'Boolean value', 'boolean|string|integer'],
'notTrueMessage' => [null, 'Translation key or message for not true value', 'string'],
'notFalseMessage' => [null, 'Translation key or message for not false value', 'string'],
];
/**
* Check if $value matches the expectation given to the validator.
* If it does not match, the function adds an error to the result.
*
* Also testing for '1' (true), '0' and '' (false) because casting varies between
* tests and actual usage. This makes the validator loose but still keeping functionality.
*/
public function isValid(mixed $value): void
{
// see comment above, check if expectation is NULL, then nothing to do!
if ($this->options['is'] === null) {
return;
}
switch (strtolower((string)$this->options['is'])) {
case 'true':
case '1':
$expectation = true;
break;
case 'false':
case '':
case '0':
$expectation = false;
break;
default:
$this->addError('The given expectation is not valid.', 1361959227);
return;
}
if ($value !== $expectation) {
if (!is_bool($value)) {
$this->addError($this->translateErrorMessage($this->notTrueMessage), 1361959230);
} else {
if ($expectation) {
$this->addError($this->translateErrorMessage($this->notTrueMessage), 1361959228);
} else {
$this->addError($this->translateErrorMessage($this->notFalseMessage), 1361959229);
}
}
}
}
}
@@ -0,0 +1,91 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Error\Result;
use TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage;
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
/**
* A generic collection validator.
*/
final class CollectionValidator extends AbstractGenericObjectValidator
{
/**
* @var array
*/
protected $supportedOptions = [
'elementValidator' => [null, 'The validator type to use for the collection elements', 'string'],
'elementType' => [null, 'The type of the elements in the collection', 'string'],
];
public function __construct(private readonly ValidatorResolver $validatorResolver) {}
/**
* Checks if the given value is valid according to the validator, and returns
* the Error Messages object which occurred.
*/
public function validate(mixed $value): Result
{
$this->result = new Result();
if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) {
if ((is_object($value) && !TypeHandlingUtility::isCollectionType(get_class($value))) && !is_array($value)) {
$this->addError('The given subject was not a collection.', 1317204797);
return $this->result;
}
if ($value instanceof LazyObjectStorage && !$value->isInitialized()) {
return $this->result;
}
if (is_object($value)) {
if ($this->isValidatedAlready($value)) {
return $this->result;
}
$this->markInstanceAsValidated($value);
}
$this->isValid($value);
}
return $this->result;
}
/**
* Checks for a collection and if needed validates the items in the collection.
* This is done with the specified element validator or a validator based on
* the given element type.
*
* Either elementValidator or elementType must be given, otherwise validation
* will be skipped.
*/
protected function isValid(mixed $value): void
{
foreach ($value as $index => $collectionElement) {
if (isset($this->options['elementValidator'])) {
$collectionElementValidator = $this->validatorResolver->createValidator($this->options['elementValidator']);
} elseif (isset($this->options['elementType'])) {
$collectionElementValidator = $this->validatorResolver->getBaseValidatorConjunction($this->options['elementType']);
} else {
return;
}
if ($collectionElementValidator instanceof ObjectValidatorInterface) {
$collectionElementValidator->setValidatedInstancesContainer($this->validatedInstancesContainer);
}
$this->result->forProperty((string)$index)->merge($collectionElementValidator->validate($collectionElement));
}
}
}
@@ -0,0 +1,48 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Error\Result;
/**
* Validator to chain many validators in a conjunction (logical and).
*/
final class ConjunctionValidator extends AbstractCompositeValidator
{
public function __construct()
{
$this->validators = new \SplObjectStorage();
$this->validatedInstancesContainer = new \SplObjectStorage();
}
/**
* Checks if the given value is valid according to the validators of the conjunction.
* Every validator has to be valid, to make the whole conjunction valid.
*
* @param mixed $value The value that should be validated
*/
public function validate(mixed $value): Result
{
$result = new Result();
foreach ($this->getValidators() as $validator) {
$result->merge($validator->validate($value));
}
return $result;
}
}
@@ -0,0 +1,123 @@
<?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\Extbase\Validation\Validator;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Exclude;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\Validation;
use TYPO3\CMS\Extbase\Error\Error;
use TYPO3\CMS\Extbase\Error\Result;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Validator that decorates a Symfony Constraint.
*/
#[Exclude]
final readonly class ConstraintDecoratingValidator implements ValidatorInterface
{
public function __construct(
private Constraint $constraint,
) {}
public function validate(mixed $value): Result
{
$validator = Validation::createValidatorBuilder()->disableTranslation()->getValidator();
$constraintViolationList = $validator->validate($value, $this->constraint);
$result = new Result();
foreach ($constraintViolationList as $constraintViolation) {
$arguments = array_values($constraintViolation->getParameters());
$code = $this->convertConstraintViolationCode($constraintViolation->getCode());
$messageTemplate = $this->convertConstraintViolationMessageTemplate($constraintViolation);
$result->addError(
new Error(
$this->translateErrorMessage($messageTemplate, $arguments),
$code,
$arguments,
),
);
}
return $result;
}
/**
* Convert UUID-based violation code to an integer.
*/
private function convertConstraintViolationCode(?string $code): int
{
if ($code !== null && $code !== '') {
$hash = hash('sha256', $code);
$code = hexdec(substr($hash, 0, 8));
}
return (int)$code;
}
/**
* Convert named placeholders like {{ value }} to sprintf compatible placeholders like %1$s.
*
* Before: 'The value {{ value }} must follow the {{ format }} format.'
* After: 'The value %1$s must follow the %2$s format.'
*/
private function convertConstraintViolationMessageTemplate(ConstraintViolationInterface $constraintViolation): string
{
$placeholderMap = [];
foreach (array_keys($constraintViolation->getParameters()) as $index => $placeholder) {
$placeholderMap[$placeholder] = '%' . ($index + 1) . '$s';
}
return strtr($constraintViolation->getMessageTemplate(), $placeholderMap);
}
/**
* @param list<scalar|\Stringable> $arguments
*/
private function translateErrorMessage(string $translateKey, array $arguments = []): string
{
if (!str_starts_with($translateKey, 'LLL:')) {
return $translateKey;
}
return LocalizationUtility::translate($translateKey, null, $arguments) ?? '';
}
public function setOptions(array $options): void
{
// Intentionally left blank.
}
public function getOptions(): array
{
return [];
}
public function setRequest(?ServerRequestInterface $request): void
{
// Intentionally left blank.
}
public function getRequest(): ?ServerRequestInterface
{
return null;
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for DateTime/DateTimeImmutable objects.
*/
final class DateTimeValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.datetime.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid DateTime object. If this is not
* the case, the function adds an error.
*/
public function isValid(mixed $value): void
{
$this->result->clear();
if ($value instanceof \DateTimeInterface) {
return;
}
$this->addError(
$this->translateErrorMessage(
$this->message,
'',
[
gettype($value),
]
),
1238087674,
[gettype($value)]
);
}
}
@@ -0,0 +1,70 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Error\Result;
/**
* Validator to chain many validators in a disjunction (logical or).
*/
final class DisjunctionValidator extends AbstractCompositeValidator
{
public function __construct()
{
$this->validators = new \SplObjectStorage();
$this->validatedInstancesContainer = new \SplObjectStorage();
}
/**
* Checks if the given value is valid according to the validators of the
* disjunction.
*
* So only one validator has to be valid, to make the whole disjunction valid.
* Errors are only returned if all validators failed.
*
* @param mixed $value The value that should be validated
*/
public function validate(mixed $value): Result
{
$validators = $this->getValidators();
if ($validators->count() > 0) {
$result = null;
foreach ($validators as $validator) {
$validatorResult = $validator->validate($value);
if ($validatorResult->hasErrors()) {
if ($result === null) {
$result = $validatorResult;
} else {
$result->merge($validatorResult);
}
} else {
if ($result === null) {
$result = $validatorResult;
} else {
$result->clear();
}
break;
}
}
} else {
$result = new Result();
}
return $result;
}
}
@@ -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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Validator for email addresses
*/
final class EmailAddressValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.emailaddress.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid email address.
*/
public function isValid(mixed $value): void
{
if (!is_string($value) || !$this->validEmail($value)) {
$this->addError($this->translateErrorMessage($this->message), 1221559976);
}
}
/**
* Checking syntax of input email address
*
* @param string $emailAddress Input string to evaluate
* @return bool Returns TRUE if the $email address (input string) is valid
*/
private function validEmail(string $emailAddress): bool
{
return GeneralUtility::validEmail($emailAddress);
}
}
@@ -0,0 +1,96 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Resource\MimeTypeCompatibilityTypeGuesser;
use TYPO3\CMS\Core\Resource\MimeTypeDetector;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* Validator comparing file extension to the expected mime-type of a given UploadedFile
* or ObjectStorage of UploadedFile objects.
*/
final class FileExtensionMimeTypeConsistencyValidator extends AbstractValidator
{
protected string $notAllowedMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.fileextensionmimetypeconsistency.notallowed';
protected array $translationOptions = ['inconsistentMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'notAllowedMessage' => [null, 'Translation key or message for inconsistent mime-type for file extension', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
$fileInfo = $this->getFileInfo($uploadedFile->getTemporaryFileName());
$mimeType = $fileInfo->getMimeType();
// The file extension of the uploaded file must match the mime-type for this file.
// Example: myfile.txt is actually a PDF file (defined by mime-type), but .txt is not associated
// for application/pdf, so this is not valid.
$fileExtension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$assumedMimesTypeOfFileExtension = (new MimeTypeDetector())->getMimeTypesForFileExtension($fileExtension);
// pass, in case no assumed mime-type was found (e.g., for individual file extension)
if ($mimeType === '' || $assumedMimesTypeOfFileExtension === []) {
return;
}
// Example in case of "exe", which has over 9000 possible MIME types:
// mime-db/db.json is only aware of "application/octet-stream", "application/x-msdos-program", "application/x-msdownload"
// However, PHP detects this as "application/vnd.microsoft.portable-executable" (PHP 8.4+) or "application/x-dosexec" (PHP < 8.4)
// DefaultConfiguration $GLOBALS['TYPO3_CONF_VARS']['SYS']['FileInfo']['mimeTypeCompatibility'] registers this (and some more),
// so we also need these fallbacks to be evaluated.
$mimeTypeCompatibility = (new MimeTypeCompatibilityTypeGuesser())->getMimeTypeCompatibilityList();
$additionalMappedMimeType = $mimeTypeCompatibility[$mimeType][$fileExtension] ?? null;
if (!in_array($mimeType, $assumedMimesTypeOfFileExtension, true)
&& !in_array($additionalMappedMimeType, $assumedMimesTypeOfFileExtension, true)
) {
$message = $this->translateErrorMessage(
$this->notAllowedMessage,
'',
[$mimeType, $fileExtension]
);
$code = 1754045716;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
}
@@ -0,0 +1,108 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator file extensions of a given UploadedFile or ObjectStorage of UploadedFile objects.
*/
final class FileExtensionValidator extends AbstractValidator
{
protected string $notAllowedMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.fileextension.notallowed';
protected array $translationOptions = ['notAllowedMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'allowedFileExtensions' => [null, 'Allowed file extensions', 'array'],
'useStorageDefaults' => [null, 'Whether to use the default allowed file extension of the storage', 'bool'],
'notAllowedMessage' => [null, 'Translation key or message for disallowed file extension', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
$this->validateOptions();
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
$allowedFileExtensions = [];
if (!empty($this->options['useStorageDefaults'])) {
$allowedFileExtensions = GeneralUtility::trimExplode(
',',
($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'] ?? '') . ','
. ($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'] ?? '') . ','
. ($GLOBALS['TYPO3_CONF_VARS']['SYS']['miscfile_ext'] ?? ''),
true
);
}
if (is_array($this->options['allowedFileExtensions'] ?? null)) {
$allowedFileExtensions = array_merge($allowedFileExtensions, $this->options['allowedFileExtensions']);
}
$allowedFileExtensions = array_map(mb_strtolower(...), $allowedFileExtensions);
$fileExtension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
if (!in_array($fileExtension, $allowedFileExtensions, true)) {
$message = $this->translateErrorMessage(
$this->notAllowedMessage,
'',
[$fileExtension]
);
$code = 1754043401;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
/**
* Checks if this validator is correctly configured
*/
private function validateOptions(): void
{
$hasAllowedFileExtensions = is_array($this->options['allowedFileExtensions'] ?? null)
&& $this->options['allowedFileExtensions'] !== [];
$shallUseStorageDefaults = !empty($this->options['useStorageDefaults']);
if (!$hasAllowedFileExtensions && !$shallUseStorageDefaults) {
throw new InvalidValidationOptionsException(
'Either the option "allowedFileExtensions" must be an array with at least one item, '
. 'or the option "useStorageDefaults" must be enabled.',
1754043328
);
}
}
}
@@ -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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Resource\Security\FileNameValidator as CoreFileNameValidator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* Validates, that the given UploadedFile or ObjectStorage of UploadedFile objects does not contain a php
* executable file by checking the given file extension.
*/
final class FileNameValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.denyphpupload.notallowed';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
if (!GeneralUtility::makeInstance(CoreFileNameValidator::class)->isValid($uploadedFile->getClientFilename())) {
$message = $this->translateErrorMessage($this->message);
$code = 1711367029;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
}
@@ -0,0 +1,114 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator for file size of a given UploadedFile or ObjectStorage of UploadedFile objects.
*/
final class FileSizeValidator extends AbstractValidator
{
protected string $lessMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.filesize.less';
protected string $exceedMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.filesize.exceed';
protected array $translationOptions = ['lessMessage', 'exceedMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'minimum' => ['0B', 'The minimum file size to accept', 'string'],
'maximum' => [PHP_INT_MAX . 'B', 'The maximum file size to accept', 'string'],
'lessMessage' => [null, 'Translation key or message for value less than minimum', 'string'],
'exceedMessage' => [null, 'Translation key or message for value exceeds maximum', 'string'],
'byteSizeUnits' => [' Bytes| Kilobyte| Megabyte| Gigabyte', 'Byte size units string for "formatSize" function', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
$this->validateOptions();
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
$fileSize = $this->getFileInfo($uploadedFile->getTemporaryFileName())->getSize();
$minFileSize = GeneralUtility::getBytesFromSizeMeasurement($this->options['minimum']);
if ($this->options['maximum'] !== PHP_INT_MAX . 'B') {
$maxFileSize = GeneralUtility::getBytesFromSizeMeasurement($this->options['maximum']);
} else {
$maxFileSize = PHP_INT_MAX;
}
$labels = htmlspecialchars($this->options['byteSizeUnits']);
if ($fileSize < $minFileSize) {
$message = $this->translateErrorMessage(
$this->lessMessage,
'',
[GeneralUtility::formatSize($minFileSize, $labels)]
);
$code = 1708595754;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if ($fileSize > $maxFileSize) {
$message = $this->translateErrorMessage(
$this->exceedMessage,
'',
[GeneralUtility::formatSize($maxFileSize, $labels)]
);
$code = 1708595755;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
/**
* Checks if this validator is correctly configured
*/
private function validateOptions(): void
{
if (!preg_match('/^(\d*\.?\d+)(B|K|M|G)$/i', $this->options['minimum'])) {
throw new InvalidValidationOptionsException('The option "minimum" has an invalid format. Valid formats are something like this: "10B|K|M|G".', 1708595605);
}
if (!preg_match('/^(\d*\.?\d+)(B|K|M|G)$/i', $this->options['maximum'])) {
throw new InvalidValidationOptionsException('The option "maximum" has an invalid format. Valid formats are something like this: "10B|K|M|G".', 1708595606);
}
}
}
@@ -0,0 +1,46 @@
<?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\Extbase\Validation\Validator;
/**
* Validator for floats.
*/
final class FloatValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.float.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* The given value is valid if it is of type float or a string matching the regular expression [0-9.e+-]
*
* @param mixed $value The value that should be validated
*/
public function isValid(mixed $value): void
{
if (is_float($value)) {
return;
}
if (!is_string($value) || !str_contains($value, '.') || preg_match('/^[0-9.e+-]+$/', $value) !== 1) {
$this->addError($this->translateErrorMessage($this->message), 1221560288);
}
}
}
@@ -0,0 +1,24 @@
<?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\Extbase\Validation\Validator;
/**
* A generic object validator which allows for specifying property validators.
* This is used as default model validator.
*/
final class GenericObjectValidator extends AbstractGenericObjectValidator {}
@@ -0,0 +1,191 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator to validate image dimensions of a given UploadedFile or ObjectStorage of UploadedFile objects.
*/
final class ImageDimensionsValidator extends AbstractValidator
{
protected string $widthMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.width.notvalid';
protected string $heightMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.height.notvalid';
protected string $minWidthMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.minwidth.notvalid';
protected string $minHeightMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.minheight.notvalid';
protected string $maxWidthMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.maxwidth.notvalid';
protected string $maxHeightMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.imagedimensions.maxheight.notvalid';
protected array $translationOptions = [
'widthMessage',
'heightMessage',
'minWidthMessage',
'minHeightMessage',
'maxWidthMessage',
'maxHeightMessage',
];
/**
* @var array
*/
protected $supportedOptions = [
'width' => [null, 'The exact width of the image', 'int'],
'height' => [null, 'The exact height of the image', 'int'],
'minWidth' => [0, 'The minimum width of the image', 'int'],
'maxWidth' => [PHP_INT_MAX, 'The maximum width of the image', 'int'],
'minHeight' => [0, 'The minimum height of the image', 'int'],
'maxHeight' => [PHP_INT_MAX, 'The maximum heigt of the image', 'int'],
'heightMessage' => [null, 'Translation key or message for invalid height', 'string'],
'widthMessage' => [null, 'Translation key or message for invalid width', 'string'],
'minWidthMessage' => [null, 'Translation key or message for invalid minimum width', 'string'],
'maxWidthMessage' => [null, 'Translation key or message for invalid maximum width', 'string'],
'minHeightMessage' => [null, 'Translation key or message for invalid minimum height', 'string'],
'maxHeightMessage' => [null, 'Translation key or message for invalid maximum height', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
$this->validateOptions();
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
$imageInfo = $this->getImageInfo($uploadedFile->getTemporaryFileName());
if ($imageInfo->getWidth() === 0 || $imageInfo->getHeight() === 0) {
// Silently ignore files, where the width or height could not be determined. Most likely no image file.
return;
}
if (isset($this->options['width']) && (int)$this->options['width'] !== $imageInfo->getWidth()) {
$message = $this->translateErrorMessage(
$this->widthMessage,
'',
[$this->options['width']]
);
$code = 1715964040;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if (isset($this->options['height']) && (int)$this->options['height'] !== $imageInfo->getHeight()) {
$message = $this->translateErrorMessage(
$this->heightMessage,
'',
[$this->options['height']]
);
$code = 1715964041;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if ((int)$this->options['minWidth'] > $imageInfo->getWidth()) {
$message = $this->translateErrorMessage(
$this->minWidthMessage,
'',
[$this->options['minWidth']]
);
$code = 1715964042;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if ((int)$this->options['minHeight'] > $imageInfo->getHeight()) {
$message = $this->translateErrorMessage(
$this->minHeightMessage,
'',
[$this->options['minHeight']]
);
$code = 1715964043;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if ((int)$this->options['maxWidth'] < $imageInfo->getWidth()) {
$message = $this->translateErrorMessage(
$this->maxWidthMessage,
'',
[$this->options['maxWidth']]
);
$code = 1715964044;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if ((int)$this->options['maxHeight'] < $imageInfo->getHeight()) {
$message = $this->translateErrorMessage(
$this->maxHeightMessage,
'',
[$this->options['maxHeight']]
);
$code = 1715964045;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
private function getImageInfo(string $filePath): ImageInfo
{
return GeneralUtility::makeInstance(ImageInfo::class, $filePath);
}
/**
* Checks if this validator is correctly configured
*/
private function validateOptions(): void
{
if ((int)$this->options['minWidth'] > (int)$this->options['maxWidth']) {
throw new InvalidValidationOptionsException('The option "minWidth" must not be greater than "maxWidth"', 1716008127);
}
if ((int)$this->options['minHeight'] > (int)$this->options['maxHeight']) {
throw new InvalidValidationOptionsException('The option "minHeight" must not be greater than "maxHeight"', 1716008128);
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for integers.
*/
final class IntegerValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.integer.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid integer.
*/
public function isValid(mixed $value): void
{
if (filter_var($value, FILTER_VALIDATE_INT) === false) {
$this->addError($this->translateErrorMessage($this->message), 1221560494);
}
}
}
@@ -0,0 +1,115 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Http\UploadedFile;
use TYPO3\CMS\Core\Resource\MimeTypeDetector;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator for mime types of a given UploadedFile or ObjectStorage of UploadedFile objects. Does also validate, if
* the given file extension matches allowed file extensions for the detected mime type.
*/
final class MimeTypeValidator extends AbstractValidator
{
protected string $notAllowedMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.mimetype.notallowed';
protected string $invalidExtensionMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validation.mimetype.invalidextension';
protected array $translationOptions = ['notAllowedMessage', 'invalidExtensionMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'allowedMimeTypes' => [null, 'Allowed mime types (using */* IANA media types)', 'array', true],
'ignoreFileExtensionCheck' => [false, 'If set to "true", the file extension check is disabled. Be aware of security considerations when setting this to "true".', 'boolean'],
'notAllowedMessage' => [null, 'Translation key or message for not allowed MIME type', 'string'],
'invalidExtensionMessage' => [null, 'Translation key or message for invalid file extension', 'string'],
];
public function isValid(mixed $value): void
{
$this->ensureFileUploadTypes($value);
$this->validateOptions();
if ($value instanceof UploadedFile) {
$this->validateUploadedFile($value);
} elseif ($value instanceof ObjectStorage && $value->count() > 0) {
$index = 0;
foreach ($value as $uploadedFile) {
$this->validateUploadedFile($uploadedFile, $index);
$index++;
}
}
}
private function validateUploadedFile(UploadedFile $uploadedFile, ?int $index = null): void
{
$fileInfo = $this->getFileInfo($uploadedFile->getTemporaryFileName());
$mimeType = $fileInfo->getMimeType();
$allowedMimeTypes = $this->options['allowedMimeTypes'];
$ignoreFileExtensionCheck = $this->options['ignoreFileExtensionCheck'];
if (!in_array($mimeType, $allowedMimeTypes, true)) {
$message = $this->translateErrorMessage(
$this->notAllowedMessage,
'',
[$mimeType]
);
$code = 1708538973;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
if (!$ignoreFileExtensionCheck && !$this->result->hasErrors()) {
// The file extension of the uploaded file must match the mime-type for this file.
// Example: myfile.txt is actually a PDF file (defined by mime-type), but .txt is not associated
// for application/pdf, so this is not valid.
$fileExtension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
$assumedMimesTypeOfFileExtension = (new MimeTypeDetector())->getMimeTypesForFileExtension($fileExtension);
if (empty(array_intersect($allowedMimeTypes, $assumedMimesTypeOfFileExtension))) {
$message = $this->translateErrorMessage(
$this->invalidExtensionMessage,
'',
[$fileExtension]
);
$code = 1718469466;
if ($index !== null) {
$this->addErrorForProperty((string)$index, $message, $code);
} else {
$this->addError($message, $code);
}
}
}
}
/**
* Checks if this validator is correctly configured
*/
private function validateOptions(): void
{
if (!is_array($this->options['allowedMimeTypes'] ?? false) || $this->options['allowedMimeTypes'] === []) {
throw new InvalidValidationOptionsException('The option "allowedMimeTypes" must be an array with at least one item.', 1708526223);
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for not empty values.
*/
final class NotEmptyValidator extends AbstractValidator
{
/**
* This validator always needs to be executed even if the given value is empty.
* See AbstractValidator::validate()
*
* @var bool
*/
protected $acceptsEmptyValues = false;
protected string $nullMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.notempty.null';
protected string $emptyMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.notempty.empty';
protected array $translationOptions = ['nullMessage', 'emptyMessage'];
protected $supportedOptions = [
'nullMessage' => [null, 'Translation key or message for null value', 'string'],
'emptyMessage' => [null, 'Translation key or message for empty value', 'string'],
];
/**
* Checks if the given value ($propertyValue) is not empty (NULL, empty string, empty array or empty object).
*/
public function isValid(mixed $value): void
{
if ($value === null) {
$this->addError($this->translateErrorMessage($this->nullMessage), 1221560910);
}
if ($value === '') {
$this->addError($this->translateErrorMessage($this->emptyMessage), 1221560718);
}
if (is_array($value) && empty($value)) {
$this->addError($this->translateErrorMessage($this->emptyMessage), 1347992400);
}
if ($value instanceof \Countable && $value->count() === 0) {
$this->addError($this->translateErrorMessage($this->emptyMessage), 1347992453);
}
}
}
@@ -0,0 +1,69 @@
<?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\Extbase\Validation\Validator;
/**
* Validator for general numbers
*/
final class NumberRangeValidator extends AbstractValidator
{
protected string $notValidMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.numberrange.notvalid';
protected string $notInRangeMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.numberrange.range';
protected array $translationOptions = ['notValidMessage', 'notInRangeMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'minimum' => [0, 'The minimum value to accept', 'integer'],
'maximum' => [PHP_INT_MAX, 'The maximum value to accept', 'integer'],
'notValidMessage' => [null, 'Translation key or message for non valid value', 'string'],
'notInRangeMessage' => [null, 'Translation key or message for value not in range', 'string'],
];
/**
* The given value is valid if it is a number in the specified range.
*/
public function isValid(mixed $value): void
{
if (!is_numeric($value)) {
$this->addError($this->translateErrorMessage($this->notValidMessage), 1221563685);
return;
}
$minimum = $this->options['minimum'];
$maximum = $this->options['maximum'];
if ($minimum > $maximum) {
$x = $minimum;
$minimum = $maximum;
$maximum = $x;
}
if ($value < $minimum || $value > $maximum) {
$this->addError($this->translateErrorMessage(
$this->notInRangeMessage,
'',
[
$minimum,
$maximum,
]
), 1221561046, [$minimum, $maximum]);
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for general numbers.
*/
final class NumberValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.number.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid number.
*/
public function isValid(mixed $value): void
{
if (!is_numeric($value)) {
$this->addError($this->translateErrorMessage($this->message), 1221563685);
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Contract for a validator
*/
interface ObjectValidatorInterface extends ValidatorInterface
{
/**
* Allows to set a container to keep track of validated instances.
*
* @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances
*/
public function setValidatedInstancesContainer(\SplObjectStorage $validatedInstancesContainer): void;
}
@@ -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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator based on regular expressions.
*/
final class RegularExpressionValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.regularexpression.nomatch';
/**
* @var array
*/
protected $supportedOptions = [
'regularExpression' => ['', 'The regular expression to use for validation, used as given', 'string', true],
'message' => [null, 'Translation key or message when regular expression results in a no match', 'string'],
];
/**
* Checks if the given value matches the specified regular expression.
*
* @throws InvalidValidationOptionsException
*/
public function isValid(mixed $value): void
{
$result = preg_match($this->options['regularExpression'], $value);
if ($result === 0) {
$this->addError(
$this->translateErrorMessage($this->message),
1221565130
);
}
if ($result === false) {
throw new InvalidValidationOptionsException('regularExpression "' . $this->options['regularExpression'] . '" in RegularExpressionValidator contained an error.', 1298273089);
}
}
}
@@ -0,0 +1,118 @@
<?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\Extbase\Validation\Validator;
use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationOptionsException;
/**
* Validator for string length.
*/
final class StringLengthValidator extends AbstractValidator
{
protected string $betweenMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.stringlength.between';
protected string $lessMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.stringlength.less';
protected string $exceedMessage = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.stringlength.exceed';
protected array $translationOptions = ['betweenMessage', 'lessMessage', 'exceedMessage'];
/**
* @var array
*/
protected $supportedOptions = [
'minimum' => [0, 'Minimum length for a valid string', 'integer'],
'maximum' => [PHP_INT_MAX, 'Maximum length for a valid string', 'integer'],
'betweenMessage' => [null, 'Translation key or message for value not between minimum and maximum', 'string'],
'lessMessage' => [null, 'Translation key or message for value less than minimum', 'string'],
'exceedMessage' => [null, 'Translation key or message for value exceeds maximum', 'string'],
];
/**
* Checks if the given value is a valid string (or can be cast to a string
* if an object is given) and its length is between minimum and maximum
* specified in the validation options.
*
* @throws InvalidValidationOptionsException
*/
public function isValid(mixed $value): void
{
if ($this->options['maximum'] < $this->options['minimum']) {
throw new InvalidValidationOptionsException('The \'maximum\' is shorter than the \'minimum\' in the StringLengthValidator.', 1238107096);
}
if (is_object($value)) {
if (!method_exists($value, '__toString')) {
$this->addError('The given object could not be converted to a string.', 1238110957);
return;
}
} elseif (!is_string($value)) {
$this->addError('The given value was not a valid string.', 1269883975);
return;
}
$value = (string)$value;
$stringLength = mb_strlen($value, 'utf-8');
$isValid = true;
if ($stringLength < $this->options['minimum']) {
$isValid = false;
}
if ($stringLength > $this->options['maximum']) {
$isValid = false;
}
if ($isValid === false) {
if ($this->options['minimum'] > 0 && $this->options['maximum'] < PHP_INT_MAX) {
$this->addError(
$this->translateErrorMessage(
$this->betweenMessage,
'',
[
$this->options['minimum'],
$this->options['maximum'],
]
),
1428504122,
[$this->options['minimum'], $this->options['maximum']]
);
} elseif ($this->options['minimum'] > 0) {
$this->addError(
$this->translateErrorMessage(
$this->lessMessage,
'',
[
$this->options['minimum'],
]
),
1238108068,
[$this->options['minimum']]
);
} else {
$this->addError(
$this->translateErrorMessage(
$this->exceedMessage,
'',
[
$this->options['maximum'],
]
),
1238108069,
[$this->options['maximum']]
);
}
}
}
}
@@ -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\Extbase\Validation\Validator;
/**
* Validator for strings.
*/
final class StringValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.string.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a string.
*/
public function isValid(mixed $value): void
{
if (!is_string($value)) {
$this->addError($this->translateErrorMessage($this->message), 1238108067);
}
}
}
@@ -0,0 +1,44 @@
<?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\Extbase\Validation\Validator;
/**
* Validator for "plain" text.
*/
final class TextValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.text.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid text (contains no XML tags).
*
* Be aware that the value of this check entirely depends on the output context.
* The validated text is not expected to be secure in every circumstance, if you
* want to be sure of that, use a customized regular expression or filter on output.
*/
public function isValid(mixed $value): void
{
if ($value !== strip_tags((string)$value)) {
$this->addError($this->translateErrorMessage($this->message), 1221565786);
}
}
}
@@ -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\Extbase\Validation\Validator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Validator for url.
*/
final class UrlValidator extends AbstractValidator
{
protected string $message = 'LLL:EXT:extbase/Resources/Private/Language/locallang.xlf:validator.url.notvalid';
protected $supportedOptions = [
'message' => [null, 'Translation key or message for invalid value', 'string'],
];
/**
* Checks if the given value is a valid url.
*/
public function isValid(mixed $value): void
{
if (!is_string($value) || !GeneralUtility::isValidUrl($value)) {
$this->addError($this->translateErrorMessage($this->message), 1238108078);
}
}
}
@@ -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\Extbase\Validation\Validator;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Extbase\Error\Result;
/**
* Contract for a validator
*/
interface ValidatorInterface
{
/**
* Checks if the given value is valid according to the validator, and returns
* the Error Messages object which occurred.
*
* @param mixed $value The value that should be validated
*/
public function validate(mixed $value): Result;
/**
* Receive validator options from framework.
*/
public function setOptions(array $options): void;
/**
* Returns the options of this validator which can be specified by setOptions().
*/
public function getOptions(): array;
/**
* Sets the request object to the validator.
*/
public function setRequest(?ServerRequestInterface $request): void;
/**
* Returns the request object of the validator.
*/
public function getRequest(): ?ServerRequestInterface;
}