eca_form: Ajax using limited validation errors leads to faulty reads of submission values

Created on 15 April 2023, about 1 year ago
Updated 19 April 2023, about 1 year ago

Problem/Motivation

We're building some complex multistep forms, using Ajax with actions like "Form: add Ajax handler" and we read values using either "Entity form: build entity" as well as "Form field: compare submitted value" to dynamically enable or disable further fields in an entity form.

There is a problem, when fields are dynamically hidden or shown, that themselves contain Ajax logic. One example is the file field, that uses Ajax when uploading a file to the form.

The problematic part is, that the Ajax form may have '#limit_validation_errors' specified. When that's the case, then Drupal's form building process will only include the specified (or even none) values into the values array of the form state (getting via $form_state->getValues()). This circumstance is not yet covered by ECA's form components, and may lead to faulty situations, when the submitted values need to be read out, but are (wrongly) not returned.

Steps to reproduce

Attaching two exported examples, one is using "Entity form: build entity" and another one is using "Form field: compare submitted value" instead (can be tested on a fresh standard installation profile). The models are using some Ajax functionality, finally showing a file field for uploading contents.

When trying this out, one could also stumble accross the fact that a file will be silently removed when the file field is being hidden again. But that's actually a different issue, which is an old but still present core bug: 📌 File(s) silently deleted when #access=false Needs work

Proposed resolution

Make ECA's form components more resilient against the described circumstance.

This needs a backport to 1.1.x, but it could be that the provided MR cannot get merged into 1.1.x (due to code differences between the two). If you need help for the backport, just let me know.

Remaining tasks

User interface changes

API changes

Data model changes

🐛 Bug report
Status

Fixed

Version

1.2

Component

Code

Created by

🇩🇪Germany mxh Offenburg

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @mxh
  • 🇩🇪Germany mxh Offenburg
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    284 pass
  • @mxh opened merge request.
  • 🇩🇪Germany mxh Offenburg
  • Issue was unassigned.
  • Status changed to Needs work about 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    Oh wow, this is a pretty complex one. Great, you have a use case to identify this and be able to fix it. I've reviewed the code, which looks OK, I've only made a couple of small comments about dealing with NULL values. Nothing major.

    Apart from reviewing this, it's hard to test other than with the 2 samples you provided. On the other hand, we have a comprehensive CyPress test suite for a client project which does a lot of form altering and validation. No ajax involved there, but it would be great to see if all tests still passed with this MR. As our project is using ECA 1.1 at the moment, it would be great to have an MR for that branch too. If that passes, the confidence is much higher that nothing broke.

    As no other changes expected for the 1.2 MR, would it be possible to provide one for 1.1 without too much of a hassle?

  • Assigned to mxh
  • 🇩🇪Germany mxh Offenburg

    Thanks for reviewing this. Will address the threads and will create a MR for 1.1.x.

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    284 pass
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    284 pass
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    271 pass
  • @mxh opened merge request.
  • Issue was unassigned.
  • Status changed to Needs review about 1 year ago
  • 🇩🇪Germany mxh Offenburg

    Created MR346 for 1.1.x and resolved all threads.

  • Open in Jenkins → Open on Drupal.org →
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    271 pass
  • Status changed to Needs work about 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    Thanks @mxh for providing the second MR for 1.1 - almost all tests in our fairly large test suite still work. But there is one use case that's been used in a number of tests which fails with this stack trace:

    Sorry, only have a screenshot at this point. Must be something with the FormBuildEntity action when that calls processForm from the FormBuilder class.

    Any idea what could be going wrong here, or do we have to dig deeper into our model and provide more details?

  • Assigned to mxh
  • 🇩🇪Germany mxh Offenburg

    Thanks for testing. Hm, that's a bad sign I guess. Are there any special characteristics on the forms being tested, e.g. using inline entity form / special form widgets or alike? I could then try to reproduce this. Assuming the fatal didn't appear before, right?

    Will try to reproduce this.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    Correct, it does NOT crash without the patch. We can provide more details tomorrow, will see if I can identify any special circumstances, or be able to provide any reproducible setup.

  • 🇩🇪Germany mxh Offenburg

    That would be super helpful, thanks.

  • 🇩🇪Germany mxh Offenburg

    It may be that a contrib or custom module is setting a form field element on its own, setting the '#type' key to be not a string.

    Is the test clicking on a button other than the default save?

    Tried various things using Drupal core components, no luck so far reproducing this. Maybe there are some contrib modules involve that do something with forms.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    First debugging phase discovered this:

    • It get to $this->formBuilder->processForm in \Drupal\eca_form\Plugin\Action\FormBuildEntity::execute twice with seemingly the same form.
    • The first time is has no issue.
    • The second time, it gets to $element[$key] = $this->doBuildForm($form_id, $element[$key], $form_state); in \Drupal\Core\Form\FormBuilder::doBuildForm and that crashes when $array_parents contains gin_sidebar and footer

    I'll continue debugging.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    OK, the first time it goes through process form is the regular form build from the form submission, and the second time is caused by the ECA model.

    The context for $this->elementInfo->getInfo($element[$key]['#type']) with key being actionis this:

    So, the delete action has an empty array as its #type which is probably because the user has no permission to delete the node.

    @mxh is that the reason? And do we have a way to prevent that from happening? Please let me know if you needed more details.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    For completeness, getting to the same point for the "regular" form processing, the action element looks like this:

    So, at that point, the #type is still a string, i.e. actions

  • 🇩🇪Germany mxh Offenburg

    So, the delete action has an empty array as its #type which is probably because the user has no permission to delete the node.

    @mxh is that the reason? And do we have a way to prevent that from happening? Please let me know if you needed more details.

    At least this one would lead to the fatal as the '#type' key is an empty array. Question now is, which component sets an empty array on that.

    What I currently suspect is, because of the 'actions' key being unset in FormBuildEntity action, another component will then merge something into 'actions' which doesn't exist anymore, leading to this result.

    I can have another look at this later this day.

  • 🇩🇪Germany mxh Offenburg

    Took a quick look into the Gin code and found the source of the problem. It's currently resided within the gin theme (3.x), in class Drupal\gin\GinContentFormHelper on line 183 the following is happening:

    $form['gin_sidebar']['actions']['#type'] = ($form['actions']['#type']) ?? [];

    .. which is setting the empty array on the '#type' key.

    Although the bug is in the Gin theme, I can try to make FormBuildEntity not running into it. Will address this later this day.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    That's incredible. Let me talk to Sascha, I'm sure he's happy to fix tat in Gin.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    I've opened an issue with Gin: 🐛 Edge case: Illegal offset error in Drupal FormBuilder Needs review

  • Status changed to RTBC about 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    Great stuff, all our tests are passing again with the patch for the Gin theme as described above.

    Therefore, this issue can be set to RTBC and I will merge and back port your 2 MRs. Thanks a lot for all your support on this, has been awesome.

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    271 pass
  • Open in Jenkins → Open on Drupal.org →
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    284 pass
  • Status changed to Fixed about 1 year ago
  • 🇩🇪Germany jurgenhaas Gottmadingen
  • 🇩🇪Germany mxh Offenburg

    Maybe it makes sense that I adjust FormBuildEntity so that it won't run into the bug in the meantime?

    It seems the 1.1.x merge was not yet done.

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    271 pass
  • 🇩🇪Germany jurgenhaas Gottmadingen

    Not sure if we needed to do that. It really seems to be an edge case where

    • the delete permission must be missing
    • the Gin theme must be used
    • the FormBuildEntity action must be used

    And even if all 3 conditions apply, we haven't seen this issue every time. I'd say, if really anyone else were facing this before Gin gets fixed, they should find this issue with the reference to the MR in Gin so that they can patch their site.

  • Issue was unassigned.
  • 🇩🇪Germany mxh Offenburg

    Agree, thanks!

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

Production build 0.69.0 2024