Skip to content
Snippets Groups Projects
Verified Commit 79c7666a authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3221793 by kim.pepper, larowlan, smustgrave, Wim Leers, joachim,...

Issue #3221793 by kim.pepper, larowlan, smustgrave, Wim Leers, joachim, gabesullice: Move file upload validation from file.module to constraint validators
parent 8219cb1f
Branches
Tags
45 merge requests!12227Issue #3181946 by jonmcl, mglaman,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4594Applying patch for Views Global Text area field to allow extra HTML tags. As video, source and iframe tag is not rendering. Due to which Media embedded video and remote-video not rendering in Views Global Text area field.,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3478Issue #3337882: Deleted menus are not removed from content type config,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
Showing
with 765 additions and 60 deletions
......@@ -9,24 +9,22 @@
use Drupal\Component\Utility\Environment;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\editor\Entity\Editor;
use Drupal\Core\Validation\DrupalTranslator;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\file\Validation\FileValidatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
/**
* Returns response for CKEditor 5 Simple image upload adapter.
......@@ -71,6 +69,13 @@ class CKEditor5ImageController extends ControllerBase {
*/
protected $eventDispatcher;
/**
* The file validator.
*
* @var \Drupal\file\Validation\FileValidatorInterface
*/
protected FileValidatorInterface $fileValidator;
/**
* Constructs a new CKEditor5ImageController.
*
......@@ -84,13 +89,20 @@ class CKEditor5ImageController extends ControllerBase {
* The lock service.
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param \Drupal\file\Validation\FileValidatorInterface|null $file_validator
* The file validator.
*/
public function __construct(FileSystemInterface $file_system, AccountInterface $current_user, MimeTypeGuesserInterface $mime_type_guesser, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher) {
public function __construct(FileSystemInterface $file_system, AccountInterface $current_user, MimeTypeGuesserInterface $mime_type_guesser, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, FileValidatorInterface $file_validator = NULL) {
$this->fileSystem = $file_system;
$this->currentUser = $current_user;
$this->mimeTypeGuesser = $mime_type_guesser;
$this->lock = $lock;
$this->eventDispatcher = $event_dispatcher;
if (!$file_validator) {
@trigger_error('Calling ' . __METHOD__ . '() without the $file_validator argument is deprecated in drupal:10.2.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$file_validator = \Drupal::service('file.validator');
}
$this->fileValidator = $file_validator;
}
/**
......@@ -103,6 +115,7 @@ public static function create(ContainerInterface $container) {
$container->get('file.mime_type.guesser'),
$container->get('lock'),
$container->get('event_dispatcher'),
$container->get('file.validator')
);
}
......@@ -144,13 +157,20 @@ public function upload(Request $request) {
$max_dimensions = 0;
}
$allowed_extensions = 'gif png jpg jpeg';
$validators = [
'file_validate_extensions' => ['gif png jpg jpeg'],
'file_validate_size' => [$max_filesize],
'file_validate_image_resolution' => [$max_dimensions],
'FileExtension' => [
'extensions' => $allowed_extensions,
],
'FileSizeLimit' => [
'fileLimit' => $max_filesize,
],
'FileImageDimensions' => [
'maxDimensions' => $max_dimensions,
],
];
$prepared_filename = $this->prepareFilename($filename, $validators);
$prepared_filename = $this->prepareFilename($filename, $allowed_extensions);
// Create the file.
$file_uri = "{$destination}/{$prepared_filename}";
......@@ -225,7 +245,7 @@ public function imageUploadEnabledAccess(Editor $editor) {
* @param \Drupal\file\FileInterface $file
* The file entity to validate.
* @param array $validators
* An array of upload validators to pass to file_validate().
* An array of upload validators to pass to the FileValidator.
*
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
* The list of constraint violations, if any.
......@@ -238,20 +258,7 @@ protected function validate(FileInterface $file, array $validators) {
$violations->filterByFieldAccess();
// Validate the file based on the field definition configuration.
$errors = file_validate($file, $validators);
if (!empty($errors)) {
$translator = new DrupalTranslator();
foreach ($errors as $error) {
$violation = new ConstraintViolation($translator->trans($error),
(string) $error,
[],
EntityAdapter::createFromEntity($file),
'',
NULL
);
$violations->add($violation);
}
}
$violations->addAll($this->fileValidator->validate($file, $validators));
return $violations;
}
......@@ -261,15 +268,14 @@ protected function validate(FileInterface $file, array $validators) {
*
* @param string $filename
* The file name.
* @param array $validators
* The array of upload validators.
* @param string $allowed_extensions
* The allowed extensions.
*
* @return string
* The prepared/munged filename.
*/
protected function prepareFilename($filename, array &$validators) {
$extensions = $validators['file_validate_extensions'][0] ?? '';
$event = new FileUploadSanitizeNameEvent($filename, $extensions);
protected function prepareFilename(string $filename, string $allowed_extensions): string {
$event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
$this->eventDispatcher->dispatch($event);
return $event->getFilename();
......
......@@ -112,9 +112,9 @@ public function buildForm(array $form, FormStateInterface $form_state, Editor $e
'#upload_location' => $image_upload['scheme'] . '://' . $image_upload['directory'],
'#default_value' => $fid ? [$fid] : NULL,
'#upload_validators' => [
'file_validate_extensions' => ['gif png jpg jpeg'],
'file_validate_size' => [$max_filesize],
'file_validate_image_resolution' => [$max_dimensions],
'FileExtension' => ['extensions' => 'gif png jpg jpeg'],
'FileSizeLimit' => ['fileLimit' => $max_filesize],
'FileImageDimensions' => ['maxDimensions' => $max_dimensions],
],
'#required' => TRUE,
];
......
......@@ -153,20 +153,39 @@ function template_preprocess_file_upload_help(&$variables) {
}
}
if (isset($upload_validators['file_validate_size'])) {
@trigger_error('\'file_validate_size\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileSizeLimit\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]);
}
if (isset($upload_validators['FileSizeLimit'])) {
$descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['FileSizeLimit']['fileLimit'])]);
}
if (isset($upload_validators['file_validate_extensions'])) {
@trigger_error('\'file_validate_extensions\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileExtension\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['file_validate_extensions'][0]]);
}
if (isset($upload_validators['FileExtension'])) {
$descriptions[] = t('Allowed types: @extensions.', ['@extensions' => $upload_validators['FileExtension']['extensions']]);
}
if (isset($upload_validators['file_validate_image_resolution'])) {
$max = $upload_validators['file_validate_image_resolution'][0];
$min = $upload_validators['file_validate_image_resolution'][1];
if (isset($upload_validators['file_validate_image_resolution']) || isset($upload_validators['FileImageDimensions'])) {
if (isset($upload_validators['file_validate_image_resolution'])) {
@trigger_error('\'file_validate_image_resolution\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileImageDimensions\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$max = $upload_validators['file_validate_image_resolution'][0];
$min = $upload_validators['file_validate_image_resolution'][1];
}
else {
$max = $upload_validators['FileImageDimensions']['maxDimensions'];
$min = $upload_validators['FileImageDimensions']['minDimensions'];
}
if ($min && $max && $min == $max) {
$descriptions[] = t('Images must be exactly <strong>@size</strong> pixels.', ['@size' => $max]);
}
elseif ($min && $max) {
$descriptions[] = t('Images must be larger than <strong>@min</strong> pixels. Images larger than <strong>@max</strong> pixels will be resized.', ['@min' => $min, '@max' => $max]);
$descriptions[] = t('Images must be larger than <strong>@min</strong> pixels. Images larger than <strong>@max</strong> pixels will be resized.', [
'@min' => $min,
'@max' => $max,
]);
}
elseif ($min) {
$descriptions[] = t('Images must be larger than <strong>@min</strong> pixels.', ['@min' => $min]);
......
......@@ -94,28 +94,17 @@ function file_field_widget_info_alter(array &$info) {
* @return array
* An array containing validation error messages.
*
* @see hook_file_validate()
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
*/
function file_validate(FileInterface $file, $validators = []) {
// Call the validation functions specified by this function's caller.
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$violations = \Drupal::service('file.validator')->validate($file, $validators);
$errors = [];
foreach ($validators as $function => $args) {
if (function_exists($function)) {
array_unshift($args, $file);
$errors = array_merge($errors, call_user_func_array($function, $args));
}
}
// Let other modules perform validation on the new file.
$errors = array_merge($errors, \Drupal::moduleHandler()->invokeAll('file_validate', [$file]));
// Ensure the file does not contain a malicious extension. At this point
// \Drupal\file\Upload\FileUploadHandler::handleFileUpload() will have munged
// the file so it does not contain a malicious extension. Contributed and
// custom code that calls this method needs to take similar steps if they need
// to permit files with malicious extensions to be uploaded.
if (empty($errors) && !\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FileSystemInterface::INSECURE_EXTENSION_REGEX, $file->getFilename())) {
$errors[] = t('For security reasons, your upload has been rejected.');
foreach ($violations as $violation) {
$errors[] = $violation->getMessage();
}
return $errors;
}
......@@ -129,8 +118,14 @@ function file_validate(FileInterface $file, $validators = []) {
* @return array
* An empty array if the file name length is smaller than the limit or an
* array containing an error message if it's not or is empty.
*
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
*/
function file_validate_name_length(FileInterface $file) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$errors = [];
if (!$file->getFilename()) {
......@@ -154,9 +149,14 @@ function file_validate_name_length(FileInterface $file) {
* An empty array if the file extension is allowed or an array containing an
* error message if it's not.
*
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
* @see hook_file_validate()
*/
function file_validate_extensions(FileInterface $file, $extensions) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$errors = [];
$regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
......@@ -189,9 +189,14 @@ function file_validate_extensions(FileInterface $file, $extensions) {
* An empty array if the file size is below limits or an array containing an
* error message if it's not.
*
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
* @see hook_file_validate()
*/
function file_validate_size(FileInterface $file, $file_limit = 0, $user_limit = 0) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$user = \Drupal::currentUser();
$errors = [];
......@@ -217,9 +222,14 @@ function file_validate_size(FileInterface $file, $file_limit = 0, $user_limit =
* An empty array if the file is a valid image or an array containing an error
* message if it's not.
*
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
* @see hook_file_validate()
*/
function file_validate_is_image(FileInterface $file) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$errors = [];
$image_factory = \Drupal::service('image.factory');
......@@ -256,9 +266,14 @@ function file_validate_is_image(FileInterface $file) {
* does not meet the requirements or an attempt to resize it fails, an array
* containing the error message will be returned.
*
* @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the
* 'file.validator' service instead.
*
* @see https://www.drupal.org/node/3363700
* @see hook_file_validate()
*/
function file_validate_image_resolution(FileInterface $file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'file.validator\' service instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$errors = [];
// Check first that the file is an image.
......
......@@ -6,9 +6,20 @@ services:
- { name: backend_overridable }
file.upload_handler:
class: Drupal\file\Upload\FileUploadHandler
arguments: [ '@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@event_dispatcher', '@file.mime_type.guesser', '@current_user', '@request_stack', '@file.repository' ]
arguments: ['@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@event_dispatcher', '@file.mime_type.guesser', '@current_user', '@request_stack', '@file.repository', '@file.validator']
Drupal\file\Upload\FileUploadHandler: '@file.upload_handler'
file.repository:
class: Drupal\file\FileRepository
arguments: [ '@file_system', '@stream_wrapper_manager', '@entity_type.manager', '@module_handler', '@file.usage', '@current_user' ]
Drupal\file\FileRepositoryInterface: '@file.repository'
file.recursive_validator_factory:
class: Drupal\file\Validation\RecursiveValidatorFactory
arguments: ['@class_resolver', '@typed_data_manager']
Drupal\file\Validation\RecursiveValidatorFactory: '@file.recursive_validator_factory'
file.recursive_validator:
class: Symfony\Component\Validator\Validator\ValidatorInterface
factory: ['@file.recursive_validator_factory', 'createValidator']
file.validator:
class: Drupal\file\Validation\FileValidator
arguments: ['@file.recursive_validator', '@validation.constraint', '@event_dispatcher', '@module_handler']
Drupal\file\Validation\FileValidatorInterface: '@file.validator'
......@@ -360,8 +360,15 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
}
// Add the extension list to the page as JavaScript settings.
if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
$extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
if (isset($element['#upload_validators']['file_validate_extensions'][0]) || isset($element['#upload_validators']['FileExtension']['extensions'])) {
if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
@trigger_error('\'file_validate_extensions\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileExtension\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$allowed_extensions = $element['#upload_validators']['file_validate_extensions'][0];
}
else {
$allowed_extensions = $element['#upload_validators']['FileExtension']['extensions'];
}
$extension_list = implode(',', array_filter(explode(' ', $allowed_extensions)));
$element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $id] = $extension_list;
}
......
......@@ -331,11 +331,11 @@ public function getUploadValidators() {
}
// There is always a file size limit due to the PHP server limit.
$validators['file_validate_size'] = [$max_filesize];
$validators['FileSizeLimit'] = ['fileLimit' => $max_filesize];
// Add the extension check if necessary.
if (!empty($settings['file_extensions'])) {
$validators['file_validate_extensions'] = [$settings['file_extensions']];
$validators['FileExtension'] = ['extensions' => $settings['file_extensions']];
}
return $validators;
......
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\file\FileInterface;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Provides a base class for file constraint validators.
*/
abstract class BaseFileConstraintValidator extends ConstraintValidator {
/**
* Checks the value is of type FileInterface.
*
* @param mixed $value
* The value to check.
*
* @return \Drupal\file\FileInterface
* The file.
*
* @throw Symfony\Component\Validator\Exception\UnexpectedTypeException
* Thrown if the value is not a FileInterface.
*/
protected function assertValueIsFile(mixed $value): FileInterface {
if (!$value instanceof FileInterface) {
throw new UnexpectedTypeException($value, FileInterface::class);
}
return $value;
}
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File extension constraint.
*
* @Constraint(
* id = "FileExtension",
* label = @Translation("File Extension", context = "Validation"),
* type = "file"
* )
*/
class FileExtensionConstraint extends Constraint {
/**
* The error message.
*
* @var string
*/
public string $message = 'Only files with the following extensions are allowed: %files-allowed.';
/**
* The allowed file extensions.
*
* @var string
*/
public string $extensions;
/**
* {@inheritdoc}
*/
public function getDefaultOption(): string {
return 'extensions';
}
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validates the file extension constraint.
*/
class FileExtensionConstraintValidator extends BaseFileConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileExtensionConstraint) {
throw new UnexpectedTypeException($constraint, FileExtensionConstraint::class);
}
$extensions = $constraint->extensions;
$regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
// Filename may differ from the basename, for instance in case files
// migrated from D7 file entities. Because of that new files are saved
// temporarily with a generated file name, without the original extension,
// we will use the generated filename property for extension validation only
// in case of temporary files; and use the file system file name in case of
// permanent files.
$subject = $file->isTemporary() ? $file->getFilename() : $file->getFileUri();
if (!preg_match($regex, $subject)) {
$this->context->addViolation($constraint->message, ['%files-allowed' => $extensions]);
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File extension secure constraint.
*
* @Constraint(
* id = "FileExtensionSecure",
* label = @Translation("File Extension Secure", context = "Validation"),
* type = "file"
* )
*/
class FileExtensionSecureConstraint extends Constraint {
/**
* The error message.
*
* @var string
*/
public string $message = 'For security reasons, your upload has been rejected.';
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validator for the FileExtensionSecureConstraint.
*/
class FileExtensionSecureConstraintValidator extends BaseFileConstraintValidator implements ContainerInjectionInterface {
/**
* Creates a new FileExtensionSecureConstraintValidator.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory.
*/
public function __construct(
protected ConfigFactoryInterface $configFactory
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('config.factory'));
}
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileExtensionSecureConstraint) {
throw new UnexpectedTypeException($constraint, FileExtensionSecureConstraint::class);
}
$allowInsecureUploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads');
if (!$allowInsecureUploads && preg_match(FileSystemInterface::INSECURE_EXTENSION_REGEX, $file->getFilename())) {
$this->context->addViolation($constraint->message);
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File extension dimensions constraint.
*
* @Constraint(
* id = "FileImageDimensions",
* label = @Translation("File Image Dimensions", context = "Validation"),
* type = "file"
* )
*/
class FileImageDimensionsConstraint extends Constraint {
/**
* The minimum dimensions.
*
* @var string|int
*/
public string | int $minDimensions = 0;
/**
* The maximum dimensions.
*
* @var string|int
*/
public string | int $maxDimensions = 0;
/**
* The resized image too small message.
*
* @var string
*/
public string $messageResizedImageTooSmall = 'The resized image is too small. The minimum dimensions are %dimensions pixels and after resizing, the image size will be %widthx%height pixels.';
/**
* The image too small message.
*
* @var string
*/
public string $messageImageTooSmall = 'The image is too small. The minimum dimensions are %dimensions pixels and the image size is %widthx%height pixels.';
/**
* The resize failed message.
*
* @var string
*/
public string $messageResizeFailed = 'The image exceeds the maximum allowed dimensions and an attempt to resize it failed.';
}
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Image\ImageFactory;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validator for the FileImageDimensionsConstraint.
*
* This validator will resize the image if exceeds the limits.
*/
class FileImageDimensionsConstraintValidator extends BaseFileConstraintValidator implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* Creates a new FileImageDimensionsConstraintValidator.
*
* @param \Drupal\Core\Image\ImageFactory $imageFactory
* The image factory.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(
protected ImageFactory $imageFactory,
protected MessengerInterface $messenger,
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('image.factory'),
$container->get('messenger'),
);
}
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileImageDimensionsConstraint) {
throw new UnexpectedTypeException($constraint, FileImageDimensionsConstraint::class);
}
$image = $this->imageFactory->get($file->getFileUri());
if (!$image->isValid()) {
return;
}
$scaling = FALSE;
$maxDimensions = $constraint->maxDimensions;
if ($maxDimensions) {
// Check that it is smaller than the given dimensions.
[$width, $height] = explode('x', $maxDimensions);
if ($image->getWidth() > $width || $image->getHeight() > $height) {
// Try to resize the image to fit the dimensions.
if ($image->scale($width, $height)) {
$scaling = TRUE;
$image->save();
if (!empty($width) && !empty($height)) {
$this->messenger->addStatus($this->t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
[
'%dimensions' => $maxDimensions,
'%new_width' => $image->getWidth(),
'%new_height' => $image->getHeight(),
]));
}
elseif (empty($width)) {
$this->messenger->addStatus($this->t('The image was resized to fit within the maximum allowed height of %height pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
[
'%height' => $height,
'%new_width' => $image->getWidth(),
'%new_height' => $image->getHeight(),
]));
}
elseif (empty($height)) {
$this->messenger->addStatus($this->t('The image was resized to fit within the maximum allowed width of %width pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
[
'%width' => $width,
'%new_width' => $image->getWidth(),
'%new_height' => $image->getHeight(),
]));
}
}
else {
$this->context->addViolation($constraint->messageResizeFailed);
}
}
}
$minDimensions = $constraint->minDimensions;
if ($minDimensions) {
// Check that it is larger than the given dimensions.
[$width, $height] = explode('x', $minDimensions);
if ($image->getWidth() < $width || $image->getHeight() < $height) {
if ($scaling) {
$this->context->addViolation($constraint->messageResizedImageTooSmall,
[
'%dimensions' => $minDimensions,
'%width' => $image->getWidth(),
'%height' => $image->getHeight(),
]);
return;
}
$this->context->addViolation($constraint->messageImageTooSmall,
[
'%dimensions' => $minDimensions,
'%width' => $image->getWidth(),
'%height' => $image->getHeight(),
]);
}
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File is image constraint.
*
* @Constraint(
* id = "FileIsImage",
* label = @Translation("File Is Image", context = "Validation"),
* type = "file"
* )
*/
class FileIsImageConstraint extends Constraint {
/**
* The error message.
*
* @var string
*/
public string $message = 'The image file is invalid or the image type is not allowed. Allowed types: %types';
}
<?php
declare(strict_types=1);
namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Image\ImageFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validator for the FileIsImageConstraint.
*/
class FileIsImageConstraintValidator extends BaseFileConstraintValidator implements ContainerInjectionInterface {
/**
* Creates a new FileIsImageConstraintValidator.
*
* @param \Drupal\Core\Image\ImageFactory $imageFactory
* The image factory.
*/
public function __construct(
protected ImageFactory $imageFactory,
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('image.factory'));
}
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileIsImageConstraint) {
throw new UnexpectedTypeException($constraint, FileIsImageConstraint::class);
}
$image = $this->imageFactory->get($file->getFileUri());
if (!$image->isValid()) {
$supportedExtensions = $this->imageFactory->getSupportedExtensions();
$this->context->addViolation($constraint->message, ['%types' => implode(', ', $supportedExtensions)]);
}
}
}
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File name length constraint.
*
* @Constraint(
* id = "FileNameLength",
* label = @Translation("File Name Length", context = "Validation"),
* type = "file"
* )
*/
class FileNameLengthConstraint extends Constraint {
/**
* The maximum file name length.
*
* @var int
*/
public int $maxLength = 240;
/**
* The message when file name is empty.
*
* @var string
*/
public string $messageEmpty = "The file's name is empty. Please give a name to the file.";
/**
* The message when file name is too long.
*
* @var string
*/
public string $messageTooLong = "The file's name exceeds the %maxLength characters limit. Please rename the file and try again.";
}
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validates the file name length constraint.
*/
class FileNameLengthConstraintValidator extends BaseFileConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint) {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileNameLengthConstraint) {
throw new UnexpectedTypeException($constraint, FileNameLengthConstraint::class);
}
if (!$file->getFilename()) {
$this->context->addViolation($constraint->messageEmpty);
}
if (mb_strlen($file->getFilename()) > $constraint->maxLength) {
$this->context->addViolation($constraint->messageTooLong, [
'%maxLength' => $constraint->maxLength,
]);
}
}
}
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* File size max constraint.
*
* @Constraint(
* id = "FileSizeLimit",
* label = @Translation("File Size Limit", context = "Validation"),
* type = "file"
* )
*/
class FileSizeLimitConstraint extends Constraint {
/**
* The message for when file size limit is exceeded.
*
* @var string
*/
public string $maxFileSizeMessage = 'The file is %filesize exceeding the maximum file size of %maxsize.';
/**
* The message for when disk quota is exceeded.
*
* @var string
*/
public string $diskQuotaMessage = 'The file is %filesize which would exceed your disk quota of %quota.';
/**
* The file limit.
*
* @var int
*/
public int $fileLimit = 0;
/**
* The user limit.
*
* @var int
*/
public int $userLimit = 0;
/**
* {@inheritdoc}
*/
public function getDefaultOption(): ?string {
return 'fileLimit';
}
}
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validates the FileSizeLimitConstraint.
*/
class FileSizeLimitConstraintValidator extends BaseFileConstraintValidator implements ContainerInjectionInterface {
/**
* Creates a new FileSizeConstraintValidator.
*
* @param \Drupal\Core\Session\AccountInterface $currentUser
* The current user.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct(
protected AccountInterface $currentUser,
protected EntityTypeManagerInterface $entityTypeManager
) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function validate(mixed $value, Constraint $constraint): void {
$file = $this->assertValueIsFile($value);
if (!$constraint instanceof FileSizeLimitConstraint) {
throw new UnexpectedTypeException($constraint, FileSizeLimitConstraint::class);
}
$fileLimit = $constraint->fileLimit;
if ($fileLimit && $file->getSize() > $fileLimit) {
$this->context->addViolation($constraint->maxFileSizeMessage, [
'%filesize' => format_size($file->getSize()),
'%maxsize' => format_size($fileLimit),
]);
}
$userLimit = $constraint->userLimit;
// Save a query by only calling spaceUsed() when a limit is provided.
if ($userLimit) {
/** @var \Drupal\file\FileStorageInterface $fileStorage */
$fileStorage = $this->entityTypeManager->getStorage('file');
$spaceUsed = $fileStorage->spaceUsed($this->currentUser->id()) + $file->getSize();
if ($spaceUsed > $userLimit) {
$this->context->addViolation($constraint->diskQuotaMessage, [
'%filesize' => format_size($file->getSize()),
'%quota' => format_size($userLimit),
]);
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment