Problem/Motivation

The Editor entity's settings contains per-text editor plugin settings.

CKEditor 5 allows two keys:

  • toolbar — order is meaningful here
  • plugins — order is not meaningful here

Because plugins' order is not meaningful, it can often be a pain to write tests for expectations around this. And it means config export orders can change around, leading to meaningless diffs.

Proposed resolution

Follow the precent set by \Drupal\user\Entity\Role::preSave() (in #2409129: Enforce order of permissions in config export), \Drupal\filter\Entity\FilterFormat::preSave(), etc. — and introduced by #2852557: Config export key order is not predictable, use config schema to order keys for maps.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

This will need a post-update hook for CKEditor 5. That means it'll need different test coverage for <10.0 and >=10.0, due to #3306545: Replace ckeditor with ckeditor5 in the 9.4.x database dumps in Drupal 10.0.x & 10.1.x.

wim leers’s picture

This will need a post-update hook for CKEditor 5. Last time we did that was in #3222756: Allow using images from external source.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new3.17 KB
wim leers’s picture

StatusFileSize
new2.21 KB
new5 KB

Fix.

wim leers’s picture

The last submitted patch, 4: 3314770-4-test-only-FAIL.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 5: 3314770-5.patch, failed testing. View results

wim leers’s picture

Hm … that failed … without an error? 🙈

	Unknown
fail: [run-tests.sh check] Line 0 of :
FATAL Drupal\Tests\ckeditor5\Functional\Update\CKEditor5UpdatePluginSettingsSortTest: test runner returned a non-zero error code (2).

😬 This makes no sense!

wim leers’s picture

Priority: Normal » Major
Issue tags: +Contributed project soft blocker
Related issues: +#3232052: Drupal 10 & CKEditor 5 readiness

But now that the config export order is consistent, we should also make our tests consistent. That will also help upgrade paths being written for the CKEditor 4 → 5 upgrade path — #3232052: Drupal 10 & CKEditor 5 readiness is the first to run into this!

Bumping to Major because of the ecosystem impact.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new607 bytes
new5.51 KB

Implements #10.

This should trigger failures in CKEditor 5's test suite. Failures that will force us to make things consistent, hence paving the path for contributed modules writing upgrade path tests 👍

Status: Needs review » Needs work

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

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.58 KB
new8.08 KB

Update expectations in SmartDefaultSettingsTest.

wim leers’s picture

StatusFileSize
new784 bytes
new8.08 KB

Maybe this explains the inexplicable failure I mentioned in #9, and which has been happening consistently? 🙈

The last submitted patch, 13: 3314770-13.patch, failed testing. View results

wim leers’s picture

StatusFileSize
new550 bytes
new7.55 KB

Yep, that was it!

Time to run all tests.

Status: Needs review » Needs work

The last submitted patch, 16: 3314770-16.patch, failed testing. View results

smustgrave’s picture

Status: Needs work » Reviewed & tested by the community

Tested locally and the tests do fail without the changes.

I want to say the failures in #16 are random. Believe something is going on with Javascript tests but I'm wrong this can be moved back.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 16: 3314770-16.patch, failed testing. View results

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new1.79 KB
new9.14 KB

#16 actually had one failure I expected: Drupal core has tests that require the default config to match the actual config export order.

Agreed the functional JS test failures are unrelated though!

(I should not have tested 9.5, I meant to test 10.0 — as explained in #2, the patch will need to be tweaked for <10 versions due to DB fixtures being different.)

Status: Needs review » Needs work

The last submitted patch, 20: 3314470-20.patch, failed testing. View results

smustgrave’s picture

Status: Needs work » Reviewed & tested by the community

So reviewing the failures I think they are random javascript ones.

bnjmnm’s picture

Status: Reviewed & tested by the community » Needs work

Doc nit!

+++ b/core/modules/ckeditor5/ckeditor5.module
@@ -656,3 +657,17 @@ function _ckeditor5_theme_css($theme = NULL): array {
+  ksort($settings['plugins']);

Could this and the other ksort get a comment acknowledging that the sorting does not impact the overall functionality, but is done for better DX + test writing?

I could see this confusing contributors who see this without having worked on this issue, with them mistakenly thinking the sort order is relevant to the functionality.

alexpott’s picture

We can enforce the order of the plugins via config schema. I.e.

    plugins:
      type: sequence
      label: 'Plugins'
      orderby: key
      sequence:
        type: ckeditor5.plugin.[%key]

This will ensure that the order is always the expected order no matter where the write comes from.

wim leers’s picture

wim leers’s picture

Title: Enforce order of CKEditor 5 plugin settings in config export » Enforce order of CKEditor 5 plugin settings in config export (as well as other sequences)
Status: Needs work » Needs review
StatusFileSize
new3.08 KB
new9.9 KB

Ironically, for one of the two uses in total in all of Drupal core of orderby, it turns out I suggested it: #2900700: Add orderby to content_moderation schema sequences. 🙈 But that's only because I had just seen the change record for it probably: https://www.drupal.org/node/2852566

Implemented #24, and audited all type: sequence occurrences in the CKEditor 5 module while I was at it.

alexpott’s picture

+++ b/core/modules/ckeditor5/src/SmartDefaultSettings.php
@@ -393,6 +393,12 @@ public function computeSmartDefaultSettings(?EditorInterface $text_editor, Filte
+    // Match the config export order for better DX. This also enables
+    // contributed modules to extend this core test.
+    $settings = $editor->getSettings();
+    ksort($settings['plugins']);
+    $editor->setSettings($settings);

I don't get the comment here - this is not a core test to extend.

wim leers’s picture

StatusFileSize
new1.53 KB
new9.79 KB

You're right — it's not in the test. 🙈 I tried to do what @bnjmnm asked but did so poorly. I think just stressing the better DX will have to do then.

I did try to not add that logic and instead call some method on the config object to get the logic in \Drupal\Core\Config\StorableConfigBase::castValue() to get executed. But that only gets executed when the config is stored. Storing the config is not necessary.

And … that made me realize I could just sacrifice some test performance to allow code to be simpler! I'll just save the generated config unnecessarily, and then orderby gets applied 🤓

Status: Needs review » Needs work

The last submitted patch, 28: 3314470-28.patch, failed testing. View results

smustgrave’s picture

Ran the tests locally and verified they do fail without the full patch.

The failure in #28 appears to be random javascript failure.

+1 for RTBC

wim leers’s picture

Status: Needs work » Needs review
wim leers’s picture

⚠️ This is now hard-blocking #3232052: Drupal 10 & CKEditor 5 readiness — other than that that module is ready to get CKEditor 5 support. Over 80,000 Drupal 9 contrib sites cannot update to CKEditor 5 until that gets committed and ships.

wim leers’s picture

Forgot to say in #32: this is no longer a soft blocker, but a hard blocker.

Especially now that editor_advanced_link is also using orderby for its type: sequence config.

bnjmnm’s picture

Status: Needs review » Needs work

There's a change to a comment I'd like to see and I'd be good to RTBC after that.

+++ b/core/modules/ckeditor5/ckeditor5.post_update.php
@@ -65,3 +65,27 @@ function ckeditor5_post_update_image_toolbar_item(&$sandbox = []) {
+    // Nothing to do if there are is only one plugin with settings, or none.

Change to:
// Nothing to do if there are fewer than two plugins with settings.

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new506 bytes
new9.78 KB

Made the change.

It was so minor so wouldn't be offended if it's not counted for credit haha

bnjmnm’s picture

Status: Needs review » Reviewed & tested by the community

This looks good then, orderby is a good way to do it, and went through the symfony docs to confirm that. While I didn't confirm if the orderby: ~ additions are needed, but I do think they're helpful as it provides context for which items need ordering vs which don't. I'm going to RTBC under with the hope that the one line doc change doesn't cause test failures somehow.

wim leers’s picture

While I didn't confirm if the orderby: ~ additions are needed, but I do think they're helpful as it provides context for which items need ordering vs which don't.

They are indeed not needed, they're the implicit default. I like things to be explicit — and by setting them explicitly here, we leave no room for doubt that this is the intended behavior: the order in which items appear in the sequence matters! 👍

catch’s picture

One ultranit, I think we can just remove the two characters on commit?

+++ b/core/modules/ckeditor5/tests/src/Kernel/SmartDefaultSettingsTest.php
@@ -497,6 +497,10 @@ public function test(string $format_id, array $filters_to_drop, array $expected_
 
+    // Save this to ensure the config export order is applied. O
+    // @see \Drupal\Core\Config\StorableConfigBase::castValue()
+    $updated_text_editor->save();

What is the O?

orderby: ~

I think this is OK, but maybe we should have a follow-up to apply it to the rest of core?

Ratan Priya’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new9.19 KB
new1.58 KB

@catch,
Made changes as per comment #38.
Needs Review.

bnjmnm’s picture

Status: Needs review » Needs work

Patch #39 includes removals that were not requiested. In #38 the orderby: ~ was accompanied by "I think this is OK" - i.e. it doesn't need to be removed.. @catch also said it was fine to remove those two stray characters on commit.

#35 is still something I'm comfortable RTBCing, provided those two characters are removed on commit, but I'm also wary of a not-the-most-recent patch being what gets committed. I'm happy to RTBC if someone either reposts #35 or an updated patch that is #35 without the two extra chars.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new721 bytes
new9.78 KB

#39: you removed all the orderby: ~ entries — those were intentional, per #37 :/

#38: Hah, nice catch! 2 excess characters indeed. A core issue for that already exists: #2855675: Add orderby key to all sequences in core.

catch’s picture

Version: 10.1.x-dev » 9.4.x-dev
Status: Reviewed & tested by the community » Needs work

Committed/pushed to 10.1.x, cherry-picked to 10.0.x and 9.5.x.

Needs a 9.4.x version if we want to commit it there.

  • catch committed 0679e65 on 10.0.x
    Issue #3314770 by Wim Leers, smustgrave, bnjmnm, alexpott: Enforce order...
  • catch committed cd4c806 on 10.1.x
    Issue #3314770 by Wim Leers, smustgrave, bnjmnm, alexpott: Enforce order...
  • catch committed 0f4b07e on 9.5.x
    Issue #3314770 by Wim Leers, smustgrave, bnjmnm, alexpott: Enforce order...
wim leers’s picture

Assigned: Unassigned » wim leers
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new3.73 KB
new10.88 KB

The difference between 9.4 and all other branches is explained in #2. Of course this won't need to change default config.

So that means the interdiff represents just the additional changes to be able to test this in 9.4.

Status: Needs review » Needs work

The last submitted patch, 45: 3314770-45-9.4.x.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review

I was able to reproduce #45's failure on PHP 8.1, but not on PHP 8.0.

But the weird thing is that I cannot for the life of me figure out what is causing this. I even added

            if ($condition['value'] == NULL) {
              var_dump($this->conditions);
              var_dump($configs);
              var_dump(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 20));
            }

… and it just does not fire! 🤯 I can't figure this out.

bnjmnm’s picture

This seems to be consistently failing on 9.5.x too.

There are differences between the drupal-9.4.0.filled.standard.php.gz on Drupal 9.5.x and 10.1.x that have me wondering how tests passed on 9.5 at all.

10.x includes an editor.editor.basic_html that includes

[settings] => Array
        (
            [toolbar] => Array(// toolbar stuff)
                

            [plugins] => Array
                (
                    [ckeditor5_heading] => Array(etc..)
                 )

On the 9.5, the same config has an empty plugins array

 [plugins] => Array
                (
                )

So the

 $this->assertNotSame($plugin_settings_before, $plugin_settings_after);

in testUpdatePluginSettingsSortPostUpdate shouldn't work since empty === empty.

I feel like this may not be the whole story unless 9.5 has been failing for the past 4 days since this was committed? It does look like there are intentional differences between the fixtures #3306545: Replace ckeditor with ckeditor5 in the 9.4.x database dumps in Drupal 10.0.x & 10.1.x. For the test to work in 9x presumably we'd need to first convert the editor to CK5?

wim leers’s picture

Status: Needs review » Patch (to be ported)

Quoting myself from #20:

(I should not have tested 9.5, I meant to test 10.0 — as explained in #2, the patch will need to be tweaked for <10 versions due to DB fixtures being different.)

So … @bnjmnm is absolutely right when he says I feel like this may not be the whole story unless 9.5 has been failing for the past 4 days since this was committed? — looks like @catch cherry-picked this to 9.5.x even though there wasn't a green test run for it yet 😅

(A mistake easily made when we are cherry-picking to 9.5.x mostly with ease, and when we are constantly updating FOUR branches…)

We should revert the https://git.drupalcode.org/project/drupal/commit/0f4b07e commit.

  • catch committed 21807c6 on 9.5.x
    Revert "Issue #3314770 by Wim Leers, smustgrave, bnjmnm, alexpott:...
catch’s picture

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

Oops. Reverted!

wim leers’s picture

Status: Patch (to be ported) » Needs review

Queued 9.5 tests for #45.

@bnjmnm said he'd take a look at #47, because I'm … stumped 😬

smustgrave’s picture

Should this go back into Needs work?

smustgrave’s picture

Status: Needs review » Needs work

For the things in #52

wim leers’s picture

So … I see that we lost track of this 😬 But 9.5.0 is on the verge of being released. So … I think this means that we just don’t commit it to 9.5.x anymore? At this point, that just means that backporting some fixes to 9.5.x will be slightly more painful, sometimes, but it's not the end of the world I think.

longwave’s picture

Version: 9.5.x-dev » 10.0.x-dev
Status: Needs work » Fixed

Agree with #55. This is too late now for 9.5.0, as a task it is ineligible for commit in a patch release, and so we will just have to live with it. A reminder that we should test patches on all branches before commit, especially majors where things might have diverged even if cherry pick is clean!

Status: Fixed » Closed (fixed)

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