Loading core/modules/ckeditor5/ckeditor5.ckeditor5.yml +12 −1 Original line number Diff line number Diff line Loading @@ -62,7 +62,7 @@ ckeditor5_heading: - <h5> - <h6> ckeditor5_htmlSupport: ckeditor5_arbitraryHtmlSupport: ckeditor5: plugins: [htmlSupport.GeneralHtmlSupport] config: Loading @@ -82,6 +82,17 @@ ckeditor5_htmlSupport: # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface::getEnabledDefinitions() conditions: [] ckeditor5_wildcardHtmlSupport: ckeditor5: plugins: [htmlSupport.GeneralHtmlSupport] drupal: label: Wildcard HTML support # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() elements: false library: core/ckeditor5.htmlSupport # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface::getEnabledDefinitions() conditions: [] ckeditor5_specialCharacters: ckeditor5: plugins: Loading core/modules/ckeditor5/src/HTMLRestrictions.php +30 −9 Original line number Diff line number Diff line Loading @@ -770,8 +770,8 @@ public function merge(HTMLRestrictions $other): HTMLRestrictions { private static function applyOperation(HTMLRestrictions $a, HTMLRestrictions $b, string $operation_method_name): HTMLRestrictions { // 1. Operation applied to wildcard tags that exist in both operands. // For example: <$block id> in both operands. $a_wildcard = self::getWildcardSubset($a); $b_wildcard = self::getWildcardSubset($b); $a_wildcard = $a->getWildcardSubset(); $b_wildcard = $b->getWildcardSubset(); $wildcard_op_result = $a_wildcard->$operation_method_name($b_wildcard); // Early return if both operands contain only wildcard tags. Loading Loading @@ -828,14 +828,23 @@ private static function getRegExForWildCardAttributeName(string $wildcard_attrib /** * Gets the subset of allowed elements whose tags are wildcards. * * @param \Drupal\ckeditor5\HTMLRestrictions $r * A set of HTML restrictions. * @return \Drupal\ckeditor5\HTMLRestrictions * The subset of the given set of HTML restrictions. */ public function getWildcardSubset(): HTMLRestrictions { return new self(array_filter($this->elements, [__CLASS__, 'isWildcardTag'], ARRAY_FILTER_USE_KEY)); } /** * Gets the subset of allowed elements whose tags are concrete. * * @return \Drupal\ckeditor5\HTMLRestrictions * The subset of the given set of HTML restrictions. */ private static function getWildcardSubset(HTMLRestrictions $r): HTMLRestrictions { return new self(array_filter($r->elements, [__CLASS__, 'isWildcardTag'], ARRAY_FILTER_USE_KEY)); public function getConcreteSubset(): HTMLRestrictions { return new self(array_filter($this->elements, function (string $tag_name) { return !self::isWildcardTag($tag_name); }, ARRAY_FILTER_USE_KEY)); } /** Loading Loading @@ -899,7 +908,7 @@ private static function resolveWildcards(HTMLRestrictions $r): HTMLRestrictions // - then $naive will be `<p class="foo">` // - merging them yields `<p class> <$block class="foo">` again // - diffing the wildcard subsets yields just `<p class>` return $r->merge($naive_resolution)->doDiff(self::getWildcardSubset($r)); return $r->merge($naive_resolution)->doDiff($r->getWildcardSubset()); } /** Loading Loading @@ -964,7 +973,10 @@ public function toCKEditor5ElementsArray(): array { * @see \Drupal\filter\Plugin\Filter\FilterHtml */ public function toFilterHtmlAllowedTagsString(): string { return implode(' ', $this->toCKEditor5ElementsArray()); // Resolve wildcard tags, because Drupal's filter_html filter plugin does // not support those. $concrete = self::resolveWildcards($this); return implode(' ', $concrete->toCKEditor5ElementsArray()); } /** Loading @@ -979,7 +991,16 @@ public function toFilterHtmlAllowedTagsString(): string { */ public function toGeneralHtmlSupportConfig(): array { $allowed = []; foreach ($this->elements as $tag => $attributes) { // Resolve any remaining wildcards based on Drupal's assumptions on // wildcards to ensure all HTML tags that Drupal thinks are supported are // truly supported by CKEditor 5. For example: the <$block> wildcard does // NOT correspond to block-level HTML tags, but to CKEditor 5 elements that // behave like blocks. Knowing the list of concrete HTML tags this maps to // is impossible without executing JavaScript, which PHP cannot do. By // generating this GHS configuration, we can guarantee that Drupal's only // possible interpretation also actually works. $elements = self::resolveWildcards($this)->getAllowedElements(); foreach ($elements as $tag => $attributes) { $to_allow = ['name' => $tag]; assert($attributes === FALSE || is_array($attributes)); if (is_array($attributes)) { Loading core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/SourceEditing.php +5 −1 Original line number Diff line number Diff line Loading @@ -76,9 +76,13 @@ public function getElementsSubset(): array { */ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array { $restrictions = HTMLRestrictions::fromString(implode(' ', $this->configuration['allowed_tags'])); // Only handle concrete HTML elements to allow the Wildcard HTML support // plugin to handle wildcards. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() $concrete_restrictions = $restrictions->getConcreteSubset(); return [ 'htmlSupport' => [ 'allow' => $restrictions->toGeneralHtmlSupportConfig(), 'allow' => $concrete_restrictions->toGeneralHtmlSupportConfig(), ], ]; } Loading core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php +40 −5 Original line number Diff line number Diff line Loading @@ -160,12 +160,12 @@ public function getEnabledDefinitions(EditorInterface $editor): array { } } // Only enable the General HTML Support plugin on text formats with no HTML // restrictions. // Only enable the arbitrary HTML Support plugin on text formats with no // HTML restrictions. // @see https://ckeditor.com/docs/ckeditor5/latest/api/html-support.html // @see https://github.com/ckeditor/ckeditor5/issues/9856 if ($editor->getFilterFormat()->getHtmlRestrictions() !== FALSE) { unset($definitions['ckeditor5_htmlSupport']); unset($definitions['ckeditor5_arbitraryHtmlSupport']); } // Evaluate `plugins` condition. Loading @@ -175,6 +175,22 @@ public function getEnabledDefinitions(EditorInterface $editor): array { } } if (!isset($definitions['ckeditor5_arbitraryHtmlSupport'])) { $restrictions = new HTMLRestrictions($this->getProvidedElements(array_keys($definitions), $editor, FALSE)); if ($restrictions->getWildcardSubset()->isEmpty()) { // This is only reached if arbitrary HTML is not enabled. If wildcard // tags (such as $block) are present, they need to be resolved via the // wildcardHtmlSupport plugin. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() unset($definitions['ckeditor5_wildcardHtmlSupport']); } } // When arbitrary HTML is already supported, there is no need to support // wildcard tags. else { unset($definitions['ckeditor5_wildcardHtmlSupport']); } return $definitions; } Loading Loading @@ -254,6 +270,25 @@ public function getCKEditor5PluginConfig(EditorInterface $editor): array { $config[$plugin_id] = $plugin->getDynamicPluginConfig($definition->getCKEditor5Config(), $editor); } // CKEditor 5 interprets wildcards from a "CKEditor 5 model element" // perspective, Drupal interprets wildcards from a "HTML element" // perspective. GHS is used to reconcile those two perspectives, to ensure // all expected HTML elements truly are supported. // The `ckeditor5_wildcardHtmlSupport` is automatically enabled when // necessary, and only when necessary. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getEnabledDefinitions() if (isset($definitions['ckeditor5_wildcardHtmlSupport'])) { $allowed_elements = new HTMLRestrictions($this->getProvidedElements(array_keys($definitions), $editor, FALSE)); // Compute the net new elements that the wildcard tags resolve into. $concrete_allowed_elements = $allowed_elements->getConcreteSubset(); $net_new_elements = $allowed_elements->diff($concrete_allowed_elements); $config['ckeditor5_wildcardHtmlSupport'] = [ 'htmlSupport' => [ 'allow' => $net_new_elements->toGeneralHtmlSupportConfig(), ], ]; } return [ 'plugins' => $this->mergeDefinitionValues('getCKEditor5Plugins', $definitions), 'config' => NestedArray::mergeDeepArray($config), Loading @@ -263,7 +298,7 @@ public function getCKEditor5PluginConfig(EditorInterface $editor): array { /** * {@inheritdoc} */ public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL): array { public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL, bool $resolve_wildcards = TRUE): array { $plugins = $this->getDefinitions(); if (!empty($plugin_ids)) { $plugins = array_intersect_key($plugins, array_flip($plugin_ids)); Loading Loading @@ -312,7 +347,7 @@ public function getProvidedElements(array $plugin_ids = [], EditorInterface $edi } } return $elements->getAllowedElements(); return $elements->getAllowedElements($resolve_wildcards); } /** Loading core/modules/ckeditor5/src/Plugin/CKEditor5PluginManagerInterface.php +13 −6 Original line number Diff line number Diff line Loading @@ -98,22 +98,29 @@ public function findPluginSupportingElement(string $tag): ?string; public function getCKEditor5PluginConfig(EditorInterface $editor): array; /** * Create a list of elements with attributes declared for the CKEditor5 build. * Gets all supported elements for the given plugins and text editor. * * @param string[] $plugin_ids * An array of plugin IDs. * @param \Drupal\editor\EditorInterface $editor * A configured text editor object. * (optional) An array of CKEditor 5 plugin IDs. When not set, gets elements * for all plugins. * @param \Drupal\editor\EditorInterface|null $editor * (optional) A configured text editor object using CKEditor 5. When not * set, plugins depending on the text editor cannot provide elements. * @param bool $resolve_wildcards * (optional) Whether to resolve wildcards. Defaults to TRUE. When set to * FALSE, the raw allowed elements will be returned (with no processing * applied hence no resolved wildcards). * * @return array * A nested array with a structure as described in * \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions(). * * @throws \LogicException * Thrown when an invalid CKEditor5PluginElementsSubsetInterface implementation is encountered. * Thrown when an invalid CKEditor5PluginElementsSubsetInterface * implementation is encountered. * * @see \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions() */ public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL): array; public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL, bool $resolve_wildcards = TRUE): array; } Loading
core/modules/ckeditor5/ckeditor5.ckeditor5.yml +12 −1 Original line number Diff line number Diff line Loading @@ -62,7 +62,7 @@ ckeditor5_heading: - <h5> - <h6> ckeditor5_htmlSupport: ckeditor5_arbitraryHtmlSupport: ckeditor5: plugins: [htmlSupport.GeneralHtmlSupport] config: Loading @@ -82,6 +82,17 @@ ckeditor5_htmlSupport: # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface::getEnabledDefinitions() conditions: [] ckeditor5_wildcardHtmlSupport: ckeditor5: plugins: [htmlSupport.GeneralHtmlSupport] drupal: label: Wildcard HTML support # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() elements: false library: core/ckeditor5.htmlSupport # @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface::getEnabledDefinitions() conditions: [] ckeditor5_specialCharacters: ckeditor5: plugins: Loading
core/modules/ckeditor5/src/HTMLRestrictions.php +30 −9 Original line number Diff line number Diff line Loading @@ -770,8 +770,8 @@ public function merge(HTMLRestrictions $other): HTMLRestrictions { private static function applyOperation(HTMLRestrictions $a, HTMLRestrictions $b, string $operation_method_name): HTMLRestrictions { // 1. Operation applied to wildcard tags that exist in both operands. // For example: <$block id> in both operands. $a_wildcard = self::getWildcardSubset($a); $b_wildcard = self::getWildcardSubset($b); $a_wildcard = $a->getWildcardSubset(); $b_wildcard = $b->getWildcardSubset(); $wildcard_op_result = $a_wildcard->$operation_method_name($b_wildcard); // Early return if both operands contain only wildcard tags. Loading Loading @@ -828,14 +828,23 @@ private static function getRegExForWildCardAttributeName(string $wildcard_attrib /** * Gets the subset of allowed elements whose tags are wildcards. * * @param \Drupal\ckeditor5\HTMLRestrictions $r * A set of HTML restrictions. * @return \Drupal\ckeditor5\HTMLRestrictions * The subset of the given set of HTML restrictions. */ public function getWildcardSubset(): HTMLRestrictions { return new self(array_filter($this->elements, [__CLASS__, 'isWildcardTag'], ARRAY_FILTER_USE_KEY)); } /** * Gets the subset of allowed elements whose tags are concrete. * * @return \Drupal\ckeditor5\HTMLRestrictions * The subset of the given set of HTML restrictions. */ private static function getWildcardSubset(HTMLRestrictions $r): HTMLRestrictions { return new self(array_filter($r->elements, [__CLASS__, 'isWildcardTag'], ARRAY_FILTER_USE_KEY)); public function getConcreteSubset(): HTMLRestrictions { return new self(array_filter($this->elements, function (string $tag_name) { return !self::isWildcardTag($tag_name); }, ARRAY_FILTER_USE_KEY)); } /** Loading Loading @@ -899,7 +908,7 @@ private static function resolveWildcards(HTMLRestrictions $r): HTMLRestrictions // - then $naive will be `<p class="foo">` // - merging them yields `<p class> <$block class="foo">` again // - diffing the wildcard subsets yields just `<p class>` return $r->merge($naive_resolution)->doDiff(self::getWildcardSubset($r)); return $r->merge($naive_resolution)->doDiff($r->getWildcardSubset()); } /** Loading Loading @@ -964,7 +973,10 @@ public function toCKEditor5ElementsArray(): array { * @see \Drupal\filter\Plugin\Filter\FilterHtml */ public function toFilterHtmlAllowedTagsString(): string { return implode(' ', $this->toCKEditor5ElementsArray()); // Resolve wildcard tags, because Drupal's filter_html filter plugin does // not support those. $concrete = self::resolveWildcards($this); return implode(' ', $concrete->toCKEditor5ElementsArray()); } /** Loading @@ -979,7 +991,16 @@ public function toFilterHtmlAllowedTagsString(): string { */ public function toGeneralHtmlSupportConfig(): array { $allowed = []; foreach ($this->elements as $tag => $attributes) { // Resolve any remaining wildcards based on Drupal's assumptions on // wildcards to ensure all HTML tags that Drupal thinks are supported are // truly supported by CKEditor 5. For example: the <$block> wildcard does // NOT correspond to block-level HTML tags, but to CKEditor 5 elements that // behave like blocks. Knowing the list of concrete HTML tags this maps to // is impossible without executing JavaScript, which PHP cannot do. By // generating this GHS configuration, we can guarantee that Drupal's only // possible interpretation also actually works. $elements = self::resolveWildcards($this)->getAllowedElements(); foreach ($elements as $tag => $attributes) { $to_allow = ['name' => $tag]; assert($attributes === FALSE || is_array($attributes)); if (is_array($attributes)) { Loading
core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/SourceEditing.php +5 −1 Original line number Diff line number Diff line Loading @@ -76,9 +76,13 @@ public function getElementsSubset(): array { */ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array { $restrictions = HTMLRestrictions::fromString(implode(' ', $this->configuration['allowed_tags'])); // Only handle concrete HTML elements to allow the Wildcard HTML support // plugin to handle wildcards. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() $concrete_restrictions = $restrictions->getConcreteSubset(); return [ 'htmlSupport' => [ 'allow' => $restrictions->toGeneralHtmlSupportConfig(), 'allow' => $concrete_restrictions->toGeneralHtmlSupportConfig(), ], ]; } Loading
core/modules/ckeditor5/src/Plugin/CKEditor5PluginManager.php +40 −5 Original line number Diff line number Diff line Loading @@ -160,12 +160,12 @@ public function getEnabledDefinitions(EditorInterface $editor): array { } } // Only enable the General HTML Support plugin on text formats with no HTML // restrictions. // Only enable the arbitrary HTML Support plugin on text formats with no // HTML restrictions. // @see https://ckeditor.com/docs/ckeditor5/latest/api/html-support.html // @see https://github.com/ckeditor/ckeditor5/issues/9856 if ($editor->getFilterFormat()->getHtmlRestrictions() !== FALSE) { unset($definitions['ckeditor5_htmlSupport']); unset($definitions['ckeditor5_arbitraryHtmlSupport']); } // Evaluate `plugins` condition. Loading @@ -175,6 +175,22 @@ public function getEnabledDefinitions(EditorInterface $editor): array { } } if (!isset($definitions['ckeditor5_arbitraryHtmlSupport'])) { $restrictions = new HTMLRestrictions($this->getProvidedElements(array_keys($definitions), $editor, FALSE)); if ($restrictions->getWildcardSubset()->isEmpty()) { // This is only reached if arbitrary HTML is not enabled. If wildcard // tags (such as $block) are present, they need to be resolved via the // wildcardHtmlSupport plugin. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig() unset($definitions['ckeditor5_wildcardHtmlSupport']); } } // When arbitrary HTML is already supported, there is no need to support // wildcard tags. else { unset($definitions['ckeditor5_wildcardHtmlSupport']); } return $definitions; } Loading Loading @@ -254,6 +270,25 @@ public function getCKEditor5PluginConfig(EditorInterface $editor): array { $config[$plugin_id] = $plugin->getDynamicPluginConfig($definition->getCKEditor5Config(), $editor); } // CKEditor 5 interprets wildcards from a "CKEditor 5 model element" // perspective, Drupal interprets wildcards from a "HTML element" // perspective. GHS is used to reconcile those two perspectives, to ensure // all expected HTML elements truly are supported. // The `ckeditor5_wildcardHtmlSupport` is automatically enabled when // necessary, and only when necessary. // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getEnabledDefinitions() if (isset($definitions['ckeditor5_wildcardHtmlSupport'])) { $allowed_elements = new HTMLRestrictions($this->getProvidedElements(array_keys($definitions), $editor, FALSE)); // Compute the net new elements that the wildcard tags resolve into. $concrete_allowed_elements = $allowed_elements->getConcreteSubset(); $net_new_elements = $allowed_elements->diff($concrete_allowed_elements); $config['ckeditor5_wildcardHtmlSupport'] = [ 'htmlSupport' => [ 'allow' => $net_new_elements->toGeneralHtmlSupportConfig(), ], ]; } return [ 'plugins' => $this->mergeDefinitionValues('getCKEditor5Plugins', $definitions), 'config' => NestedArray::mergeDeepArray($config), Loading @@ -263,7 +298,7 @@ public function getCKEditor5PluginConfig(EditorInterface $editor): array { /** * {@inheritdoc} */ public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL): array { public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL, bool $resolve_wildcards = TRUE): array { $plugins = $this->getDefinitions(); if (!empty($plugin_ids)) { $plugins = array_intersect_key($plugins, array_flip($plugin_ids)); Loading Loading @@ -312,7 +347,7 @@ public function getProvidedElements(array $plugin_ids = [], EditorInterface $edi } } return $elements->getAllowedElements(); return $elements->getAllowedElements($resolve_wildcards); } /** Loading
core/modules/ckeditor5/src/Plugin/CKEditor5PluginManagerInterface.php +13 −6 Original line number Diff line number Diff line Loading @@ -98,22 +98,29 @@ public function findPluginSupportingElement(string $tag): ?string; public function getCKEditor5PluginConfig(EditorInterface $editor): array; /** * Create a list of elements with attributes declared for the CKEditor5 build. * Gets all supported elements for the given plugins and text editor. * * @param string[] $plugin_ids * An array of plugin IDs. * @param \Drupal\editor\EditorInterface $editor * A configured text editor object. * (optional) An array of CKEditor 5 plugin IDs. When not set, gets elements * for all plugins. * @param \Drupal\editor\EditorInterface|null $editor * (optional) A configured text editor object using CKEditor 5. When not * set, plugins depending on the text editor cannot provide elements. * @param bool $resolve_wildcards * (optional) Whether to resolve wildcards. Defaults to TRUE. When set to * FALSE, the raw allowed elements will be returned (with no processing * applied hence no resolved wildcards). * * @return array * A nested array with a structure as described in * \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions(). * * @throws \LogicException * Thrown when an invalid CKEditor5PluginElementsSubsetInterface implementation is encountered. * Thrown when an invalid CKEditor5PluginElementsSubsetInterface * implementation is encountered. * * @see \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions() */ public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL): array; public function getProvidedElements(array $plugin_ids = [], EditorInterface $editor = NULL, bool $resolve_wildcards = TRUE): array; }