Problem/Motivation

Field widgets are using #limit_validation_errors to inform the validation handlers that they only need to care about a specific field being edited/updated in partial form updates (e.g. AJAX interactions).

Examples of these are the 'Add another item' button for multi-valued fields (\Drupal\Core\Field\WidgetBase::formMultipleElements()) and the 'Upload' buttons for File/Image fields (\Drupal\file\Plugin\Field\FieldWidget\FileWidget::process()).

The problem is that \Drupal\Core\Entity\ContentEntityForm::validateForm() does not take this into account when filtering validation errors to fields that are being edited by the form, which, combined with the fact that some $form_state properties are being stripped by Form API to the element(s) specified in #limit_validation_errors, leads to constraint violations that are built incorrectly (i.e. without a proper validation path). This further leads to confusing errors like #2614250: Number widget validation can break AJAX actions and #2553983: Required textarea with summary breaks ajax events for other fields..

Proposed resolution

Restrict validation errors to the field(s) that were actually edited in the entity form, as specified by $form_state->getLimitValidationErrors().

Remaining tasks

Review.

User interface changes

Nope.

API changes

Nope.

Data model changes

Nope.

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
Issue tags: +Contributed project blocker, +Triaged core major
StatusFileSize
new5.45 KB
new7.09 KB

This should fix all the (seemingly separate) problems we're seeing in this area.

This problem has already been triaged as major in #2027059-60: Improve the documentation of WidgetBase::errorElement() for mapping violation property paths to form elements, so I'm borrowing some tags from that issue.

The last submitted patch, 2: 2901943-test-only.patch, failed testing. View results

berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
    @@ -179,10 +179,28 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    +    if ($limit_validation_errors = $form_state->getLimitValidationErrors()) {
    +      foreach ($limit_validation_errors as $section) {
    +        $field_name = reset($section);
    +        if ($entity->hasField($field_name)) {
    +          $edited_fields += [$field_name];
    +        }
    +      }
    +    }
    +    else {
    +      $edited_fields = $this->getEditedFieldNames($form_state);
    +    }
    +
    

    Nice. @amateescu++

    Wondering if we could make this a bit shorter by always starting with the edited field names, an array filter on the limit validation errors and then an array intersect but that might not actually be easier to understand and quite possibly slower as when we have limit validation errors, it is almost always for a single field. So this seems fine.

  2. +++ b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php
    @@ -0,0 +1,159 @@
    +
    +  use TestFileCreationTrait {
    +    getTestFiles as drupalGetTestFiles;
    

    do we care about renaming the method if we specifically use the trait, I think we usually just do that for BC in the base classes?

  3. +++ b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormFieldValidationFilteringTest.php
    @@ -0,0 +1,159 @@
    +  /**
    +   * Tests field widgets with #limit_validation_errors.
    +   */
    +  public function testFieldWidgetsWithLimitedValidationErrors() {
    +    $assert_session = $this->assertSession();
    

    Does it make sense to also verify that we do get validation errors when e.g. trying to save now with an empty required field?

    I guess we have plenty of tests that already do that, but might not hurt to do it in the context of this scenario too?

amateescu’s picture

StatusFileSize
new6.02 KB
new7.65 KB
new2.82 KB

@Berdir, thanks for taking a look :)

Re #5:

  1. I tried various ways of writing that code but the one that ended up in the patch seemed to be the most readable to me.
  2. Hmm, you're right, I just copied that from another test and didn't think twice about it. Fixed.
  3. Sure it does, and not only that but we should also be testing that a required multi-value field still throws an error when adding another item. Fixed that as well.

The last submitted patch, 6: 2901943-6-test-only.patch, failed testing. View results

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me and fix tons of issues.

drupalfan2’s picture

subscribe

drupalfan2’s picture

I temporarily solved this removing the following 4 lines:

/*
    // Remove violations of inaccessible fields and not edited fields.
    $violations
      ->filterByFieldAccess($this->currentUser())
      ->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $this->getEditedFieldNames($form_state)));

    $this->flagViolations($violations, $form, $form_state);
*/        

Works great then.

amateescu’s picture

@drupalfan2, or you could apply the patch from #6 and have it fixed properly until this issue lands in core.

drupalfan2’s picture

But removing this 4 lines (see #10) is much easier.
Hoping for a solution in core.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 6: 2901943-6.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Reviewed & tested by the community

That was a random fail in Drupal\KernelTests\Core\Entity\ContentEntityChangedTest.

ibustos’s picture

Status: Reviewed & tested by the community » Needs work

@amateescu suggested that #2784015: Widget validation crashes on ItemList violations for widgets with a custom errorElement() implementation would be fixed by this issue but the latest patch here doesn't fix that so either this needs some more work or that other issue can be reopened.

amateescu’s picture

Status: Needs work » Reviewed & tested by the community

@ibustos, I looked a bit more into that issue and you're right, the patch here does not fix it. I just reopened it and posted a test-only patch, so it can be fixed on its own.

catch’s picture

Version: 8.3.x-dev » 8.4.x-dev
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -179,10 +179,28 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
+          $edited_fields += [$field_name];

Minor but what's wrong with $edited_fields[] here? Do we really need the explicit +=, and if so could use a comment explaining why.

amateescu’s picture

StatusFileSize
new7.7 KB
new743 bytes

We don't really need it, I think I wanted to be sure that we don't have duplicate field names in that array, but we can also do it like this.

catch’s picture

Thanks that's a bit more explicit.

Committed 12a28a5 and pushed to 8.5.x. Thanks!

We're in a freeze for the 8.4.0 release, I think this is backportable for 8.4.1 though so leaving RTBC to cherry-pick later.

  • catch committed 12a28a5 on 8.5.x
    Issue #2901943 by amateescu, Berdir: Content entity form validation does...
catch’s picture

Status: Reviewed & tested by the community » Fixed

  • catch committed a9375d3 on 8.4.x
    Issue #2901943 by amateescu, Berdir: Content entity form validation does...

Status: Fixed » Closed (fixed)

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

jds1’s picture

Can we get this reopened/RTBC so it doesn't fall off the radar for 8.4.1? Just ran into this issue and see it is auto-closed. Thanks!

amateescu’s picture

@if-jds, this patch was already committed to the 8.4.x branch so it will be part of the 8.4.1 release.