diff --git a/core/modules/ckeditor/tests/modules/src/Kernel/CKEditorTest.php b/core/modules/ckeditor/tests/modules/src/Kernel/CKEditorTest.php index 4fbead9..625e28c 100644 --- a/core/modules/ckeditor/tests/modules/src/Kernel/CKEditorTest.php +++ b/core/modules/ckeditor/tests/modules/src/Kernel/CKEditorTest.php @@ -49,7 +49,22 @@ protected function setUp() { 'filter_html' => [ 'status' => 1, 'settings' => [ - 'allowed_html' => '


', + 'allowed_html' => [ + 'id' => [ + 'id' => '*', + ], + 'h3' => [], + 'h4' => [], + 'h5' => [], + 'h6' => [], + 'p' => [], + 'br' => [], + 'strong' => [], + 'a' => [ + 'href' => '*', + 'hreflang' => '*', + ], + ], ] ], ], diff --git a/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php b/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php index aecc228..87f8dc4 100644 --- a/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php +++ b/core/modules/ckeditor/tests/src/Kernel/Plugin/CKEditorPlugin/InternalTest.php @@ -115,7 +115,10 @@ public function formatTagsSettingsTestCases() { 'filter_html' => [ 'status' => 1, 'settings' => [ - 'allowed_html' => '

', + 'allowed_html' => [ + 'h1' => [], + 'h2' => [], + ], 'filter_html_help' => 1, 'filter_html_nofollow' => 0, ], diff --git a/core/modules/editor/src/Tests/EditorSecurityTest.php b/core/modules/editor/src/Tests/EditorSecurityTest.php index d00fe0e..f1991d9 100644 --- a/core/modules/editor/src/Tests/EditorSecurityTest.php +++ b/core/modules/editor/src/Tests/EditorSecurityTest.php @@ -92,7 +92,17 @@ protected function setUp() { 'filter_html' => [ 'status' => 1, 'settings' => [ - 'allowed_html' => '


', + 'allowed_html' => [ + 'h2' => [], + 'h3' => [], + 'h4' => [], + 'h5' => [], + 'h6' => [], + 'p' => [], + 'br' => [], + 'strong' => [], + 'a' => [], + ], ] ], ], @@ -107,7 +117,17 @@ protected function setUp() { 'filter_html' => [ 'status' => 1, 'settings' => [ - 'allowed_html' => '


', + 'allowed_html' => [ + 'h2' => [], + 'h3' => [], + 'h4' => [], + 'h5' => [], + 'h6' => [], + 'p' => [], + 'br' => [], + 'strong' => [], + 'a' => [], + ], ] ], ], @@ -127,7 +147,18 @@ protected function setUp() { 'filter_html' => [ 'status' => 1, 'settings' => [ - 'allowed_html' => '


', + 'allowed_html' => [ + 'h2' => [], + 'h3' => [], + 'h4' => [], + 'h5' => [], + 'h6' => [], + 'p' => [], + 'br' => [], + 'strong' => [], + 'a' => [], + 'embed' => [], + ], ] ], ], diff --git a/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml index 261bd90..dc4dd04 100644 --- a/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml +++ b/core/modules/editor/tests/editor_private_test/config/install/filter.format.private_images.yml @@ -15,7 +15,12 @@ filters: status: false weight: -10 settings: - allowed_html: '' + allowed_html: + img: + src: '*' + alt: '*' + data-entity-type: '*' + data-entity-uuid: '*' filter_html_help: true filter_html_nofollow: false dependencies: diff --git a/core/modules/filter/config/schema/filter.schema.yml b/core/modules/filter/config/schema/filter.schema.yml index b45a475..6756f1e 100644 --- a/core/modules/filter/config/schema/filter.schema.yml +++ b/core/modules/filter/config/schema/filter.schema.yml @@ -51,8 +51,14 @@ filter_settings.filter_html: label: 'Filter HTML' mapping: allowed_html: - type: string + type: sequence label: 'Allowed HTML' + sequence: + type: sequence + label: 'Attributes' + sequence: + type: string + label: 'Value' filter_html_help: type: boolean label: 'HTML help' diff --git a/core/modules/filter/filter.install b/core/modules/filter/filter.install new file mode 100644 index 0000000..9c73649 --- /dev/null +++ b/core/modules/filter/filter.install @@ -0,0 +1,24 @@ +listAll('filter.format.') as $name) { + $filter = $config_factory->getEditable($name); + $allowed_html = $filter->get('filters.filter_html.settings.allowed_html'); + if ($allowed_html !== NULL) { + $updated_allowed_html = FilterHtmlAllowedMarkupSchema::generateSettings($allowed_html); + $filter->set('filters.filter_html.settings.allowed_html', $updated_allowed_html); + $filter->save(); + } + } +} diff --git a/core/modules/filter/src/FilterHtmlAllowedMarkupSchema.php b/core/modules/filter/src/FilterHtmlAllowedMarkupSchema.php new file mode 100644 index 0000000..0ee5823 --- /dev/null +++ b/core/modules/filter/src/FilterHtmlAllowedMarkupSchema.php @@ -0,0 +1,78 @@ +', ' />', $allowed_html_string); + // Protect any trailing * characters in attribute names, since DomDocument + // strips them as invalid. + $star_protector = '__zqh6vxfbk3cg__'; + $html = str_replace('*', $star_protector, $html); + $body_child_nodes = Html::load($html) + ->getElementsByTagName('body') + ->item(0)->childNodes; + + foreach ($body_child_nodes as $node) { + if ($node->nodeType !== XML_ELEMENT_NODE) { + // Skip the empty text nodes inside tags. + continue; + } + $tag = $node->tagName; + $allowed_html_settings[$tag] = []; + if ($node->hasAttributes()) { + // Iterate over any attributes, and mark them as allowed. + foreach ($node->attributes as $name => $attribute) { + // Put back any trailing * on wildcard attribute name. + $name = str_replace($star_protector, '*', $name); + $value = str_replace($star_protector, '*', $attribute->value); + if (empty($value)) { + $value = '*'; + } + $allowed_html_settings[$tag][$name] = $value; + } + } + } + + return $allowed_html_settings; + } + + /** + * Generate a string to represent the allowed HTML. + * + * @param array $allowed_html_settings + * The allowed HTML + * @return string + */ + public static function generateString($allowed_html_settings) { + $allowed_html_tags = []; + foreach ($allowed_html_settings as $tag => $attributes) { + $attribute_strings = []; + foreach ($attributes as $attribute => $value) { + $attribute_strings[] = $value === '*' ? $attribute : sprintf('%s="%s"', $attribute, $value); + } + $allowed_html_tags[] = sprintf('<%s%s%s>', $tag, count($attribute_strings) > 0 ? ' ' : '', implode(' ', $attribute_strings)); + } + return implode(' ', $allowed_html_tags); + } + +} diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtml.php b/core/modules/filter/src/Plugin/Filter/FilterHtml.php index 882bcc0..0242dc9 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtml.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtml.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; use Drupal\Component\Utility\Html; +use Drupal\filter\FilterHtmlAllowedMarkupSchema; use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; @@ -15,12 +16,14 @@ * by only having the attribute name, or allowing a fixed list of values, or * allowing a value with a wildcard prefix. * + * @todo, figure out how to set allowed_html without forcing certain elements. + * * @Filter( * id = "filter_html", * title = @Translation("Limit allowed HTML tags and correct faulty HTML"), * type = Drupal\filter\Plugin\FilterInterface::TYPE_HTML_RESTRICTOR, * settings = { - * "allowed_html" = "