Problem/Motivation

There's 3 tests fail using PHP 8.1 - confirm it locally on 8.1.0RC6 too

- Drupal\Tests\ckeditor5\Kernel\CKEditor5PluginManagerTest
- Drupal\Tests\ckeditor5\Kernel\SmartDefaultSettingsTest
- Drupal\Tests\ckeditor5\Kernel\ValidatorsTest

Steps to reproduce

See https://www.drupal.org/pift-ci-job/2235570
Automatic conversion of false to array is deprecated

See https://3v4l.org/ahgI7 for why the deprecation is being triggered.

Proposed resolution

  • Stop triggering the deprecation
  • Improve the documentation
  • Rename the method to reflect what it actually does.

Remaining tasks

Renamed a method that was only added in 9.3.x-alpha to a better name.

User interface changes

API changes

Data model changes

None

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

andypost created an issue. See original summary.

alexpott’s picture

Title: Fix CKEditor 5 tests on PHP 8.1 » Determine what to do when $elements[$tag] is FALSE in HTMLRestrictionsUtilities::providedElementsAttributesFix() (as result CKEditor 5 tests on PHP 8.1)
Status: Active » Needs review
FileSize
906 bytes

Titling the issue properly. \Drupal\ckeditor5\HTMLRestrictionsUtilities seems a super important class. The docs seem a bit thin as I have no idea if PHP 8.1 is allowing us to find a significant issue or not.

The patch attached fixes the deprecation.

I cannot get \Drupal\Tests\ckeditor5\Kernel\SmartDefaultSettingsTest to pass locally regardless of PHP version because...

1) Drupal\Tests\ckeditor5\Kernel\SmartDefaultSettingsTest::test with data set "restricted_html can be switched to CKEditor 5 after dropping the two markup-creating filters (3 upgrade messages)" ('restricted_html', array(true, true), array(array(array('heading', 'bold', 'italic', '|', 'link', 'blockQuote', 'code', 'bulletedList', 'numberedList', 'sourceEditing')), array(array(array('heading2', 'heading3', 'heading4', 'heading5', 'heading6')), array(array('<cite>', '<dl>', '<dt>', '<dd>', '<a hreflang>', '<blockquote cite>', '<ul type>', '<ol start type>', '<h2 id>', '<h3 id>', '<h4 id>', '<h5 id>', '<h6 id>')))), '<br> <p>', array(array('CKEditor 5 only works with HT...ymore.', 'CKEditor 5 only works with HT...ymore.')), array('The following plugins were en...</em>.', 'The following tags were permi...d&gt;.', 'This format's HTML filters in...d&gt;.'), array('CKEditor 5 needs at least the...ilter.'))
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
 Array &0 (
     '' => Array &1 (
-        0 => 'CKEditor 5 only works with HTML-based text formats. The "<em class="placeholder">Convert URLs into links</em>" (<em class="placeholder">filter_url</em>) filter implies this text format is not HTML anymore.'
-        1 => 'CKEditor 5 only works with HTML-based text formats. The "<em class="placeholder">Convert line breaks into HTML (i.e. &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;)</em>" (<em class="placeholder">filter_autop</em>) filter implies this text format is not HTML anymore.'
+        0 => 'CKEditor 5 only works with HTML-based text formats. The "<em class="placeholder">Convert line breaks into HTML (i.e. &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;)</em>" (<em class="placeholder">filter_autop</em>) filter implies this text format is not HTML anymore.'
+        1 => 'CKEditor 5 only works with HTML-based text formats. The "<em class="placeholder">Convert URLs into links</em>" (<em class="placeholder">filter_url</em>) filter implies this text format is not HTML anymore.'
     )
 )

The array order is swapped.

Also in Drupal\Tests\ckeditor5\Kernel\ValidatorsTest the line $restricted_html_format_filters = Yaml::parseFile('profiles/standard/config/install/filter.format.restricted_html.yml')['filters']; causes problems for local tests. It assumes that the tests are being run in from /core which is incorrect. If I fix this line to determine the path relative to the test then the tests fails for the same array ordering reasons above.

We need to fix this brittle-ness. Will create an issue for making these test able to run locally.

alexpott’s picture

andypost’s picture

+++ b/core/modules/ckeditor5/src/HTMLRestrictionsUtilities.php
@@ -152,6 +152,12 @@ public static function cleanAllowedHtmlArray(array $elements): array {
+      // @todo it is not at all clear here if we should set to an empty array or
+      //   simply return.

there's few people been involved there
https://git.drupalcode.org/project/ckeditor5/-/blame/1.0.x/src/HTMLRestr...

andypost’s picture

Status: Needs review » Needs work
+++ b/core/modules/ckeditor5/src/HTMLRestrictionsUtilities.php
@@ -152,6 +152,12 @@ public static function cleanAllowedHtmlArray(array $elements): array {
   public static function providedElementsAttributes(array &$elements, string $tag, string $attribute, $value): void {
     $attribute_already_allows_all = isset($elements[$tag][$attribute]) && $elements[$tag][$attribute] === TRUE;
...
+    if (isset($elements[$tag]) && $elements[$tag] === FALSE) {

I can't get how FALSE could appear here, looks it needs assert() here to be able to catch which tag's settings caused it

maybe it's related to upgrade path

andypost’s picture

btw needs to ping @mixologic to update CI images to latest RC6 as PHP 8.1.0RC5 used in tests

alexpott’s picture

@andypost this get's set to FALSE in \Drupal\ckeditor5\HTMLRestrictionsUtilities::allowedElementsStringToHtmlFilterArray() specifically the line that does:

          $elements[$tag] = FALSE;

It has nothing to do with upgrade paths.

andypost’s picture

Used to debug it and it's clear that FALSE means that no attributes allowed for tag also looking at data structures it could be optimized - no reason to use keyed array as values always TRUE

    if (isset($elements[$tag]) && $elements[$tag] === FALSE) {
      file_put_contents('log.log', var_export(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), TRUE), FILE_APPEND);
      file_put_contents('log.log', var_export($tag, TRUE), FILE_APPEND);
      file_put_contents('log.log', var_export($elements, TRUE), FILE_APPEND);
    }
'h2'
array (
  'p' => false,
  'h2' => false,
  'h3' => false,
  'h4' => false,
  'h5' => false,
  'h6' => false,
  '$block' => 
  array (
    'class' => 
    array (
      'text-align-left' => true,
      'text-align-center' => true,
      'text-align-right' => true,
      'text-align-justify' => true,
    ),
  ),
)
andypost’s picture

Status: Needs work » Needs review
FileSize
588 bytes

I'm sure it should always set the value as tests expect it so just changed default value

andypost’s picture

Status: Needs review » Needs work

The last submitted patch, 9: 3249240-9.patch, failed testing. View results

alexpott’s picture

I think I agree that #9 is the correct fix. But then we need to follow on from this and change\Drupal\ckeditor5\HTMLRestrictionsUtilities::allowedElementsStringToHtmlSupportConfig from

      assert($attributes === FALSE || is_array($attributes));
      if (is_array($attributes)) {
        foreach ($attributes as $name => $value) {
          assert($value === TRUE || Inspector::assertAllStrings($value));
          $to_allow['attributes'][$name] = $value;
        }
      }

to

      assert(is_array($attributes));
      foreach ($attributes as $name => $value) {
        assert($value === TRUE || Inspector::assertAllStrings($value));
        $to_allow['attributes'][$name] = $value;
      }

I think we might be able to lose the assert(is_array($attributes)); because we're going to foreach on it anyway.

And \Drupal\ckeditor5\HTMLRestrictionsUtilities::cleanAllowedHtmlArray() can also be cleaned up to be just

  public static function cleanAllowedHtmlArray(array $elements): array {
    // When recursively merging elements arrays, unkeyed boolean values can
    // appear in attribute config arrays. This removes them.
    return array_map('array_filter', $elements);
  }

Can also remove the if (is_array($attributes)) { check in \Drupal\ckeditor5\HTMLRestrictionsUtilities::toReadableElements()

alexpott’s picture

Hmmm.... the problem is the code in \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::findPluginSupportingElement() with the change in #9 this is behaving differently as an empty array is considered to be offering less broad supported than an array with some elements. If this was a FALSE then it'd be considered to broader.

This is the key part of the code:

        // If the selected plugin and the plugin being checked both have arrays
        // for $tag configuration, they both have attribute configuration. Check
        // which attribute configuration is more permissive.
        if ($selected_plugin && is_array($selected_config) && is_array($provided_elements[$tag])) {

I think this has two consequences. The fix in #9 is not correct we need to keep the FALSE value to mean all all attributes. But also it means that the fix in #2 is wrong. We should not go from a FALSE value to an array value because that is making the config less broad.

alexpott’s picture

After running tests and mucking about with ckeditor5 I'm not convinced the solution proposed #13 #13 is correct. Discovered where this ArrayPI comes from - it's all documented in \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions() - we need to support 'tag' => FALSE and 'tag' => ['class' => TRUE]

The fix in #2 is the correct fix but I think we can improve the documentation here to make it easier to work out in the future.

alexpott’s picture

Status: Needs work » Needs review
FileSize
6.92 KB

Patched attached is the same as #2 with the following additions:

  • Improved the documentation of \Drupal\ckeditor5\HTMLRestrictionsUtilities to point to \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions() as that explains the array structure.
  • Fixed incorrect @return type in HTMLRestrictionsUtilities::toReadableElements()
  • Renamed HTMLRestrictionsUtilities::providedElementsAttributes to HTMLRestrictionsUtilities::addAllowedAttributeToElements() because that is what it does. I'm not sure what the original name was trying to say. The docs make it clear what the method does.... Adds allowed attributes to the elements array. - I've not changed them
  • Used an early return instead of a variable in the method as this is clearer and less complex.
  • Removed some premature optimisation noticed when renaming the method - there's no need to repeat the logic test for working out if tag/attribute combination is already set to allow all.
  • Fixed the reference to \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::WILDCARD_ELEMENT_METHODS - that constant does not exist there. It's \Drupal\ckeditor5\HTMLRestrictionsUtilities::WILDCARD_ELEMENT_METHODS

I think of this is in-scope because the current docs and method name make it harder to work out what is going on.

Wim Leers’s picture

I am digging into this too, but as part of reproducing this I ran into significant PHPUnit performance pains, and so I got nerdsniped into fixing those — see #3249443: drupal_phpunit_find_extension_directories() uses infinite recursion ⇒ more directories = slower tests.

alexpott’s picture

Title: Determine what to do when $elements[$tag] is FALSE in HTMLRestrictionsUtilities::providedElementsAttributesFix() (as result CKEditor 5 tests on PHP 8.1) » HTMLRestrictionsUtilities:: providedElementsAttributes() causes deprecations on PHP 8.1
Issue summary: View changes

Here's why the deprecation is occurring: https://3v4l.org/ahgI7

Fixing the issue title to reflect the fact that the current behaviour is correct we just don't want it to trigger a deprecation.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
  1. #2:
    \Drupal\ckeditor5\HTMLRestrictionsUtilities seems a super important class. The docs seem a bit thin as I have no idea if PHP 8.1 is allowing us to find a significant issue or not.

    I 💯% agree! That's why for a long time I've been advocating for #3228334: Refactor HTMLRestrictionsUtilities to a HtmlRestrictions value object to happen, with thorough unit test coverage and significantly improved docs. In fact, I already made good progress on that issue, but newly CKEditor 5 JS-related data loss criticals forced me to pushing it forward.

    (It's not yet been moved into core's ckeditor5.module component because I still need to move the MR over.)

  2. #8: the interpretation FALSE means that no attributes allowed for tag is correct. But

    looking at data structures it could be optimized

    is wrong. We can't and shouldn't change this. This structure has meaning: it's the same structure as \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions()'s return value.

  3. For that reason, #9 is incorrect, as #13 also concluded.
  4. #13: Yep, it's an ArrayPI. Well, actually … it's not. It's just a complex datastructure with a lot of semantics embedded in the structure of the array. Wait, I guess that means it is an ArrayPI? 🤓😅

    You saying that makes me want to point to #3228334: Refactor HTMLRestrictionsUtilities to a HtmlRestrictions value object once more. Reading between the lines, I suspect you agree that issue is critically important?

  5. #15: +1 to all that, and … sorry you had to suffer through it. This pain is why I've been advocating for that for months 🙈
    +++ b/core/modules/ckeditor5/src/HTMLRestrictionsUtilities.php
    @@ -141,21 +153,35 @@ public static function cleanAllowedHtmlArray(array $elements): array {
    +    if (isset($elements[$tag]) && $elements[$tag] === FALSE) {
    +      // If the tag is already allowed with no attributes then the value will be
    +      // FALSE. We need to convert the value to an empty array so that attribute
    +      // configuration can be added.
    +      $elements[$tag] = [];
    +    }
    

    This is the critical addition to avoid the PHP 8.1-only deprecation error.

larowlan’s picture

  • larowlan committed 65aaec6 on 9.4.x
    Issue #3249240 by alexpott, andypost, Wim Leers:...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

The change looks good and makes sense to me. Nice work with all the documentation additions @alexpott.

Looking forward to seeing how #3228334: Refactor HTMLRestrictionsUtilities to a HtmlRestrictions value object ends up - plus one for a value object.

Committed 65aaec6 and pushed to 9.4.x.

Backported to 9.3.x

  • larowlan committed 4657f66 on 9.3.x
    Issue #3249240 by alexpott, andypost, Wim Leers:...

Status: Fixed » Closed (fixed)

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