Problem/Motivation

When the machine name is the same as an existing one and ajax is used a fatal error happens.

This occurs because the default value is changed after the AJAX submission to have the value the user entered. The user entered value becomes the default value because the form's entity is created from values in form state when the form is rebuilt. See \Drupal\Core\Entity\EntityForm::afterBuild() - which eventually calls \Drupal\Core\Entity\EntityForm::copyFormValuesToEntity(). And then in \Drupal\Core\Render\Element\MachineName::validateMachineName() we do if ($element['#default_value'] !== $element['#value']) { to detect if the exists check needs to be triggered. Because the $element['#default_value'] is now the new value value we don't do the exists check.

Steps to reproduce:

  • Go to /admin/config/content/formats/add
  • Set name with basic_html
  • Change text editor to CKEditor
  • Save.
  • See WSOD with Drupal\Core\Entity\EntityStorageException: 'filter_format' entity with ID 'basic_html' already exists

Affected forms:

  • \Drupal\filter\FilterFormatFormBase::form
  • \Drupal\media\MediaTypeForm::form
  • \Drupal\responsive_image\ResponsiveImageStyleForm::form

Potentially affected forms:

  • \Drupal\action\ActionFormBase::form
  • \Drupal\block_content\BlockContentTypeForm::form
  • \Drupal\comment\CommentTypeForm::form
  • \Drupal\contact\ContactFormEditForm::form
  • \Drupal\field_ui\Form\EntityDisplayModeFormBase::form
  • \Drupal\image\Form\ImageStyleFormBase::form
  • \Drupal\menu_ui\MenuForm::form
  • \Drupal\node\NodeTypeForm::form
  • \Drupal\search\Form\SearchPageFormBase::form
  • \Drupal\shortcut\ShortcutSetForm::form
  • \Drupal\system\Form\DateFormatFormBase::form
  • \Drupal\taxonomy\VocabularyForm::form
  • \Drupal\user\RoleForm::form
  • \Drupal\workflows\Form\WorkflowAddForm::form
  • \Drupal\workspace\Form\WorkspaceForm::form

Proposed resolution

Set an explicit property on form state to revalidate when an error is set. (#86)
OR
Store the initial value in form state (#99)

Remaining tasks

Review

User interface changes

None

API changes

None. Both solutions use a form state property to more reliably do the exists check.

Data model changes

None

CommentFileSizeAuthor
#118 2557299-118.patch17.36 KBalexpott
#118 116-118-interdiff.txt639 bytesalexpott
#116 2557299-116.patch17.37 KBalexpott
#116 110-116-interdiff.txt1.93 KBalexpott
#110 2557299-110.patch17.47 KBalexpott
#110 107-110-interdiff.txt7.56 KBalexpott
#107 interdiff-2557299-105-107.txt821 bytesmartin107
#107 2557299-107.patch17.49 KBmartin107
#105 2557299-105.patch17.5 KBalexpott
#105 104-105-interdiff.txt1.51 KBalexpott
#104 2557299-104.patch16.89 KBalexpott
#104 99-104-interdiff.txt2.55 KBalexpott
#99 2557299-99.patch16.79 KBalexpott
#99 97-99-interdiff.txt871 bytesalexpott
#97 2557299-97.patch16.82 KBalexpott
#97 95-97-interdiff.txt2.45 KBalexpott
#95 2557299-94.patch16.09 KBalexpott
#94 86-94-interdiff.txt2.52 KBalexpott
#90 2557299-90.patch8.53 KBmahtab_alam
#87 80-86-interdiff.txt11.69 KBalexpott
#86 2557299-86.patch15.31 KBalexpott
#86 85-86-interdiff.txt6.75 KBalexpott
#85 interdiff-2557299-80-85.txt8 KBphenaproxima
#85 2557299-85.patch13.6 KBphenaproxima
#80 2557299-80.patch14.83 KBManuel Garcia
#80 interdiff-2557299-75-80.txt832 bytesManuel Garcia
#80 2557299-80-FAIL.patch13.2 KBManuel Garcia
#80 interdiff-2557299-75-80-FAIL.txt832 bytesManuel Garcia
#29 ajax-machine-name-2557299-29.patch3.23 KBswentel
#29 interdiff.txt674 bytesswentel
#23 ajax-machine-name-2557299-23-fail.patch1.17 KBswentel
#23 ajax-machine-name-2557299-23-test-fix.patch3.51 KBswentel
#13 editor_configure_ajax-2557299-13.patch3.98 KBLKS90
#13 interdiff_9-13.txt2.15 KBLKS90
#9 filter-format-dupes-2557299.pass_.patch1.83 KBlarowlan
#7 filter-format-dupes-2557299.fail_.patch1.14 KBlarowlan
#3 machine_name_verification-2557299-2.patch608 bytessegi
#27 interdiff-2557299.txt1.11 KBswentel
#27 ajax-machine-name-2557299-27.patch3.28 KBswentel
#31 interdiff.txt811 bytesborisson_
#31 any_ajax_call-2557299-31.patch3.24 KBborisson_
#34 interdiff.txt1.07 KBborisson_
#34 any_ajax_call-2557299-34.patch3.27 KBborisson_
#35 any_ajax_call-2557299-35.patch9.24 KBborisson_
#35 test-only.patch5.96 KBborisson_
#37 interdiff.txt1.28 KBborisson_
#37 any_ajax_call-2557299-37.patch9.25 KBborisson_
#43 any_ajax_call_machine_name-2557299-43.patch9.26 KBmbovan
#43 any_ajax_call_machine_name-2557299-43-interdiff.txt1.81 KBmbovan
#45 any_ajax_call_machine_name-2557299-45-test-only.patch8.17 KBBerdir
#45 any_ajax_call_machine_name-2557299-45.patch10.33 KBBerdir
#45 any_ajax_call_machine_name-2557299-45-interdiff.txt2.55 KBBerdir
#51 any_ajax_call_machine_name-2557299-51.patch10.38 KBManuel Garcia
#56 2557299-56.patch10.33 KBManuel Garcia
#58 2557299-58.patch10.33 KBManuel Garcia
#60 interdiff.txt2.3 KBManuel Garcia
#60 2557299-60.patch10.23 KBManuel Garcia
#65 2557299-65.patch10.71 KBphenaproxima
#68 2557299-68.patch9.82 KBphenaproxima
#68 interdiff-2557299-65-68.txt3.23 KBphenaproxima
#71 2557299-71-FAIL.patch13.23 KBphenaproxima
#71 2557299-71.patch14.86 KBphenaproxima
#75 interdiff-2557299-71-75.txt821 bytesManuel Garcia
#75 2557299-75.patch14.83 KBManuel Garcia
#75 interdiff-2557299-71-75-FAIL.txt821 bytesManuel Garcia
#75 2557299-75-FAIL.patch13.2 KBManuel Garcia
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

giancarlosotelo created an issue. See original summary.

larowlan’s picture

Priority: Normal » Major
segi’s picture

I could reproduce the problem. I think I found a bug in the validation function, when the function try to check that there is similar format name or not.
The machine name was checked with not equal. I think we have to check the test as well.
This bug independents from ajax or it is a new bug?

segi’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 3: machine_name_verification-2557299-2.patch, failed testing.

larowlan’s picture

Assigned: Unassigned » larowlan
larowlan’s picture

Component: ajax system » editor.module
Status: Needs work » Needs review
FileSize
1.14 KB

Failing test

Status: Needs review » Needs work

The last submitted patch, 7: filter-format-dupes-2557299.fail_.patch, failed testing.

larowlan’s picture

Status: Needs work » Needs review
FileSize
1.83 KB

So the issue is the limit validation errors added by editor module means the machine name validation is discarded, but the default value is set, which means the next time around the machine name doesn't see the value as changed and doesn't run validation.

The fix is to also include the format in the validation.

larowlan’s picture

Title: Machine name verification is not working when ajax is used and leads to a fatal error » Editor configure ajax call disregards machine name verification when ajax is used and leads to a fatal error
larowlan’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 9: filter-format-dupes-2557299.pass_.patch, failed testing.

LKS90’s picture

Status: Needs work » Needs review
FileSize
2.15 KB
3.98 KB

I fixed the test fails which suddenly appeared with the patch in EditorAdminTest.
The problem were missing required fields in the $edit array when using the selectUnicornEditor() function, I fixed it by adding a parameter to this helper function, the $edit array passed by reference. So an existing $edit array is modified by the function and used in it's drupalPostForm() call.

giancarlosotelo’s picture

Well the patch solved the problem with the editor but this problem is not only about that.

Every machine name verification that has an ajax call after is broken and also affects some modules e.g flag, monitoring.

The problem is bigger but I don't know if we should fix each module with a similar solution or try to fix this in core and probably in the MachineName class.

// Verify that the machine name is unique.
    if ($element['#default_value'] !== $element['#value']) {
      $function = $element['#machine_name']['exists'];
      if (call_user_func($function, $element['#value'], $element, $form_state)) {
        $form_state->setError($element, t('The machine-readable name is already in use. It must be unique.'));
      }
    }
segi’s picture

I think we have to fix it somewhere deeper maybe in MachineName class.

swentel’s picture

Yep, this is a general problem - I'd say this almost is critical worthy.

swentel’s picture

Component: editor.module » forms system
Issue tags: +rc target triage

Moving to forms component - I agree with #13 and #14 to look into forms system / MachineName class somewhere as this affects a lot of contrib and custom modules.

swentel’s picture

Title: Editor configure ajax call disregards machine name verification when ajax is used and leads to a fatal error » Any ajax call disregards machine name verification when ajax is used and leads to a fatal error

Changing title as well

swentel’s picture

Wow, machine names and ajax requests are really fun. The validation is actually happening when the ajax request is triggered, but the error is not shown. Then usually during an ajax request, a form rebuild is triggered and '#value' in form_state is now populated and then the actual submission doesn't validate the name anymore since default_value and value are equal.

Another example how broken this behavior is: edit some existing entity, edit the machine name, trigger an ajax request, then change the machine name to the original, then hit submit. You'll get the "The machine-readable name is already in use. It must be unique." message. Granted, this is a situation that won't happen that much, but it's possible.

Been looking into a patch, but have been running against form api stuff. Will look further tomorrow.

xjm’s picture

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

Thanks for tagging this for rc target triage! To have committers consider it for inclusion in RC, we should add a statement to the issue summary of why we need to make this change during RC, including what happens if we do not make the change and what any disruptions from it are. We can add a section <h3>Why this should be an RC target</h3> to the summary. Sounds like it has a significant impact on contrib?

swentel’s picture

Issue summary: View changes
swentel’s picture

Issue summary: View changes
swentel’s picture

Update the issue summary.

Patch attached with proof of concept - it at least now works for facetapi & search api - hopefully core is fine and no other things break ..
Attaching fail only from larowlan + fix.

swentel’s picture

+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -10,6 +10,7 @@
+use Drupal\Core\Logger\RfcLogLevel;

will fix when the test is back

The last submitted patch, 23: ajax-machine-name-2557299-23-fail.patch, failed testing.

The last submitted patch, 23: ajax-machine-name-2557299-23-test-fix.patch, failed testing.

swentel’s picture

The last submitted patch, 27: ajax-machine-name-2557299-27.patch, failed testing.

swentel’s picture

tim.plunkett’s picture

Haven't looked at it manually yet, just the patch itself.

  1. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +    $needs_revalidation = $form_state->get('needs_revalidation', $element['#name']);
    ...
    +        $form_state->set('needs_revalidation', $element['#name']);
    

    Can we use get/setTemporaryValue here?

  2. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +      self::isUnique($element, $form_state);
    ...
    +      self::isUnique($element, $form_state);
    

    Might as well use static::

borisson_’s picture

This can't work with the temporary value store, because that is not accessible between requests.
Because this is currently being set in an ajax request, this will not work.

So I think this is ready.

Wim Leers’s picture

Title: Any ajax call disregards machine name verification when ajax is used and leads to a fatal error » Any AJAX call disregards machine name verification when AJAX is used and leads to a fatal error
Status: Needs review » Needs work

I think @tim.plunkett needs to the final review, or another Form API maintainer.

  1. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +    }
    +
    +  }
    

    Nit: extraneous newline.

  2. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   * Verify that the machine name is unique.
    

    Nit: s/Verify/Verifies/

  3. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   * @param $element
    

    Missing typehint: array.

  4. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   * @param FormStateInterface $form_state
    

    Needs FQCN.

  5. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   *   The form state interface.
    

    Eh, not an interface methinks :)

  6. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -232,12 +232,38 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   * @param (optional) $set_needs_revalidation
    

    Missing typehint: bool.

  7. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    --- a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
    +++ b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
    

    If this is actually a Form API bug, then I think this should also have explicit test coverage in Form API tests. Otherwise it could easily regress in the future.

borisson_’s picture

Status: Needs work » Needs review
FileSize
1.07 KB
3.27 KB

This fixes #33 1 - 6

We should still add a test form api test as well.

borisson_’s picture

Assigned: larowlan » Unassigned
FileSize
9.24 KB
5.96 KB

Added a new test. test-only also serves as an interdiff.

swentel’s picture

  1. +++ b/core/modules/system/src/Tests/Form/FormTest.php
    @@ -750,4 +750,37 @@ function testRequiredAttribute() {
    +   * Test machine name still required after an ajax submit.
    

    s/name still/name is still

  2. +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php
    @@ -0,0 +1,130 @@
    +   * Handles changes to the selected facet sources.
    

    facet :)

borisson_’s picture

Fixes remarks made in #36.

borisson_’s picture

Status: Needs review » Needs work

The test-only patch in #35 didn't fail, so the test isn't good enough. Back to NW.

borisson_’s picture

I looked at this again and I can't figure out how to write a failing test-only patch.

xjm’s picture

Issue tags: -rc target triage

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mbovan’s picture

Rerolled and small changes.

Tried to debug test-only case and it seems that $form_state->getValue('id') doesn't have a value when the form is rebuilt on an ajax request.
Core usually uses #machine_name with an entity, so maybe we can think about extending the form test and replacing the form state value with a test entity ID, since the state can be preserved in that case.

Ginovski’s picture

Tested with inmail and collect modules, works with both. +1 from me.

Berdir’s picture

Ok, this is a good one. @mbovan was on the right track.

The ajax callback uses limit validation errors, but that is interestingly not active during validation and in #after_build callbacks, which is where config entity forms update $this->entity. So I changed the test a bit to simulate behavior, with a validate callback that sets the value and then submit that rebuilds the form. I'm actually wondering if that isn't a flaw/bug in the form system but I have no idea how to prevent that. And after build actually even happens *before* validation, so we're technically building and keeping the entity which is based on unvalidated input. Weird but I think not actually a real issue since we do then run validation and don't do anything if there are validation errors

That makes it fail/pass as expected. It's not exactly like an entity form but I think that's OK since we also have test that are actually using entity forms and this is more understandable/reproducible than those. Real use case tests and a simplified, documented test that doesn't require an unhealthy amount of knowledge about the form *and* entity systems to know what's going on ;)

The last submitted patch, 45: any_ajax_call_machine_name-2557299-45-test-only.patch, failed testing.

mbovan’s picture

Since #45 addresses #33.7 (Test fail in \Drupal\system\Tests\Form\FormTest), this look good to me.

tim.plunkett’s picture

  1. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -240,12 +240,37 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +    if ($needs_revalidation) {
    +      static::isUnique($element, $form_state);
    +    }
    +    elseif ($element['#default_value'] !== $element['#value']) {
    +      static::isUnique($element, $form_state);
    +    }
    ...
    +  public static function isUnique($element, FormStateInterface $form_state, $set_needs_revalidation = TRUE) {
    

    Right now this is the same as

    if ($needs_revalidation || $element['#default_value'] !== $element['#value']) {

    Where are the calls to isUnique where $set_needs_revalidation would be FALSE?

  2. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -240,12 +240,37 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   *   The form element
    

    Missing .

  3. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -240,12 +240,37 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +   * @param (optional) bool $set_needs_revalidation
    +   *   Whether to set a property on form state in case of an error to revalidate
    

    The (optional) part goes in the description

Berdir’s picture

Status: Needs review » Needs work

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Manuel Garcia’s picture

Status: Needs work » Needs review
FileSize
10.38 KB

#45 was not applying any more, rerolled

Manuel Garcia’s picture

I've just tested this patch to see if it solves the issue with the ckeditor configuration page described in #2701393: Switching between editors on the format configuration causes errors upon save, but the bug persists.

Status: Needs review » Needs work

The last submitted patch, 51: any_ajax_call_machine_name-2557299-51.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Dave Reid’s picture

FYI this can be worked around for now by forcing the #default_value to be NULL for any new entities:

    $form['id'] = [
      '#type' => 'machine_name',
      '#default_value' => $entity->isNew() ? NULL : $entity->id(), // <--- THIS
      '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
      '#machine_name' => [
        'exists' => ['\Drupal\my_entity\Entity\MyEntity', 'load'],
      ],
      '#disabled' => !$entity->isNew(),
    ];
Manuel Garcia’s picture

Status: Needs work » Needs review
FileSize
10.33 KB

Rerolling again. Also switched to short array syntax the code we're adding to CKEditorAdminTest.php.

Status: Needs review » Needs work

The last submitted patch, 56: 2557299-56.patch, failed testing. View results

Manuel Garcia’s picture

Status: Needs work » Needs review
FileSize
10.33 KB

Oops!

Status: Needs review » Needs work

The last submitted patch, 58: 2557299-58.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Manuel Garcia’s picture

Status: Needs work » Needs review
FileSize
2.3 KB
10.23 KB

Attempting to fix the failing tests...

darvanen’s picture

Status: Needs review » Needs work

I think #48 still needs to be addressed.

xjm’s picture

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Krilo_89’s picture

The patch from #60 fixes #2934154: [PP-1] Remove workaround to enforce machine name validation on MediaType forms when the workaround from #2932226: Media Type entities don't validate machine name properly is removed.

I added a test in #2934154: [PP-1] Remove workaround to enforce machine name validation on MediaType forms which fails the current workaround from #2932226: Media Type entities don't validate machine name properly. In the second patch I added the patch from #60 and removed the old workaround, to show a passing test.
And in the third patch I only included the test and removed the old workaround, so when this issue is committed, #2934154: [PP-1] Remove workaround to enforce machine name validation on MediaType forms can be committed too.

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
10.71 KB

Addressed the feedback in #48 and re-rolled for 8.6.x (hence, no interdiff).

Let's get this in, so we can finally land #2934154: [PP-1] Remove workaround to enforce machine name validation on MediaType forms too.

Manuel Garcia’s picture

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -213,7 +213,8 @@ public static function processMachineName(&$element, FormStateInterface $form_st
    *

@@ -246,12 +247,34 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
+  public static function isUnique($element, FormStateInterface $form_state, $set_needs_revalidation = TRUE) {

Why add more public API? I don't think we should add this method because it's not necessary. It should just be part of the validateMachineName() as before.

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
9.82 KB
3.23 KB

Fixed! Plus a little more minor cleanup. Let's see if this flies.

phenaproxima’s picture

I have some questions myself, directed at those who understand this patch more than I do:

  • We're testing AJAX, but none of the tests are functional JS tests. Wouldn't it be better (or at least a good idea) to have one?
  • Should we be unsetting needs_revalidation if the machine name is, in fact, unique?
tim.plunkett’s picture

+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -246,10 +247,14 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
+    if ($form_state->get('needs_revalidation') === $element['#name'] || $element['#default_value'] !== $element['#value']) {
...
+        $form_state->set('needs_revalidation', $element['#name']);

What if there are multiple machine name elements per form? Wouldn't needs_revalidation be overwritten incorrectly?


I think the answer to both questions in #69 is "yes"

phenaproxima’s picture

This problem manifests very visibly in the Media module (specifically in MediaTypeForm), so I have adapted Media's existing functional JS tests to prove the problem, and its fix. I've also reverted the temporary workaround Media had implemented to hack past this problem in #2932226: Media Type entities don't validate machine name properly.

Given that, I'm not sure we need additional dedicated JS tests.

phenaproxima’s picture

Issue tags: +Media Initiative

Tagging as part of the Media Initiative.

phenaproxima’s picture

Version: 8.5.x-dev » 8.6.x-dev

Switching to 8.6.x, since both patches in #71 were rolled against that.

The last submitted patch, 71: 2557299-71-FAIL.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Manuel Garcia’s picture

The last submitted patch, 75: 2557299-75.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

The last submitted patch, 75: 2557299-75-FAIL.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Manuel Garcia’s picture

Huh?
Not sure why it fails, FormTestMachineNameValidationForm extends Drupal\Core\Form\FormBase which uses the MessengerTrait.. so it should work no?

phenaproxima’s picture

You need to use $this->messenger() — the method — not $this->messenger, the property, since it is not explicitly set until $this->messenger() is called.

Manuel Garcia’s picture

The last submitted patch, 80: 2557299-80-FAIL.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alexpott’s picture

Issue tags: +Media critical

This issue is critical for Media and I would argue should be targeted for 8.6.x as now the oEmbed patch has landed there's going to be more media type creation.

alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -213,7 +213,8 @@ public static function processMachineName(&$element, FormStateInterface $form_st
    +   * Note that #maxlength is validated by the form validator already, in
    +   * \Drupal\Core\Form\FormValidatorInterface::validateForm().
    

    This change is odd. The interface does not do the validation. We should file a follow up to fix this. It is unrelated to the problem.

  2. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -246,10 +247,13 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    +    // Verify that the machine name is unique. Check form state if we need to
    +    // explicitly validate the element. This happens every time when an error
    +    // has been set, since this breaks on AJAX requests
    +    if ($form_state->get('needs_revalidation') === $element['#name'] || $element['#default_value'] !== $element['#value']) {
    

    I wonder what happens if a form has multiple machine name fields. It looks like this won't work.

  3. +++ b/core/modules/system/tests/src/Functional/Form/FormTest.php
    @@ -770,4 +770,36 @@ public function testRequiredAttribute() {
    +  /**
    +   * Tests that a machine name is still required after an AJAX submit.
    +   *
    +   * This protects against the regression in https://www.drupal.org/node/2557299.
    +   */
    +  public function testMachineNameRequiredFormAjaxSubmit() {
    

    I think this test needs to be Javascript test to properly test AJAX.

tim.plunkett’s picture

  1. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaTypeCreationTest.php
    @@ -97,11 +99,14 @@ public function testMediaTypeCreationReuseSourceField() {
    +    // Choose the source plugin _before_ setting the label and machine name in
    

    Nice markdown :)

  2. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaTypeCreationTest.php
    @@ -97,11 +99,14 @@ public function testMediaTypeCreationReuseSourceField() {
    +    // https://www.drupal.org/project/drupal/issues/2557299.
    
    @@ -109,14 +114,9 @@ public function testMediaTypeCreationReuseSourceField() {
    +    // https://www.drupal.org/project/drupal/issues/2557299.
    

    Do we usually link to the bug we're fixing when we fix it?

  1. +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
    @@ -246,10 +247,13 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
    -    if ($element['#default_value'] !== $element['#value']) {
    ...
    +    if ($form_state->get('needs_revalidation') === $element['#name'] || $element['#default_value'] !== $element['#value']) {
    

    Stepped through this with @phenaproxima. This code goes back to the origin of machine name validation as a standalone concept.

    However, I don't understand why we're checking #default_value at all.

    My suggestion would be to switch the condition to if (isset($element['#value'])) { and always run the validation.

  2. +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php
    @@ -0,0 +1,147 @@
    +    $form['snack_configure_button'] = [
    +      '#type' => 'submit',
    +      '#name' => 'snack_configure',
    +      '#value' => 'Configure snack',
    +      '#limit_validation_errors' => [['snack']],
    +      '#validate' => ['::buildAjaxSnackConfigureFormValidate'],
    +      '#submit' => ['::buildAjaxSnackConfigureFormSubmit'],
    +      '#executes_submit_callback' => TRUE,
    +      '#ajax' => [
    +        'callback' => '::buildAjaxSnackConfigureForm',
    +        'wrapper' => 'snack-config-form',
    +      ],
    +      '#attributes' => ['class' => ['js-hide']],
    +    ];
    
    +++ b/core/modules/system/tests/src/Functional/Form/FormTest.php
    @@ -770,4 +770,36 @@ public function testRequiredAttribute() {
    +  public function testMachineNameRequiredFormAjaxSubmit() {
    

    When this test is rewritten as a Functional JS test, this extra button shouldn't be necessary

  3. +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php
    @@ -0,0 +1,147 @@
    +    $form['snack_configs']['apple']['#type'] = 'details';
    +    $form['snack_configs']['apple']['#title'] = 'Configure Apple';
    +    $form['snack_configs']['apple']['#open'] = TRUE;
    +    $form['snack_configs']['pear']['#type'] = 'details';
    +    $form['snack_configs']['pear']['#title'] = 'Configure pear';
    +    $form['snack_configs']['pear']['#open'] = TRUE;
    +    $form['snack_configs']['potato']['#type'] = 'details';
    +    $form['snack_configs']['potato']['#title'] = 'Configure potato';
    +    $form['snack_configs']['potato']['#open'] = TRUE;
    

    🤔 Can't see why we need this for the test.

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
13.6 KB
8 KB

Okay, this should address all of the feedback. Let's see if this flies.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -247,7 +245,7 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
     // Verify that the machine name is unique.
-    if ($element['#default_value'] !== $element['#value']) {
+    if (isset($element['#value'])) {
       $function = $element['#machine_name']['exists'];
       if (call_user_func($function, $element['#value'], $element, $form_state)) {
         $form_state->setError($element, t('The machine-readable name is already in use. It must be unique.'));

This won't work. Forms that are editing something with a machine name will always fail the existing check with this change. That's why the default value check is there.

alexpott’s picture

FileSize
11.69 KB

@tim.plunkett asked for an interdiff back to #80

The last submitted patch, 85: 2557299-85.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

I wish this were solvable in a more holistic way (aka rewriting how exists callbacks work) but that is out of scope.
Thanks for the updated approach and test, @alexpott

mahtab_alam’s picture

alexpott’s picture

@mahtab_alam what's the purpose of #90? It's missing the new files (all the tests).

phenaproxima’s picture

I can't "officially" RTBC this since I worked on it recently, but +1 for RTBC of #86.

alexpott’s picture

So #90 is just #86 without the new files so I'm removing @mahtab_alam from the issue credit as #90 doesn't seem to change anything and there's no comment explaining the purpose.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
2.52 KB

I wonder if there's a better approach available. The problem is being caused by the fact that the default value is assumed to the initial value the form element was created with. This is not true for Ajax requests where the value before the submission becomes the new default value. So we can store the initial values and check that instead. That makes the whole thing a bit less opaque at least to me.

If @tim.plunkett as Forms API maintainer feels this is a bad path to pursue then we can revert to #86.

alexpott’s picture

Status: Needs review » Needs work

The last submitted patch, 95: 2557299-94.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
2.45 KB
16.82 KB

Fixing the tests. Can't use $element['#id'] because that changes on AJAX submission doh.

Status: Needs review » Needs work

The last submitted patch, 97: 2557299-97.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
871 bytes
16.79 KB

Whoops.

alexpott’s picture

#2981332: Media type form and Filter format form set machine name #default_value incorrectly for new entities shows yet another solution that doesn't rely on state at all but fixes the default value setting.

alexpott’s picture

Priority: Major » Critical
Issue summary: View changes

I've re-written the issue summary to better explain what the problem is and its scale. I think due to the scale in core and probably contrib this issue is critical. People shouldn't be able WSOD their Drupal install via regular operation.

alexpott’s picture

Issue summary: View changes
alexpott’s picture

The steps to reproduce in the issue summary can be adapted for the responsive image case:

  1. Install responsive_image
  2. Go to admin/config/media/responsive-image-style/add
  3. Set the label/machine name to narrow
  4. Choose the Breakpoint group
  5. Press save
  6. See WSOD with The website encountered an unexpected error. Please try again later.</br></br><em class="placeholder">Drupal\Core\Entity\EntityStorageException</em>: &#039;responsive_image_style&#039; entity with ID &#039;narrow&#039; already exists.
alexpott’s picture

+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -150,6 +150,17 @@ public static function processMachineName(&$element, FormStateInterface $form_st
+      $initial_values[$element['#name']] = $element['#default_value'];

I don't think $element['#name'] is a good key. It's possible that if multiple forms for the same type of entity are on the same page this might cause a problem. Therefore changing this to use $element['#parents'].

alexpott’s picture

Improved the documentation.

alexpott’s picture

Spent a little bit more time thinking about this. For me the real issue is that \Drupal\Core\Entity\EntityForm::afterBuild() results in an entity being set with invalid values because it occurs before form validation. I'm not sure this is the best behaviour but changing it feels very very difficult because the whole purpose is so that AJAX submissions get a updated $this->entity to work with. Anyhow, I still think #105 is the best solution.

martin107’s picture

I still think #105 is the best solution.

Yep, I think that is correct.... at least #105 is a well through out solution.

So while looking over the code I saw just one a minor issue.

For the new test code I have removed a few calls to t().

From https://www.drupal.org/docs/8/phpunit/phpunit-browser-test-tutorial

When to use t() in browser tests
Never! Nope, not in assertion messages, not for button labels, not for text you assert on the page. You always want to test the literal string on the page, you don't want to test the Drupal translation system.

phenaproxima’s picture

+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -150,6 +151,18 @@ public static function processMachineName(&$element, FormStateInterface $form_st
+    $element_path = implode('][', $element['#parents']);

One thing that worries me about using $element['#parents'] as a key is that additional process callbacks later on might change it, which if I understand things correctly could trigger this bug again. I'm not sure how much of an edge case that is, but it's worth mentioning here. Perhaps this should be set in an after_build callback, or maybe #array_parents should be used instead of #parents? Or maybe we should generate an arbitrary hash to identify the element in form state? Or should we not even worry about this?

alexpott’s picture

Yeah I had a similar fear with using #name too. At least #parents allows fields with the same #name in the same form with different parents. But yeah maybe #array_parents would be better because that should not change.

alexpott’s picture

FileSize
7.56 KB
17.47 KB

Moved to using #array_parents and also merged the new JS test with an existing one that is fit for the same purpose.

seanB’s picture

Status: Needs review » Reviewed & tested by the community

Manually tried to break it but it seemed to work as expected for several forms (node types/media types/text formats).
The tests look good, obsolete media code is removed and all feedback is addressed. I think it's done!

tim.plunkett’s picture

What's wrong with #name? I see that discussed in #104, but I was fairly certain we now have separate form_states per form, so it wouldn't matter how many forms are on a page...

alexpott’s picture

@tim.plunkett I think using #array_parents would allow someone to have two machine names with the same #name on the same form where they are using #tree to so they don't overwrite each others form values.

alexpott’s picture

I've tried testing #113 but there are problems with forms like

    $form['name'] = [
      '#type' => 'textfield',
    ];
    $form['tree'] = [
      '#type' => 'container',
      '#tree' => TRUE,
    ];<code>

$form['tree']['name'] = [
'#type' => 'textfield',
'#default_value' => 'blah',
];

That seem to be well beyond the scope of this issue. The default value from $form['tree']['name'] is copied to $form['name'] ?!?!

So #name is probably okay as forms don't seem to work very well when there are duplicates but I'm not sure that we should rely on the fact that FAPI struggles with multiple elements with the same #name as that feels like a bug to me.

phenaproxima’s picture

The element #name, as I understand is, is the value that will appear in the HTML as <input name="...">. I'm pretty sure those can't be duplicates anyway, because otherwise the values submitted to the server would be fubar'ed. So #name should, in theory, work fine (I'd like a Form API expert to validate that statement, though). I also feel like #array_parents is also pretty safe Drupal-specific unique identifier, though, I don't feel terribly strongly about changing it to #name.

alexpott’s picture

@tim.plunkett nothing is wrong with #name! I've overcomplicated it. I've just learnt when you #tree the parents get added to the name automatically so it's perfect for our use.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 116: 2557299-116.patch, failed testing. View results

alexpott’s picture

Forgot about the unit test.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Glad we got that nailed down. Interdiffs look good to me, so back to RTBC once all backends are green.

tim.plunkett’s picture

Looks great, thanks for that change. Signing off as a FAPI maintainer ✅

  • catch committed cb67958 on 8.6.x
    Issue #2557299 by alexpott, Manuel Garcia, borisson_, phenaproxima,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

I think it might be worth a follow-up for EntityForm::afterBuild() - although whether it's actually fixable is a different matter. A couple of the comments in the test coverage are hard to read, but I could not think of a way to make them more readable, just a difficult thing to describe.

Wim Leers’s picture

Yay, this not only helps Media, but also the CKEditor module :)

Status: Fixed » Closed (fixed)

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