Issue #3375447 by kim.pepper, larowlan: Create an UploadedFile validator and deprecate error checking methods on UploadedFileInterface

merge-requests/5102/head
Lee Rowlands 2023-10-24 08:41:32 +10:00
parent c009376247
commit 6bd3ad841f
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
15 changed files with 595 additions and 112 deletions

View File

@ -974,6 +974,10 @@ services:
parent: default_plugin_manager
tags:
- { name: plugin_manager_cache_clear }
validation.basic_recursive_validator_factory:
class: Drupal\Core\Validation\BasicRecursiveValidatorFactory
arguments: ['@class_resolver']
Drupal\Core\Validation\BasicRecursiveValidatorFactory: '@validation.basic_recursive_validator_factory'
lock:
class: Drupal\Core\Lock\DatabaseLockBackend
arguments: ['@database']

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Core\Validation;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Validator\RecursiveValidator;
/**
* A factory for creating Symfony recursive validators.
*/
class BasicRecursiveValidatorFactory {
/**
* Constructs a new RecursiveValidatorFactory.
*
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $classResolver
* The class resolver.
*/
public function __construct(
protected readonly ClassResolverInterface $classResolver,
) {}
/**
* Creates a new RecursiveValidator.
*
* @return \Symfony\Component\Validator\Validator\RecursiveValidator
* The validator.
*/
public function createValidator(): RecursiveValidator {
return new RecursiveValidator(
new ExecutionContextFactory(new DrupalTranslator()),
new LazyLoadingMetadataFactory(),
new ConstraintValidatorFactory($this->classResolver),
);
}
}

View File

@ -5,7 +5,6 @@
* Defines a "managed_file" Form API field and a "file" field for Field module.
*/
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Entity\EntityStorageInterface;
@ -27,13 +26,7 @@ use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\file\Upload\FileValidationException;
use Drupal\file\Upload\FormUploadedFile;
use Symfony\Component\HttpFoundation\File\Exception\FileException as SymfonyFileException;
use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException;
use Symfony\Component\HttpFoundation\File\Exception\NoFileException;
use Symfony\Component\HttpFoundation\File\Exception\PartialFileException;
// cspell:ignore abiword widthx
@ -651,11 +644,41 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$files = [];
/** @var \Drupal\file\Validation\UploadedFileValidatorInterface $uploaded_file_validator */
$uploaded_file_validator = \Drupal::service('file.uploaded_file_validator');
/** @var \Symfony\Component\HttpFoundation\File\UploadedFile $uploaded_file */
foreach ($uploaded_files as $i => $uploaded_file) {
try {
$violations = $uploaded_file_validator->validate($uploaded_file);
if (count($violations) > 0) {
// We only get one violation for uploaded files.
\Drupal::messenger()->addError($violations->get(0)->getMessage());
$files[$i] = FALSE;
continue;
}
$form_uploaded_file = new FormUploadedFile($uploaded_file);
$result = $file_upload_handler->handleFileUpload($form_uploaded_file, $validators, $destination, $replace);
$result = $file_upload_handler->handleFileUpload($form_uploaded_file, $validators, $destination, $replace, FALSE);
if ($result->hasViolations()) {
$errors = [];
foreach ($result->getViolations() as $violation) {
$errors[] = $violation->getMessage();
}
$message = [
'error' => [
'#markup' => t('The specified file %name could not be uploaded.', ['%name' => $uploaded_file->getClientOriginalName()]),
],
'item_list' => [
'#theme' => 'item_list',
'#items' => $errors,
],
];
// @todo Add support for render arrays in
// \Drupal\Core\Messenger\MessengerInterface::addMessage()?
// @see https://www.drupal.org/node/2505497.
\Drupal::messenger()->addError($renderer->renderPlain($message));
$files[$i] = FALSE;
continue;
}
$file = $result->getFile();
// If the filename has been modified, let the user know.
if ($result->isRenamed()) {
@ -677,39 +700,6 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
\Drupal::messenger()->addError(t('The file could not be uploaded because the destination "%destination" is invalid.', ['%destination' => $destination]));
$files[$i] = FALSE;
}
catch (IniSizeFileException | FormSizeFileException $e) {
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', [
'%file' => $uploaded_file->getFilename(),
'%maxsize' => ByteSizeMarkup::create(Environment::getUploadMaxSize()),
]));
$files[$i] = FALSE;
}
catch (PartialFileException | NoFileException $e) {
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', [
'%file' => $uploaded_file->getFilename(),
]));
$files[$i] = FALSE;
}
catch (SymfonyFileException $e) {
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $uploaded_file->getFilename()]));
$files[$i] = FALSE;
}
catch (FileValidationException $e) {
$message = [
'error' => [
'#markup' => t('The specified file %name could not be uploaded.', ['%name' => $e->getFilename()]),
],
'item_list' => [
'#theme' => 'item_list',
'#items' => $e->getErrors(),
],
];
// @todo Add support for render arrays in
// \Drupal\Core\Messenger\MessengerInterface::addMessage()?
// @see https://www.drupal.org/node/2505497.
\Drupal::messenger()->addError($renderer->renderPlain($message));
$files[$i] = FALSE;
}
catch (FileWriteException $e) {
\Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.'));
\Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $uploaded_file->getClientOriginalName(), '%destination' => $destination . '/' . $uploaded_file->getClientOriginalName()]);

View File

@ -28,3 +28,7 @@ services:
class: Drupal\file\Validation\FileValidator
arguments: ['@file.recursive_validator', '@validation.constraint', '@event_dispatcher', '@module_handler']
Drupal\file\Validation\FileValidatorInterface: '@file.validator'
file.uploaded_file_validator:
class: Drupal\file\Validation\UploadedFileValidator
arguments: ['@validation.basic_recursive_validator_factory']
Drupal\file\Validation\UploadedFileValidatorInterface: '@file.uploaded_file_validator'

View File

@ -156,47 +156,58 @@ class FileUploadHandler {
* - FileSystemInterface::EXISTS_RENAME - Append _{incrementing number}
* until the filename is unique.
* - FileSystemInterface::EXISTS_ERROR - Throw an exception.
* @param bool $throw
* (optional) Whether to throw an exception if the file is invalid.
*
* @return \Drupal\file\Upload\FileUploadResult
* The created file entity.
*
* @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
* Thrown when a file upload error occurred.
* Thrown when a file upload error occurred and $throws is TRUE.
* @throws \Drupal\Core\File\Exception\FileWriteException
* Thrown when there is an error moving the file.
* Thrown when there is an error moving the file and $throws is TRUE.
* @throws \Drupal\Core\File\Exception\FileException
* Thrown when a file system error occurs.
* Thrown when a file system error occurs and $throws is TRUE.
* @throws \Drupal\file\Upload\FileValidationException
* Thrown when file validation fails.
* Thrown when file validation fails and $throws is TRUE.
*/
public function handleFileUpload(UploadedFileInterface $uploadedFile, array $validators = [], string $destination = 'temporary://', int $replace = FileSystemInterface::EXISTS_REPLACE): FileUploadResult {
public function handleFileUpload(UploadedFileInterface $uploadedFile, array $validators = [], string $destination = 'temporary://', int $replace = FileSystemInterface::EXISTS_REPLACE, bool $throw = TRUE): FileUploadResult {
$originalName = $uploadedFile->getClientOriginalName();
if (!$uploadedFile->isValid()) {
// @phpstan-ignore-next-line
if ($throw && !$uploadedFile->isValid()) {
@trigger_error('Calling ' . __METHOD__ . '() with the $throw argument as TRUE is deprecated in drupal:10.3.0 and will be removed in drupal:11.0.0. Use \Drupal\file\Upload\FileUploadResult::getViolations() instead. See https://www.drupal.org/node/3375456', E_USER_DEPRECATED);
// @phpstan-ignore-next-line
switch ($uploadedFile->getError()) {
case \UPLOAD_ERR_INI_SIZE:
// @phpstan-ignore-next-line
throw new IniSizeFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_FORM_SIZE:
// @phpstan-ignore-next-line
throw new FormSizeFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_PARTIAL:
// @phpstan-ignore-next-line
throw new PartialFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_NO_FILE:
// @phpstan-ignore-next-line
throw new NoFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_CANT_WRITE:
// @phpstan-ignore-next-line
throw new CannotWriteFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_NO_TMP_DIR:
// @phpstan-ignore-next-line
throw new NoTmpDirFileException($uploadedFile->getErrorMessage());
case \UPLOAD_ERR_EXTENSION:
// @phpstan-ignore-next-line
throw new ExtensionFileException($uploadedFile->getErrorMessage());
}
// @phpstan-ignore-next-line
throw new FileException($uploadedFile->getErrorMessage());
}
@ -239,14 +250,23 @@ class FileUploadHandler {
// Add in our check of the file name length.
$validators['FileNameLength'] = [];
$result = new FileUploadResult();
// Call the validation functions specified by this function's caller.
$violations = $this->fileValidator->validate($file, $validators);
$errors = [];
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
if (count($violations) > 0) {
$result->addViolations($violations);
return $result;
}
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
if ($throw) {
$errors = [];
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
}
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
}
}
$file->setFileUri($destinationFilename);
@ -267,8 +287,7 @@ class FileUploadHandler {
}
}
$result = (new FileUploadResult())
->setOriginalFilename($originalName)
$result->setOriginalFilename($originalName)
->setSanitizedFilename($filename)
->setFile($file);
@ -282,11 +301,17 @@ class FileUploadHandler {
// We can now validate the file object itself before it's saved.
$violations = $file->validate();
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
if ($throw) {
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
}
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
}
}
if (!empty($errors)) {
throw new FileValidationException('File validation failed', $filename, $errors);
if (count($violations) > 0) {
$result->addViolations($violations);
return $result;
}
// If we made it this far it's safe to record this file in the database.

View File

@ -3,6 +3,9 @@
namespace Drupal\file\Upload;
use Drupal\file\FileInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Value object for a file upload result.
@ -37,6 +40,20 @@ class FileUploadResult {
*/
protected $file;
/**
* The constraint violations.
*
* @var \Symfony\Component\Validator\ConstraintViolationListInterface
*/
protected ConstraintViolationListInterface $violations;
/**
* Creates a new FileUploadResult.
*/
public function __construct() {
$this->violations = new ConstraintViolationList();
}
/**
* Flags the result as having had a security rename.
*
@ -131,4 +148,41 @@ class FileUploadResult {
return $this->file;
}
/**
* Adds a constraint violation.
*
* @param \Symfony\Component\Validator\ConstraintViolationInterface $violation
* The constraint violation.
*/
public function addViolation(ConstraintViolationInterface $violation): void {
$this->violations->add($violation);
}
/**
* Adds constraint violations.
*
* @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
* The constraint violations.
*/
public function addViolations(ConstraintViolationListInterface $violations): void {
$this->violations->addAll($violations);
}
/**
* Gets the constraint violations.
*
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
* The constraint violations.
*/
public function getViolations(): ConstraintViolationListInterface {
return $this->violations;
}
/**
* Returns TRUE if there are constraint violations.
*/
public function hasViolations(): bool {
return $this->violations->count() > 0;
}
}

View File

@ -35,22 +35,43 @@ class FormUploadedFile implements UploadedFileInterface {
/**
* {@inheritdoc}
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
*
* @see https://www.drupal.org/node/3375456
*/
public function isValid(): bool {
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\file\Validation\UploadedFileValidatorInterface::validate() instead. See https://www.drupal.org/node/3375456', E_USER_DEPRECATED);
return $this->uploadedFile->isValid();
}
/**
* {@inheritdoc}
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
*
* @see https://www.drupal.org/node/3375456
*/
public function getErrorMessage(): string {
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\file\Validation\UploadedFileValidatorInterface::validate() instead. See https://www.drupal.org/node/3375456', E_USER_DEPRECATED);
return $this->uploadedFile->getErrorMessage();
}
/**
* {@inheritdoc}
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
*
* @see https://www.drupal.org/node/3375456
*/
public function getError(): int {
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use \Drupal\file\Validation\UploadedFileValidatorInterface::validate() instead. See https://www.drupal.org/node/3375456', E_USER_DEPRECATED);
return $this->uploadedFile->getError();
}

View File

@ -23,6 +23,11 @@ interface UploadedFileInterface {
*
* @return bool
* TRUE if the file has been uploaded with HTTP and no error occurred.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
* @see https://www.drupal.org/node/3375456
*/
public function isValid(): bool;
@ -31,6 +36,12 @@ interface UploadedFileInterface {
*
* @return string
* The error message regarding a failed upload.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
*
* @see https://www.drupal.org/node/3375456
*/
public function getErrorMessage(): string;
@ -42,6 +53,12 @@ interface UploadedFileInterface {
*
* @return int
* The upload error code.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
* \Drupal\file\Validation\UploadedFileValidatorInterface::validate()
* instead.
*
* @see https://www.drupal.org/node/3375456
*/
public function getError(): int;

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\file\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* A constraint for UploadedFile objects.
*/
class UploadedFileConstraint extends Constraint {
/**
* The upload max size. Defaults to checking the environment.
*
* @var int|null
*/
public ?int $maxSize;
/**
* The upload ini size error message.
*
* @var string
*/
public string $uploadIniSizeErrorMessage = 'The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.';
/**
* The upload form size error message.
*
* @var string
*/
public string $uploadFormSizeErrorMessage = 'The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.';
/**
* The upload partial error message.
*
* @var string
*/
public string $uploadPartialErrorMessage = 'The file %file could not be saved because the upload did not complete.';
/**
* The upload no file error message.
*
* @var string
*/
public string $uploadNoFileErrorMessage = 'The file %file could not be saved because the upload did not complete.';
/**
* The generic file upload error message.
*
* @var string
*/
public string $uploadErrorMessage = 'The file %file could not be saved. An unknown error has occurred.';
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Drupal\file\Validation\Constraint;
use Drupal\Component\Utility\Environment;
use Drupal\Core\StringTranslation\ByteSizeMarkup;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Constraint validator for uploaded files.
*
* Use FileValidatorInterface for validating file entities.
*
* @see \Drupal\Core\Validation\FileValidatorInterface
*/
class UploadedFileConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
if (!$constraint instanceof UploadedFileConstraint) {
throw new UnexpectedTypeException($constraint, UploadedFileConstraint::class);
}
if (!$value instanceof UploadedFile) {
throw new UnexpectedTypeException($value, UploadedFile::class);
}
if ($value->isValid()) {
return;
}
$maxSize = $constraint->maxSize ?? Environment::getUploadMaxSize();
match ($value->getError()) {
\UPLOAD_ERR_INI_SIZE => $this->context->buildViolation($constraint->uploadIniSizeErrorMessage, [
'%file' => $value->getClientOriginalName(),
'%maxsize' => ByteSizeMarkup::create($maxSize),
])->setCode((string) \UPLOAD_ERR_INI_SIZE)
->addViolation(),
\UPLOAD_ERR_FORM_SIZE => $this->context->buildViolation($constraint->uploadFormSizeErrorMessage, [
'%file' => $value->getClientOriginalName(),
'%maxsize' => ByteSizeMarkup::create($maxSize),
])->setCode((string) \UPLOAD_ERR_FORM_SIZE)
->addViolation(),
\UPLOAD_ERR_PARTIAL => $this->context->buildViolation($constraint->uploadPartialErrorMessage, [
'%file' => $value->getClientOriginalName(),
])->setCode((string) \UPLOAD_ERR_PARTIAL)
->addViolation(),
\UPLOAD_ERR_NO_FILE => $this->context->buildViolation($constraint->uploadNoFileErrorMessage, [
'%file' => $value->getClientOriginalName(),
])->setCode((string) \UPLOAD_ERR_NO_FILE)
->addViolation(),
default => $this->context->buildViolation($constraint->uploadErrorMessage, [
'%file' => $value->getClientOriginalName(),
])->setCode((string) $value->getError())
->addViolation()
};
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\file\Validation;
use Drupal\Core\Validation\BasicRecursiveValidatorFactory;
use Drupal\file\Validation\Constraint\UploadedFileConstraint;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Validator for uploaded files.
*/
class UploadedFileValidator implements UploadedFileValidatorInterface {
/**
* The symfony validator.
*
* @var \Symfony\Component\Validator\Validator\ValidatorInterface
*/
protected ValidatorInterface $validator;
/**
* Creates a new UploadedFileValidator.
*
* @param \Drupal\Core\Validation\BasicRecursiveValidatorFactory $validatorFactory
* The validator factory.
*/
public function __construct(
protected readonly BasicRecursiveValidatorFactory $validatorFactory,
) {}
/**
* {@inheritdoc}
*/
public function validate(UploadedFile $uploadedFile, array $options = []): ConstraintViolationListInterface {
$constraint = new UploadedFileConstraint($options);
return $this->getValidator()->validate($uploadedFile, $constraint);
}
/**
* Get the Symfony validator instance.
*
* @return \Symfony\Component\Validator\Validator\ValidatorInterface
* The Symfony validator.
*/
protected function getValidator(): ValidatorInterface {
if (!isset($this->validator)) {
$this->validator = $this->validatorFactory->createValidator();
}
return $this->validator;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Drupal\file\Validation;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Validator for uploaded files.
*/
interface UploadedFileValidatorInterface {
/**
* Validates an uploaded file.
*
* @param \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile
* The uploaded file.
* @param array $options
* An array of options accepted by
* \Drupal\file\Validation\Constraint\UploadedFileConstraint.
*
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
* The constraint violation list.
*/
public function validate(UploadedFile $uploadedFile, array $options = []): ConstraintViolationListInterface;
}

View File

@ -1,52 +0,0 @@
<?php
namespace Drupal\Tests\file\Kernel;
use Drupal\Component\Utility\Environment;
use Drupal\Core\StringTranslation\ByteSizeMarkup;
use Drupal\file\Upload\UploadedFileInterface;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
/**
* Tests the file upload handler.
*
* @group file
*/
class FileUploadHandlerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['file'];
/**
* The file upload handler under test.
*
* @var \Drupal\file\Upload\FileUploadHandler
*/
protected $fileUploadHandler;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->fileUploadHandler = $this->container->get('file.upload_handler');
}
/**
* Tests file size upload errors.
*/
public function testFileSaveUploadSingleErrorFormSize() {
$file_name = $this->randomMachineName();
$file_info = $this->createMock(UploadedFileInterface::class);
$file_info->expects($this->once())->method('getError')->willReturn(UPLOAD_ERR_FORM_SIZE);
$file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
$file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
$this->expectException(FormSizeFileException::class);
$this->expectExceptionMessage(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
$this->fileUploadHandler->handleFileUpload($file_info);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Drupal\Tests\file\Kernel\Upload;
use Drupal\file\Upload\UploadedFileInterface;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
/**
* Provides tests for legacy file upload handler code.
*
* @group file
* @group legacy
* @coversDefaultClass \Drupal\file\Upload\FileUploadHandler
*/
class LegacyFileUploadHandlerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['file'];
/**
* @covers ::handleFileUpload
*/
public function testThrow(): void {
$fileUploadHandler = $this->container->get('file.upload_handler');
$uploadedFile = $this->createMock(UploadedFileInterface::class);
$uploadedFile->expects($this->once())
->method('isValid')
->willReturn(FALSE);
$this->expectDeprecation('Calling Drupal\file\Upload\FileUploadHandler::handleFileUpload() with the $throw argument as TRUE is deprecated in drupal:10.3.0 and will be removed in drupal:11.0.0. Use \Drupal\file\Upload\FileUploadResult::getViolations() instead. See https://www.drupal.org/node/3375456');
$this->expectException(FileException::class);
$result = $fileUploadHandler->handleFileUpload(uploadedFile: $uploadedFile, throw: TRUE);
}
}

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\file\Kernel\Validation;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\file\Validation\UploadedFileValidator;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Tests the uploaded file validator.
*
* @coversDefaultClass \Drupal\file\Validation\UploadedFileValidator
* @group file
*/
class UploadedFileValidatorTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['file'];
/**
* The validator under test.
*
* @var \Drupal\file\Validation\UploadedFileValidator
*/
protected UploadedFileValidator $validator;
/**
* The file name.
*
* @var string
*/
protected string $filename;
/**
* The temporary file path.
*
* @var string
*/
protected string $path;
/**
* The max 4 MB filesize to use for testing.
*
* @var int
*/
protected int $maxSize = 4194304;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$fileSystem = $this->container->get('file_system');
/** @var \Drupal\file\Validation\UploadedFileValidator $validator */
$this->validator = new UploadedFileValidator(
$this->container->get('validation.basic_recursive_validator_factory'),
);
$this->filename = $this->randomMachineName() . '.txt';
$this->path = 'temporary://' . $this->filename;
$fileSystem->saveData('foo', $this->path);
}
/**
* @covers ::validate
*/
public function testValidateSuccess(): void {
$uploadedFile = new UploadedFile(
path: $this->path,
originalName: $this->filename,
test: TRUE,
);
$violations = $this->validator->validate($uploadedFile);
$this->assertCount(0, $violations);
}
/**
* @covers ::validate
* @dataProvider validateProvider
*/
public function testValidateFail(int $errorCode, string $message): void {
$uploadedFile = new UploadedFile(
path: $this->path,
originalName: $this->filename,
error: $errorCode,
test: TRUE,
);
$violations = $this->validator->validate($uploadedFile, [
'maxSize' => $this->maxSize,
]);
$this->assertCount(1, $violations);
$violation = $violations->get(0);
$this->assertInstanceOf(TranslatableMarkup::class, $violation->getMessage());
$this->assertEquals(sprintf($message, $this->filename), $violation->getMessage());
$this->assertEquals($errorCode, $violation->getCode());
}
/**
* Data provider for ::testValidateFail.
*/
public function validateProvider(): array {
return [
'ini size' => [
\UPLOAD_ERR_INI_SIZE,
'The file %s could not be saved because it exceeds 4 MB, the maximum allowed size for uploads.',
],
'form size' => [
\UPLOAD_ERR_FORM_SIZE,
'The file %s could not be saved because it exceeds 4 MB, the maximum allowed size for uploads.',
],
'partial file' => [
\UPLOAD_ERR_PARTIAL,
'The file %s could not be saved because the upload did not complete.',
],
'no file' => [
\UPLOAD_ERR_NO_FILE,
'The file %s could not be saved because the upload did not complete.',
],
'default' => [
\UPLOAD_ERR_CANT_WRITE,
'The file %s could not be saved. An unknown error has occurred.',
],
];
}
}