Problem/Motivation

In #2940383: [META] Unify file upload logic of REST and JSON:API we are trying to unify the logic for file field uploads. However, currently file field upload validation is split across core, file, and rest modules. We should move the current hook based implementation in file.module to use Symfony constraint validators so we can have a consistent api for use by FileFieldUploader.

Steps to reproduce

Proposed resolution

Move file module validation to validators and deprecate.

Remaining tasks

User interface changes

API changes

file_validate and validators are moved to core and deprecated.

Data model changes

Release notes snippet

CommentFileSizeAuthor
#70 3221793-70.patch131.53 KBkim.pepper
#62 3221793-62-interdiff.txt32.7 KBkim.pepper
#62 3221793-62.patch131.16 KBkim.pepper
#60 3221793-60-interdiff.txt16.78 KBkim.pepper
#60 3221793-60.patch141.78 KBkim.pepper
#56 3221793-56-interdiff.txt1.89 KBkim.pepper
#56 3221793-56.patch139.87 KBkim.pepper
#54 3221793-54-interdiff.txt34.16 KBkim.pepper
#54 3221793-54.patch139.95 KBkim.pepper
#53 3221793-53-interdiff.txt2.16 KBkim.pepper
#53 3221793-53.patch132.04 KBkim.pepper
#51 3221793-51-interdiff.txt7.11 KBkim.pepper
#51 3221793-51.patch129.88 KBkim.pepper
#49 3221793-49-interdiff.txt31.95 KBkim.pepper
#49 3221793-49.patch125.79 KBkim.pepper
#47 3221793-47-interdiff.txt19.47 KBkim.pepper
#47 3221793-47.patch112.68 KBkim.pepper
#45 3221793-45-interdiff.txt23.67 KBkim.pepper
#45 3221793-45.patch95.83 KBkim.pepper
#43 3221793-43-interdiff.txt30.87 KBkim.pepper
#43 3221793-43.patch76.17 KBkim.pepper
#42 3221793-42-interdiff.txt4.46 KBkim.pepper
#42 3221793-42.patch47.07 KBkim.pepper
#41 3221793-41-interdiff.txt12.83 KBkim.pepper
#41 3221793-41.patch44.44 KBkim.pepper
#39 3221793-39-interdiff.txt9.65 KBkim.pepper
#39 3221793-39.patch44.15 KBkim.pepper
#37 3221793-37-interdiff.txt32.07 KBkim.pepper
#37 3221793-37.patch35.1 KBkim.pepper
#34 3221793-34-interdiff.txt9.06 KBkim.pepper
#34 3221793-34.patch12.13 KBkim.pepper
#33 3221793-33.patch8.08 KBkim.pepper
#24 3221793-24-interdiff.txt2.72 KBkim.pepper
#24 3221793-24.patch25.01 KBkim.pepper
#22 3221793-22.patch24.98 KBkim.pepper
#14 3221793-14-interdiff.txt2.96 KBkim.pepper
#14 3221793-14.patch28.47 KBkim.pepper
#12 3221793-12-interdiff.txt1.63 KBkim.pepper
#12 3221793-12.patch28.45 KBkim.pepper
#11 3221793-11-interdiff.txt3.13 KBkim.pepper
#11 3221793-11.patch28.45 KBkim.pepper
#9 3221793-9-interdiff.txt1.49 KBkim.pepper
#7 3221793-7-interdiff.txt29.41 KBkim.pepper
#9 3221793-9.patch28.44 KBkim.pepper
#4 3221793-4.patch13.76 KBkim.pepper
#7 3221793-7.patch28.26 KBkim.pepper

Comments

kim.pepper created an issue. See original summary.

kim.pepper’s picture

Add this to the meta

kim.pepper’s picture

Title: Move file upload validation from file module to core » Move file upload validation from file.module to constraint validator
Issue summary: View changes
kim.pepper’s picture

Status: Active » Needs review
StatusFileSize
new13.76 KB

Initial patch showing how the constraint validator API would look.

berdir’s picture

Didn't look too closely yet, one related issue I'd like to point out is #2867336: File size validator should only respect the explicitly configured maximum file size.

That's a very awkward if you use s3 upload or any other mechanism that doesn't rely on manually uploading files on your server. If a file already exists then there is no reason to validate it against the upload max size IMHO.

I understand it's tricky to refactor things and change them at the same time, but I'd love to find a way to finally resolve that isuse.

gabesullice’s picture

It's very exciting to see this materializing! Don't mind my review too much since it's mostly made up of nitpicks on what I understand is an initial patch to illustrate the concept.


I understand it's tricky to refactor things and change them at the same time, but I'd love to find a way to finally resolve that isuse.

@Berdir Yeah... I hear you, but I'd be very reluctant to do that. From my limited experience with the file system, its issues appear to be plagued by scope creep and a desire to fix everything every other thing before fixing any one thing. Let's race these issues against each other instead.


  1. +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
    @@ -331,6 +333,30 @@ public function getUploadValidators() {
    +    $constraints[] = new FileSize(['max_filesize' => $max_filesize]);
    
    +++ b/core/modules/file/src/Validation/Constraint/FileSizeLimit.php
    @@ -0,0 +1,29 @@
    +class FileSizeLimit extends Constraint {
    

    Are these supposed to be the same: FileSize vs FileSizeLimit?

  2. +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
    @@ -331,6 +333,30 @@ public function getUploadValidators() {
    +    if (!empty($settings['file_extensions'])) {
    +      $constraints[] = new FileExtension(['extensions' => $settings['file_extensions']]);
    +    }
    

    Are there default file extension constraints we should have here?

  3. +++ b/core/modules/file/src/Validation/Constraint/FileExtension.php
    @@ -0,0 +1,33 @@
    +  public $message = 'Only files with the following extensions are allowed: %files-allowed.';
    

    Nit: s/files-allowed/allowed-extensions/

  4. +++ b/core/modules/file/src/Validation/Constraint/FileExtensionValidator.php
    @@ -0,0 +1,23 @@
    +class FileExtensionValidator extends ConstraintValidator {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function validate($value, Constraint $constraint) {
    +    $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($constraint->extensions)) . ')$/i';
    +    if (!preg_match($regex, $value->getFilename())) {
    +      $this->context->addViolation($constraint->message, ['%files-allowed' => $constraint->extensions]);
    +    }
    +  }
    

    Are there unit tests in file.module for extension validation that we can port to this constraint?

  5. +++ b/core/modules/file/src/Validation/Constraint/FileSizeLimit.php
    @@ -0,0 +1,29 @@
    +  const DEFAULT_FILE_LIMIT = 0;
    

    Nit: s/FILE/SIZE/

  6. +++ b/core/modules/file/src/Validation/Constraint/FileSizeUserQuota.php
    @@ -0,0 +1,28 @@
    +  const DEFAULT_USER_LIMIT = 0;
    

    Nit: s/LIMIT/QUOTA/

kim.pepper’s picture

StatusFileSize
new29.41 KB
new28.26 KB

Thanks for the review @gabesullice Still early days, but I wanted to get a sense of what the API would look like and what BC we'd need to handle.

I've added a bunch of deprecations so I expect there to be lots of fails here.

Re: #6:

  1. Fixed
  2. Not really, because they should be defined by the field definition, right?
  3. Fixed
  4. \Drupal\Tests\file\Functional\FileFieldValidateTest::testFileExtension() has a bunch of browser tests that we can look at.
  5. Fixed
  6. Fixed

Status: Needs review » Needs work

The last submitted patch, 7: 3221793-7.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new28.44 KB
new1.49 KB

Handle missing arguments in _file_convert_validators_to_constraints().

Status: Needs review » Needs work

The last submitted patch, 9: 3221793-9.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new28.45 KB
new3.13 KB

Fixes \Drupal\Tests\file\Kernel\ValidateTest

kim.pepper’s picture

StatusFileSize
new28.45 KB
new1.63 KB

More file validator fixes.

The last submitted patch, 11: 3221793-11.patch, failed testing. View results

kim.pepper’s picture

StatusFileSize
new28.47 KB
new2.96 KB

Fix E_USER_DEPRECATED

The last submitted patch, 12: 3221793-12.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 14: 3221793-14.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Postponed

Having 2nd thoughts about this issue. Initially I thought we could swap file_validate out for a new service based on symfony constraints, however file_validate is tightly bound to the form api, and there are hundreds of contrib modules using it. grep.xnddx.ru/search?text=file_validate

Going to postpone until the other issues are in.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

kim.pepper’s picture

I'm shifting away from Symfony Validators to a (hopefully) simpler approach in #3353583: Allow validators passed to file_validate to use callable syntax

kim.pepper’s picture

Status: Postponed » Needs review
StatusFileSize
new24.98 KB

Re-roll to see what's breaking.

Status: Needs review » Needs work

The last submitted patch, 22: 3221793-22.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new25.01 KB
new2.72 KB

Fix test fails

kim.pepper’s picture

I wonder if we should go further up the stack and replace '#upload_validators' in the Form API with an array of \Symfony\Component\Validator\Constraint instances? This probably means replacing how form validation works to be more inline with Symfony Validator component, and there seems to be some pushback on using it at all #3054535: Discuss whether to decouple from Symfony Validator

kim.pepper’s picture

Also not sure how the form API and existing validators tie into the whole Typed Data API and plugin validators like \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator.

joachim’s picture

  1. +++ b/core/modules/file/file.module
    @@ -99,25 +104,49 @@ function file_field_widget_info_alter(array &$info) {
    + *   The validation constraints.
    

    Keyed by what? And in what order?

  2. +++ b/core/modules/file/file.module
    @@ -99,25 +104,49 @@ function file_field_widget_info_alter(array &$info) {
    +function _file_convert_validators_to_constraints(FileInterface $file, array $validators): array {
    

    Could this go on a service somewhere instead of being a function? We can mark it as @internal.

  3. +++ b/core/modules/file/file.module
    @@ -99,25 +104,49 @@ function file_field_widget_info_alter(array &$info) {
    +      // We can't convert the validator, so lets just call it.
    

    Typo: should be "let's".

  4. +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
    @@ -341,6 +343,30 @@ public function getUploadValidators() {
    +  public function getUploadConstraints(): array {
    

    I think we need some docs on this class to explain why we have a mishmash of procedural validators and Symfony constraints.

    I assume it's because of BC? But shouldn't there also be an issue to deprecate the functional validators?

kim.pepper’s picture

Thanks @joachim. I'm still looking for the right approach for this.

Re: #25 Should we introduce Constraints to the Form API? Or should we just focus on upload validators and converting them over?

Re #26 If we do start to use Constraints, should we try and reuse the plugins that typed data api uses?

kim.pepper’s picture

Chatted with @larowlan about this at DrupalSouth and decided the best approach is utilise constraint validation plugins.

We can create plugins with the same IDs as the function names for BC, and deprecate the functions.

larowlan’s picture

Capturing some conversation from DrupalSouth

\Drupal\Core\TypedData\TypedDataManager::getValidator has some prior art to create a validator

\Drupal\Core\TypedData\TypedData::validate has how to validate once you have the validator

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

Can the issue summary also be updated to include agreed upon solution please

Thanks!

larowlan’s picture

To reflect current discussion

kim.pepper’s picture

Version: 10.1.x-dev » 11.x-dev
Status: Needs work » Needs review
Issue tags: +Needs issue summary update, +Needs architectural review
StatusFileSize
new8.08 KB

Starting on the new approach described above in #29 and #30.

Posting here for architectural review.

kim.pepper’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
StatusFileSize
new12.13 KB
new9.06 KB

Added an additional validation for FileExtension to test passing in arguments. We will need to use the existing '$value' option as we don't currently an associative array of options, just the function arguments.

I also updated the issue summary with the current approach and remaining tasks.

Status: Needs review » Needs work

The last submitted patch, 34: 3221793-34.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
kim.pepper’s picture

Related issues: +#3363745: Deprecate file_validate_image_resolution() and replace with a service
StatusFileSize
new35.1 KB
new32.07 KB

Still a work in progress. Did more work building out constraints and added logic for deprecating existing hook functions.

Ran into file_validate_image_resolution() which I think doesn't belong as a validator. I created #3363745: Deprecate file_validate_image_resolution() and replace with a service which I think is a blocker for this issue.

smustgrave’s picture

Status: Needs review » Needs work

If CC error could be addressed.

kim.pepper’s picture

Title: Move file upload validation from file.module to constraint validator » Move file upload validation from file.module to constraint validators
Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new44.15 KB
new9.65 KB

Re: #37 I dug a bit deeper and trying to remove file_validate_image_resolution() from the validators is going to be complex. I have added a \Drupal\file\Plugin\Validation\Constraint\FileImageDimensionsConstraint. Happy to hear opinions on how we can do this better.

Remaining tasks:

  • Convert all the file validation functions to plugins
  • Deprecate the legacy functions
  • Replace usages in core

Status: Needs review » Needs work

The last submitted patch, 39: 3221793-39.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new44.44 KB
new12.83 KB

Fixes test fails and adds a test for FileNameLengthConstraintValidator.

kim.pepper’s picture

StatusFileSize
new47.07 KB
new4.46 KB

Adds a test event subscriber.

kim.pepper’s picture

Issue summary: View changes
StatusFileSize
new76.17 KB
new30.87 KB

Deprecates file_validate() and replaces usages.

Remaining tasks:

  • Convert all the file validation functions to plugins
  • Deprecate the legacy file validate hook functions
  • Deprecate file_validate()
  • Replace usages in core

Status: Needs review » Needs work

The last submitted patch, 43: 3221793-43.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new95.83 KB
new23.67 KB

Replaced more usages of file_validate() and file_validate_* functions.

Status: Needs review » Needs work

The last submitted patch, 45: 3221793-45.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new112.68 KB
new19.47 KB

Fixing test fails.

Status: Needs review » Needs work

The last submitted patch, 47: 3221793-47.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new125.79 KB
new31.95 KB

JSON API has an undeclared dependency on file module so a lot of these fails are because we have a new file service that will error out when the container is built, whereas before we were just calling the file_validate() function and not checking if it was available.

Added 'file' as a module dependency to a load of the JSON-API tests.

The patch grows bigger, and the night grows darker...

Status: Needs review » Needs work

The last submitted patch, 49: 3221793-49.patch, failed testing. View results

kim.pepper’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new129.88 KB
new7.11 KB

Fixes tests.

Remaining tasks:

  • Convert all the file validation functions to plugins
  • Deprecate file_validate()
  • Replace usages in core
  • Deprecate the legacy file validate hook functions
  • Ensure we have the same test coverage for new ConstaintValidators as we did for legacy file_validate_* functions

Status: Needs review » Needs work

The last submitted patch, 51: 3221793-51.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new132.04 KB
new2.16 KB

Trying a different approach to fix test issue by checking $file instanceof ConstraintViolationListInterface instead of $file instanceof EntityConstraintViolationListInterface.

kim.pepper’s picture

Issue summary: View changes
StatusFileSize
new139.95 KB
new34.16 KB
  1. Renamed FileSizeMax to FileSizeLimit.
  2. Copied over existing file_validate_* tests to ensure we don't lose any test coverage.

No more remaining tasks! Just reviews needed.

Status: Needs review » Needs work

The last submitted patch, 54: 3221793-54.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new139.87 KB
new1.89 KB

Don't use markup in test assertions.

Updated the CR to provide much more details and mapping old to new form API changes.

kim.pepper’s picture

Issue summary: View changes
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Searched for file_validate_size() and all instances appear to be addressed.

Fyi expectDeprecation is deprecated itself but seems #3322785: [meta] Several expect*() methods have been deprecated in PHPUnit 9.6 there is still a plan being made.

larowlan’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Needs work

This is looking amazing. I've done half the review and will finish it off tomorrow, here's observations so far

  1. +++ b/core/modules/ckeditor5/src/Controller/CKEditor5ImageController.php
    @@ -238,20 +258,7 @@ protected function validate(FileInterface $file, array $validators) {
    -    $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));
    

    🔥

  2. +++ b/core/modules/ckeditor5/src/Controller/CKEditor5ImageController.php
    @@ -261,15 +268,14 @@ protected function validate(FileInterface $file, array $validators) {
    -  protected function prepareFilename($filename, array &$validators) {
    -    $extensions = $validators['file_validate_extensions'][0] ?? '';
    -    $event = new FileUploadSanitizeNameEvent($filename, $extensions);
    +  protected function prepareFilename($filename, string $allowed_extensions) {
    +    $event = new FileUploadSanitizeNameEvent($filename, $allowed_extensions);
    

    Technically this is a BC break but we don't provide API for controllers and it looks like nothing is extending it in contrib https://git.drupalcode.org/search?group_id=2&scope=blobs&search=%22exten... so I think we're fine as long as this is mentioned in the CR

  3. +++ b/core/modules/file/file.field.inc
    @@ -152,16 +152,16 @@ function template_preprocess_file_upload_help(&$variables) {
    -  if (isset($upload_validators['file_validate_size'])) {
    -    $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'])) {
    -    $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['FileImageDimensions'])) {
    +    $max = $upload_validators['FileImageDimensions']['maxDimensions'];
    +    $min = $upload_validators['FileImageDimensions']['minDimensions'];
    

    We will need to handle both formats here, as there will be existing render arrays with the theme hook in contrib/custom code with this in the old format.

    We should trigger a deprecation error if they are

  4. +++ b/core/modules/file/src/Element/ManagedFile.php
    @@ -360,8 +360,8 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
    -    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']['FileExtension']['extensions'])) {
    +      $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['FileExtension']['extensions'])));
           $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $id] = $extension_list;
    

    We will need to handle both formats here, as there will be existing forms in contrib/custom code with this in the old format.

    We should trigger a deprecation error if they are

  5. +++ b/core/modules/file/src/Plugin/Validation/Constraint/BaseFileConstraintValidator.php
    @@ -0,0 +1,35 @@
    +    if (!$value instanceof FileInterface) {
    +      throw new UnexpectedTypeException($value, FileInterface::class);
    +    }
    

    nice

  6. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileExtensionConstraint.php
    @@ -0,0 +1,41 @@
    +  public function getDefaultOption(): ?string {
    

    we should be safe to narrow this to :string

  7. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileExtensionSecureConstraintValidator.php
    @@ -0,0 +1,51 @@
    +    if (!$allowInsecureUploads  && preg_match(FileSystemInterface::INSECURE_EXTENSION_REGEX, $file->getFilename())) {
    

    🧐 think there's an extra space here

  8. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileImageDimensionsConstraint.php
    @@ -0,0 +1,55 @@
    + * File extension secure constraint.
    

    copy/paste?

  9. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileImageDimensionsConstraintValidator.php
    @@ -0,0 +1,123 @@
    +    if ($image->isValid()) {
    

    I think if we flip this and return early it would simplify the code, e.g.

    if (!$image->isValid()) {
      return;
    }
  10. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileImageDimensionsConstraintValidator.php
    @@ -0,0 +1,123 @@
    +          if ($image->scale($width, $height)) {
    

    we can probably flip this too, and add the violation and return early, avoiding the else and another layer of nesting

  11. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileImageDimensionsConstraintValidator.php
    @@ -0,0 +1,123 @@
    +            if (!empty($width) && !empty($height)) {
    ...
    +            elseif (empty($width)) {
    ...
    +            elseif (empty($height)) {
    

    we can return from each of these and avoid elseif

  12. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileImageDimensionsConstraintValidator.php
    @@ -0,0 +1,123 @@
    +            $this->context->addViolation($constraint->messageResizedImageTooSmall,
    

    we can return here and avoid an else too

  13. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileIsImageConstraintValidator.php
    @@ -0,0 +1,51 @@
    + * Validator for the FileExtensionSecureConstraint.
    

    copy/paste

  14. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileNameLengthConstraint.php
    @@ -0,0 +1,39 @@
    +  public string $messageTooLong = "The file's name exceeds the 240 characters limit. Please rename the file and try again.";
    

    Should the 240 in this string be placeholdered? I think the maxLength would be configurable via the constraint constructor right?

  15. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileNameLengthConstraintValidator.php
    @@ -0,0 +1,30 @@
    +    if (strlen($file->getFilename()) > $constraint->maxLength) {
    

    should this be using mb_strlen to allow for utf-8 characters?

  16. +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
    @@ -0,0 +1,74 @@
    + * Validates the file name size constraint.
    

    copy paste?

  17. +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
    @@ -451,38 +470,6 @@ protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $fie
    -  protected function validate(FileInterface $file, array $validators) {
    -    $this->resourceValidate($file);
    -
    

    Note to other reviewers, this just uses the parent now

    however, we can probably drop the use of the trait now

  18. +++ b/core/modules/file/src/Validation/FileValidationEvent.php
    @@ -0,0 +1,27 @@
    +class FileValidationEvent extends Event {
    

    Part way through review, up to here

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new141.78 KB
new16.78 KB
  1. 👍️
  2. Updated CR
  3. Fixed
  4. Fixed
  5. 👍️
  6. Fixed
  7. Fixed
  8. Fixed
  9. Fixed
  10. We can't return early as we might add another minDimensions violation further down.
  11. Fixed
  12. Fixed
  13. Fixed
  14. Fixed
  15. Fixed. Although this is existing functionality.
  16. Fixed
  17. 👍️
  18. 👍️
larowlan’s picture

Status: Needs review » Needs work
Issue tags: -Needs architectural review

Got to the end, couple of questions about tests

  1. +++ b/core/modules/file/file.field.inc
    @@ -153,20 +153,39 @@ function template_preprocess_file_upload_help(&$variables) {
    +    @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);
    

    do we need a legacy test for this and the equivalent BC layer in the managed file element ?

  2. +++ b/core/modules/file/src/Validation/FileValidationEvent.php
    @@ -0,0 +1,27 @@
    +    public readonly ConstraintViolationListInterface $violations,
    

    was going to ask 'should there be a getter for this' then realised its public, 👌

  3. +++ b/core/modules/file/src/Validation/FileValidator.php
    @@ -0,0 +1,95 @@
    + * Implements the file validator.
    

    minor: can we improve this? 'Provides a class for file validation' perhaps?

  4. +++ b/core/modules/file/src/Validation/FileValidator.php
    @@ -0,0 +1,95 @@
    +    $entityAdapter = EntityAdapter::createFromEntity($file);
    

    I _think_ you can just call $file->getTypedData()

  5. +++ b/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
    @@ -95,7 +95,7 @@ protected function setUp(): void {
    -    $this->assertFileHooksCalled(['validate', 'insert']);
    +    $this->assertFileHooksCalled(['insert']);
    

    Should we add a test event listener using the new event that writes the 'validate' entry and keep these as they were? Would reduce the size of the patch and retain the existing coverage.

  6. +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileExtensionConstraintValidatorTest.php
    @@ -0,0 +1,133 @@
    +    $violations = $this->validator->validate($file, $validators);
    +    $this->assertCount(1, $violations, 'Invalid extension blocked.');
    

    should we assert the message is as expected?

  7. +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileImageDimensionsConstraintValidatorTest.php
    @@ -0,0 +1,144 @@
    +    $violations = $this->validator->validate($this->image, $validators);
    +    $this->assertCount(1, $violations, 'Got an error for an image that was not wide enough.');
    ...
    +    $violations = $this->validator->validate($this->image, $validators);
    +    $this->assertCount(1, $violations, 'Got an error for an image that was not tall enough.');
    ...
    +    $violations = $this->validator->validate($this->image, $validators);
    +    $this->assertCount(1, $violations, 'Small images report an error.');
    ...
    +      $violations = $this->validator->validate($this->image, $validators);
    +      $this->assertCount(1, $violations, 'An error reported for an oversized image that can not be scaled down.');
    ...
    +      $violations = $this->validator->validate($this->image, $validators);
    +      $this->assertCount(1, $violations, 'Oversize images that cannot be scaled get an error.');
    

    here too, I think we should be asserting the expected message to ensure the violation is what we expect rather than just the count

  8. +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileIsImageConstraintValidatorTest.php
    @@ -0,0 +1,67 @@
    +    $this->assertCount(1, $violations, 'An error reported for our non-image file.');
    
    +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileNameLengthConstraintValidatorTest.php
    @@ -0,0 +1,44 @@
    +    $this->assertCount(1, $violations, 'An error reported for 241 length filename.');
    ...
    +    $this->assertCount(1, $violations, 'An error reported for 0 length filename.');
    
    +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileSizeLimitConstraintValidatorTest.php
    @@ -0,0 +1,33 @@
    +    $this->assertCount(1, $violations, 'Error for the file being over the limit.');
    ...
    +    $this->assertCount(1, $violations, 'Error for the user being over their limit.');
    ...
    +    $this->assertCount(2, $violations, 'Errors for both the file and their limit.');
    

    here too

  9. +++ b/core/modules/file/tests/src/Kernel/Validation/FileValidatorTest.php
    @@ -0,0 +1,91 @@
    +  protected function setUp(): void {
    +    parent::setUp();
    +    $this->installConfig(['system']);
    +    $this->installEntitySchema('file');
    +    $this->installEntitySchema('user');
    +    $this->installSchema('file', ['file_usage']);
    +
    +    $uri = 'public://druplicon.txt';
    +    $this->file = File::create([
    +      'uid' => 1,
    

    if we extend the abstract base class from above I think we can remove all this setup

Removing the needs architectural review tag as I think this looks good.

The cleanup in the API resources makes it clear this is the right model, we were mixing constraints and errors in a weird mis-mash. Now its a single approach (or it will be once we can remove the BC layer).

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new131.16 KB
new32.7 KB
  1. Added a legacy test \Drupal\Tests\file\Kernel\LegacyFileThemeTest::testTemplatePreprocessFileUploadHelp()
  2. 👍️
  3. Fixed
  4. I think it just calls ::createFromEntity() underneath, but lets find out.
  5. I modified the file_validator_test module's event listener to set the test state that the FileUploadTest was previously checking, and re-added the check for 'validation' hook being fired (event though it's an event now). I had to rework the FileValidatorTest as we are no longer adding a violation in the event listener.
  6. Removed this test as it's more fully covered by the tests below that use the dataProvider.
  7. Fixed
  8. Fixed
  9. Fixed. Moved it from Drupal\Tests\file\Kernel\Plugin\Validation\Constraint\FileConstraintValidatorTestBase to \Drupal\Tests\file\Kernel\Validation\FileValidatorTestBase
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

From what I can tell all of @larowlan's points have been addressed.

wim leers’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +validation
Related issues: +#3365498: Add Missing Symfony Validators to Drupal Constraint Validator

🤩

+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileExtensionConstraint.php
@@ -0,0 +1,41 @@
+class FileExtensionConstraint extends Constraint {

+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileIsImageConstraint.php
@@ -0,0 +1,27 @@
+class FileIsImageConstraint extends Constraint {

+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraint.php
@@ -0,0 +1,53 @@
+class FileSizeLimitConstraint extends Constraint {

I think that many of these could use \Symfony\Component\Validator\Constraints\FileValidator instead? 😅 That would mean less code to maintain on the Drupal side!

Related: #3365498: Add Missing Symfony Validators to Drupal Constraint Validator.

wim leers’s picture

Issue tags: +Pittsburgh2023
smustgrave’s picture

Status: Needs review » Needs work

@Wim Leers showed me this and explained a lot about how the Constraints can be leveraged. Think if we can leverage them we should!

kim.pepper’s picture

I think that many of these could use \Symfony\Component\Validator\Constraints\FileValidator instead? 😅 That would mean less code to maintain on the Drupal side!

Had a look through the Symfony constraints. As mentioned in Slack, we have a \Drupal\file\FileInterface and \Drupal\Core\Image\ImageInterface we are validating, so the Symfony validators won't work.

kim.pepper’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for checking that. Will remark then.

kim.pepper’s picture

StatusFileSize
new131.53 KB

Re-roll of #62

larowlan’s picture

Issue credits

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Just one comment and one follow up request, feel free to self RTBC or push back if not appropriate.

  1. +++ b/core/modules/file/tests/file_test/src/Form/FileTestForm.php
    @@ -94,18 +94,18 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    -      $validators['file_validate_is_image'] = [];
    
    +++ b/core/modules/file/tests/file_test/src/Form/FileTestSaveUploadFromForm.php
    @@ -141,18 +141,18 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    -      $validators['file_validate_is_image'] = [];
    

    there's a lot of duplicate code between these two, can we get a followup to move to a base-class or trait?

  2. +++ b/core/modules/file/tests/src/Kernel/LegacyValidatorTest.php
    @@ -8,8 +8,9 @@
    +class LegacyValidatorTest extends FileManagedUnitTestBase {
    
    +++ b/core/modules/file/tests/src/Kernel/Plugin/Validation/Constraint/FileIsImageConstraintValidatorTest.php
    @@ -0,0 +1,68 @@
    +  protected function setUp(): void {
    +    parent::setUp();
    +
    +    $this->image = File::create();
    +    $this->image->setFileUri('core/misc/druplicon.png');
    +    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
    +    $file_system = \Drupal::service('file_system');
    +    $this->image->setFilename($file_system->basename($this->image->getFileUri()));
    +
    +    $this->nonImage = File::create();
    +    $this->nonImage->setFileUri('core/assets/vendor/jquery/jquery.min.js');
    +    $this->nonImage->setFilename($file_system->basename($this->nonImage->getFileUri()));
    +  }
    

    these two classes have identical setup methods - could/should we use a base class? We have one already in this patch, could some of this duplication go there?

kim.pepper’s picture

Status: Needs work » Reviewed & tested by the community
Related issues: +#3368857: Remove duplication from FileTestForm and FileTestSaveUploadFromForm
  1. Added follow-up issue #3368857: Remove duplication from FileTestForm and FileTestSaveUploadFromForm
  2. LegacyValidatorTest will be removed in the next major release, so we'd end up with only one use of the trait or base class.

  • larowlan committed 79c7666a on 11.x
    Issue #3221793 by kim.pepper, larowlan, smustgrave, Wim Leers, joachim,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed 79c7666 and pushed to 11.x. Thanks!

Published the changed record

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

damienmckenna’s picture

FYI this introduced a subtle bug whereby if a validator passed through #upload_validators did not exist as a function it was assumed to be a constraint plugin, whereas some contrib modules used it as a namespaced variable that would ultimately be passed through to hook_file_validate(). This has caused problems with several contrib modules, including Webform (#3409599: Drupal\Component\Plugin\Exception\PluginNotFoundException: The "webform_file_validate_extensions" plugin does not exist).

kim.pepper’s picture