Problem/Motivation
When two different wildcard tags resolve to the same concrete tag, HTMLRestrictions::resolveWildcards() keeps only the attributes from the last one. The naive resolution assigns each wildcard's attributes instead of merging them:
foreach ($wildcard_tags as $wildcard_tag) {
if (isset($r->elements[$wildcard_tag])) {
$naively_resolved_wildcard_elements[$wildcard_tag] = $tag_config;
}
}
So the result depends on the order of the wildcards:
<p> <$text-container class="foo"> <$any-html5-element id>resolves to<p id>.<p> <$any-html5-element id> <$text-container class="foo">resolves to<p class="foo">.
The expected result in both cases is <p class="foo" id>.
This has a real impact. The Alignment plugin declares <$text-container class="text-align-left text-align-center text-align-right text-align-justify">. The contrib module CKEditor5 ID Attributes declares <$any-html5-element id>. With both enabled, the elements CKEditor 5 requires for "Limit allowed HTML tags and correct faulty HTML" no longer include the text-align-* classes. Saving the text format removes them from the allowed tags, and filter_html then strips alignment from existing content. This was reported against the contrib module in #3528997, but the bug is in core.
Steps to reproduce
use Drupal\ckeditor5\HTMLRestrictions;
$r = HTMLRestrictions::fromString('<p> <$text-container class="foo"> <$any-html5-element id>');
print $r->toFilterHtmlAllowedTagsString();
// Prints "<p id>". Expected "<p class="foo" id>".
Or, through the UI:
- Install the CKEditor5 ID Attributes module.
- Edit a text format that uses CKEditor 5 and "Limit allowed HTML tags and correct faulty HTML".
- Add the Text alignment and ID Attributes buttons to the toolbar, and save.
- The "Allowed HTML tags" no longer include
class="text-align-…"on<p>or<h2>–<h6>.
Proposed resolution
In resolveWildcards(), merge the attributes from each wildcard instead of assigning them:
$naive_resolution = self::emptySet();
foreach ($r->elements as $tag_name => $tag_config) {
if (self::isWildcardTag($tag_name)) {
$wildcard_tags = self::getWildcardTags($tag_name);
foreach ($wildcard_tags as $wildcard_tag) {
if (isset($r->elements[$wildcard_tag])) {
$naive_resolution = $naive_resolution->merge(new self([$wildcard_tag => $tag_config]));
}
}
}
}
Tested outside core (on 10.6.18):
<p> <$text-container class="foo"> <$any-html5-element id>resolves to<p class="foo" id>, in either order.<p class="bar"> <$text-container class="foo"> <$any-html5-element id>resolves to<p class="bar foo" id>.
Remaining tasks
- Merge request with the fix.
- Add test cases to
HTMLRestrictionsTestcovering two different wildcards on the same tag, in both orders. - Review.
User interface changes
None.
Introduced terminology
None.
API changes
None. resolveWildcards() is private.
Data model changes
None.
Release notes snippet
Not needed.
Comments
Comment #2
benjifisherThe analysis and issue description were written by Claude.
I am assigning this issue to myself so that I can validate the analysis and the steps to reproduce before I ask anyone else to look at this issue.