Problem/Motivation

On a fresh installation when someone accidentally switches the text editor from CKEditor to None and then back to CKEditor an unexpected error occurs.

Drupal\Core\Entity\EntityStorageException: 'editor' entity with ID 'basic_html' already exists. in Drupal\Core\Entity\EntityStorageBase->doPreSave() (line 430 of /home/r0kr9/www/core/lib/Drupal/Core/Entity/EntityStorageBase.php).

Steps to reproduce:

  1. Install Drupal
  2. Head to admin/config/content/formats/manage/basic_html
  3. Switch Text Editor to None
  4. Switch Text Editor back to CKEditor
  5. Save configuration

Ckeditor error

Proposed resolution

Fix it

Remaining tasks

Do it

User interface changes

None

API changes

None

Data model changes

None

CommentFileSizeAuthor
#53 2701393-53.patch3.2 KBoknate
#53 2701393-interdiff--48-53.txt595 bytesoknate
#48 2701393-48.patch3.19 KBoknate
#48 2701393-48--FAIL.patch2.18 KBoknate
#45 2701393-45.patch6.33 KBsavkaviktor16@gmail.com
#39 2701393-23_110x.patch6.32 KBtedbow
#39 2701393-34_110x.patch6.54 KBtedbow
#39 2701393-34_60x_try3.patch6.54 KBtedbow
#37 2701393-23_60x_try2.patch6.33 KBtedbow
#37 2701393-34_60x_try2.patch6.53 KBtedbow
#34 2701393-34_60x.patch6.53 KBtedbow
#34 2701393-23_60x.patch6.32 KBtedbow
#34 2701393-34.patch5.97 KBtedbow
#23 2701393-editor-22.patch5.76 KBtim.plunkett
#23 2701393-editor-22-interdiff.txt2.15 KBtim.plunkett
#18 2701393-editor-18.patch5.35 KBtim.plunkett
#18 2701393-editor-18-interdiff.txt1.61 KBtim.plunkett
#14 2701393-editor-14-PASS.patch5.07 KBtim.plunkett
#14 2701393-editor-14-FAIL.patch4.51 KBtim.plunkett
#14 2701393-editor-14-fix-do-not-test.patch5.07 KBtim.plunkett
#12 2701393-editor-12.patch895 bytestim.plunkett
#4 2701393-4-do-not-test.patch1.04 KBWim Leers
H1sIs2W2lw.gif366.22 KBthpoul
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

thpoul created an issue. See original summary.

Wim Leers’s picture

Interesting find! Will look into this as soon as I find the time.

Wim Leers’s picture

Assigned: Unassigned » Wim Leers

Reproduced.

Wim Leers’s picture

Title: EntityStorageException when trying to save a predefined text format on clean installation » Form API-induced madness: EntityStorageException when trying to save a predefined text format on clean installation
Assigned: Wim Leers » Unassigned
Priority: Major » Normal
FileSize
1.04 KB

I was fairly confident that this is a direct consequence of #2502785: Remove support for $form_state->setCached() for GET requests. But turns out it's not.

Now, I don't see why we would even need to use form state in this form. But if I try to remove it, then neither editor_form_filter_admin_format_validate() nor editor_form_filter_admin_format_editor_configure() are called anymore… which makes absolutely zero sense. (See attached patch.)

The reason this problem exists, is the atrocious DX of Form API, particularly when using AJAX.

I literally don't understand. I spent an hour debugging this, and I understand less of Form API now than before I started. I also don't want to spend days debugging every single detail of Form API to figure out what crazy edge case is causing this behavior.

swentel’s picture

Wim Leers’s picture

Thanks swentel!

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.

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.

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.

Wim Leers’s picture

Component: ckeditor.module » forms system

Let's get feedback.

tim.plunkett’s picture

Version: 8.4.x-dev » 8.5.x-dev
Component: forms system » editor.module
Status: Active » Needs review
Issue tags: +Needs tests
FileSize
895 bytes

Here's the flow as I see it.

1)
Go to /admin/config/content/formats/manage/basic_html
Initially, $form_state->get('editor') === NULL
editor_form_filter_format_form_alter() runs, specifically line 113
Now, $form_state->get('editor') exists, and $form_state->get('editor')->isNew() === FALSE, since it is the editor from config

2)
Switch "Text editor" select to "None"
$form_state->get('editor')->isNew() === FALSE, since it is the editor from config
editor_form_filter_format_form_alter() runs again, also line 113
Initially, $form_state->get('editor') is the entity from config, so isNew() === FALSE
editor_form_filter_admin_format_editor_configure() runs, as it is the submit handler associated with the hidden 'Configure' button, which is set as the #ajax][trigger_as element for the "Text editor" select element
The value in form state for the editor is '', signifying "None", as was selected.
So line 192 runs, $form_state->set('editor', FALSE)

3)
Switch "Text editor" select back to "CKEditor"
editor_form_filter_format_form_alter() runs, but because it checks for === NULL and the value is FALSE, nothing happens there.
editor_form_filter_admin_format_editor_configure() runs, and it moves into the conditional on line 195, which then creates a new Editor entity, for which ->isNew() returns TRUE

4)
Click "Save configuration"
editor_form_filter_admin_format_submit() runs, retrieves the editor, and saves it
The check in EntityStorageBase on 424, if ($id_exists && $entity->isNew()) {, is TRUE
The exception is thrown


To sum up: the form_alter is run every time you do anything.
Because the select element is set to trigger the hidden "Configure" button, it runs on change
And the Configure button's #submit runs even when AJAXified.

This is not a behavioral change from D7 or anything. IMO, the logic around the === '', === NULL, and setting to FALSE is very confusing.

My proposal would be to simplify this check, and always load from storage when there is no entity yet.
I manually tested for both edit and add forms.
Needs automated tests still.

Status: Needs review » Needs work

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

tim.plunkett’s picture

Title: Form API-induced madness: EntityStorageException when trying to save a predefined text format on clean installation » EntityStorageException when trying to save a predefined text format on clean installation
Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
5.07 KB
4.51 KB
5.07 KB

Here's some tests, and a more accurate fix.

Had to switch the #empty_value from '' to '_none' to get Mink to find the option. See also #2448545: Convert '_none' option to a constant, deprecate form_select_options(), deprecate form_get_options() for removal, move form_select_options() to a new class.

The last submitted patch, 14: 2701393-editor-14-FAIL.patch, failed testing. View results

tim.plunkett’s picture

Hmm, my patches got mixed up. Slight difference between the do-not-test and the PASS, I prefer the one in do-not-test.
(That patches intended to be one showing the changes without the _none portion, but I guess I got my diffs mixed up)

Wim Leers’s picture

OMG, thanks for the super fast feedback, and the obviously superb research in #12 that fully got to the bottom of this! ❤️❤️ 👏

I prefer the one in do-not-test.

I do too. Mind reuploading that one?

+++ b/core/modules/editor/tests/src/FunctionalJavascript/EditorFilterFormConfigurationTest.php
@@ -0,0 +1,54 @@
+ * @todo.

Suggested: Tests Text Editor's integration with the Text Format configuration form.

I think this can then be RTBC :)

tim.plunkett’s picture

Title: EntityStorageException when trying to save a predefined text format on clean installation » Switching between editors on the format configuration causes errors upon save
FileSize
1.61 KB
5.35 KB

Added those docs, and filled in others.

mahalingam_cs’s picture

Applied the patch from #18 and it worked perfect. Have tested just the behavior/Functionality and NOT checked the code.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

👍

xjm’s picture

Status: Reviewed & tested by the community » Needs work

Thanks!

+++ b/core/modules/editor/editor.module
@@ -185,14 +186,20 @@ function editor_form_filter_format_form_alter(&$form, FormStateInterface $form_s
+  if (!$editor && !$format->isNew()) {
+    $editor = editor_load($format->id());
+    $form_state->set('editor', $editor);

This could probably use an inline comment explaining why/when it's necessary.

tim.plunkett’s picture

Status: Needs work » Needs review

While writing the docs I made a single cleanup to the code.
Also, removed an accidental addition to the test.

tim.plunkett’s picture

Attaching the patch helps

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

  • xjm committed 31d6b58 on 8.5.x
    Issue #2701393 by tim.plunkett, Wim Leers, thpoul, xjm: Switching...
xjm’s picture

Version: 8.5.x-dev » 8.4.x-dev
Priority: Normal » Major
Status: Reviewed & tested by the community » Fixed
Issue tags: +Triaged core major

Triggering a fatal through the UI is a major, so promoting to major. This is quite the fun Easter egg for the site builder. :P

I also manually tested to confirm that the WSOD is resolved and that there don't seem to be any other side effects.

This also seems backportable; changing the internal form state/values seems very minor in comparison to the bug it's fixing, and I confirmed that it works fine with no cache clear once I apply the patch, whether the editor was already set to "None" or not.

Committed and pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!

  • xjm committed ac93a4c on 8.4.x
    Issue #2701393 by tim.plunkett, Wim Leers, thpoul, xjm: Switching...
tedbow’s picture

This test seemed to have failed randomly here: https://www.drupal.org/pift-ci-job/794008

  • xjm committed 0e83b9d on 8.4.x
    Revert "Issue #2701393 by tim.plunkett, Wim Leers, thpoul, xjm:...
xjm’s picture

Status: Fixed » Needs work

Yep, this one is a different random fail from https://www.drupal.org/pift-ci-job/794678, also in the added coverage for this issue. So reverted this one too.

#2912399: Extend the CKEditorIntegrationTest for DrupalImage was also reverted now which caused a different failure in a different CKEditor test when committed around the same time.

  • xjm committed 58b16f4 on 8.5.x
    Revert "Issue #2701393 by tim.plunkett, Wim Leers, thpoul, xjm:...
tedbow’s picture

  1. +++ b/core/modules/editor/tests/src/FunctionalJavascript/EditorFilterFormConfigurationTest.php
    @@ -0,0 +1,59 @@
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $assert_session->elementExists('css', '#ckeditor-button-configuration');
    

    I think instead of waiting for an ajax request to complete we should just wait for the element to existi.

    $this->assertNotEmpty($this->assertSession()->waitForElement('css', '#ckeditor-button-configuration'));

  2. +++ b/core/modules/editor/tests/src/FunctionalJavascript/EditorFilterFormConfigurationTest.php
    @@ -0,0 +1,59 @@
    +    $page->pressButton('Save configuration');
    +    $assert_session->pageTextContains('The text format Plain text has been updated.');
    

    This is where the random fail happened.

    I think because there is no wait here after the button is press for the text to appear when the testbot is running too slow it will fail.

Wim Leers’s picture

#32: since you already did the analysis, would you like to roll a patch? Happy to re-RTBC.

tedbow’s picture

Here is a patch that addresses #32.2 since that is where the test failure actually happened.

I also included 2 patches that run the new test 60 times with and without my suggested changes. This is to see if we can get a random failure.

I am actually less confident in my assessment in #32. I tried to test it with the method I used here #2902191-6: Determine cause and fix random fail in \Drupal\Tests\settings_tray\FunctionalJavascript\SettingsTrayBlockFormTest::testBlocks which basically to add a usleep(250000); call to index.php which consistently caused the random test to fail but that is not happening with this test.

Maybe I am missing something or maybe the 60x patches will cause the random failure.

The last submitted patch, 34: 2701393-23_60x.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

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

tedbow’s picture

Status: Needs work » Needs review
FileSize
6.53 KB
6.33 KB

Whoops copy/paste error with run-tests.sh in the 60x patches in #34

The last submitted patch, 37: 2701393-34_60x_try2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Today is not my day, did not actually fix 2701393-34_60x_try2.patch
ironically if this happened yesterday I won't have noticed because #2784443: Move off-canvas functionality from Settings tray module into drupal.dialog.ajax library so that other modules can use it(🎉) wasn't committed and so my c/p error would have gone unnoticed.

also 2701393-23_60x_try2.patch passed so we didn't prove that the current patch in #23 will cause a random failure. uping to 110

Status: Needs review » Needs work

The last submitted patch, 39: 2701393-23_110x.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

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.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

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

Wim Leers’s picture

Issue tags: +Needs reroll

JS tests are now finally less brittle. Several new CKEditor functional JS tests were committed in the past few weeks: #2912399: Extend the CKEditorIntegrationTest for DrupalImage + #2916589: Extend the CKEditorIntegrationTest for DrupalImageCaption. Let's get this going again too!

MerryHamster’s picture

Version: 8.6.x-dev » 8.7.x-dev
savkaviktor16@gmail.com’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
6.33 KB

Re-rolled

Status: Needs review » Needs work

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

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

oknate’s picture

Status: Needs work » Needs review
FileSize
2.18 KB
3.19 KB

I found this bug and accidentally created a duplicate: #3084699: Fatal error when deselecting/reselecting an editor

I'm moving my patch over here. I'll review #45 and compare the approaches.

oknate’s picture

Here's why I think we should go with my new approach:
1) Less changes. I think we can fix this without adding a new '_none' value, which is better for BC.
2) My test is already converted over to FunctionalJavascript, so it tests Ajax submission. Other than that, it's very similar to the one added by @tim.plunkett. 👍

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

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
Related issues: +#3084699: Fatal error when deselecting/reselecting an editor

Thanks, @oknate!

Wim Leers’s picture

Status: Reviewed & tested by the community » Needs work

Oh, this needs coding standards fixes. 🤓

oknate’s picture

Status: Needs work » Needs review
FileSize
595 bytes
3.2 KB

This should kill the two coding standard warnings with one stone.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

  • webchick committed 74e9ecf on 8.8.x
    Issue #2701393 by tedbow, tim.plunkett, oknate, Wim Leers, Saviktor,...
webchick’s picture

Status: Reviewed & tested by the community » Fixed

Nice catch! Also GREAT that we have a base now from which to expand test coverage for CKEditor config!

Committed and pushed to 8.8.x. Thanks!

oknate’s picture

Thanks!

Status: Fixed » Closed (fixed)

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

patrickfgoddard’s picture

Still encountering this in d10.2.5. Same scenario as originally posted above: Editing basic_html (admin/config/content/formats/manage/basic_html), toggle "Text Editor" from ckeditor5 to none then back again. Hit submit and same error:

Drupal\Core\Entity\EntityStorageException: 'editor' entity with ID 'basic_html' already exists. in Drupal\Core\Entity\EntityStorageBase->doPreSave() (line 519 of /app/web/core/lib/Drupal/Core/Entity/EntityStorageBase.php).