TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This object holds a validation error.
|
||||
*/
|
||||
class Error extends \TYPO3\CMS\Extbase\Error\Error
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Unknown validation error';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $code = 1201447005;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation;
|
||||
|
||||
use TYPO3\CMS\Extbase\Exception as ExtbaseException;
|
||||
|
||||
/**
|
||||
* A generic validation exception
|
||||
*/
|
||||
class Exception extends ExtbaseException {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
/**
|
||||
* An "Invalid TypeHint" Exception
|
||||
*/
|
||||
class InvalidTypeHintException extends Exception {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
/**
|
||||
* A "InvalidValidationConfiguration" Exception
|
||||
*/
|
||||
class InvalidValidationConfigurationException extends Exception {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
/**
|
||||
* A "InvalidValidationOptions" Exception
|
||||
*/
|
||||
class InvalidValidationOptionsException extends Exception {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Exception;
|
||||
|
||||
/**
|
||||
* A "NoSuchValidator" Exception
|
||||
*/
|
||||
class NoSuchValidatorException extends Exception {}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
|
||||
final class ValidatorClassNameResolver
|
||||
{
|
||||
/**
|
||||
* This method is marked internal due to several facts:
|
||||
*
|
||||
* - The functionality is not 100% tested and still contains some bugs
|
||||
* - The functionality might not be needed any longer if Extbase switches
|
||||
* to the symfony/validator component.
|
||||
*
|
||||
* This method can be used by extension developers. As long as it remains,
|
||||
* its functionality will not change. It might even become more stable.
|
||||
* However, developers should be aware that this method might vanish
|
||||
* without any deprecation.
|
||||
*
|
||||
* @throws NoSuchValidatorException
|
||||
* @internal
|
||||
*/
|
||||
public static function resolve(string $validatorIdentifier): string
|
||||
{
|
||||
// Trim leading slash if $validatorName is FQCN like \TYPO3\CMS\Extbase\Validation\Validator\FloatValidator
|
||||
$validatorIdentifier = ltrim($validatorIdentifier, '\\');
|
||||
$validatorClassName = $validatorIdentifier;
|
||||
if (strpbrk($validatorIdentifier, '\\') === false) {
|
||||
// Shorthand built in
|
||||
$validatorClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . self::getValidatorType($validatorIdentifier);
|
||||
}
|
||||
if (!str_ends_with($validatorClassName, 'Validator')) {
|
||||
$validatorClassName .= 'Validator';
|
||||
}
|
||||
if (!class_exists($validatorClassName)) {
|
||||
throw new NoSuchValidatorException('Validator class ' . $validatorClassName . ' does not exist', 1365799920);
|
||||
}
|
||||
if (!is_subclass_of($validatorClassName, ValidatorInterface::class)) {
|
||||
throw new NoSuchValidatorException(
|
||||
'Validator class ' . $validatorClassName . ' must implement ' . ValidatorInterface::class,
|
||||
1365776838
|
||||
);
|
||||
}
|
||||
return $validatorClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to map PHP types to validator types.
|
||||
*
|
||||
* @param string $type Data type to unify
|
||||
* @return string unified data type
|
||||
*/
|
||||
private static function getValidatorType(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'int' => 'Integer',
|
||||
'bool' => 'Boolean',
|
||||
'double' => 'Float',
|
||||
'numeric' => 'Number',
|
||||
default => ucfirst($type),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?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;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Reflection\ClassSchema\TypeAdapter;
|
||||
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
|
||||
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
|
||||
use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ConstraintDecoratingValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* Validator resolver to automatically find an appropriate validator for a given subject.
|
||||
*
|
||||
* @internal only to be used within Extbase, not part of TYPO3 Core API.
|
||||
*/
|
||||
class ValidatorResolver implements SingletonInterface
|
||||
{
|
||||
protected array $baseValidatorConjunctions = [];
|
||||
|
||||
public function __construct(protected readonly ReflectionService $reflectionService) {}
|
||||
|
||||
/**
|
||||
* Get a validator for a given data type. Returns a validator implementing
|
||||
* the ValidatorInterface or NULL if no validator could be resolved.
|
||||
*
|
||||
* @param string $validatorType Either one of the built-in data types or fully qualified validator class name
|
||||
*/
|
||||
public function createValidator(
|
||||
string $validatorType,
|
||||
array $validatorOptions = [],
|
||||
?ServerRequestInterface $request = null
|
||||
): ?ValidatorInterface {
|
||||
try {
|
||||
$validatorObjectName = ValidatorClassNameResolver::resolve($validatorType);
|
||||
/** @var ValidatorInterface $validator */
|
||||
$validator = GeneralUtility::makeInstance($validatorObjectName);
|
||||
$validator->setOptions($validatorOptions);
|
||||
$validator->setRequest($request);
|
||||
return $validator;
|
||||
} catch (NoSuchValidatorException $e) {
|
||||
GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->debug($e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves and returns the base validator conjunction for the given data type.
|
||||
* If no validator could be resolved (which usually means that no validation is necessary), NULL is returned.
|
||||
*
|
||||
* @param string $targetClassName The data type to search a validator for. Usually the fully qualified object name
|
||||
*/
|
||||
public function getBaseValidatorConjunction(
|
||||
string $targetClassName,
|
||||
?ServerRequestInterface $request = null
|
||||
): ConjunctionValidator {
|
||||
if (!isset($this->baseValidatorConjunctions[$targetClassName])) {
|
||||
$conjunctionValidator = GeneralUtility::makeInstance(ConjunctionValidator::class);
|
||||
$this->baseValidatorConjunctions[$targetClassName] = $conjunctionValidator;
|
||||
// The simpleType check reduces lookups to the class loader
|
||||
if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) {
|
||||
$this->buildBaseValidatorConjunction($conjunctionValidator, $targetClassName, $request);
|
||||
}
|
||||
}
|
||||
return $this->baseValidatorConjunctions[$targetClassName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a base validator conjunction for the given data type.
|
||||
*
|
||||
* The base validation rules are those which were declared directly in a class (typically
|
||||
* a model) through some #[Validate] attributes on properties.
|
||||
*
|
||||
* If a property holds a class for which a base validator exists, that property will be
|
||||
* checked as well, regardless of a validation attribute.
|
||||
*
|
||||
* Additionally, if a custom validator was defined for the class in question, it will be added
|
||||
* to the end of the conjunction. A custom validator is found if it follows the naming convention
|
||||
* "Replace '\Model\' by '\Validator\' and append 'Validator'".
|
||||
*
|
||||
* Example: $targetClassName is TYPO3\Foo\Domain\Model\Quux, then the validator will be found if it has the
|
||||
* name TYPO3\Foo\Domain\Validator\QuuxValidator
|
||||
*
|
||||
* @param class-string $targetClassName The data type to build the validation conjunction for. Needs to be the fully qualified class name.
|
||||
* @throws NoSuchValidatorException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function buildBaseValidatorConjunction(
|
||||
ConjunctionValidator $conjunctionValidator,
|
||||
string $targetClassName,
|
||||
?ServerRequestInterface $request
|
||||
): void {
|
||||
$classSchema = $this->reflectionService->getClassSchema($targetClassName);
|
||||
|
||||
// Model based validator
|
||||
/** @var GenericObjectValidator $objectValidator */
|
||||
$objectValidator = $this->createValidator(GenericObjectValidator::class);
|
||||
foreach ($classSchema->getProperties() as $property) {
|
||||
$primaryType = $property->getPrimaryType();
|
||||
if (!$primaryType instanceof TypeAdapter) {
|
||||
// @todo: The type is only necessary here for further analyzing whether it's a simple type or
|
||||
// a collection. If this is evaluated in the ClassSchema, this whole code part is not needed
|
||||
// any longer and can be removed.
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('There is no @var annotation or type declaration for property "%s" in class "%s".', $property->getName(), $targetClassName),
|
||||
1363778104
|
||||
);
|
||||
}
|
||||
|
||||
$propertyTargetClassName = $primaryType->getClassName() ?? $primaryType->getBuiltinType();
|
||||
|
||||
// Skip transient properties for auto-generated validators (model-typed properties).
|
||||
// Transient properties are not persisted and may not have public accessors.
|
||||
if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName) && !$property->isTransient()) {
|
||||
// The outer simpleType check reduces lookups to the class loader
|
||||
// @todo: Whether the property holds a simple type or not and whether it holds a collection is known in
|
||||
// in the ClassSchema. The information could be made available and not evaluated here again.
|
||||
$primaryCollectionValueType = $property->getPrimaryCollectionValueType();
|
||||
if ($primaryType->isCollection() && $primaryCollectionValueType instanceof TypeAdapter) {
|
||||
/** @var CollectionValidator $collectionValidator */
|
||||
$collectionValidator = $this->createValidator(
|
||||
CollectionValidator::class,
|
||||
[
|
||||
'elementType' => $primaryCollectionValueType->getClassName() ?? $primaryCollectionValueType->getBuiltinType(),
|
||||
],
|
||||
$request
|
||||
);
|
||||
$objectValidator->addPropertyValidator($property->getName(), $collectionValidator);
|
||||
} elseif (class_exists($propertyTargetClassName)
|
||||
&& !TypeHandlingUtility::isCoreType($propertyTargetClassName)
|
||||
&& !in_array(SingletonInterface::class, class_implements($propertyTargetClassName) ?: [], true)
|
||||
) {
|
||||
// class_exists($propertyTargetClassName) checks, if the type of the property is an object
|
||||
// instead of a simple type. Like DateTime or another model.
|
||||
//
|
||||
// !TypeHandlingUtility::isCoreType($propertyTargetClassName) checks if the type of the property
|
||||
// is not a core type, which are Enums and File objects for example.
|
||||
// @todo: check why these types shouldn't be validated.
|
||||
//
|
||||
// !in_array(SingletonInterface::class, class_implements($propertyTargetClassName, true), true)
|
||||
// checks if the class is an instance of a Singleton
|
||||
// @todo: check why Singletons shouldn't be validated.
|
||||
//
|
||||
// (Alexander Schnitzler) By looking at this code I assume that this is the path for 1:1
|
||||
// relations in models. Still, the question remains why it excludes core types and singletons.
|
||||
// It makes sense on a theoretical level, but I don't see a technical issue allowing these as
|
||||
// well.
|
||||
$validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $request);
|
||||
if ($validatorForProperty->count() > 0) {
|
||||
$objectValidator->addPropertyValidator($property->getName(), $validatorForProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($property->getValidators() as $validatorDefinition) {
|
||||
// @todo: At this point we already have the class name of the validator, thus there is not need
|
||||
// calling ValidatorClassNameResolver::resolve inside
|
||||
// \TYPO3\CMS\Extbase\Validation\ValidatorResolver::createValidator once again. However, to
|
||||
// keep things simple for now, we still use the method createValidator here. In the future,
|
||||
// createValidator must only accept FQCN's.
|
||||
if (isset($validatorDefinition['constraint'])) {
|
||||
$newValidator = new ConstraintDecoratingValidator($validatorDefinition['constraint']);
|
||||
} else {
|
||||
$newValidator = $this->createValidator(
|
||||
$validatorDefinition['className'],
|
||||
$validatorDefinition['options'],
|
||||
$request,
|
||||
);
|
||||
}
|
||||
if ($newValidator === null) {
|
||||
throw new NoSuchValidatorException(
|
||||
'Invalid #[Validate] attribute in ' . $targetClassName . '::' . $property->getName() . ': '
|
||||
. 'Could not resolve class name for validator "' . $validatorDefinition['className'] . '".',
|
||||
1241098027
|
||||
);
|
||||
}
|
||||
$objectValidator->addPropertyValidator($property->getName(), $newValidator);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($objectValidator->getPropertyValidators())) {
|
||||
$conjunctionValidator->addValidator($objectValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user