Skip to content
Snippets Groups Projects
Commit 72700914 authored by Ben Mullins's avatar Ben Mullins
Browse files

Issue #3260857 by Wim Leers, lauriii: Expand...

Issue #3260857 by Wim Leers, lauriii: Expand SourceEditingRedundantTagsConstraintValidator to also check attributes and attribute values
parent 6e7ed05e
No related branches found
No related tags found
37 merge requests!7471uncessary 5 files are moved from media-library folder to misc folder,!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4289Issue #1344552 by marcingy, Niklas Fiekas, Ravi.J, aleevas, Eduardo Morales...,!4114Issue #2707291: Disable body-level scrolling when a dialog is open as a modal,!3630Issue #2815301 by Chi, DanielVeza, kostyashupenko, smustgrave: Allow to create...,!3291Issue #3336463: Rewrite rules for gzipped CSS and JavaScript aggregates never match,!3143Issue #3313342: [PHP 8.1] Deprecated function: strpos(): Passing null to parameter #1 LayoutBuilderUiCacheContext.php on line 28,!3102Issue #3164428 by DonAtt, longwave, sahil.goyal, Anchal_gupta, alexpott: Use...,!2853#3274419 Makes BaseFieldOverride inherit the internal property from the base field.,!2719Issue #3110137: Remove Classy from core.,!2437Issue #3238257 by hooroomoo, Wim Leers: Fragment link pointing to <textarea>...,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2074Issue #2707689: NodeForm::actions() checks for delete access on new entities,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1484Exposed filters get values from URL when Ajax is on,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1254Issue #3238915: Refactor (if feasible) uses of the jQuery ready function to use VanillaJS,!1162Issue #3100350: Unable to save '/' root path alias,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!957Added throwing of InvalidPluginDefinitionException from getDefinition().,!925Issue #2339235: Remove taxonomy hard dependency on node module,!877Issue #2708101: Default value for link text is not saved,!873Issue #2875228: Site install not using batch API service,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!844Resolve #3036010 "Updaters",!712Issue #2909128: Autocomplete intermittent on Chrome Android,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -7,11 +7,11 @@
use Symfony\Component\Validator\Constraint;
/**
* For disallowing Source Editing tags that are already supported by a plugin.
* For disallowing Source Editing elements already supported by a plugin.
*
* @Constraint(
* id = "SourceEditingRedundantTags",
* label = @Translation("Source editing should only use otherwise unavailable tags", context = "Validation"),
* label = @Translation("Source editing should only use otherwise unavailable tags and attributes", context = "Validation"),
* )
*
* @internal
......@@ -19,17 +19,17 @@
class SourceEditingRedundantTagsConstraint extends Constraint {
/**
* When a Source Editing tag is added that an enabled plugin supports.
* When a Source Editing element is added that an enabled plugin supports.
*
* @var string
*/
public $enabledPluginsMessage = 'The following tag(s) are already supported by enabled plugins and should not be added to the Source Editing "Manually editable HTML tags" field: %overlapping_tags.';
public $enabledPluginsMessage = 'The following @element_type(s) are already supported by enabled plugins and should not be added to the Source Editing "Manually editable HTML tags" field: %overlapping_tags.';
/**
* When a Source Editing tag is added that a disabled plugin supports.
* When a Source Editing element is added that a disabled plugin supports.
*
* @var string
*/
public $availablePluginsMessage = 'The following tag(s) are already supported by available plugins and should not be added to the Source Editing "Manually editable HTML tags" field. Instead, enable the following plugins to support these tags: %overlapping_tags.';
public $availablePluginsMessage = 'The following @element_type(s) are already supported by available plugins and should not be added to the Source Editing "Manually editable HTML tags" field. Instead, enable the following plugins to support these @element_types: %overlapping_tags.';
}
......@@ -5,7 +5,9 @@
namespace Drupal\ckeditor5\Plugin\Validation\Constraint;
use Drupal\ckeditor5\HTMLRestrictions;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
......@@ -18,6 +20,7 @@
class SourceEditingRedundantTagsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
use PluginManagerDependentValidatorTrait;
use StringTranslationTrait;
use TextEditorObjectDependentValidatorTrait;
/**
......@@ -37,11 +40,21 @@ public function validate($value, Constraint $constraint) {
$text_editor = $this->createTextEditorObjectFromContext();
$enabled_plugins = $this->pluginManager->getEnabledDefinitions($text_editor);
$disabled_plugins = array_diff_key($this->pluginManager->getDefinitions(), $enabled_plugins);
// Only consider plugins that can be explicitly enabled by the user: plugins
// that have a toolbar item and do not have conditions. Those are the only
// plugins that are truly available for the site builder to enable without
// other consequences.
// In the future, we may choose to expand this, but it will require complex
// infrastructure to generate messages that explain which of the conditions
// are already fulfilled and which are not.
$disabled_plugins = array_filter($disabled_plugins, function (CKEditor5PluginDefinition $definition) {
return $definition->hasToolbarItems() && !$definition->hasConditions();
});
unset($enabled_plugins['ckeditor5_sourceEditing']);
// An array of tags enabled by every plugin other than Source Editing.
$enabled_plugin_tags = new HTMLRestrictions($this->pluginManager->getProvidedElements(array_keys($enabled_plugins)));
$disabled_plugin_tags = new HTMLRestrictions($this->pluginManager->getProvidedElements(array_keys($disabled_plugins)));
$enabled_plugin_tags = new HTMLRestrictions($this->pluginManager->getProvidedElements(array_keys($enabled_plugins), $text_editor, FALSE));
$disabled_plugin_tags = new HTMLRestrictions($this->pluginManager->getProvidedElements(array_keys($disabled_plugins), $text_editor, FALSE));
// The single tag for which source editing is enabled, which we are checking
// now.
......@@ -57,32 +70,76 @@ public function validate($value, Constraint $constraint) {
if (count($source_enabled_tags->getAllowedElements()) !== 1) {
return;
}
// This validation constraint currently only validates tags, not attributes;
// so if all attributes are allowed (TRUE) or some attributes are allowed
// (an array), return early. Only proceed when no attributes are allowed
// (FALSE).
// @todo Support attributes and attribute values in
// https://www.drupal.org/project/drupal/issues/3260857
$tags = array_keys($source_enabled_tags->getAllowedElements());
if ($source_enabled_tags->getAllowedElements()[reset($tags)] !== FALSE) {
return;
}
$enabled_plugin_overlap = $enabled_plugin_tags->intersect($source_enabled_tags);
$disabled_plugin_overlap = $disabled_plugin_tags->intersect($source_enabled_tags);
$disabled_plugin_overlap = $disabled_plugin_tags
// Merge the enabled plugin tags, to allow wildcards to be resolved.
->merge($enabled_plugin_tags)
// Compute the overlap.
->intersect($source_enabled_tags)
// Exclude the enabled plugin tags from the overlap; we merged these
// previously to be able to resolve wildcards.
->diff($enabled_plugin_overlap);
foreach ([$enabled_plugin_overlap, $disabled_plugin_overlap] as $overlap) {
$checking_enabled = $overlap === $enabled_plugin_overlap;
if (!$overlap->isEmpty()) {
$plugins_to_check_against = $checking_enabled ? $enabled_plugins : $disabled_plugins;
$tags_plugin_report = $this->pluginsSupplyingTagsMessage($overlap, $plugins_to_check_against);
$tags_plugin_report = $this->pluginsSupplyingTagsMessage($overlap, $plugins_to_check_against, $enabled_plugin_tags);
$message = $checking_enabled ? $constraint->enabledPluginsMessage : $constraint->availablePluginsMessage;
// Determine which element type is relevant for the violation message.
assert(count($overlap->getAllowedElements(FALSE)) === 1);
$overlap_tag = array_keys($overlap->getAllowedElements(FALSE))[0];
$element_type = self::tagHasAttributeRestrictions($overlap, $overlap_tag) && array_key_exists($overlap_tag, $enabled_plugin_tags->getAllowedElements())
? $this->t('attribute')
: $this->t('tag');
// If the entirety (so not just the tag but also the attributes, and not
// just some of the attribute values, but all of them) of the HTML
// elements being configured to be edited via the Source Editing plugin
// is supported by a CKEditor 5 plugin, complain. But if some attribute
// or some attribute value is still not yet supported, do not generate a
// violation message.
// If there is overlap, but some attribute/attribute value is still not
// supported, exit this iteration without generating a violation
// message. Essentially: when assessing a particular value
// (for example `<foo bar baz>`), only CKEditor 5 plugins providing an
// exact match (`<foo bar baz>`) or a superset (`<foo bar baz qux>`) can
// trigger a violation, not subsets (`<foo>`).
if (!$source_enabled_tags->diff($overlap)->isEmpty()) {
continue;
}
// If we reach this, it means the entirety (so not just the tag but also
// the attributes, and not just some of the attribute values, but all of
// them) of the HTML elements being configured to be edited via the
// Source Editing plugin's 'allowed_tags' configuration is supported by
// a CKEditor 5 plugin. This earns a violation.
$this->context->buildViolation($message)
->setParameter('@element_type', $element_type)
->setParameter('%overlapping_tags', $tags_plugin_report)
->addViolation();
}
}
}
/**
* Inspects whether the given tag has attribute restrictions.
*
* @param \Drupal\ckeditor5\HTMLRestrictions $r
* A set of HTML restrictions to inspect.
* @param string $tag_name
* The tag to check for attribute restrictions in $r.
*
* @return bool
* TRUE if the given tag has attribute restrictions, FALSE otherwise.
*/
private static function tagHasAttributeRestrictions(HTMLRestrictions $r, string $tag_name): bool {
$all_elements = $r->getAllowedElements(FALSE);
assert(isset($all_elements[$tag_name]));
return is_array($r->getAllowedElements(FALSE)[$tag_name]);
}
/**
* Creates a message listing plugins and the overlapping tags they provide.
*
......@@ -90,16 +147,34 @@ public function validate($value, Constraint $constraint) {
* An array of overlapping tags.
* @param \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition[] $plugin_definitions
* An array of plugin definitions where overlap was found.
* @param \Drupal\ckeditor5\HTMLRestrictions $enabled_plugin_restrictions
* The set of HTML restrictions for all already enabled CKEditor 5 plugins.
*
* @return string
* A list of plugins that provide the overlapping tags.
*/
private function pluginsSupplyingTagsMessage(HTMLRestrictions $overlap, array $plugin_definitions): string {
private function pluginsSupplyingTagsMessage(HTMLRestrictions $overlap, array $plugin_definitions, HTMLRestrictions $enabled_plugin_restrictions): string {
$message_array = [];
$message_string = '';
foreach ($plugin_definitions as $definition) {
if ($definition->hasElements()) {
$plugin_capabilities = HTMLRestrictions::fromString(implode(' ', $definition->getElements()));
// If this plugin supports wildcards, resolve them.
if (!$plugin_capabilities->getWildcardSubset()->isEmpty()) {
$plugin_capabilities = $plugin_capabilities
// Resolve wildcards.
->merge($enabled_plugin_restrictions)
->diff($enabled_plugin_restrictions);
}
// Skip plugins that provide a subset, only mention the plugin that
// actually provides the overlap.
// For example: avoid listing the image alignment/captioning plugins
// when matching `<img src>`; only lists the main image plugin.
if (!$overlap->diff($plugin_capabilities)->isEmpty()) {
continue;
}
foreach ($plugin_capabilities->intersect($overlap)->toCKEditor5ElementsArray() as $element) {
$message_array[(string) $definition->label()][] = $element;
}
......
......@@ -334,11 +334,13 @@ public function test(string $format_id, array $filters_to_drop, array $expected_
}
// The resulting Editor config entity should be valid.
$typed_config = $this->typedConfig->createFromNameAndData(
$updated_text_editor->getConfigDependencyName(),
$updated_text_editor->toArray(),
);
$this->assertCount(0, $typed_config->validate());
$violations = $this->validatePairToViolationsArray($updated_text_editor, $text_format, FALSE);
// At this point, the fundamental compatibility errors do not matter, they
// have been checked above; whatever remains is expected.
if (isset($violations[''])) {
unset($violations['']);
}
$this->assertSame([], $violations);
// If the text format has HTML restrictions, ensure that a strict superset
// is allowed after switching to CKEditor 5.
......
......@@ -647,6 +647,7 @@ public function providerPair(): array {
'heading',
'bold',
'italic',
'link',
'sourceEditing',
'textPartLanguage',
],
......@@ -666,8 +667,25 @@ public function providerPair(): array {
],
'ckeditor5_sourceEditing' => [
'allowed_tags' => [
// Tag-only; supported by enabled plugin.
'<strong>',
// Tag-only; supported by disabled plugin.
'<table>',
// Tag-only; supported by no plugin.
'<exotic>',
// Tag + attributes; all supported by enabled plugin.
'<span lang>',
// Tag + attributes; all supported by an ineligible disabled
// plugin (has no toolbar item, has conditions).
'<img src>',
// Tag + attributes; all supported by disabled plugin.
'<code class="language-*">',
// Tag + attributes; tag already supported by enabled plugin,
// attributes supported by disabled plugin
'<h2 class="text-align-center">',
// Tag + attributes; tag already supported by enabled plugin,
// attribute not supported by no plugin.
'<a hreflang>',
],
],
],
......@@ -679,6 +697,9 @@ public function providerPair(): array {
'violations' => [
'settings.plugins.ckeditor5_sourceEditing.allowed_tags.0' => 'The following tag(s) are already supported by enabled plugins and should not be added to the Source Editing "Manually editable HTML tags" field: <em class="placeholder">Bold (&lt;strong&gt;)</em>.',
'settings.plugins.ckeditor5_sourceEditing.allowed_tags.1' => 'The following tag(s) are already supported by available plugins and should not be added to the Source Editing "Manually editable HTML tags" field. Instead, enable the following plugins to support these tags: <em class="placeholder">Table (&lt;table&gt;)</em>.',
'settings.plugins.ckeditor5_sourceEditing.allowed_tags.3' => 'The following attribute(s) are already supported by enabled plugins and should not be added to the Source Editing "Manually editable HTML tags" field: <em class="placeholder">Language (&lt;span lang&gt;)</em>.',
'settings.plugins.ckeditor5_sourceEditing.allowed_tags.5' => 'The following tag(s) are already supported by available plugins and should not be added to the Source Editing "Manually editable HTML tags" field. Instead, enable the following plugins to support these tags: <em class="placeholder">Code Block (&lt;code class=&quot;language-*&quot;&gt;)</em>.',
'settings.plugins.ckeditor5_sourceEditing.allowed_tags.6' => 'The following attribute(s) are already supported by available plugins and should not be added to the Source Editing "Manually editable HTML tags" field. Instead, enable the following plugins to support these attributes: <em class="placeholder">Alignment (&lt;h2 class=&quot;text-align-center&quot;&gt;), Align center (&lt;h2 class=&quot;text-align-center&quot;&gt;)</em>.',
],
];
$data['INVALID some invalid Source Editable tags provided by plugin and another available in a not enabled plugin'] = [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment