From 26a51db1329e068d5bb3854ceb9c9217c69b3981 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 17:20:39 +0900 Subject: [PATCH 01/30] Icons Regstry: improveme SVG sanitizer --- .../class-gutenberg-icons-registry-7-1.php | 605 ++++++++++++++++++ ...lass-gutenberg-icons-registry-7-1-test.php | 110 ++++ 2 files changed, 715 insertions(+) create mode 100644 phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php diff --git a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php index e827056064e7b1..f4aaa8c2717e00 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php @@ -50,6 +50,611 @@ protected function __construct() { } } + /** + * Sanitizes the icon SVG content. + * + * Uses WP_HTML_Processor to extract the SVG element in its entirety before + * applying wp_kses. This avoids issues where HTML tags like

inside the + * content would terminate the SVG element when parsed as HTML, and ensures + * proper handling of SVG structure including self-closing tags. + * + * @param string $icon_content The icon SVG content to sanitize. + * @return string The sanitized icon SVG content. + */ + protected function sanitize_icon_content( $icon_content ) { + // Core attributes applicable to most elements. + $core_attributes = array( + 'id' => true, + 'class' => true, + 'style' => true, + ); + + // ARIA and accessibility attributes. + $aria_attributes = array( + 'aria-hidden' => true, + 'aria-label' => true, + 'aria-labelledby' => true, + 'aria-describedby' => true, + 'role' => true, + 'focusable' => true, + 'tabindex' => true, + ); + + // Presentation attributes for graphics elements (shapes, text, use, image). + $presentation_attributes = array( + 'fill' => true, + 'fill-opacity' => true, + 'fill-rule' => true, + 'stroke' => true, + 'stroke-width' => true, + 'stroke-linecap' => true, + 'stroke-linejoin' => true, + 'stroke-miterlimit' => true, + 'stroke-dasharray' => true, + 'stroke-dashoffset' => true, + 'stroke-opacity' => true, + 'opacity' => true, + 'transform' => true, + 'clip-path' => true, + 'clip-rule' => true, + 'mask' => true, + 'filter' => true, + 'visibility' => true, + 'display' => true, + 'color' => true, + 'color-interpolation' => true, + 'color-rendering' => true, + 'vector-effect' => true, + 'paint-order' => true, + ); + + // Marker attributes (only for shape elements). + $marker_attributes = array( + 'marker-start' => true, + 'marker-mid' => true, + 'marker-end' => true, + ); + + // Container attributes for grouping elements. + $container_attributes = array( + 'transform' => true, + 'clip-path' => true, + 'mask' => true, + 'filter' => true, + 'visibility' => true, + 'display' => true, + 'opacity' => true, + ); + + /* + * Allowed tags for wp_kses(). WP_HTML_Processor::normalize() with + * constraints (similar structure to this array) is proposed to improve + * HTML/SVG sanitization in the future. + * + * @see https://github.com/dmsnell/wordpress-develop/pull/20 + */ + $allowed_tags = array( + // Root SVG element. + 'svg' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'xmlns' => true, + 'xmlns:xlink' => true, + 'width' => true, + 'height' => true, + 'viewbox' => true, + 'preserveaspectratio' => true, + 'x' => true, + 'y' => true, + ) + ), + // Basic shape elements (with markers). + 'path' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'd' => true, + 'pathLength' => true, + ) + ), + 'circle' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'cx' => true, + 'cy' => true, + 'r' => true, + ) + ), + 'ellipse' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'cx' => true, + 'cy' => true, + 'rx' => true, + 'ry' => true, + ) + ), + 'line' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'x1' => true, + 'x2' => true, + 'y1' => true, + 'y2' => true, + ) + ), + 'polygon' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'points' => true, + ) + ), + 'polyline' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'points' => true, + ) + ), + 'rect' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'rx' => true, + 'ry' => true, + ) + ), + // Grouping and structural elements. + 'g' => array_merge( + $core_attributes, + $aria_attributes, + $container_attributes + ), + 'defs' => $core_attributes, + 'symbol' => array_merge( + $core_attributes, + $aria_attributes, + $container_attributes, + array( + 'viewBox' => true, + 'preserveAspectRatio' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + ) + ), + 'use' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'href' => true, + 'xlink:href' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + ) + ), + 'clipPath' => array_merge( + $core_attributes, + array( + 'clipPathUnits' => true, + 'transform' => true, + ) + ), + 'mask' => array_merge( + $core_attributes, + array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'maskUnits' => true, + 'maskContentUnits' => true, + ) + ), + // Gradient elements. + 'linearGradient' => array_merge( + $core_attributes, + array( + 'x1' => true, + 'x2' => true, + 'y1' => true, + 'y2' => true, + 'gradientUnits' => true, + 'gradientTransform' => true, + 'spreadMethod' => true, + 'href' => true, + 'xlink:href' => true, + ) + ), + 'radialGradient' => array_merge( + $core_attributes, + array( + 'cx' => true, + 'cy' => true, + 'r' => true, + 'fx' => true, + 'fy' => true, + 'fr' => true, + 'gradientUnits' => true, + 'gradientTransform' => true, + 'spreadMethod' => true, + 'href' => true, + 'xlink:href' => true, + ) + ), + 'stop' => array_merge( + $core_attributes, + array( + 'offset' => true, + 'stop-color' => true, + 'stop-opacity' => true, + ) + ), + // Pattern element. + 'pattern' => array_merge( + $core_attributes, + array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'patternUnits' => true, + 'patternContentUnits' => true, + 'patternTransform' => true, + 'viewBox' => true, + 'preserveAspectRatio' => true, + 'href' => true, + 'xlink:href' => true, + ) + ), + // Filter elements. + 'filter' => array_merge( + $core_attributes, + array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'filterUnits' => true, + 'primitiveUnits' => true, + ) + ), + 'feBlend' => array( + 'in' => true, + 'in2' => true, + 'mode' => true, + 'result' => true, + ), + 'feColorMatrix' => array( + 'in' => true, + 'type' => true, + 'values' => true, + 'result' => true, + ), + 'feComponentTransfer' => array( + 'in' => true, + 'result' => true, + ), + 'feComposite' => array( + 'in' => true, + 'in2' => true, + 'operator' => true, + 'k1' => true, + 'k2' => true, + 'k3' => true, + 'k4' => true, + 'result' => true, + ), + 'feConvolveMatrix' => array( + 'in' => true, + 'order' => true, + 'kernelMatrix' => true, + 'divisor' => true, + 'bias' => true, + 'targetX' => true, + 'targetY' => true, + 'edgeMode' => true, + 'preserveAlpha' => true, + 'result' => true, + ), + 'feDiffuseLighting' => array( + 'in' => true, + 'surfaceScale' => true, + 'diffuseConstant' => true, + 'result' => true, + ), + 'feDisplacementMap' => array( + 'in' => true, + 'in2' => true, + 'scale' => true, + 'xChannelSelector' => true, + 'yChannelSelector' => true, + 'result' => true, + ), + 'feDistantLight' => array( + 'azimuth' => true, + 'elevation' => true, + ), + 'feFlood' => array( + 'flood-color' => true, + 'flood-opacity' => true, + 'result' => true, + ), + 'feGaussianBlur' => array( + 'in' => true, + 'stdDeviation' => true, + 'edgeMode' => true, + 'result' => true, + ), + 'feImage' => array( + 'href' => true, + 'xlink:href' => true, + 'preserveAspectRatio' => true, + 'result' => true, + ), + 'feMerge' => array( + 'result' => true, + ), + 'feMergeNode' => array( + 'in' => true, + ), + 'feMorphology' => array( + 'in' => true, + 'operator' => true, + 'radius' => true, + 'result' => true, + ), + 'feOffset' => array( + 'in' => true, + 'dx' => true, + 'dy' => true, + 'result' => true, + ), + 'fePointLight' => array( + 'x' => true, + 'y' => true, + 'z' => true, + ), + 'feSpecularLighting' => array( + 'in' => true, + 'surfaceScale' => true, + 'specularConstant' => true, + 'specularExponent' => true, + 'result' => true, + ), + 'feSpotLight' => array( + 'x' => true, + 'y' => true, + 'z' => true, + 'pointsAtX' => true, + 'pointsAtY' => true, + 'pointsAtZ' => true, + 'specularExponent' => true, + 'limitingConeAngle' => true, + ), + 'feTile' => array( + 'in' => true, + 'result' => true, + ), + 'feTurbulence' => array( + 'baseFrequency' => true, + 'numOctaves' => true, + 'seed' => true, + 'stitchTiles' => true, + 'type' => true, + 'result' => true, + ), + 'feFuncA' => array( + 'type' => true, + 'tableValues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, + ), + 'feFuncB' => array( + 'type' => true, + 'tableValues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, + ), + 'feFuncG' => array( + 'type' => true, + 'tableValues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, + ), + 'feFuncR' => array( + 'type' => true, + 'tableValues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, + ), + // Text elements. + 'text' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'x' => true, + 'y' => true, + 'dx' => true, + 'dy' => true, + 'rotate' => true, + 'textLength' => true, + 'lengthAdjust' => true, + 'text-anchor' => true, + 'font-family' => true, + 'font-size' => true, + 'font-weight' => true, + 'font-style' => true, + 'font-variant' => true, + 'text-decoration' => true, + 'writing-mode' => true, + 'letter-spacing' => true, + 'word-spacing' => true, + 'dominant-baseline' => true, + 'alignment-baseline' => true, + 'baseline-shift' => true, + ) + ), + 'tspan' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'x' => true, + 'y' => true, + 'dx' => true, + 'dy' => true, + 'rotate' => true, + 'textLength' => true, + 'lengthAdjust' => true, + 'text-anchor' => true, + 'font-family' => true, + 'font-size' => true, + 'font-weight' => true, + 'font-style' => true, + 'text-decoration' => true, + ) + ), + 'textPath' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'href' => true, + 'xlink:href' => true, + 'startOffset' => true, + 'method' => true, + 'spacing' => true, + 'text-anchor' => true, + ) + ), + // Descriptive elements. + 'title' => array(), + 'desc' => array(), + 'metadata' => array(), + // Image element. + 'image' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array( + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'href' => true, + 'xlink:href' => true, + 'preserveAspectRatio' => true, + ) + ), + // Marker element. + 'marker' => array_merge( + $core_attributes, + array( + 'markerUnits' => true, + 'refX' => true, + 'refY' => true, + 'markerWidth' => true, + 'markerHeight' => true, + 'orient' => true, + 'preserveAspectRatio' => true, + 'viewBox' => true, + ) + ), + // Animation elements. + 'animate' => array_merge( + $core_attributes, + array( + 'attributeName' => true, + 'from' => true, + 'to' => true, + 'dur' => true, + 'repeatCount' => true, + 'begin' => true, + 'end' => true, + 'values' => true, + 'keyTimes' => true, + 'keySplines' => true, + 'calcMode' => true, + 'additive' => true, + 'accumulate' => true, + ) + ), + 'animateTransform' => array_merge( + $core_attributes, + array( + 'attributeName' => true, + 'type' => true, + 'from' => true, + 'to' => true, + 'dur' => true, + 'repeatCount' => true, + 'begin' => true, + 'end' => true, + 'values' => true, + 'keyTimes' => true, + 'keySplines' => true, + 'calcMode' => true, + 'additive' => true, + 'accumulate' => true, + ) + ), + ); + + $processor = WP_HTML_Processor::create_fragment( $icon_content ); + if ( ! $processor || ! $processor->next_token() || 'SVG' !== $processor->get_tag() ) { + return ''; + } + + $svg = $processor->serialize_token(); + $depth = $processor->get_current_depth(); + while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) { + $svg .= $processor->serialize_token(); + } + return wp_kses( $svg, $allowed_tags ); + } + /** * Modified to also search in icon labels */ diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php new file mode 100644 index 00000000000000..f596a268d29b44 --- /dev/null +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -0,0 +1,110 @@ +registry = Gutenberg_Icons_Registry_7_1::get_instance(); + } + + /** + * Invokes the Gutenberg_Icons_Registry_7_1::register method on the registry instance. + * + * @param string $icon_name Icon name including namespace. + * @param array $icon_properties Icon properties (label, content, filePath). + * @return bool True if the icon was registered successfully. + */ + private function register( $icon_name, $icon_properties ) { + $method = new ReflectionMethod( $this->registry, 'register' ); + $method->setAccessible( true ); + return $method->invoke( $this->registry, $icon_name, $icon_properties ); + } + + /** + * Invokes the Gutenberg_Icons_Registry_7_1::sanitize_icon_content method on the registry instance. + * + * @param string $icon_content The icon SVG content to sanitize. + * @return string The sanitized icon SVG content. + */ + private function sanitize_icon_content( $icon_content ) { + $method = new ReflectionMethod( $this->registry, 'sanitize_icon_content' ); + $method->setAccessible( true ); + return $method->invoke( $this->registry, $icon_content ); + } + + /** + * @dataProvider data_sanitize_icon_content + * @covers Gutenberg_Icons_Registry_7_1::sanitize_icon_content + * + * @param string $input The icon content to sanitize. + * @param string $expected The expected sanitized output. + */ + public function test_sanitize_icon_content( $input, $expected ) { + $sanitized = $this->sanitize_icon_content( $input ); + $this->assertSame( $expected, $sanitized ); + } + + /** + * Data provider for test_sanitize_icon_content. + * + * @return array[] Array of arrays with input and expected sanitized output. + */ + public function data_sanitize_icon_content() { + return array( + 'strips script tags' => array( + '', + 'alert(1)', + ), + 'strips event handlers' => array( + '', + '', + ), + 'returns empty for plain text' => array( + 'plain text without svg', + '', + ), + 'returns empty for html without svg' => array( + '

not svg

content

', + '', + ), + 'allows https in href' => array( + '', + '', + ), + 'strips javascript protocol in href' => array( + '', + '', + ), + 'preserves allowed attributes' => array( + '', + '', + ), + 'preserves allowed elements' => array( + '', + '', + ), + 'strips disallowed tags' => array( + '', + '', + ), + 'keeps svg with inner title and desc' => array( + 'Icon titleDescription', + 'Icon titleDescription', + ), + ); + } +} From 4ae7479cdb4336e55cac82cab633fbb0c41f70d0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 18:48:10 +0900 Subject: [PATCH 02/30] WIP --- .../class-gutenberg-icons-registry-7-1.php | 9 ++-- ...lass-gutenberg-icons-registry-7-1-test.php | 51 +++++++++++++------ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php index f4aaa8c2717e00..ac72cdee17ce05 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php @@ -127,9 +127,9 @@ protected function sanitize_icon_content( $icon_content ) { ); /* - * Allowed tags for wp_kses(). WP_HTML_Processor::normalize() with - * constraints (similar structure to this array) is proposed to improve - * HTML/SVG sanitization in the future. + * Allowed tags for wp_kses(). Only SVG elements are permitted; foreignObject + * and HTML tags (e.g. p, div) are intentionally excluded as they are not + * valid in icon SVG content per the SVG specification. * * @see https://github.com/dmsnell/wordpress-develop/pull/20 */ @@ -649,9 +649,10 @@ protected function sanitize_icon_content( $icon_content ) { $svg = $processor->serialize_token(); $depth = $processor->get_current_depth(); - while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) { + while ( $processor->next_token() && $processor->get_current_depth() > $depth ) { $svg .= $processor->serialize_token(); } + $svg .= ''; return wp_kses( $svg, $allowed_tags ); } diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php index f596a268d29b44..fb5c92ce58a9a5 100644 --- a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -65,43 +65,62 @@ public function test_sanitize_icon_content( $input, $expected ) { */ public function data_sanitize_icon_content() { return array( - 'strips script tags' => array( + // Correctly extracts the SVG element. + 'strips html-like tags inside svg' => array( + '

paragraph content

div content
', + '', + ), + 'strips foreignObject and html inside' => array( + '

paragraph content

', + '', + ), + 'extracts only first svg when multiple present' => array( + '', + '', + ), + + // Dangerous content is stripped. + 'strips script tags' => array( '', 'alert(1)', ), - 'strips event handlers' => array( + 'strips event handlers' => array( '', '', ), - 'returns empty for plain text' => array( + 'strips javascript protocol in href' => array( + '', + '', + ), + 'strips disallowed tags' => array( + '', + '', + ), + + // Returns empty string when input is not SVG. + 'returns empty for plain text' => array( 'plain text without svg', '', ), - 'returns empty for html without svg' => array( + 'returns empty for html without svg' => array( '
not svg

content

', '', ), - 'allows https in href' => array( + + // Valid SVG elements and attributes are preserved. + 'allows https in href' => array( '', '', ), - 'strips javascript protocol in href' => array( - '', - '', - ), - 'preserves allowed attributes' => array( + 'preserves allowed attributes' => array( '', '', ), - 'preserves allowed elements' => array( + 'preserves allowed elements' => array( '', '', ), - 'strips disallowed tags' => array( - '', - '', - ), - 'keeps svg with inner title and desc' => array( + 'keeps svg with inner title and desc' => array( 'Icon titleDescription', 'Icon titleDescription', ), From a777fff82d1f5525f67bf979454a4c6dce7aa71a Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 20:54:32 +0900 Subject: [PATCH 03/30] Improve unit test --- .../class-gutenberg-icons-registry-7-1.php | 364 +++++++++--------- ...lass-gutenberg-icons-registry-7-1-test.php | 119 ++++-- 2 files changed, 270 insertions(+), 213 deletions(-) diff --git a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php index ac72cdee17ce05..efc417d17eeca8 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php @@ -127,9 +127,9 @@ protected function sanitize_icon_content( $icon_content ) { ); /* - * Allowed tags for wp_kses(). Only SVG elements are permitted; foreignObject - * and HTML tags (e.g. p, div) are intentionally excluded as they are not - * valid in icon SVG content per the SVG specification. + * Allowed tags for wp_kses(). WP_HTML_Processor::normalize() with + * constraints (similar structure to this array) is proposed to improve + * HTML/SVG sanitization in the future. * * @see https://github.com/dmsnell/wordpress-develop/pull/20 */ @@ -158,7 +158,7 @@ protected function sanitize_icon_content( $icon_content ) { $marker_attributes, array( 'd' => true, - 'pathLength' => true, + 'pathlength' => true, ) ), 'circle' => array_merge( @@ -240,8 +240,8 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $container_attributes, array( - 'viewBox' => true, - 'preserveAspectRatio' => true, + 'viewbox' => true, + 'preserveaspectratio' => true, 'x' => true, 'y' => true, 'width' => true, @@ -261,40 +261,40 @@ protected function sanitize_icon_content( $icon_content ) { 'height' => true, ) ), - 'clipPath' => array_merge( + 'clippath' => array_merge( $core_attributes, array( - 'clipPathUnits' => true, + 'clippathunits' => true, 'transform' => true, ) ), 'mask' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'maskUnits' => true, - 'maskContentUnits' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'maskunits' => true, + 'maskcontentunits' => true, ) ), // Gradient elements. - 'linearGradient' => array_merge( + 'lineargradient' => array_merge( $core_attributes, array( 'x1' => true, 'x2' => true, 'y1' => true, 'y2' => true, - 'gradientUnits' => true, - 'gradientTransform' => true, - 'spreadMethod' => true, + 'gradientunits' => true, + 'gradienttransform' => true, + 'spreadmethod' => true, 'href' => true, 'xlink:href' => true, ) ), - 'radialGradient' => array_merge( + 'radialgradient' => array_merge( $core_attributes, array( 'cx' => true, @@ -303,9 +303,9 @@ protected function sanitize_icon_content( $icon_content ) { 'fx' => true, 'fy' => true, 'fr' => true, - 'gradientUnits' => true, - 'gradientTransform' => true, - 'spreadMethod' => true, + 'gradientunits' => true, + 'gradienttransform' => true, + 'spreadmethod' => true, 'href' => true, 'xlink:href' => true, ) @@ -322,48 +322,48 @@ protected function sanitize_icon_content( $icon_content ) { 'pattern' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'patternUnits' => true, - 'patternContentUnits' => true, - 'patternTransform' => true, - 'viewBox' => true, - 'preserveAspectRatio' => true, - 'href' => true, - 'xlink:href' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'patternunits' => true, + 'patterncontentunits' => true, + 'patterntransform' => true, + 'viewbox' => true, + 'preserveaspectratio' => true, + 'href' => true, + 'xlink:href' => true, ) ), // Filter elements. 'filter' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'filterUnits' => true, - 'primitiveUnits' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'filterunits' => true, + 'primitiveunits' => true, ) ), - 'feBlend' => array( + 'feblend' => array( 'in' => true, 'in2' => true, 'mode' => true, 'result' => true, ), - 'feColorMatrix' => array( + 'fecolormatrix' => array( 'in' => true, 'type' => true, 'values' => true, 'result' => true, ), - 'feComponentTransfer' => array( + 'fecomponenttransfer' => array( 'in' => true, 'result' => true, ), - 'feComposite' => array( + 'fecomposite' => array( 'in' => true, 'in2' => true, 'operator' => true, @@ -373,135 +373,135 @@ protected function sanitize_icon_content( $icon_content ) { 'k4' => true, 'result' => true, ), - 'feConvolveMatrix' => array( - 'in' => true, - 'order' => true, - 'kernelMatrix' => true, - 'divisor' => true, - 'bias' => true, - 'targetX' => true, - 'targetY' => true, - 'edgeMode' => true, - 'preserveAlpha' => true, - 'result' => true, - ), - 'feDiffuseLighting' => array( - 'in' => true, - 'surfaceScale' => true, - 'diffuseConstant' => true, - 'result' => true, - ), - 'feDisplacementMap' => array( + 'feconvolvematrix' => array( + 'in' => true, + 'order' => true, + 'kernelmatrix' => true, + 'divisor' => true, + 'bias' => true, + 'targetx' => true, + 'targety' => true, + 'edgemode' => true, + 'preservealpha' => true, + 'result' => true, + ), + 'fediffuselighting' => array( 'in' => true, - 'in2' => true, - 'scale' => true, - 'xChannelSelector' => true, - 'yChannelSelector' => true, + 'surfacescale' => true, + 'diffuseconstant' => true, 'result' => true, ), - 'feDistantLight' => array( + 'fedisplacementmap' => array( + 'in' => true, + 'in2' => true, + 'scale' => true, + 'xchannelselector' => true, + 'ychannelselector' => true, + 'result' => true, + ), + 'fedistantlight' => array( 'azimuth' => true, 'elevation' => true, ), - 'feFlood' => array( + 'feflood' => array( 'flood-color' => true, 'flood-opacity' => true, 'result' => true, ), - 'feGaussianBlur' => array( - 'in' => true, - 'stdDeviation' => true, - 'edgeMode' => true, - 'result' => true, + 'fegaussianblur' => array( + 'in' => true, + 'stddeviation' => true, + 'edgemode' => true, + 'result' => true, ), - 'feImage' => array( - 'href' => true, - 'xlink:href' => true, - 'preserveAspectRatio' => true, - 'result' => true, + 'feimage' => array( + 'href' => true, + 'xlink:href' => true, + 'preserveaspectratio' => true, + 'result' => true, ), - 'feMerge' => array( + 'femerge' => array( 'result' => true, ), - 'feMergeNode' => array( + 'femergenode' => array( 'in' => true, ), - 'feMorphology' => array( + 'femorphology' => array( 'in' => true, 'operator' => true, 'radius' => true, 'result' => true, ), - 'feOffset' => array( + 'feoffset' => array( 'in' => true, 'dx' => true, 'dy' => true, 'result' => true, ), - 'fePointLight' => array( + 'fepointlight' => array( 'x' => true, 'y' => true, 'z' => true, ), - 'feSpecularLighting' => array( - 'in' => true, - 'surfaceScale' => true, - 'specularConstant' => true, - 'specularExponent' => true, - 'result' => true, - ), - 'feSpotLight' => array( - 'x' => true, - 'y' => true, - 'z' => true, - 'pointsAtX' => true, - 'pointsAtY' => true, - 'pointsAtZ' => true, - 'specularExponent' => true, - 'limitingConeAngle' => true, - ), - 'feTile' => array( + 'fespecularlighting' => array( + 'in' => true, + 'surfacescale' => true, + 'specularconstant' => true, + 'specularexponent' => true, + 'result' => true, + ), + 'fespotlight' => array( + 'x' => true, + 'y' => true, + 'z' => true, + 'pointsatx' => true, + 'pointsaty' => true, + 'pointsatz' => true, + 'specularexponent' => true, + 'limitingconeangle' => true, + ), + 'fetile' => array( 'in' => true, 'result' => true, ), - 'feTurbulence' => array( - 'baseFrequency' => true, - 'numOctaves' => true, + 'feturbulence' => array( + 'basefrequency' => true, + 'numoctaves' => true, 'seed' => true, - 'stitchTiles' => true, + 'stitchtiles' => true, 'type' => true, 'result' => true, ), - 'feFuncA' => array( - 'type' => true, - 'tableValues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, + 'fefunca' => array( + 'type' => true, + 'tablevalues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, ), - 'feFuncB' => array( + 'fefuncb' => array( 'type' => true, - 'tableValues' => true, + 'tablevalues' => true, 'slope' => true, 'intercept' => true, 'amplitude' => true, 'exponent' => true, 'offset' => true, ), - 'feFuncG' => array( + 'fefuncg' => array( 'type' => true, - 'tableValues' => true, + 'tablevalues' => true, 'slope' => true, 'intercept' => true, 'amplitude' => true, 'exponent' => true, 'offset' => true, ), - 'feFuncR' => array( + 'fefuncr' => array( 'type' => true, - 'tableValues' => true, + 'tablevalues' => true, 'slope' => true, 'intercept' => true, 'amplitude' => true, @@ -514,26 +514,26 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, array( - 'x' => true, - 'y' => true, - 'dx' => true, - 'dy' => true, - 'rotate' => true, - 'textLength' => true, - 'lengthAdjust' => true, - 'text-anchor' => true, - 'font-family' => true, - 'font-size' => true, - 'font-weight' => true, - 'font-style' => true, - 'font-variant' => true, - 'text-decoration' => true, - 'writing-mode' => true, + 'x' => true, + 'y' => true, + 'dx' => true, + 'dy' => true, + 'rotate' => true, + 'textlength' => true, + 'lengthadjust' => true, + 'text-anchor' => true, + 'font-family' => true, + 'font-size' => true, + 'font-weight' => true, + 'font-style' => true, + 'font-variant' => true, + 'text-decoration' => true, + 'writing-mode' => true, 'letter-spacing' => true, - 'word-spacing' => true, - 'dominant-baseline' => true, - 'alignment-baseline' => true, - 'baseline-shift' => true, + 'word-spacing' => true, + 'dominant-baseline' => true, + 'alignment-baseline' => true, + 'baseline-shift' => true, ) ), 'tspan' => array_merge( @@ -546,8 +546,8 @@ protected function sanitize_icon_content( $icon_content ) { 'dx' => true, 'dy' => true, 'rotate' => true, - 'textLength' => true, - 'lengthAdjust' => true, + 'textlength' => true, + 'lengthadjust' => true, 'text-anchor' => true, 'font-family' => true, 'font-size' => true, @@ -556,17 +556,17 @@ protected function sanitize_icon_content( $icon_content ) { 'text-decoration' => true, ) ), - 'textPath' => array_merge( + 'textpath' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, array( - 'href' => true, - 'xlink:href' => true, - 'startOffset' => true, - 'method' => true, - 'spacing' => true, - 'text-anchor' => true, + 'href' => true, + 'xlink:href' => true, + 'startoffset' => true, + 'method' => true, + 'spacing' => true, + 'text-anchor' => true, ) ), // Descriptive elements. @@ -579,65 +579,65 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'href' => true, - 'xlink:href' => true, - 'preserveAspectRatio' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'href' => true, + 'xlink:href' => true, + 'preserveaspectratio' => true, ) ), // Marker element. 'marker' => array_merge( $core_attributes, array( - 'markerUnits' => true, - 'refX' => true, - 'refY' => true, - 'markerWidth' => true, - 'markerHeight' => true, + 'markerunits' => true, + 'refx' => true, + 'refy' => true, + 'markerwidth' => true, + 'markerheight' => true, 'orient' => true, - 'preserveAspectRatio' => true, - 'viewBox' => true, + 'preserveaspectratio' => true, + 'viewbox' => true, ) ), // Animation elements. 'animate' => array_merge( $core_attributes, array( - 'attributeName' => true, + 'attributename' => true, 'from' => true, 'to' => true, 'dur' => true, - 'repeatCount' => true, + 'repeatcount' => true, 'begin' => true, 'end' => true, 'values' => true, - 'keyTimes' => true, - 'keySplines' => true, - 'calcMode' => true, + 'keytimes' => true, + 'keysplines' => true, + 'calcmode' => true, 'additive' => true, 'accumulate' => true, ) ), - 'animateTransform' => array_merge( + 'animatetransform' => array_merge( $core_attributes, array( - 'attributeName' => true, - 'type' => true, - 'from' => true, - 'to' => true, - 'dur' => true, - 'repeatCount' => true, - 'begin' => true, - 'end' => true, - 'values' => true, - 'keyTimes' => true, - 'keySplines' => true, - 'calcMode' => true, - 'additive' => true, - 'accumulate' => true, + 'attributename' => true, + 'type' => true, + 'from' => true, + 'to' => true, + 'dur' => true, + 'repeatcount' => true, + 'begin' => true, + 'end' => true, + 'values' => true, + 'keytimes' => true, + 'keysplines' => true, + 'calcmode' => true, + 'additive' => true, + 'accumulate' => true, ) ), ); @@ -649,7 +649,7 @@ protected function sanitize_icon_content( $icon_content ) { $svg = $processor->serialize_token(); $depth = $processor->get_current_depth(); - while ( $processor->next_token() && $processor->get_current_depth() > $depth ) { + while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) { $svg .= $processor->serialize_token(); } $svg .= ''; diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php index fb5c92ce58a9a5..9a5f2de962d807 100644 --- a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -65,64 +65,121 @@ public function test_sanitize_icon_content( $input, $expected ) { */ public function data_sanitize_icon_content() { return array( - // Correctly extracts the SVG element. - 'strips html-like tags inside svg' => array( + 'extracts only first svg when multiple present' => array( + '', + '', + ), + 'returns empty svg when html-like tags present' => array( '

paragraph content

div content
', '', ), - 'strips foreignObject and html inside' => array( - '

paragraph content

', + 'strips namespace attributes' => array( + '', '', ), - 'extracts only first svg when multiple present' => array( - '', - '', - ), - // Dangerous content is stripped. - 'strips script tags' => array( + // Dangerous content is stripped (wp_kses). + 'strips foreignObject but keeps text content' => array( + '

paragraph content

', + 'paragraph contentalert(1)', + ), + 'strips script tags' => array( '', 'alert(1)', ), - 'strips event handlers' => array( + 'strips event handlers' => array( '', '', ), - 'strips javascript protocol in href' => array( + 'strips javascript protocol in href' => array( '', '', ), - 'strips disallowed tags' => array( + 'strips data protocol in href' => array( + '', + '', + ), + 'strips disallowed tags' => array( '', '', ), // Returns empty string when input is not SVG. - 'returns empty for plain text' => array( + 'returns empty for empty string' => array( + '', + '', + ), + 'returns empty for whitespace only' => array( + " \n\t ", + '', + ), + 'returns empty for plain text' => array( 'plain text without svg', '', ), - 'returns empty for html without svg' => array( + 'returns empty for html without svg' => array( '
not svg

content

', '', ), + 'returns empty when svg is not first element' => array( + '

before

', + '', + ), - // Valid SVG elements and attributes are preserved. - 'allows https in href' => array( - '', - '', - ), - 'preserves allowed attributes' => array( - '', - '', - ), - 'preserves allowed elements' => array( - '', - '', - ), - 'keeps svg with inner title and desc' => array( - 'Icon titleDescription', - 'Icon titleDescription', + // Root SVG element. + 'preserves root svg element' => array( + '', + '', + ), + // Basic shape elements. + 'preserves basic shape elements' => array( + '', + '', + ), + // Grouping and structural elements. + 'preserves grouping and structural elements' => array( + '', + '', + ), + // Gradient elements. + 'preserves gradient elements' => array( + '', + '', + ), + // Pattern element. + 'preserves pattern element' => array( + '', + '', + ), + // Filter elements. + 'preserves filter elements' => array( + '', + '', + ), + // Text elements. + 'preserves text elements' => array( + 'ABpath', + 'ABpath', + ), + // Descriptive elements. + 'preserves descriptive elements' => array( + 'Icon titleDescription', + 'Icon titleDescription', + ), + // Image element. + 'preserves image element' => array( + '', + '', + ), + // Marker element. + 'preserves marker element' => array( + '', + '', + ), + // Animation elements. + 'preserves animation elements' => array( + '', + '', ), ); } From 2bd0b84f0050aec79828edb0426b2022874b4465 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 21:36:19 +0900 Subject: [PATCH 04/30] Format --- .../class-gutenberg-icons-registry-7-1.php | 232 +++++++++--------- ...lass-gutenberg-icons-registry-7-1-test.php | 2 +- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php index efc417d17eeca8..3235f2ba3e2805 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php @@ -271,12 +271,12 @@ protected function sanitize_icon_content( $icon_content ) { 'mask' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'maskunits' => true, - 'maskcontentunits' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'maskunits' => true, + 'maskcontentunits' => true, ) ), // Gradient elements. @@ -322,29 +322,29 @@ protected function sanitize_icon_content( $icon_content ) { 'pattern' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'patternunits' => true, - 'patterncontentunits' => true, - 'patterntransform' => true, - 'viewbox' => true, - 'preserveaspectratio' => true, - 'href' => true, - 'xlink:href' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'patternunits' => true, + 'patterncontentunits' => true, + 'patterntransform' => true, + 'viewbox' => true, + 'preserveaspectratio' => true, + 'href' => true, + 'xlink:href' => true, ) ), // Filter elements. 'filter' => array_merge( $core_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'filterunits' => true, - 'primitiveunits' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'filterunits' => true, + 'primitiveunits' => true, ) ), 'feblend' => array( @@ -374,30 +374,30 @@ protected function sanitize_icon_content( $icon_content ) { 'result' => true, ), 'feconvolvematrix' => array( - 'in' => true, - 'order' => true, - 'kernelmatrix' => true, - 'divisor' => true, - 'bias' => true, - 'targetx' => true, - 'targety' => true, - 'edgemode' => true, - 'preservealpha' => true, - 'result' => true, + 'in' => true, + 'order' => true, + 'kernelmatrix' => true, + 'divisor' => true, + 'bias' => true, + 'targetx' => true, + 'targety' => true, + 'edgemode' => true, + 'preservealpha' => true, + 'result' => true, ), 'fediffuselighting' => array( - 'in' => true, - 'surfacescale' => true, - 'diffuseconstant' => true, - 'result' => true, + 'in' => true, + 'surfacescale' => true, + 'diffuseconstant' => true, + 'result' => true, ), 'fedisplacementmap' => array( - 'in' => true, - 'in2' => true, - 'scale' => true, - 'xchannelselector' => true, - 'ychannelselector' => true, - 'result' => true, + 'in' => true, + 'in2' => true, + 'scale' => true, + 'xchannelselector' => true, + 'ychannelselector' => true, + 'result' => true, ), 'fedistantlight' => array( 'azimuth' => true, @@ -409,16 +409,16 @@ protected function sanitize_icon_content( $icon_content ) { 'result' => true, ), 'fegaussianblur' => array( - 'in' => true, - 'stddeviation' => true, - 'edgemode' => true, - 'result' => true, + 'in' => true, + 'stddeviation' => true, + 'edgemode' => true, + 'result' => true, ), 'feimage' => array( - 'href' => true, - 'xlink:href' => true, - 'preserveaspectratio' => true, - 'result' => true, + 'href' => true, + 'xlink:href' => true, + 'preserveaspectratio' => true, + 'result' => true, ), 'femerge' => array( 'result' => true, @@ -444,21 +444,21 @@ protected function sanitize_icon_content( $icon_content ) { 'z' => true, ), 'fespecularlighting' => array( - 'in' => true, - 'surfacescale' => true, - 'specularconstant' => true, - 'specularexponent' => true, - 'result' => true, + 'in' => true, + 'surfacescale' => true, + 'specularconstant' => true, + 'specularexponent' => true, + 'result' => true, ), 'fespotlight' => array( - 'x' => true, - 'y' => true, - 'z' => true, - 'pointsatx' => true, - 'pointsaty' => true, - 'pointsatz' => true, - 'specularexponent' => true, - 'limitingconeangle' => true, + 'x' => true, + 'y' => true, + 'z' => true, + 'pointsatx' => true, + 'pointsaty' => true, + 'pointsatz' => true, + 'specularexponent' => true, + 'limitingconeangle' => true, ), 'fetile' => array( 'in' => true, @@ -473,13 +473,13 @@ protected function sanitize_icon_content( $icon_content ) { 'result' => true, ), 'fefunca' => array( - 'type' => true, - 'tablevalues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, + 'type' => true, + 'tablevalues' => true, + 'slope' => true, + 'intercept' => true, + 'amplitude' => true, + 'exponent' => true, + 'offset' => true, ), 'fefuncb' => array( 'type' => true, @@ -514,26 +514,26 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, array( - 'x' => true, - 'y' => true, - 'dx' => true, - 'dy' => true, - 'rotate' => true, - 'textlength' => true, - 'lengthadjust' => true, - 'text-anchor' => true, - 'font-family' => true, - 'font-size' => true, - 'font-weight' => true, - 'font-style' => true, - 'font-variant' => true, - 'text-decoration' => true, - 'writing-mode' => true, + 'x' => true, + 'y' => true, + 'dx' => true, + 'dy' => true, + 'rotate' => true, + 'textlength' => true, + 'lengthadjust' => true, + 'text-anchor' => true, + 'font-family' => true, + 'font-size' => true, + 'font-weight' => true, + 'font-style' => true, + 'font-variant' => true, + 'text-decoration' => true, + 'writing-mode' => true, 'letter-spacing' => true, - 'word-spacing' => true, - 'dominant-baseline' => true, - 'alignment-baseline' => true, - 'baseline-shift' => true, + 'word-spacing' => true, + 'dominant-baseline' => true, + 'alignment-baseline' => true, + 'baseline-shift' => true, ) ), 'tspan' => array_merge( @@ -561,12 +561,12 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, array( - 'href' => true, - 'xlink:href' => true, - 'startoffset' => true, - 'method' => true, - 'spacing' => true, - 'text-anchor' => true, + 'href' => true, + 'xlink:href' => true, + 'startoffset' => true, + 'method' => true, + 'spacing' => true, + 'text-anchor' => true, ) ), // Descriptive elements. @@ -579,13 +579,13 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'href' => true, - 'xlink:href' => true, - 'preserveaspectratio' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'href' => true, + 'xlink:href' => true, + 'preserveaspectratio' => true, ) ), // Marker element. @@ -625,19 +625,19 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array( 'attributename' => true, - 'type' => true, - 'from' => true, - 'to' => true, - 'dur' => true, - 'repeatcount' => true, - 'begin' => true, - 'end' => true, - 'values' => true, - 'keytimes' => true, - 'keysplines' => true, - 'calcmode' => true, - 'additive' => true, - 'accumulate' => true, + 'type' => true, + 'from' => true, + 'to' => true, + 'dur' => true, + 'repeatcount' => true, + 'begin' => true, + 'end' => true, + 'values' => true, + 'keytimes' => true, + 'keysplines' => true, + 'calcmode' => true, + 'additive' => true, + 'accumulate' => true, ) ), ); diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php index 9a5f2de962d807..054313a8d0dd34 100644 --- a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -73,7 +73,7 @@ public function data_sanitize_icon_content() { '

paragraph content

div content
', '', ), - 'strips namespace attributes' => array( + 'strips namespace attributes' => array( '', '', ), From 3dc5e281e8a3e84e0d680f4120fdb647bbb8b5ba Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 21:40:52 +0900 Subject: [PATCH 05/30] Add missing elements --- .../class-gutenberg-icons-registry-7-1.php | 59 +++++++++++++++++++ ...lass-gutenberg-icons-registry-7-1-test.php | 16 ++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php index 3235f2ba3e2805..48a4ccb6abd9ad 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php @@ -235,6 +235,15 @@ protected function sanitize_icon_content( $icon_content ) { $container_attributes ), 'defs' => $core_attributes, + 'view' => array_merge( + $core_attributes, + array( + 'viewbox' => true, + 'preserveaspectratio' => true, + 'zoomandpan' => true, + 'viewtarget' => true, + ) + ), 'symbol' => array_merge( $core_attributes, $aria_attributes, @@ -261,6 +270,25 @@ protected function sanitize_icon_content( $icon_content ) { 'height' => true, ) ), + 'switch' => array_merge( + $core_attributes, + $aria_attributes, + $container_attributes + ), + // Linking element. + 'a' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $container_attributes, + array( + 'href' => true, + 'xlink:href' => true, + 'target' => true, + 'rel' => true, + 'type' => true, + ) + ), 'clippath' => array_merge( $core_attributes, array( @@ -621,6 +649,26 @@ protected function sanitize_icon_content( $icon_content ) { 'accumulate' => true, ) ), + 'animatemotion' => array_merge( + $core_attributes, + array( + 'path' => true, + 'keypoints' => true, + 'rotate' => true, + 'keytimes' => true, + 'keysplines' => true, + 'calcmode' => true, + 'from' => true, + 'to' => true, + 'values' => true, + 'dur' => true, + 'repeatcount' => true, + 'begin' => true, + 'end' => true, + 'additive' => true, + 'accumulate' => true, + ) + ), 'animatetransform' => array_merge( $core_attributes, array( @@ -640,6 +688,17 @@ protected function sanitize_icon_content( $icon_content ) { 'accumulate' => true, ) ), + 'set' => array_merge( + $core_attributes, + array( + 'attributename' => true, + 'to' => true, + 'begin' => true, + 'dur' => true, + 'end' => true, + 'repeatcount' => true, + ) + ), ); $processor = WP_HTML_Processor::create_fragment( $icon_content ); diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php index 054313a8d0dd34..caa4fbbcec3f7d 100644 --- a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -141,6 +141,18 @@ public function data_sanitize_icon_content() { '', '', ), + 'preserves switch element' => array( + '', + '', + ), + 'preserves view element' => array( + '', + '', + ), + 'preserves linking element' => array( + '', + '', + ), // Gradient elements. 'preserves gradient elements' => array( '', @@ -178,8 +190,8 @@ public function data_sanitize_icon_content() { ), // Animation elements. 'preserves animation elements' => array( - '', - '', + '', + '', ), ); } From 8708824e531aaea39c982501a2ec2df30becee1e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 21:49:38 +0900 Subject: [PATCH 06/30] Remove redundant def element --- ...lass-gutenberg-icons-registry-7-1-test.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php index caa4fbbcec3f7d..66b21bf7dc53fe 100644 --- a/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php +++ b/phpunit/experimental/class-gutenberg-icons-registry-7-1-test.php @@ -146,8 +146,8 @@ public function data_sanitize_icon_content() { '', ), 'preserves view element' => array( - '', - '', + '', + '', ), 'preserves linking element' => array( '', @@ -155,23 +155,23 @@ public function data_sanitize_icon_content() { ), // Gradient elements. 'preserves gradient elements' => array( - '', - '', + '', + '', ), // Pattern element. 'preserves pattern element' => array( - '', - '', + '', + '', ), // Filter elements. 'preserves filter elements' => array( - '', - '', + '', + '', ), // Text elements. 'preserves text elements' => array( - 'ABpath', - 'ABpath', + 'ABpath', + 'ABpath', ), // Descriptive elements. 'preserves descriptive elements' => array( From 5c36d1fd5c359cb30ec6f27237d13302b54b1bff Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 12 Mar 2026 21:50:41 +0900 Subject: [PATCH 07/30] Add temporary backport changelog --- backport-changelog/7.1/TODO.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/7.1/TODO.md diff --git a/backport-changelog/7.1/TODO.md b/backport-changelog/7.1/TODO.md new file mode 100644 index 00000000000000..bd6ca721e6248b --- /dev/null +++ b/backport-changelog/7.1/TODO.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/TODO + +* https://github.com/WordPress/gutenberg/pull/75550 From d4f40226d059533901c648f55a7fe9efb2c74a03 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 15 Jun 2026 20:47:14 +0900 Subject: [PATCH 08/30] Consolidate icon registry sanitize tests into a single test file The sanitize_icon_content tests lived in a separate file after the trunk merge port. Merge them into class-wp-icons-registry-gutenberg-test.php so all WP_Icons_Registry_Gutenberg coverage (register and sanitize) shares one class and the register/sanitize reflection helpers sit together, avoiding duplicate set_up/tear_down scaffolding. Co-Authored-By: Claude --- ...icons-registry-gutenberg-sanitize-test.php | 204 ------------------ ...class-wp-icons-registry-gutenberg-test.php | 162 ++++++++++++++ 2 files changed, 162 insertions(+), 204 deletions(-) delete mode 100644 phpunit/experimental/class-wp-icons-registry-gutenberg-sanitize-test.php diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-sanitize-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-sanitize-test.php deleted file mode 100644 index 89116e52a090f3..00000000000000 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-sanitize-test.php +++ /dev/null @@ -1,204 +0,0 @@ -registry = WP_Icons_Registry_Gutenberg::get_instance(); - } - - public function tear_down() { - $instance_property = new ReflectionProperty( WP_Icons_Registry_Gutenberg::class, 'instance' ); - - /* - * ReflectionProperty::setAccessible is: - * - redundant as of 8.1.0, which made all properties accessible - * - deprecated as of 8.5.0 - * - needed until 8.1.0, as property `instance` is private - */ - if ( PHP_VERSION_ID < 80100 ) { - $instance_property->setAccessible( true ); - } - - $instance_property->setValue( null, null ); - - $this->registry = null; - parent::tear_down(); - } - - /** - * Invokes the WP_Icons_Registry_Gutenberg::sanitize_icon_content method on the registry instance. - * - * @param string $icon_content The icon SVG content to sanitize. - * @return string The sanitized icon SVG content. - */ - private function sanitize_icon_content( $icon_content ) { - $method = new ReflectionMethod( $this->registry, 'sanitize_icon_content' ); - $method->setAccessible( true ); - return $method->invoke( $this->registry, $icon_content ); - } - - /** - * @dataProvider data_sanitize_icon_content - * @covers WP_Icons_Registry_Gutenberg::sanitize_icon_content - * - * @param string $input The icon content to sanitize. - * @param string $expected The expected sanitized output. - */ - public function test_sanitize_icon_content( $input, $expected ) { - $sanitized = $this->sanitize_icon_content( $input ); - $this->assertSame( $expected, $sanitized ); - } - - /** - * Data provider for test_sanitize_icon_content. - * - * @return array[] Array of arrays with input and expected sanitized output. - */ - public function data_sanitize_icon_content() { - return array( - 'extracts only first svg when multiple present' => array( - '', - '', - ), - 'returns empty svg when html-like tags present' => array( - '

paragraph content

div content
', - '', - ), - 'strips namespace attributes' => array( - '', - '', - ), - - // Dangerous content is stripped (wp_kses). - 'strips foreignObject but keeps text content' => array( - '

paragraph content

', - 'paragraph contentalert(1)', - ), - 'strips script tags' => array( - '', - 'alert(1)', - ), - 'strips event handlers' => array( - '', - '', - ), - 'strips javascript protocol in href' => array( - '', - '', - ), - 'strips data protocol in href' => array( - '', - '', - ), - 'strips disallowed tags' => array( - '', - '', - ), - - // Returns empty string when input is not SVG. - 'returns empty for empty string' => array( - '', - '', - ), - 'returns empty for whitespace only' => array( - " \n\t ", - '', - ), - 'returns empty for plain text' => array( - 'plain text without svg', - '', - ), - 'returns empty for html without svg' => array( - '
not svg

content

', - '', - ), - 'returns empty when svg is not first element' => array( - '

before

', - '', - ), - - // Root SVG element. - 'preserves root svg element' => array( - '', - '', - ), - // Basic shape elements. - 'preserves basic shape elements' => array( - '', - '', - ), - // Grouping and structural elements. - 'preserves grouping and structural elements' => array( - '', - '', - ), - 'preserves switch element' => array( - '', - '', - ), - 'preserves view element' => array( - '', - '', - ), - 'preserves linking element' => array( - '', - '', - ), - // Gradient elements. - 'preserves gradient elements' => array( - '', - '', - ), - // Pattern element. - 'preserves pattern element' => array( - '', - '', - ), - // Filter elements. - 'preserves filter elements' => array( - '', - '', - ), - // Text elements. - 'preserves text elements' => array( - 'ABpath', - 'ABpath', - ), - // Descriptive elements. - 'preserves descriptive elements' => array( - 'Icon titleDescription', - 'Icon titleDescription', - ), - // Image element. - 'preserves image element' => array( - '', - '', - ), - // Marker element. - 'preserves marker element' => array( - '', - '', - ), - // Animation elements. - 'preserves animation elements' => array( - '', - '', - ), - ); - } -} diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 68f60b730ff8bc..71f186de5894b1 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -60,6 +60,18 @@ private function register( $icon_name, $icon_properties ) { return $method->invoke( $this->registry, $icon_name, $icon_properties ); } + /** + * Invokes the WP_Icons_Registry_Gutenberg::sanitize_icon_content method on the registry instance. + * + * @param string $icon_content The icon SVG content to sanitize. + * @return string The sanitized icon SVG content. + */ + private function sanitize_icon_content( $icon_content ) { + $method = new ReflectionMethod( $this->registry, 'sanitize_icon_content' ); + $method->setAccessible( true ); + return $method->invoke( $this->registry, $icon_content ); + } + /** * Should accept valid icon names. */ @@ -124,4 +136,154 @@ public function test_register_invalid_name() { $this->assertFalse( $result ); } } + + /** + * @dataProvider data_sanitize_icon_content + * @covers WP_Icons_Registry_Gutenberg::sanitize_icon_content + * + * @param string $input The icon content to sanitize. + * @param string $expected The expected sanitized output. + */ + public function test_sanitize_icon_content( $input, $expected ) { + $sanitized = $this->sanitize_icon_content( $input ); + $this->assertSame( $expected, $sanitized ); + } + + /** + * Data provider for test_sanitize_icon_content. + * + * @return array[] Array of arrays with input and expected sanitized output. + */ + public function data_sanitize_icon_content() { + return array( + 'extracts only first svg when multiple present' => array( + '', + '', + ), + 'returns empty svg when html-like tags present' => array( + '

paragraph content

div content
', + '', + ), + 'strips namespace attributes' => array( + '', + '', + ), + + // Dangerous content is stripped (wp_kses). + 'strips foreignObject but keeps text content' => array( + '

paragraph content

', + 'paragraph contentalert(1)', + ), + 'strips script tags' => array( + '', + 'alert(1)', + ), + 'strips event handlers' => array( + '', + '', + ), + 'strips javascript protocol in href' => array( + '', + '', + ), + 'strips data protocol in href' => array( + '', + '', + ), + 'strips disallowed tags' => array( + '', + '', + ), + + // Returns empty string when input is not SVG. + 'returns empty for empty string' => array( + '', + '', + ), + 'returns empty for whitespace only' => array( + " \n\t ", + '', + ), + 'returns empty for plain text' => array( + 'plain text without svg', + '', + ), + 'returns empty for html without svg' => array( + '
not svg

content

', + '', + ), + 'returns empty when svg is not first element' => array( + '

before

', + '', + ), + + // Root SVG element. + 'preserves root svg element' => array( + '', + '', + ), + // Basic shape elements. + 'preserves basic shape elements' => array( + '', + '', + ), + // Grouping and structural elements. + 'preserves grouping and structural elements' => array( + '', + '', + ), + 'preserves switch element' => array( + '', + '', + ), + 'preserves view element' => array( + '', + '', + ), + 'preserves linking element' => array( + '', + '', + ), + // Gradient elements. + 'preserves gradient elements' => array( + '', + '', + ), + // Pattern element. + 'preserves pattern element' => array( + '', + '', + ), + // Filter elements. + 'preserves filter elements' => array( + '', + '', + ), + // Text elements. + 'preserves text elements' => array( + 'ABpath', + 'ABpath', + ), + // Descriptive elements. + 'preserves descriptive elements' => array( + 'Icon titleDescription', + 'Icon titleDescription', + ), + // Image element. + 'preserves image element' => array( + '', + '', + ), + // Marker element. + 'preserves marker element' => array( + '', + '', + ), + // Animation elements. + 'preserves animation elements' => array( + '', + '', + ), + ); + } } From 06dbc861a6cee8bc87ca1ea8f8b74caad23ab87c Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 15 Jun 2026 20:58:12 +0900 Subject: [PATCH 09/30] Reject malformed SVG when the processor errors or pauses incomplete WP_Icons_Registry_Gutenberg::sanitize_icon_content drives WP_HTML_Processor to extract the SVG element, but the processor can stop early on input it cannot parse (get_last_error()) or on a truncated tail (paused_at_incomplete_token()). In those cases the accumulated string is only a partial serialization, which wp_kses would otherwise normalize into seemingly-valid but lossy markup. Bail out with an empty string so such icons are rejected rather than registered in a broken state. Add unit tests for both the incomplete-token and unsupported-markup paths. Addresses review feedback: https://github.com/WordPress/gutenberg/pull/75550#discussion_r3403230565 Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 6 ++++++ .../class-wp-icons-registry-gutenberg-test.php | 12 +++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index c13c0ab86b8c3f..8c54f0803c9d7d 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -832,6 +832,12 @@ protected function sanitize_icon_content( $icon_content ) { while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) { $svg .= $processor->serialize_token(); } + if ( + null !== $processor->get_last_error() + || $processor->paused_at_incomplete_token() + ) { + return ''; + } $svg .= ''; return wp_kses( $svg, $allowed_tags ); } diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 71f186de5894b1..6a3c9b5e1eef7f 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -168,7 +168,6 @@ public function data_sanitize_icon_content() { '', '', ), - // Dangerous content is stripped (wp_kses). 'strips foreignObject but keeps text content' => array( '

paragraph content

', @@ -194,7 +193,6 @@ public function data_sanitize_icon_content() { '', '', ), - // Returns empty string when input is not SVG. 'returns empty for empty string' => array( '', @@ -216,7 +214,6 @@ public function data_sanitize_icon_content() { '

before

', '', ), - // Root SVG element. 'preserves root svg element' => array( '', @@ -284,6 +281,15 @@ public function data_sanitize_icon_content() { '', '', ), + // Returns empty string when the processor cannot fully parse the SVG. + 'returns empty when paused on incomplete token' => array( + ' array( + 'TEXT NOT SUPPORTED HERE!', + '', + ), ); } } From ae4140d958393b8e19215946f38317341c71922e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 15 Jun 2026 21:16:48 +0900 Subject: [PATCH 10/30] Branch xmlns:xlink test expectation by WordPress version WordPress 7.1 preserves the xmlns:xlink namespace attribute when serializing inline SVG through WP_HTML_Processor, while 7.0 strips it (changeset 62492). Gutenberg CI runs against both the current and previous WordPress versions, so a single hardcoded expectation would fail on one of them. Gate the expected xlink fragment on is_wp_version_compatible( '7.1' ) so the sanitize test passes across the supported version matrix, and rename the case since it no longer unconditionally strips the attribute. Co-Authored-By: Claude --- ...class-wp-icons-registry-gutenberg-test.php | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 6a3c9b5e1eef7f..413beb869f0738 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -155,6 +155,20 @@ public function test_sanitize_icon_content( $input, $expected ) { * @return array[] Array of arrays with input and expected sanitized output. */ public function data_sanitize_icon_content() { + /* + * WordPress 7.1 preserves the `xmlns:xlink` namespace attribute when + * serializing inline SVG through WP_HTML_Processor; WordPress 7.0 strips + * it. Branch the expectation so the test passes on both the current and + * the previous WordPress version exercised in CI. + * + * @see https://core.trac.wordpress.org/changeset/62492 + * + * TODO: Remove this conditional once WordPress 7.0 support is dropped. + */ + $xlink = is_wp_version_compatible( '7.1' ) + ? ' xmlns:xlink="http://www.w3.org/1999/xlink"' + : ''; + return array( 'extracts only first svg when multiple present' => array( '', @@ -164,9 +178,9 @@ public function data_sanitize_icon_content() { '

paragraph content

div content
', '', ), - 'strips namespace attributes' => array( + 'handles xmlns:xlink namespace attribute' => array( '', - '', + '', ), // Dangerous content is stripped (wp_kses). 'strips foreignObject but keeps text content' => array( @@ -217,7 +231,7 @@ public function data_sanitize_icon_content() { // Root SVG element. 'preserves root svg element' => array( '', - '', + '', ), // Basic shape elements. 'preserves basic shape elements' => array( From c360d0c119ce8c37655c9a1648fdf169c647d4d0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 15 Jun 2026 21:55:28 +0900 Subject: [PATCH 11/30] Allow all ARIA states/properties and data-* attributes in icon SVGs Icon SVGs commonly carry accessibility and data hooks, but the sanitizer only permitted a handful of ARIA attributes and no data-* attributes, silently stripping valid markup. Expand the allowlist to the full WAI-ARIA states and properties set, and add the data-* wildcard supported by wp_kses(). ARIA attributes cannot use an aria-* wildcard in wp_kses(), so each is listed explicitly; data-* is a wildcard wp_kses() understands natively. Addresses review feedback: https://github.com/WordPress/gutenberg/pull/79172#issuecomment-4706613135 Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 77 +++++++++++++++++++---- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 8c54f0803c9d7d..58caf2ed4bd59e 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -183,22 +183,75 @@ protected function register( $icon_name, $icon_properties ) { * @return string The sanitized icon SVG content. */ protected function sanitize_icon_content( $icon_content ) { - // Core attributes applicable to most elements. + // Core attributes applicable to most elements. `data-*` is a wildcard + // supported by wp_kses() and matches any data attribute. $core_attributes = array( - 'id' => true, - 'class' => true, - 'style' => true, + 'id' => true, + 'class' => true, + 'style' => true, + 'data-*' => true, ); - // ARIA and accessibility attributes. + /* + * ARIA and accessibility attributes. wp_kses() does not support an + * `aria-*` wildcard, so every ARIA state and property is listed + * explicitly. The list mirrors the WAI-ARIA states and properties. + * + * @see https://www.w3.org/TR/wai-aria-1.2/#state_prop_def + */ $aria_attributes = array( - 'aria-hidden' => true, - 'aria-label' => true, - 'aria-labelledby' => true, - 'aria-describedby' => true, - 'role' => true, - 'focusable' => true, - 'tabindex' => true, + 'aria-activedescendant' => true, + 'aria-atomic' => true, + 'aria-autocomplete' => true, + 'aria-busy' => true, + 'aria-checked' => true, + 'aria-colcount' => true, + 'aria-colindex' => true, + 'aria-colspan' => true, + 'aria-controls' => true, + 'aria-current' => true, + 'aria-describedby' => true, + 'aria-description' => true, + 'aria-details' => true, + 'aria-disabled' => true, + 'aria-dropeffect' => true, + 'aria-errormessage' => true, + 'aria-expanded' => true, + 'aria-flowto' => true, + 'aria-grabbed' => true, + 'aria-haspopup' => true, + 'aria-hidden' => true, + 'aria-invalid' => true, + 'aria-keyshortcuts' => true, + 'aria-label' => true, + 'aria-labelledby' => true, + 'aria-level' => true, + 'aria-live' => true, + 'aria-modal' => true, + 'aria-multiline' => true, + 'aria-multiselectable' => true, + 'aria-orientation' => true, + 'aria-owns' => true, + 'aria-placeholder' => true, + 'aria-posinset' => true, + 'aria-pressed' => true, + 'aria-readonly' => true, + 'aria-relevant' => true, + 'aria-required' => true, + 'aria-roledescription' => true, + 'aria-rowcount' => true, + 'aria-rowindex' => true, + 'aria-rowspan' => true, + 'aria-selected' => true, + 'aria-setsize' => true, + 'aria-sort' => true, + 'aria-valuemax' => true, + 'aria-valuemin' => true, + 'aria-valuenow' => true, + 'aria-valuetext' => true, + 'role' => true, + 'focusable' => true, + 'tabindex' => true, ); // Presentation attributes for graphics elements (shapes, text, use, image). From d5c596774aa75e4f16a0111a34f8c5ed8ce1ae0d Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:16:58 +0900 Subject: [PATCH 12/30] Add type hints to sanitize_icon_content() Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 58caf2ed4bd59e..ac32207662607e 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -182,7 +182,7 @@ protected function register( $icon_name, $icon_properties ) { * @param string $icon_content The icon SVG content to sanitize. * @return string The sanitized icon SVG content. */ - protected function sanitize_icon_content( $icon_content ) { + protected function sanitize_icon_content( string $icon_content ): string { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. $core_attributes = array( From 5a99e53cea8f18863617b4a38812fb7b421dc928 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:18:49 +0900 Subject: [PATCH 13/30] Use array_fill_keys() for SVG attribute allow-lists Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 196 +++++++++++----------- 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index ac32207662607e..7bfdc152bf56ab 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -185,11 +185,9 @@ protected function register( $icon_name, $icon_properties ) { protected function sanitize_icon_content( string $icon_content ): string { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. - $core_attributes = array( - 'id' => true, - 'class' => true, - 'style' => true, - 'data-*' => true, + $core_attributes = array_fill_keys( + array( 'id', 'class', 'style', 'data-*' ), + true ); /* @@ -199,105 +197,113 @@ protected function sanitize_icon_content( string $icon_content ): string { * * @see https://www.w3.org/TR/wai-aria-1.2/#state_prop_def */ - $aria_attributes = array( - 'aria-activedescendant' => true, - 'aria-atomic' => true, - 'aria-autocomplete' => true, - 'aria-busy' => true, - 'aria-checked' => true, - 'aria-colcount' => true, - 'aria-colindex' => true, - 'aria-colspan' => true, - 'aria-controls' => true, - 'aria-current' => true, - 'aria-describedby' => true, - 'aria-description' => true, - 'aria-details' => true, - 'aria-disabled' => true, - 'aria-dropeffect' => true, - 'aria-errormessage' => true, - 'aria-expanded' => true, - 'aria-flowto' => true, - 'aria-grabbed' => true, - 'aria-haspopup' => true, - 'aria-hidden' => true, - 'aria-invalid' => true, - 'aria-keyshortcuts' => true, - 'aria-label' => true, - 'aria-labelledby' => true, - 'aria-level' => true, - 'aria-live' => true, - 'aria-modal' => true, - 'aria-multiline' => true, - 'aria-multiselectable' => true, - 'aria-orientation' => true, - 'aria-owns' => true, - 'aria-placeholder' => true, - 'aria-posinset' => true, - 'aria-pressed' => true, - 'aria-readonly' => true, - 'aria-relevant' => true, - 'aria-required' => true, - 'aria-roledescription' => true, - 'aria-rowcount' => true, - 'aria-rowindex' => true, - 'aria-rowspan' => true, - 'aria-selected' => true, - 'aria-setsize' => true, - 'aria-sort' => true, - 'aria-valuemax' => true, - 'aria-valuemin' => true, - 'aria-valuenow' => true, - 'aria-valuetext' => true, - 'role' => true, - 'focusable' => true, - 'tabindex' => true, + $aria_attributes = array_fill_keys( + array( + 'aria-activedescendant', + 'aria-atomic', + 'aria-autocomplete', + 'aria-busy', + 'aria-checked', + 'aria-colcount', + 'aria-colindex', + 'aria-colspan', + 'aria-controls', + 'aria-current', + 'aria-describedby', + 'aria-description', + 'aria-details', + 'aria-disabled', + 'aria-dropeffect', + 'aria-errormessage', + 'aria-expanded', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-keyshortcuts', + 'aria-label', + 'aria-labelledby', + 'aria-level', + 'aria-live', + 'aria-modal', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-owns', + 'aria-placeholder', + 'aria-posinset', + 'aria-pressed', + 'aria-readonly', + 'aria-relevant', + 'aria-required', + 'aria-roledescription', + 'aria-rowcount', + 'aria-rowindex', + 'aria-rowspan', + 'aria-selected', + 'aria-setsize', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + 'role', + 'focusable', + 'tabindex', + ), + true ); // Presentation attributes for graphics elements (shapes, text, use, image). - $presentation_attributes = array( - 'fill' => true, - 'fill-opacity' => true, - 'fill-rule' => true, - 'stroke' => true, - 'stroke-width' => true, - 'stroke-linecap' => true, - 'stroke-linejoin' => true, - 'stroke-miterlimit' => true, - 'stroke-dasharray' => true, - 'stroke-dashoffset' => true, - 'stroke-opacity' => true, - 'opacity' => true, - 'transform' => true, - 'clip-path' => true, - 'clip-rule' => true, - 'mask' => true, - 'filter' => true, - 'visibility' => true, - 'display' => true, - 'color' => true, - 'color-interpolation' => true, - 'color-rendering' => true, - 'vector-effect' => true, - 'paint-order' => true, + $presentation_attributes = array_fill_keys( + array( + 'fill', + 'fill-opacity', + 'fill-rule', + 'stroke', + 'stroke-width', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-opacity', + 'opacity', + 'transform', + 'clip-path', + 'clip-rule', + 'mask', + 'filter', + 'visibility', + 'display', + 'color', + 'color-interpolation', + 'color-rendering', + 'vector-effect', + 'paint-order', + ), + true ); // Marker attributes (only for shape elements). - $marker_attributes = array( - 'marker-start' => true, - 'marker-mid' => true, - 'marker-end' => true, + $marker_attributes = array_fill_keys( + array( 'marker-start', 'marker-mid', 'marker-end' ), + true ); // Container attributes for grouping elements. - $container_attributes = array( - 'transform' => true, - 'clip-path' => true, - 'mask' => true, - 'filter' => true, - 'visibility' => true, - 'display' => true, - 'opacity' => true, + $container_attributes = array_fill_keys( + array( + 'transform', + 'clip-path', + 'mask', + 'filter', + 'visibility', + 'display', + 'opacity', + ), + true ); /* From 97d3002fcc6c248244dc5a3b69455718abd8e03c Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:19:03 +0900 Subject: [PATCH 14/30] Use @link tag for dmsnell PR reference in docblock Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 7bfdc152bf56ab..02d6d158892c0a 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -311,7 +311,7 @@ protected function sanitize_icon_content( string $icon_content ): string { * constraints (similar structure to this array) is proposed to improve * HTML/SVG sanitization in the future. * - * @see https://github.com/dmsnell/wordpress-develop/pull/20 + * @link https://github.com/dmsnell/wordpress-develop/pull/20 */ $allowed_tags = array( // Root SVG element. From 39deef10e0402a24fd6b95f8a7298c01bc206f66 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:26:57 +0900 Subject: [PATCH 15/30] Skip leading comments and XML declarations before the SVG element SVG markup may legitimately begin with an XML declaration (parsed as a bogus comment), comments, a doctype, or whitespace. Advance past those leading tokens instead of assuming the first token is the SVG element, so such icons are no longer wrongly rejected. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 24 ++++++++++++++++++- ...class-wp-icons-registry-gutenberg-test.php | 13 ++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 02d6d158892c0a..64b08e539f2d19 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -882,7 +882,29 @@ protected function sanitize_icon_content( string $icon_content ): string { ); $processor = WP_HTML_Processor::create_fragment( $icon_content ); - if ( ! $processor || ! $processor->next_token() || 'SVG' !== $processor->get_tag() ) { + if ( ! $processor ) { + return ''; + } + + // Skip leading comments, XML declarations, doctype, and whitespace to + // reach the root SVG element. + while ( $processor->next_token() ) { + $token_type = $processor->get_token_type(); + if ( '#tag' === $token_type ) { + break; + } + if ( + '#comment' === $token_type + || '#doctype' === $token_type + || ( '#text' === $token_type && '' === trim( $processor->get_modifiable_text() ) ) + ) { + continue; + } + // Any other leading token (e.g. non-whitespace text) is invalid. + return ''; + } + + if ( 'SVG' !== $processor->get_tag() ) { return ''; } diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 413beb869f0738..1e9ff8e219314b 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -228,6 +228,19 @@ public function data_sanitize_icon_content() { '

before

', '', ), + // Skips leading comments, XML declarations, and whitespace. + 'extracts svg after xml declaration' => array( + '', + '', + ), + 'extracts svg after leading comment' => array( + '', + '', + ), + 'extracts svg after leading whitespace' => array( + " \n\t", + '', + ), // Root SVG element. 'preserves root svg element' => array( '', From 29eca951308b863817638784ee6d738abd8a4738 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:30:01 +0900 Subject: [PATCH 16/30] Return null from sanitize_icon_content() on failure Return null instead of an empty string when the content is not valid SVG, so failure is signalled distinctly rather than through an empty string. The caller already treats both as invalid via empty(). Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 12 ++++----- ...class-wp-icons-registry-gutenberg-test.php | 26 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 64b08e539f2d19..17d2bf81b673d2 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -180,9 +180,9 @@ protected function register( $icon_name, $icon_properties ) { * proper handling of SVG structure including self-closing tags. * * @param string $icon_content The icon SVG content to sanitize. - * @return string The sanitized icon SVG content. + * @return string|null The sanitized icon SVG content, or null on failure. */ - protected function sanitize_icon_content( string $icon_content ): string { + protected function sanitize_icon_content( string $icon_content ): ?string { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. $core_attributes = array_fill_keys( @@ -883,7 +883,7 @@ protected function sanitize_icon_content( string $icon_content ): string { $processor = WP_HTML_Processor::create_fragment( $icon_content ); if ( ! $processor ) { - return ''; + return null; } // Skip leading comments, XML declarations, doctype, and whitespace to @@ -901,11 +901,11 @@ protected function sanitize_icon_content( string $icon_content ): string { continue; } // Any other leading token (e.g. non-whitespace text) is invalid. - return ''; + return null; } if ( 'SVG' !== $processor->get_tag() ) { - return ''; + return null; } $svg = $processor->serialize_token(); @@ -917,7 +917,7 @@ protected function sanitize_icon_content( string $icon_content ): string { null !== $processor->get_last_error() || $processor->paused_at_incomplete_token() ) { - return ''; + return null; } $svg .= ''; return wp_kses( $svg, $allowed_tags ); diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 1e9ff8e219314b..0f876e71ebd5ab 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -141,8 +141,8 @@ public function test_register_invalid_name() { * @dataProvider data_sanitize_icon_content * @covers WP_Icons_Registry_Gutenberg::sanitize_icon_content * - * @param string $input The icon content to sanitize. - * @param string $expected The expected sanitized output. + * @param string $input The icon content to sanitize. + * @param string|null $expected The expected sanitized output. */ public function test_sanitize_icon_content( $input, $expected ) { $sanitized = $this->sanitize_icon_content( $input ); @@ -207,26 +207,26 @@ public function data_sanitize_icon_content() { '', '', ), - // Returns empty string when input is not SVG. - 'returns empty for empty string' => array( - '', + // Returns null when input is not SVG. + 'returns null for empty string' => array( '', + null, ), - 'returns empty for whitespace only' => array( + 'returns null for whitespace only' => array( " \n\t ", - '', + null, ), - 'returns empty for plain text' => array( + 'returns null for plain text' => array( 'plain text without svg', - '', + null, ), - 'returns empty for html without svg' => array( + 'returns null for html without svg' => array( '
not svg

content

', - '', + null, ), - 'returns empty when svg is not first element' => array( + 'returns null when svg is not first element' => array( '

before

', - '', + null, ), // Skips leading comments, XML declarations, and whitespace. 'extracts svg after xml declaration' => array( From 0993ad8e0e771e622c03cc4f411059964fe1f4b5 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 13:48:53 +0900 Subject: [PATCH 17/30] Drop parameter type hint to stay compatible with the parent signature WP_Icons_Registry::sanitize_icon_content() in WordPress core declares no parameter type, so adding 'string' to the override narrows the parameter and triggers a fatal incompatible-declaration error on WP 7.0 and trunk. Leave the parameter untyped: it accepts any future parent parameter type (contravariance), while the covariant ?string return type is kept. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 17d2bf81b673d2..68a4fa60634e3d 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -179,10 +179,14 @@ protected function register( $icon_name, $icon_properties ) { * content would terminate the SVG element when parsed as HTML, and ensures * proper handling of SVG structure including self-closing tags. * + * The parameter is intentionally left untyped to stay signature-compatible + * with the parent WP_Icons_Registry::sanitize_icon_content() shipped in + * WordPress core, which declares no parameter type. + * * @param string $icon_content The icon SVG content to sanitize. * @return string|null The sanitized icon SVG content, or null on failure. */ - protected function sanitize_icon_content( string $icon_content ): ?string { + protected function sanitize_icon_content( $icon_content ): ?string { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. $core_attributes = array_fill_keys( From a46f91352dbda00385027c07ae6d96b503ede972 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 15:06:37 +0900 Subject: [PATCH 18/30] Revert "Return null from sanitize_icon_content() on failure" This reverts commit 29eca951308b863817638784ee6d738abd8a4738. The override must stay signature-compatible with WP_Icons_Registry::sanitize_icon_content() in WordPress core, which is untyped and returns an empty string on failure. Restore the empty-string failure returns, drop the ?string return type, and revert the test expectations accordingly. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 18 ++++++------- ...class-wp-icons-registry-gutenberg-test.php | 26 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 68a4fa60634e3d..0d9b098730ccfb 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -179,14 +179,14 @@ protected function register( $icon_name, $icon_properties ) { * content would terminate the SVG element when parsed as HTML, and ensures * proper handling of SVG structure including self-closing tags. * - * The parameter is intentionally left untyped to stay signature-compatible - * with the parent WP_Icons_Registry::sanitize_icon_content() shipped in - * WordPress core, which declares no parameter type. + * The signature is intentionally left without type declarations to stay + * compatible with the parent WP_Icons_Registry::sanitize_icon_content() + * shipped in WordPress core, which declares none. * * @param string $icon_content The icon SVG content to sanitize. - * @return string|null The sanitized icon SVG content, or null on failure. + * @return string The sanitized icon SVG content. */ - protected function sanitize_icon_content( $icon_content ): ?string { + protected function sanitize_icon_content( $icon_content ) { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. $core_attributes = array_fill_keys( @@ -887,7 +887,7 @@ protected function sanitize_icon_content( $icon_content ): ?string { $processor = WP_HTML_Processor::create_fragment( $icon_content ); if ( ! $processor ) { - return null; + return ''; } // Skip leading comments, XML declarations, doctype, and whitespace to @@ -905,11 +905,11 @@ protected function sanitize_icon_content( $icon_content ): ?string { continue; } // Any other leading token (e.g. non-whitespace text) is invalid. - return null; + return ''; } if ( 'SVG' !== $processor->get_tag() ) { - return null; + return ''; } $svg = $processor->serialize_token(); @@ -921,7 +921,7 @@ protected function sanitize_icon_content( $icon_content ): ?string { null !== $processor->get_last_error() || $processor->paused_at_incomplete_token() ) { - return null; + return ''; } $svg .= ''; return wp_kses( $svg, $allowed_tags ); diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 0f876e71ebd5ab..1e9ff8e219314b 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -141,8 +141,8 @@ public function test_register_invalid_name() { * @dataProvider data_sanitize_icon_content * @covers WP_Icons_Registry_Gutenberg::sanitize_icon_content * - * @param string $input The icon content to sanitize. - * @param string|null $expected The expected sanitized output. + * @param string $input The icon content to sanitize. + * @param string $expected The expected sanitized output. */ public function test_sanitize_icon_content( $input, $expected ) { $sanitized = $this->sanitize_icon_content( $input ); @@ -207,26 +207,26 @@ public function data_sanitize_icon_content() { '', '', ), - // Returns null when input is not SVG. - 'returns null for empty string' => array( + // Returns empty string when input is not SVG. + 'returns empty for empty string' => array( + '', '', - null, ), - 'returns null for whitespace only' => array( + 'returns empty for whitespace only' => array( " \n\t ", - null, + '', ), - 'returns null for plain text' => array( + 'returns empty for plain text' => array( 'plain text without svg', - null, + '', ), - 'returns null for html without svg' => array( + 'returns empty for html without svg' => array( '
not svg

content

', - null, + '', ), - 'returns null when svg is not first element' => array( + 'returns empty when svg is not first element' => array( '

before

', - null, + '', ), // Skips leading comments, XML declarations, and whitespace. 'extracts svg after xml declaration' => array( From cf0d2a321da2477cc26b51dd898df09cbe6bcbfd Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 15:22:51 +0900 Subject: [PATCH 19/30] Use array_fill_keys() for the remaining SVG attribute lists Convert the per-element attribute literals inside $allowed_tags from array to array_fill_keys( string[], true ), matching the shared attribute groups already converted earlier. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 932 +++++++++++++--------- 1 file changed, 544 insertions(+), 388 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 0d9b098730ccfb..019966bbc7b238 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -323,15 +323,18 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'xmlns' => true, - 'xmlns:xlink' => true, - 'width' => true, - 'height' => true, - 'viewbox' => true, - 'preserveaspectratio' => true, - 'x' => true, - 'y' => true, + array_fill_keys( + array( + 'xmlns', + 'xmlns:xlink', + 'width', + 'height', + 'viewbox', + 'preserveaspectratio', + 'x', + 'y', + ), + true ) ), // Basic shape elements (with markers). @@ -340,9 +343,12 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'd' => true, - 'pathlength' => true, + array_fill_keys( + array( + 'd', + 'pathlength', + ), + true ) ), 'circle' => array_merge( @@ -350,10 +356,13 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'cx' => true, - 'cy' => true, - 'r' => true, + array_fill_keys( + array( + 'cx', + 'cy', + 'r', + ), + true ) ), 'ellipse' => array_merge( @@ -361,11 +370,14 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'cx' => true, - 'cy' => true, - 'rx' => true, - 'ry' => true, + array_fill_keys( + array( + 'cx', + 'cy', + 'rx', + 'ry', + ), + true ) ), 'line' => array_merge( @@ -373,11 +385,14 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'x1' => true, - 'x2' => true, - 'y1' => true, - 'y2' => true, + array_fill_keys( + array( + 'x1', + 'x2', + 'y1', + 'y2', + ), + true ) ), 'polygon' => array_merge( @@ -385,8 +400,11 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'points' => true, + array_fill_keys( + array( + 'points', + ), + true ) ), 'polyline' => array_merge( @@ -394,8 +412,11 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'points' => true, + array_fill_keys( + array( + 'points', + ), + true ) ), 'rect' => array_merge( @@ -403,13 +424,16 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'rx' => true, - 'ry' => true, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'rx', + 'ry', + ), + true ) ), // Grouping and structural elements. @@ -421,37 +445,46 @@ protected function sanitize_icon_content( $icon_content ) { 'defs' => $core_attributes, 'view' => array_merge( $core_attributes, - array( - 'viewbox' => true, - 'preserveaspectratio' => true, - 'zoomandpan' => true, - 'viewtarget' => true, + array_fill_keys( + array( + 'viewbox', + 'preserveaspectratio', + 'zoomandpan', + 'viewtarget', + ), + true ) ), 'symbol' => array_merge( $core_attributes, $aria_attributes, $container_attributes, - array( - 'viewbox' => true, - 'preserveaspectratio' => true, - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, + array_fill_keys( + array( + 'viewbox', + 'preserveaspectratio', + 'x', + 'y', + 'width', + 'height', + ), + true ) ), 'use' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'href' => true, - 'xlink:href' => true, - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, + array_fill_keys( + array( + 'href', + 'xlink:href', + 'x', + 'y', + 'width', + 'height', + ), + true ) ), 'switch' => array_merge( @@ -465,320 +498,425 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $container_attributes, - array( - 'href' => true, - 'xlink:href' => true, - 'target' => true, - 'rel' => true, - 'type' => true, + array_fill_keys( + array( + 'href', + 'xlink:href', + 'target', + 'rel', + 'type', + ), + true ) ), 'clippath' => array_merge( $core_attributes, - array( - 'clippathunits' => true, - 'transform' => true, + array_fill_keys( + array( + 'clippathunits', + 'transform', + ), + true ) ), 'mask' => array_merge( $core_attributes, - array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'maskunits' => true, - 'maskcontentunits' => true, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'maskunits', + 'maskcontentunits', + ), + true ) ), // Gradient elements. 'lineargradient' => array_merge( $core_attributes, - array( - 'x1' => true, - 'x2' => true, - 'y1' => true, - 'y2' => true, - 'gradientunits' => true, - 'gradienttransform' => true, - 'spreadmethod' => true, - 'href' => true, - 'xlink:href' => true, + array_fill_keys( + array( + 'x1', + 'x2', + 'y1', + 'y2', + 'gradientunits', + 'gradienttransform', + 'spreadmethod', + 'href', + 'xlink:href', + ), + true ) ), 'radialgradient' => array_merge( $core_attributes, - array( - 'cx' => true, - 'cy' => true, - 'r' => true, - 'fx' => true, - 'fy' => true, - 'fr' => true, - 'gradientunits' => true, - 'gradienttransform' => true, - 'spreadmethod' => true, - 'href' => true, - 'xlink:href' => true, + array_fill_keys( + array( + 'cx', + 'cy', + 'r', + 'fx', + 'fy', + 'fr', + 'gradientunits', + 'gradienttransform', + 'spreadmethod', + 'href', + 'xlink:href', + ), + true ) ), 'stop' => array_merge( $core_attributes, - array( - 'offset' => true, - 'stop-color' => true, - 'stop-opacity' => true, + array_fill_keys( + array( + 'offset', + 'stop-color', + 'stop-opacity', + ), + true ) ), // Pattern element. 'pattern' => array_merge( $core_attributes, - array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'patternunits' => true, - 'patterncontentunits' => true, - 'patterntransform' => true, - 'viewbox' => true, - 'preserveaspectratio' => true, - 'href' => true, - 'xlink:href' => true, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'patternunits', + 'patterncontentunits', + 'patterntransform', + 'viewbox', + 'preserveaspectratio', + 'href', + 'xlink:href', + ), + true ) ), // Filter elements. 'filter' => array_merge( $core_attributes, - array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'filterunits' => true, - 'primitiveunits' => true, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'filterunits', + 'primitiveunits', + ), + true ) ), - 'feblend' => array( - 'in' => true, - 'in2' => true, - 'mode' => true, - 'result' => true, - ), - 'fecolormatrix' => array( - 'in' => true, - 'type' => true, - 'values' => true, - 'result' => true, - ), - 'fecomponenttransfer' => array( - 'in' => true, - 'result' => true, - ), - 'fecomposite' => array( - 'in' => true, - 'in2' => true, - 'operator' => true, - 'k1' => true, - 'k2' => true, - 'k3' => true, - 'k4' => true, - 'result' => true, - ), - 'feconvolvematrix' => array( - 'in' => true, - 'order' => true, - 'kernelmatrix' => true, - 'divisor' => true, - 'bias' => true, - 'targetx' => true, - 'targety' => true, - 'edgemode' => true, - 'preservealpha' => true, - 'result' => true, - ), - 'fediffuselighting' => array( - 'in' => true, - 'surfacescale' => true, - 'diffuseconstant' => true, - 'result' => true, - ), - 'fedisplacementmap' => array( - 'in' => true, - 'in2' => true, - 'scale' => true, - 'xchannelselector' => true, - 'ychannelselector' => true, - 'result' => true, - ), - 'fedistantlight' => array( - 'azimuth' => true, - 'elevation' => true, - ), - 'feflood' => array( - 'flood-color' => true, - 'flood-opacity' => true, - 'result' => true, - ), - 'fegaussianblur' => array( - 'in' => true, - 'stddeviation' => true, - 'edgemode' => true, - 'result' => true, - ), - 'feimage' => array( - 'href' => true, - 'xlink:href' => true, - 'preserveaspectratio' => true, - 'result' => true, - ), - 'femerge' => array( - 'result' => true, - ), - 'femergenode' => array( - 'in' => true, - ), - 'femorphology' => array( - 'in' => true, - 'operator' => true, - 'radius' => true, - 'result' => true, - ), - 'feoffset' => array( - 'in' => true, - 'dx' => true, - 'dy' => true, - 'result' => true, - ), - 'fepointlight' => array( - 'x' => true, - 'y' => true, - 'z' => true, - ), - 'fespecularlighting' => array( - 'in' => true, - 'surfacescale' => true, - 'specularconstant' => true, - 'specularexponent' => true, - 'result' => true, - ), - 'fespotlight' => array( - 'x' => true, - 'y' => true, - 'z' => true, - 'pointsatx' => true, - 'pointsaty' => true, - 'pointsatz' => true, - 'specularexponent' => true, - 'limitingconeangle' => true, - ), - 'fetile' => array( - 'in' => true, - 'result' => true, - ), - 'feturbulence' => array( - 'basefrequency' => true, - 'numoctaves' => true, - 'seed' => true, - 'stitchtiles' => true, - 'type' => true, - 'result' => true, - ), - 'fefunca' => array( - 'type' => true, - 'tablevalues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, - ), - 'fefuncb' => array( - 'type' => true, - 'tablevalues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, - ), - 'fefuncg' => array( - 'type' => true, - 'tablevalues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, - ), - 'fefuncr' => array( - 'type' => true, - 'tablevalues' => true, - 'slope' => true, - 'intercept' => true, - 'amplitude' => true, - 'exponent' => true, - 'offset' => true, + 'feblend' => array_fill_keys( + array( + 'in', + 'in2', + 'mode', + 'result', + ), + true + ), + 'fecolormatrix' => array_fill_keys( + array( + 'in', + 'type', + 'values', + 'result', + ), + true + ), + 'fecomponenttransfer' => array_fill_keys( + array( + 'in', + 'result', + ), + true + ), + 'fecomposite' => array_fill_keys( + array( + 'in', + 'in2', + 'operator', + 'k1', + 'k2', + 'k3', + 'k4', + 'result', + ), + true + ), + 'feconvolvematrix' => array_fill_keys( + array( + 'in', + 'order', + 'kernelmatrix', + 'divisor', + 'bias', + 'targetx', + 'targety', + 'edgemode', + 'preservealpha', + 'result', + ), + true + ), + 'fediffuselighting' => array_fill_keys( + array( + 'in', + 'surfacescale', + 'diffuseconstant', + 'result', + ), + true + ), + 'fedisplacementmap' => array_fill_keys( + array( + 'in', + 'in2', + 'scale', + 'xchannelselector', + 'ychannelselector', + 'result', + ), + true + ), + 'fedistantlight' => array_fill_keys( + array( + 'azimuth', + 'elevation', + ), + true + ), + 'feflood' => array_fill_keys( + array( + 'flood-color', + 'flood-opacity', + 'result', + ), + true + ), + 'fegaussianblur' => array_fill_keys( + array( + 'in', + 'stddeviation', + 'edgemode', + 'result', + ), + true + ), + 'feimage' => array_fill_keys( + array( + 'href', + 'xlink:href', + 'preserveaspectratio', + 'result', + ), + true + ), + 'femerge' => array_fill_keys( + array( + 'result', + ), + true + ), + 'femergenode' => array_fill_keys( + array( + 'in', + ), + true + ), + 'femorphology' => array_fill_keys( + array( + 'in', + 'operator', + 'radius', + 'result', + ), + true + ), + 'feoffset' => array_fill_keys( + array( + 'in', + 'dx', + 'dy', + 'result', + ), + true + ), + 'fepointlight' => array_fill_keys( + array( + 'x', + 'y', + 'z', + ), + true + ), + 'fespecularlighting' => array_fill_keys( + array( + 'in', + 'surfacescale', + 'specularconstant', + 'specularexponent', + 'result', + ), + true + ), + 'fespotlight' => array_fill_keys( + array( + 'x', + 'y', + 'z', + 'pointsatx', + 'pointsaty', + 'pointsatz', + 'specularexponent', + 'limitingconeangle', + ), + true + ), + 'fetile' => array_fill_keys( + array( + 'in', + 'result', + ), + true + ), + 'feturbulence' => array_fill_keys( + array( + 'basefrequency', + 'numoctaves', + 'seed', + 'stitchtiles', + 'type', + 'result', + ), + true + ), + 'fefunca' => array_fill_keys( + array( + 'type', + 'tablevalues', + 'slope', + 'intercept', + 'amplitude', + 'exponent', + 'offset', + ), + true + ), + 'fefuncb' => array_fill_keys( + array( + 'type', + 'tablevalues', + 'slope', + 'intercept', + 'amplitude', + 'exponent', + 'offset', + ), + true + ), + 'fefuncg' => array_fill_keys( + array( + 'type', + 'tablevalues', + 'slope', + 'intercept', + 'amplitude', + 'exponent', + 'offset', + ), + true + ), + 'fefuncr' => array_fill_keys( + array( + 'type', + 'tablevalues', + 'slope', + 'intercept', + 'amplitude', + 'exponent', + 'offset', + ), + true ), // Text elements. 'text' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'x' => true, - 'y' => true, - 'dx' => true, - 'dy' => true, - 'rotate' => true, - 'textlength' => true, - 'lengthadjust' => true, - 'text-anchor' => true, - 'font-family' => true, - 'font-size' => true, - 'font-weight' => true, - 'font-style' => true, - 'font-variant' => true, - 'text-decoration' => true, - 'writing-mode' => true, - 'letter-spacing' => true, - 'word-spacing' => true, - 'dominant-baseline' => true, - 'alignment-baseline' => true, - 'baseline-shift' => true, + array_fill_keys( + array( + 'x', + 'y', + 'dx', + 'dy', + 'rotate', + 'textlength', + 'lengthadjust', + 'text-anchor', + 'font-family', + 'font-size', + 'font-weight', + 'font-style', + 'font-variant', + 'text-decoration', + 'writing-mode', + 'letter-spacing', + 'word-spacing', + 'dominant-baseline', + 'alignment-baseline', + 'baseline-shift', + ), + true ) ), 'tspan' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'x' => true, - 'y' => true, - 'dx' => true, - 'dy' => true, - 'rotate' => true, - 'textlength' => true, - 'lengthadjust' => true, - 'text-anchor' => true, - 'font-family' => true, - 'font-size' => true, - 'font-weight' => true, - 'font-style' => true, - 'text-decoration' => true, + array_fill_keys( + array( + 'x', + 'y', + 'dx', + 'dy', + 'rotate', + 'textlength', + 'lengthadjust', + 'text-anchor', + 'font-family', + 'font-size', + 'font-weight', + 'font-style', + 'text-decoration', + ), + true ) ), 'textpath' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'href' => true, - 'xlink:href' => true, - 'startoffset' => true, - 'method' => true, - 'spacing' => true, - 'text-anchor' => true, + array_fill_keys( + array( + 'href', + 'xlink:href', + 'startoffset', + 'method', + 'spacing', + 'text-anchor', + ), + true ) ), // Descriptive elements. @@ -790,97 +928,115 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array( - 'x' => true, - 'y' => true, - 'width' => true, - 'height' => true, - 'href' => true, - 'xlink:href' => true, - 'preserveaspectratio' => true, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'href', + 'xlink:href', + 'preserveaspectratio', + ), + true ) ), // Marker element. 'marker' => array_merge( $core_attributes, - array( - 'markerunits' => true, - 'refx' => true, - 'refy' => true, - 'markerwidth' => true, - 'markerheight' => true, - 'orient' => true, - 'preserveaspectratio' => true, - 'viewbox' => true, + array_fill_keys( + array( + 'markerunits', + 'refx', + 'refy', + 'markerwidth', + 'markerheight', + 'orient', + 'preserveaspectratio', + 'viewbox', + ), + true ) ), // Animation elements. 'animate' => array_merge( $core_attributes, - array( - 'attributename' => true, - 'from' => true, - 'to' => true, - 'dur' => true, - 'repeatcount' => true, - 'begin' => true, - 'end' => true, - 'values' => true, - 'keytimes' => true, - 'keysplines' => true, - 'calcmode' => true, - 'additive' => true, - 'accumulate' => true, + array_fill_keys( + array( + 'attributename', + 'from', + 'to', + 'dur', + 'repeatcount', + 'begin', + 'end', + 'values', + 'keytimes', + 'keysplines', + 'calcmode', + 'additive', + 'accumulate', + ), + true ) ), 'animatemotion' => array_merge( $core_attributes, - array( - 'path' => true, - 'keypoints' => true, - 'rotate' => true, - 'keytimes' => true, - 'keysplines' => true, - 'calcmode' => true, - 'from' => true, - 'to' => true, - 'values' => true, - 'dur' => true, - 'repeatcount' => true, - 'begin' => true, - 'end' => true, - 'additive' => true, - 'accumulate' => true, + array_fill_keys( + array( + 'path', + 'keypoints', + 'rotate', + 'keytimes', + 'keysplines', + 'calcmode', + 'from', + 'to', + 'values', + 'dur', + 'repeatcount', + 'begin', + 'end', + 'additive', + 'accumulate', + ), + true ) ), 'animatetransform' => array_merge( $core_attributes, - array( - 'attributename' => true, - 'type' => true, - 'from' => true, - 'to' => true, - 'dur' => true, - 'repeatcount' => true, - 'begin' => true, - 'end' => true, - 'values' => true, - 'keytimes' => true, - 'keysplines' => true, - 'calcmode' => true, - 'additive' => true, - 'accumulate' => true, + array_fill_keys( + array( + 'attributename', + 'type', + 'from', + 'to', + 'dur', + 'repeatcount', + 'begin', + 'end', + 'values', + 'keytimes', + 'keysplines', + 'calcmode', + 'additive', + 'accumulate', + ), + true ) ), 'set' => array_merge( $core_attributes, - array( - 'attributename' => true, - 'to' => true, - 'begin' => true, - 'dur' => true, - 'end' => true, - 'repeatcount' => true, + array_fill_keys( + array( + 'attributename', + 'to', + 'begin', + 'dur', + 'end', + 'repeatcount', + ), + true ) ), ); From c7ff855363b889556d562fffed8a7eb03c834a4a Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 15:52:13 +0900 Subject: [PATCH 20/30] Add backport changelog --- backport-changelog/7.1/12182 copy.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/7.1/12182 copy.md diff --git a/backport-changelog/7.1/12182 copy.md b/backport-changelog/7.1/12182 copy.md new file mode 100644 index 00000000000000..e1624630f55ab7 --- /dev/null +++ b/backport-changelog/7.1/12182 copy.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/12197 + +* https://github.com/WordPress/gutenberg/pull/75550 From d3ee80324b58d9977255d192828aea80bf5e5079 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 16:22:59 +0900 Subject: [PATCH 21/30] Rename backport changelog file to match its PR number The file was an accidental copy with a placeholder name. Renaming it to 12197.md aligns it with the wordpress-develop PR it references. --- backport-changelog/7.1/{12182 copy.md => 12197.md} | 0 backport-changelog/7.1/TODO.md | 3 --- 2 files changed, 3 deletions(-) rename backport-changelog/7.1/{12182 copy.md => 12197.md} (100%) delete mode 100644 backport-changelog/7.1/TODO.md diff --git a/backport-changelog/7.1/12182 copy.md b/backport-changelog/7.1/12197.md similarity index 100% rename from backport-changelog/7.1/12182 copy.md rename to backport-changelog/7.1/12197.md diff --git a/backport-changelog/7.1/TODO.md b/backport-changelog/7.1/TODO.md deleted file mode 100644 index bd6ca721e6248b..00000000000000 --- a/backport-changelog/7.1/TODO.md +++ /dev/null @@ -1,3 +0,0 @@ -https://github.com/WordPress/wordpress-develop/pull/TODO - -* https://github.com/WordPress/gutenberg/pull/75550 From 381209c194f5ad216db742ebd27f76b7f569c108 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 16:33:26 +0900 Subject: [PATCH 22/30] Skip ReflectionMethod::setAccessible() on PHP 8.1+ setAccessible() is a no-op since PHP 8.1, where reflection can access private methods by default. Guarding the call avoids the now-redundant invocation on modern PHP versions. --- .../experimental/class-wp-icons-registry-gutenberg-test.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 1e9ff8e219314b..4c5c916ed87a28 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -68,7 +68,9 @@ private function register( $icon_name, $icon_properties ) { */ private function sanitize_icon_content( $icon_content ) { $method = new ReflectionMethod( $this->registry, 'sanitize_icon_content' ); - $method->setAccessible( true ); + if ( PHP_VERSION_ID < 80100 ) { + $method->setAccessible( true ); + } return $method->invoke( $this->registry, $icon_content ); } From 20abfeb79bac4150d12c28eb0cafa57b9ae37c08 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 19 Jun 2026 13:29:22 +0900 Subject: [PATCH 23/30] Use @link for WAI-ARIA spec reference in sanitize_icon_content() Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 019966bbc7b238..9726e3e4f71b4b 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -199,7 +199,7 @@ protected function sanitize_icon_content( $icon_content ) { * `aria-*` wildcard, so every ARIA state and property is listed * explicitly. The list mirrors the WAI-ARIA states and properties. * - * @see https://www.w3.org/TR/wai-aria-1.2/#state_prop_def + * @link https://www.w3.org/TR/wai-aria-1.2/#state_prop_def */ $aria_attributes = array_fill_keys( array( From 33f767dfdaffb3bccc35be66d775a43bc15b2a54 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 19 Jun 2026 13:29:32 +0900 Subject: [PATCH 24/30] Use @link for Trac changeset reference in icons registry test Co-Authored-By: Claude --- phpunit/experimental/class-wp-icons-registry-gutenberg-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 4c5c916ed87a28..2b84f5d79abbbf 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -163,7 +163,7 @@ public function data_sanitize_icon_content() { * it. Branch the expectation so the test passes on both the current and * the previous WordPress version exercised in CI. * - * @see https://core.trac.wordpress.org/changeset/62492 + * @link https://core.trac.wordpress.org/changeset/62492 * * TODO: Remove this conditional once WordPress 7.0 support is dropped. */ From 5b2e3d4c0671c1ded1a716ffceb666d45558ce51 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 19 Jun 2026 13:40:14 +0900 Subject: [PATCH 25/30] Sort SVG sanitizer attribute lists alphabetically Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 316 +++++++++++----------- 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 9726e3e4f71b4b..850553eb64a160 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -190,7 +190,7 @@ protected function sanitize_icon_content( $icon_content ) { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. $core_attributes = array_fill_keys( - array( 'id', 'class', 'style', 'data-*' ), + array( 'class', 'data-*', 'id', 'style' ), true ); @@ -252,8 +252,8 @@ protected function sanitize_icon_content( $icon_content ) { 'aria-valuemin', 'aria-valuenow', 'aria-valuetext', - 'role', 'focusable', + 'role', 'tabindex', ), true @@ -262,50 +262,50 @@ protected function sanitize_icon_content( $icon_content ) { // Presentation attributes for graphics elements (shapes, text, use, image). $presentation_attributes = array_fill_keys( array( + 'clip-path', + 'clip-rule', + 'color', + 'color-interpolation', + 'color-rendering', + 'display', 'fill', 'fill-opacity', 'fill-rule', + 'filter', + 'mask', + 'opacity', + 'paint-order', 'stroke', - 'stroke-width', + 'stroke-dasharray', + 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', - 'stroke-dasharray', - 'stroke-dashoffset', 'stroke-opacity', - 'opacity', + 'stroke-width', 'transform', - 'clip-path', - 'clip-rule', - 'mask', - 'filter', - 'visibility', - 'display', - 'color', - 'color-interpolation', - 'color-rendering', 'vector-effect', - 'paint-order', + 'visibility', ), true ); // Marker attributes (only for shape elements). $marker_attributes = array_fill_keys( - array( 'marker-start', 'marker-mid', 'marker-end' ), + array( 'marker-end', 'marker-mid', 'marker-start' ), true ); // Container attributes for grouping elements. $container_attributes = array_fill_keys( array( - 'transform', 'clip-path', - 'mask', - 'filter', - 'visibility', 'display', + 'filter', + 'mask', 'opacity', + 'transform', + 'visibility', ), true ); @@ -325,13 +325,13 @@ protected function sanitize_icon_content( $icon_content ) { $presentation_attributes, array_fill_keys( array( - 'xmlns', - 'xmlns:xlink', - 'width', 'height', - 'viewbox', 'preserveaspectratio', + 'viewbox', + 'width', 'x', + 'xmlns', + 'xmlns:xlink', 'y', ), true @@ -426,12 +426,12 @@ protected function sanitize_icon_content( $icon_content ) { $marker_attributes, array_fill_keys( array( - 'x', - 'y', - 'width', 'height', 'rx', 'ry', + 'width', + 'x', + 'y', ), true ) @@ -447,10 +447,10 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( - 'viewbox', 'preserveaspectratio', - 'zoomandpan', + 'viewbox', 'viewtarget', + 'zoomandpan', ), true ) @@ -461,12 +461,12 @@ protected function sanitize_icon_content( $icon_content ) { $container_attributes, array_fill_keys( array( - 'viewbox', + 'height', 'preserveaspectratio', + 'viewbox', + 'width', 'x', 'y', - 'width', - 'height', ), true ) @@ -477,12 +477,12 @@ protected function sanitize_icon_content( $icon_content ) { $presentation_attributes, array_fill_keys( array( + 'height', 'href', - 'xlink:href', + 'width', 'x', + 'xlink:href', 'y', - 'width', - 'height', ), true ) @@ -501,10 +501,10 @@ protected function sanitize_icon_content( $icon_content ) { array_fill_keys( array( 'href', - 'xlink:href', - 'target', 'rel', + 'target', 'type', + 'xlink:href', ), true ) @@ -523,12 +523,12 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( - 'x', - 'y', - 'width', 'height', - 'maskunits', 'maskcontentunits', + 'maskunits', + 'width', + 'x', + 'y', ), true ) @@ -538,15 +538,15 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( + 'gradienttransform', + 'gradientunits', + 'href', + 'spreadmethod', 'x1', 'x2', + 'xlink:href', 'y1', 'y2', - 'gradientunits', - 'gradienttransform', - 'spreadmethod', - 'href', - 'xlink:href', ), true ) @@ -557,14 +557,14 @@ protected function sanitize_icon_content( $icon_content ) { array( 'cx', 'cy', - 'r', + 'fr', 'fx', 'fy', - 'fr', - 'gradientunits', 'gradienttransform', - 'spreadmethod', + 'gradientunits', 'href', + 'r', + 'spreadmethod', 'xlink:href', ), true @@ -586,17 +586,17 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( - 'x', - 'y', - 'width', 'height', - 'patternunits', + 'href', 'patterncontentunits', 'patterntransform', - 'viewbox', + 'patternunits', 'preserveaspectratio', - 'href', + 'viewbox', + 'width', + 'x', 'xlink:href', + 'y', ), true ) @@ -606,12 +606,12 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( - 'x', - 'y', - 'width', - 'height', 'filterunits', + 'height', 'primitiveunits', + 'width', + 'x', + 'y', ), true ) @@ -628,9 +628,9 @@ protected function sanitize_icon_content( $icon_content ) { 'fecolormatrix' => array_fill_keys( array( 'in', + 'result', 'type', 'values', - 'result', ), true ), @@ -645,36 +645,36 @@ protected function sanitize_icon_content( $icon_content ) { array( 'in', 'in2', - 'operator', 'k1', 'k2', 'k3', 'k4', + 'operator', 'result', ), true ), 'feconvolvematrix' => array_fill_keys( array( - 'in', - 'order', - 'kernelmatrix', - 'divisor', 'bias', - 'targetx', - 'targety', + 'divisor', 'edgemode', + 'in', + 'kernelmatrix', + 'order', 'preservealpha', 'result', + 'targetx', + 'targety', ), true ), 'fediffuselighting' => array_fill_keys( array( - 'in', - 'surfacescale', 'diffuseconstant', + 'in', 'result', + 'surfacescale', ), true ), @@ -682,10 +682,10 @@ protected function sanitize_icon_content( $icon_content ) { array( 'in', 'in2', + 'result', 'scale', 'xchannelselector', 'ychannelselector', - 'result', ), true ), @@ -706,19 +706,19 @@ protected function sanitize_icon_content( $icon_content ) { ), 'fegaussianblur' => array_fill_keys( array( - 'in', - 'stddeviation', 'edgemode', + 'in', 'result', + 'stddeviation', ), true ), 'feimage' => array_fill_keys( array( 'href', - 'xlink:href', 'preserveaspectratio', 'result', + 'xlink:href', ), true ), @@ -745,9 +745,9 @@ protected function sanitize_icon_content( $icon_content ) { ), 'feoffset' => array_fill_keys( array( - 'in', 'dx', 'dy', + 'in', 'result', ), true @@ -763,23 +763,23 @@ protected function sanitize_icon_content( $icon_content ) { 'fespecularlighting' => array_fill_keys( array( 'in', - 'surfacescale', + 'result', 'specularconstant', 'specularexponent', - 'result', + 'surfacescale', ), true ), 'fespotlight' => array_fill_keys( array( - 'x', - 'y', - 'z', + 'limitingconeangle', 'pointsatx', 'pointsaty', 'pointsatz', 'specularexponent', - 'limitingconeangle', + 'x', + 'y', + 'z', ), true ), @@ -794,58 +794,58 @@ protected function sanitize_icon_content( $icon_content ) { array( 'basefrequency', 'numoctaves', + 'result', 'seed', 'stitchtiles', 'type', - 'result', ), true ), 'fefunca' => array_fill_keys( array( - 'type', - 'tablevalues', - 'slope', - 'intercept', 'amplitude', 'exponent', + 'intercept', 'offset', + 'slope', + 'tablevalues', + 'type', ), true ), 'fefuncb' => array_fill_keys( array( - 'type', - 'tablevalues', - 'slope', - 'intercept', 'amplitude', 'exponent', + 'intercept', 'offset', + 'slope', + 'tablevalues', + 'type', ), true ), 'fefuncg' => array_fill_keys( array( - 'type', - 'tablevalues', - 'slope', - 'intercept', 'amplitude', 'exponent', + 'intercept', 'offset', + 'slope', + 'tablevalues', + 'type', ), true ), 'fefuncr' => array_fill_keys( array( - 'type', - 'tablevalues', - 'slope', - 'intercept', 'amplitude', 'exponent', + 'intercept', 'offset', + 'slope', + 'tablevalues', + 'type', ), true ), @@ -856,26 +856,26 @@ protected function sanitize_icon_content( $icon_content ) { $presentation_attributes, array_fill_keys( array( - 'x', - 'y', + 'alignment-baseline', + 'baseline-shift', + 'dominant-baseline', 'dx', 'dy', - 'rotate', - 'textlength', - 'lengthadjust', - 'text-anchor', 'font-family', 'font-size', - 'font-weight', 'font-style', 'font-variant', - 'text-decoration', - 'writing-mode', + 'font-weight', + 'lengthadjust', 'letter-spacing', + 'rotate', + 'text-anchor', + 'text-decoration', + 'textlength', 'word-spacing', - 'dominant-baseline', - 'alignment-baseline', - 'baseline-shift', + 'writing-mode', + 'x', + 'y', ), true ) @@ -886,19 +886,19 @@ protected function sanitize_icon_content( $icon_content ) { $presentation_attributes, array_fill_keys( array( - 'x', - 'y', 'dx', 'dy', - 'rotate', - 'textlength', - 'lengthadjust', - 'text-anchor', 'font-family', 'font-size', - 'font-weight', 'font-style', + 'font-weight', + 'lengthadjust', + 'rotate', + 'text-anchor', 'text-decoration', + 'textlength', + 'x', + 'y', ), true ) @@ -910,11 +910,11 @@ protected function sanitize_icon_content( $icon_content ) { array_fill_keys( array( 'href', - 'xlink:href', - 'startoffset', 'method', 'spacing', + 'startoffset', 'text-anchor', + 'xlink:href', ), true ) @@ -930,13 +930,13 @@ protected function sanitize_icon_content( $icon_content ) { $presentation_attributes, array_fill_keys( array( - 'x', - 'y', - 'width', 'height', 'href', - 'xlink:href', 'preserveaspectratio', + 'width', + 'x', + 'xlink:href', + 'y', ), true ) @@ -946,13 +946,13 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( + 'markerheight', 'markerunits', - 'refx', - 'refy', 'markerwidth', - 'markerheight', 'orient', 'preserveaspectratio', + 'refx', + 'refy', 'viewbox', ), true @@ -963,19 +963,19 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( + 'accumulate', + 'additive', 'attributename', - 'from', - 'to', - 'dur', - 'repeatcount', 'begin', + 'calcmode', + 'dur', 'end', - 'values', - 'keytimes', + 'from', 'keysplines', - 'calcmode', - 'additive', - 'accumulate', + 'keytimes', + 'repeatcount', + 'to', + 'values', ), true ) @@ -984,21 +984,21 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( - 'path', - 'keypoints', - 'rotate', - 'keytimes', - 'keysplines', + 'accumulate', + 'additive', + 'begin', 'calcmode', + 'dur', + 'end', 'from', + 'keypoints', + 'keysplines', + 'keytimes', + 'path', + 'repeatcount', + 'rotate', 'to', 'values', - 'dur', - 'repeatcount', - 'begin', - 'end', - 'additive', - 'accumulate', ), true ) @@ -1007,20 +1007,20 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, array_fill_keys( array( + 'accumulate', + 'additive', 'attributename', - 'type', - 'from', - 'to', - 'dur', - 'repeatcount', 'begin', + 'calcmode', + 'dur', 'end', - 'values', - 'keytimes', + 'from', 'keysplines', - 'calcmode', - 'additive', - 'accumulate', + 'keytimes', + 'repeatcount', + 'to', + 'type', + 'values', ), true ) @@ -1030,11 +1030,11 @@ protected function sanitize_icon_content( $icon_content ) { array_fill_keys( array( 'attributename', - 'to', 'begin', 'dur', 'end', 'repeatcount', + 'to', ), true ) From e39539b8b17fc2677db874766334f3aafd67b875 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 19 Jun 2026 15:46:07 +0900 Subject: [PATCH 26/30] Extract get_allowed_attribute_list() helper to build kses attribute lists Replace the repeated array_fill_keys( array( ... ), true ) calls in sanitize_icon_content() with a single variadic helper, removing the boilerplate duplicated across every SVG element definition. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 1131 +++++++++------------ 1 file changed, 485 insertions(+), 646 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 850553eb64a160..90fd69f508e9f3 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -171,6 +171,16 @@ protected function register( $icon_name, $icon_properties ) { return true; } + /** + * Builds the allowed attribute list for wp_kses() from attribute names. + * + * @param non-empty-string ...$attribute_names Attribute names to allow. + * @return array Attribute names mapped to true. + */ + private function get_allowed_attribute_list( ...$attribute_names ): array { + return array_fill_keys( $attribute_names, true ); + } + /** * Sanitizes the icon SVG content. * @@ -189,10 +199,7 @@ protected function register( $icon_name, $icon_properties ) { protected function sanitize_icon_content( $icon_content ) { // Core attributes applicable to most elements. `data-*` is a wildcard // supported by wp_kses() and matches any data attribute. - $core_attributes = array_fill_keys( - array( 'class', 'data-*', 'id', 'style' ), - true - ); + $core_attributes = $this->get_allowed_attribute_list( 'class', 'data-*', 'id', 'style' ); /* * ARIA and accessibility attributes. wp_kses() does not support an @@ -201,113 +208,101 @@ protected function sanitize_icon_content( $icon_content ) { * * @link https://www.w3.org/TR/wai-aria-1.2/#state_prop_def */ - $aria_attributes = array_fill_keys( - array( - 'aria-activedescendant', - 'aria-atomic', - 'aria-autocomplete', - 'aria-busy', - 'aria-checked', - 'aria-colcount', - 'aria-colindex', - 'aria-colspan', - 'aria-controls', - 'aria-current', - 'aria-describedby', - 'aria-description', - 'aria-details', - 'aria-disabled', - 'aria-dropeffect', - 'aria-errormessage', - 'aria-expanded', - 'aria-flowto', - 'aria-grabbed', - 'aria-haspopup', - 'aria-hidden', - 'aria-invalid', - 'aria-keyshortcuts', - 'aria-label', - 'aria-labelledby', - 'aria-level', - 'aria-live', - 'aria-modal', - 'aria-multiline', - 'aria-multiselectable', - 'aria-orientation', - 'aria-owns', - 'aria-placeholder', - 'aria-posinset', - 'aria-pressed', - 'aria-readonly', - 'aria-relevant', - 'aria-required', - 'aria-roledescription', - 'aria-rowcount', - 'aria-rowindex', - 'aria-rowspan', - 'aria-selected', - 'aria-setsize', - 'aria-sort', - 'aria-valuemax', - 'aria-valuemin', - 'aria-valuenow', - 'aria-valuetext', - 'focusable', - 'role', - 'tabindex', - ), - true + $aria_attributes = $this->get_allowed_attribute_list( + 'aria-activedescendant', + 'aria-atomic', + 'aria-autocomplete', + 'aria-busy', + 'aria-checked', + 'aria-colcount', + 'aria-colindex', + 'aria-colspan', + 'aria-controls', + 'aria-current', + 'aria-describedby', + 'aria-description', + 'aria-details', + 'aria-disabled', + 'aria-dropeffect', + 'aria-errormessage', + 'aria-expanded', + 'aria-flowto', + 'aria-grabbed', + 'aria-haspopup', + 'aria-hidden', + 'aria-invalid', + 'aria-keyshortcuts', + 'aria-label', + 'aria-labelledby', + 'aria-level', + 'aria-live', + 'aria-modal', + 'aria-multiline', + 'aria-multiselectable', + 'aria-orientation', + 'aria-owns', + 'aria-placeholder', + 'aria-posinset', + 'aria-pressed', + 'aria-readonly', + 'aria-relevant', + 'aria-required', + 'aria-roledescription', + 'aria-rowcount', + 'aria-rowindex', + 'aria-rowspan', + 'aria-selected', + 'aria-setsize', + 'aria-sort', + 'aria-valuemax', + 'aria-valuemin', + 'aria-valuenow', + 'aria-valuetext', + 'focusable', + 'role', + 'tabindex', ); // Presentation attributes for graphics elements (shapes, text, use, image). - $presentation_attributes = array_fill_keys( - array( - 'clip-path', - 'clip-rule', - 'color', - 'color-interpolation', - 'color-rendering', - 'display', - 'fill', - 'fill-opacity', - 'fill-rule', - 'filter', - 'mask', - 'opacity', - 'paint-order', - 'stroke', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-linecap', - 'stroke-linejoin', - 'stroke-miterlimit', - 'stroke-opacity', - 'stroke-width', - 'transform', - 'vector-effect', - 'visibility', - ), - true + $presentation_attributes = $this->get_allowed_attribute_list( + 'clip-path', + 'clip-rule', + 'color', + 'color-interpolation', + 'color-rendering', + 'display', + 'fill', + 'fill-opacity', + 'fill-rule', + 'filter', + 'mask', + 'opacity', + 'paint-order', + 'stroke', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke-width', + 'transform', + 'vector-effect', + 'visibility', ); // Marker attributes (only for shape elements). - $marker_attributes = array_fill_keys( - array( 'marker-end', 'marker-mid', 'marker-start' ), - true - ); + $marker_attributes = $this->get_allowed_attribute_list( 'marker-end', 'marker-mid', 'marker-start' ); // Container attributes for grouping elements. - $container_attributes = array_fill_keys( - array( - 'clip-path', - 'display', - 'filter', - 'mask', - 'opacity', - 'transform', - 'visibility', - ), - true + $container_attributes = $this->get_allowed_attribute_list( + 'clip-path', + 'display', + 'filter', + 'mask', + 'opacity', + 'transform', + 'visibility', ); /* @@ -323,18 +318,15 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'height', - 'preserveaspectratio', - 'viewbox', - 'width', - 'x', - 'xmlns', - 'xmlns:xlink', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'preserveaspectratio', + 'viewbox', + 'width', + 'x', + 'xmlns', + 'xmlns:xlink', + 'y', ) ), // Basic shape elements (with markers). @@ -343,12 +335,9 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'd', - 'pathlength', - ), - true + $this->get_allowed_attribute_list( + 'd', + 'pathlength', ) ), 'circle' => array_merge( @@ -356,13 +345,10 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'cx', - 'cy', - 'r', - ), - true + $this->get_allowed_attribute_list( + 'cx', + 'cy', + 'r', ) ), 'ellipse' => array_merge( @@ -370,14 +356,11 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'cx', - 'cy', - 'rx', - 'ry', - ), - true + $this->get_allowed_attribute_list( + 'cx', + 'cy', + 'rx', + 'ry', ) ), 'line' => array_merge( @@ -385,14 +368,11 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'x1', - 'x2', - 'y1', - 'y2', - ), - true + $this->get_allowed_attribute_list( + 'x1', + 'x2', + 'y1', + 'y2', ) ), 'polygon' => array_merge( @@ -400,11 +380,8 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'points', - ), - true + $this->get_allowed_attribute_list( + 'points', ) ), 'polyline' => array_merge( @@ -412,11 +389,8 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'points', - ), - true + $this->get_allowed_attribute_list( + 'points', ) ), 'rect' => array_merge( @@ -424,16 +398,13 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'height', - 'rx', - 'ry', - 'width', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'rx', + 'ry', + 'width', + 'x', + 'y', ) ), // Grouping and structural elements. @@ -445,46 +416,37 @@ protected function sanitize_icon_content( $icon_content ) { 'defs' => $core_attributes, 'view' => array_merge( $core_attributes, - array_fill_keys( - array( - 'preserveaspectratio', - 'viewbox', - 'viewtarget', - 'zoomandpan', - ), - true + $this->get_allowed_attribute_list( + 'preserveaspectratio', + 'viewbox', + 'viewtarget', + 'zoomandpan', ) ), 'symbol' => array_merge( $core_attributes, $aria_attributes, $container_attributes, - array_fill_keys( - array( - 'height', - 'preserveaspectratio', - 'viewbox', - 'width', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'preserveaspectratio', + 'viewbox', + 'width', + 'x', + 'y', ) ), 'use' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'height', - 'href', - 'width', - 'x', - 'xlink:href', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'href', + 'width', + 'x', + 'xlink:href', + 'y', ) ), 'switch' => array_merge( @@ -498,425 +460,320 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $container_attributes, - array_fill_keys( - array( - 'href', - 'rel', - 'target', - 'type', - 'xlink:href', - ), - true + $this->get_allowed_attribute_list( + 'href', + 'rel', + 'target', + 'type', + 'xlink:href', ) ), 'clippath' => array_merge( $core_attributes, - array_fill_keys( - array( - 'clippathunits', - 'transform', - ), - true + $this->get_allowed_attribute_list( + 'clippathunits', + 'transform', ) ), 'mask' => array_merge( $core_attributes, - array_fill_keys( - array( - 'height', - 'maskcontentunits', - 'maskunits', - 'width', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'maskcontentunits', + 'maskunits', + 'width', + 'x', + 'y', ) ), // Gradient elements. 'lineargradient' => array_merge( $core_attributes, - array_fill_keys( - array( - 'gradienttransform', - 'gradientunits', - 'href', - 'spreadmethod', - 'x1', - 'x2', - 'xlink:href', - 'y1', - 'y2', - ), - true + $this->get_allowed_attribute_list( + 'gradienttransform', + 'gradientunits', + 'href', + 'spreadmethod', + 'x1', + 'x2', + 'xlink:href', + 'y1', + 'y2', ) ), 'radialgradient' => array_merge( $core_attributes, - array_fill_keys( - array( - 'cx', - 'cy', - 'fr', - 'fx', - 'fy', - 'gradienttransform', - 'gradientunits', - 'href', - 'r', - 'spreadmethod', - 'xlink:href', - ), - true + $this->get_allowed_attribute_list( + 'cx', + 'cy', + 'fr', + 'fx', + 'fy', + 'gradienttransform', + 'gradientunits', + 'href', + 'r', + 'spreadmethod', + 'xlink:href', ) ), 'stop' => array_merge( $core_attributes, - array_fill_keys( - array( - 'offset', - 'stop-color', - 'stop-opacity', - ), - true + $this->get_allowed_attribute_list( + 'offset', + 'stop-color', + 'stop-opacity', ) ), // Pattern element. 'pattern' => array_merge( $core_attributes, - array_fill_keys( - array( - 'height', - 'href', - 'patterncontentunits', - 'patterntransform', - 'patternunits', - 'preserveaspectratio', - 'viewbox', - 'width', - 'x', - 'xlink:href', - 'y', - ), - true - ) - ), - // Filter elements. - 'filter' => array_merge( - $core_attributes, - array_fill_keys( - array( - 'filterunits', - 'height', - 'primitiveunits', - 'width', - 'x', - 'y', - ), - true - ) - ), - 'feblend' => array_fill_keys( - array( - 'in', - 'in2', - 'mode', - 'result', - ), - true - ), - 'fecolormatrix' => array_fill_keys( - array( - 'in', - 'result', - 'type', - 'values', - ), - true - ), - 'fecomponenttransfer' => array_fill_keys( - array( - 'in', - 'result', - ), - true - ), - 'fecomposite' => array_fill_keys( - array( - 'in', - 'in2', - 'k1', - 'k2', - 'k3', - 'k4', - 'operator', - 'result', - ), - true - ), - 'feconvolvematrix' => array_fill_keys( - array( - 'bias', - 'divisor', - 'edgemode', - 'in', - 'kernelmatrix', - 'order', - 'preservealpha', - 'result', - 'targetx', - 'targety', - ), - true - ), - 'fediffuselighting' => array_fill_keys( - array( - 'diffuseconstant', - 'in', - 'result', - 'surfacescale', - ), - true - ), - 'fedisplacementmap' => array_fill_keys( - array( - 'in', - 'in2', - 'result', - 'scale', - 'xchannelselector', - 'ychannelselector', - ), - true - ), - 'fedistantlight' => array_fill_keys( - array( - 'azimuth', - 'elevation', - ), - true - ), - 'feflood' => array_fill_keys( - array( - 'flood-color', - 'flood-opacity', - 'result', - ), - true - ), - 'fegaussianblur' => array_fill_keys( - array( - 'edgemode', - 'in', - 'result', - 'stddeviation', - ), - true - ), - 'feimage' => array_fill_keys( - array( + $this->get_allowed_attribute_list( + 'height', 'href', + 'patterncontentunits', + 'patterntransform', + 'patternunits', 'preserveaspectratio', - 'result', - 'xlink:href', - ), - true - ), - 'femerge' => array_fill_keys( - array( - 'result', - ), - true - ), - 'femergenode' => array_fill_keys( - array( - 'in', - ), - true - ), - 'femorphology' => array_fill_keys( - array( - 'in', - 'operator', - 'radius', - 'result', - ), - true - ), - 'feoffset' => array_fill_keys( - array( - 'dx', - 'dy', - 'in', - 'result', - ), - true - ), - 'fepointlight' => array_fill_keys( - array( + 'viewbox', + 'width', 'x', + 'xlink:href', 'y', - 'z', - ), - true - ), - 'fespecularlighting' => array_fill_keys( - array( - 'in', - 'result', - 'specularconstant', - 'specularexponent', - 'surfacescale', - ), - true + ) ), - 'fespotlight' => array_fill_keys( - array( - 'limitingconeangle', - 'pointsatx', - 'pointsaty', - 'pointsatz', - 'specularexponent', + // Filter elements. + 'filter' => array_merge( + $core_attributes, + $this->get_allowed_attribute_list( + 'filterunits', + 'height', + 'primitiveunits', + 'width', 'x', 'y', - 'z', - ), - true - ), - 'fetile' => array_fill_keys( - array( - 'in', - 'result', - ), - true - ), - 'feturbulence' => array_fill_keys( - array( - 'basefrequency', - 'numoctaves', - 'result', - 'seed', - 'stitchtiles', - 'type', - ), - true - ), - 'fefunca' => array_fill_keys( - array( - 'amplitude', - 'exponent', - 'intercept', - 'offset', - 'slope', - 'tablevalues', - 'type', - ), - true - ), - 'fefuncb' => array_fill_keys( - array( - 'amplitude', - 'exponent', - 'intercept', - 'offset', - 'slope', - 'tablevalues', - 'type', - ), - true - ), - 'fefuncg' => array_fill_keys( - array( - 'amplitude', - 'exponent', - 'intercept', - 'offset', - 'slope', - 'tablevalues', - 'type', - ), - true + ) ), - 'fefuncr' => array_fill_keys( - array( - 'amplitude', - 'exponent', - 'intercept', - 'offset', - 'slope', - 'tablevalues', - 'type', - ), - true + 'feblend' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'mode', + 'result', + ), + 'fecolormatrix' => $this->get_allowed_attribute_list( + 'in', + 'result', + 'type', + 'values', + ), + 'fecomponenttransfer' => $this->get_allowed_attribute_list( + 'in', + 'result', + ), + 'fecomposite' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'k1', + 'k2', + 'k3', + 'k4', + 'operator', + 'result', + ), + 'feconvolvematrix' => $this->get_allowed_attribute_list( + 'bias', + 'divisor', + 'edgemode', + 'in', + 'kernelmatrix', + 'order', + 'preservealpha', + 'result', + 'targetx', + 'targety', + ), + 'fediffuselighting' => $this->get_allowed_attribute_list( + 'diffuseconstant', + 'in', + 'result', + 'surfacescale', + ), + 'fedisplacementmap' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'result', + 'scale', + 'xchannelselector', + 'ychannelselector', + ), + 'fedistantlight' => $this->get_allowed_attribute_list( + 'azimuth', + 'elevation', + ), + 'feflood' => $this->get_allowed_attribute_list( + 'flood-color', + 'flood-opacity', + 'result', + ), + 'fegaussianblur' => $this->get_allowed_attribute_list( + 'edgemode', + 'in', + 'result', + 'stddeviation', + ), + 'feimage' => $this->get_allowed_attribute_list( + 'href', + 'preserveaspectratio', + 'result', + 'xlink:href', + ), + 'femerge' => $this->get_allowed_attribute_list( + 'result', + ), + 'femergenode' => $this->get_allowed_attribute_list( + 'in', + ), + 'femorphology' => $this->get_allowed_attribute_list( + 'in', + 'operator', + 'radius', + 'result', + ), + 'feoffset' => $this->get_allowed_attribute_list( + 'dx', + 'dy', + 'in', + 'result', + ), + 'fepointlight' => $this->get_allowed_attribute_list( + 'x', + 'y', + 'z', + ), + 'fespecularlighting' => $this->get_allowed_attribute_list( + 'in', + 'result', + 'specularconstant', + 'specularexponent', + 'surfacescale', + ), + 'fespotlight' => $this->get_allowed_attribute_list( + 'limitingconeangle', + 'pointsatx', + 'pointsaty', + 'pointsatz', + 'specularexponent', + 'x', + 'y', + 'z', + ), + 'fetile' => $this->get_allowed_attribute_list( + 'in', + 'result', + ), + 'feturbulence' => $this->get_allowed_attribute_list( + 'basefrequency', + 'numoctaves', + 'result', + 'seed', + 'stitchtiles', + 'type', + ), + 'fefunca' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', + ), + 'fefuncb' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', + ), + 'fefuncg' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', + ), + 'fefuncr' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', ), // Text elements. 'text' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'alignment-baseline', - 'baseline-shift', - 'dominant-baseline', - 'dx', - 'dy', - 'font-family', - 'font-size', - 'font-style', - 'font-variant', - 'font-weight', - 'lengthadjust', - 'letter-spacing', - 'rotate', - 'text-anchor', - 'text-decoration', - 'textlength', - 'word-spacing', - 'writing-mode', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'alignment-baseline', + 'baseline-shift', + 'dominant-baseline', + 'dx', + 'dy', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'lengthadjust', + 'letter-spacing', + 'rotate', + 'text-anchor', + 'text-decoration', + 'textlength', + 'word-spacing', + 'writing-mode', + 'x', + 'y', ) ), 'tspan' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'dx', - 'dy', - 'font-family', - 'font-size', - 'font-style', - 'font-weight', - 'lengthadjust', - 'rotate', - 'text-anchor', - 'text-decoration', - 'textlength', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'dx', + 'dy', + 'font-family', + 'font-size', + 'font-style', + 'font-weight', + 'lengthadjust', + 'rotate', + 'text-anchor', + 'text-decoration', + 'textlength', + 'x', + 'y', ) ), 'textpath' => array_merge( $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'href', - 'method', - 'spacing', - 'startoffset', - 'text-anchor', - 'xlink:href', - ), - true + $this->get_allowed_attribute_list( + 'href', + 'method', + 'spacing', + 'startoffset', + 'text-anchor', + 'xlink:href', ) ), // Descriptive elements. @@ -928,115 +785,97 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'height', - 'href', - 'preserveaspectratio', - 'width', - 'x', - 'xlink:href', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'href', + 'preserveaspectratio', + 'width', + 'x', + 'xlink:href', + 'y', ) ), // Marker element. 'marker' => array_merge( $core_attributes, - array_fill_keys( - array( - 'markerheight', - 'markerunits', - 'markerwidth', - 'orient', - 'preserveaspectratio', - 'refx', - 'refy', - 'viewbox', - ), - true + $this->get_allowed_attribute_list( + 'markerheight', + 'markerunits', + 'markerwidth', + 'orient', + 'preserveaspectratio', + 'refx', + 'refy', + 'viewbox', ) ), // Animation elements. 'animate' => array_merge( $core_attributes, - array_fill_keys( - array( - 'accumulate', - 'additive', - 'attributename', - 'begin', - 'calcmode', - 'dur', - 'end', - 'from', - 'keysplines', - 'keytimes', - 'repeatcount', - 'to', - 'values', - ), - true + $this->get_allowed_attribute_list( + 'accumulate', + 'additive', + 'attributename', + 'begin', + 'calcmode', + 'dur', + 'end', + 'from', + 'keysplines', + 'keytimes', + 'repeatcount', + 'to', + 'values', ) ), 'animatemotion' => array_merge( $core_attributes, - array_fill_keys( - array( - 'accumulate', - 'additive', - 'begin', - 'calcmode', - 'dur', - 'end', - 'from', - 'keypoints', - 'keysplines', - 'keytimes', - 'path', - 'repeatcount', - 'rotate', - 'to', - 'values', - ), - true + $this->get_allowed_attribute_list( + 'accumulate', + 'additive', + 'begin', + 'calcmode', + 'dur', + 'end', + 'from', + 'keypoints', + 'keysplines', + 'keytimes', + 'path', + 'repeatcount', + 'rotate', + 'to', + 'values', ) ), 'animatetransform' => array_merge( $core_attributes, - array_fill_keys( - array( - 'accumulate', - 'additive', - 'attributename', - 'begin', - 'calcmode', - 'dur', - 'end', - 'from', - 'keysplines', - 'keytimes', - 'repeatcount', - 'to', - 'type', - 'values', - ), - true + $this->get_allowed_attribute_list( + 'accumulate', + 'additive', + 'attributename', + 'begin', + 'calcmode', + 'dur', + 'end', + 'from', + 'keysplines', + 'keytimes', + 'repeatcount', + 'to', + 'type', + 'values', ) ), 'set' => array_merge( $core_attributes, - array_fill_keys( - array( - 'attributename', - 'begin', - 'dur', - 'end', - 'repeatcount', - 'to', - ), - true + $this->get_allowed_attribute_list( + 'attributename', + 'begin', + 'dur', + 'end', + 'repeatcount', + 'to', ) ), ); From b88395f89272ce092ceae0e7bee833e2938f679f Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:34:11 +0900 Subject: [PATCH 27/30] Fix incorrect PHPDoc tags Co-authored-by: Weston Ruter --- lib/class-wp-icons-registry-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index d79cfa5b16ff4d..2e6678521e4b0b 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -201,7 +201,7 @@ protected function sanitize_icon_content( $icon_content ) { // supported by wp_kses() and matches any data attribute. $core_attributes = $this->get_allowed_attribute_list( 'class', 'data-*', 'id', 'style' ); - /* + /** * ARIA and accessibility attributes. wp_kses() does not support an * `aria-*` wildcard, so every ARIA state and property is listed * explicitly. The list mirrors the WAI-ARIA states and properties. @@ -305,7 +305,7 @@ protected function sanitize_icon_content( $icon_content ) { 'visibility', ); - /* + /** * Allowed tags for wp_kses(). WP_HTML_Processor::normalize() with * constraints (similar structure to this array) is proposed to improve * HTML/SVG sanitization in the future. From 1fe8e6c02b811d2c106994a5a20a42cd3fb617ec Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 22 Jun 2026 21:47:21 +0900 Subject: [PATCH 28/30] Resolve leftover merge conflict in icons registry test The trunk merge committed unresolved conflict markers, causing a PHP parse error. Keep both branches' tests and move the sanitizer tests to the end of the class. Co-Authored-By: Claude --- ...class-wp-icons-registry-gutenberg-test.php | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 26da2030052101..1783a899991983 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -140,7 +140,64 @@ public function test_register_invalid_name() { } /** -<<<<<<< HEAD + * Should register an icon that provides its content through `file_path`. + */ + public function test_register_icon_with_file_path() { + $file_path = tempnam( get_temp_dir(), 'gutenberg-icon-' ); + file_put_contents( $file_path, '' ); + + $name = 'test-plugin/file-path-icon'; + $settings = array( + 'label' => 'Icon', + 'file_path' => $file_path, + ); + + $result = $this->register( $name, $settings ); + $this->assertTrue( $result ); + $this->assertTrue( $this->registry->is_registered( $name ) ); + + $registered_icons = $this->registry->get_registered_icons( $name ); + $this->assertCount( 1, $registered_icons ); + $this->assertStringContainsString( ' 'Icon', + 'content' => '', + 'file_path' => '/path/to/icon.svg', + ); + + $result = $this->register( $name, $settings ); + $this->assertFalse( $result ); + $this->assertFalse( $this->registry->is_registered( $name ) ); + } + + /** + * Should fail to register an icon that provides neither `content` nor `file_path`. + * + * @expectedIncorrectUsage WP_Icons_Registry_Gutenberg::register + */ + public function test_register_icon_without_content_or_file_path() { + $name = 'test-plugin/no-content'; + $settings = array( + 'label' => 'Icon', + ); + + $result = $this->register( $name, $settings ); + $this->assertFalse( $result ); + $this->assertFalse( $this->registry->is_registered( $name ) ); + } + + /** * @dataProvider data_sanitize_icon_content * @covers WP_Icons_Registry_Gutenberg::sanitize_icon_content * @@ -321,62 +378,5 @@ public function data_sanitize_icon_content() { '', ), ); -======= - * Should register an icon that provides its content through `file_path`. - */ - public function test_register_icon_with_file_path() { - $file_path = tempnam( get_temp_dir(), 'gutenberg-icon-' ); - file_put_contents( $file_path, '' ); - - $name = 'test-plugin/file-path-icon'; - $settings = array( - 'label' => 'Icon', - 'file_path' => $file_path, - ); - - $result = $this->register( $name, $settings ); - $this->assertTrue( $result ); - $this->assertTrue( $this->registry->is_registered( $name ) ); - - $registered_icons = $this->registry->get_registered_icons( $name ); - $this->assertCount( 1, $registered_icons ); - $this->assertStringContainsString( ' 'Icon', - 'content' => '', - 'file_path' => '/path/to/icon.svg', - ); - - $result = $this->register( $name, $settings ); - $this->assertFalse( $result ); - $this->assertFalse( $this->registry->is_registered( $name ) ); - } - - /** - * Should fail to register an icon that provides neither `content` nor `file_path`. - * - * @expectedIncorrectUsage WP_Icons_Registry_Gutenberg::register - */ - public function test_register_icon_without_content_or_file_path() { - $name = 'test-plugin/no-content'; - $settings = array( - 'label' => 'Icon', - ); - - $result = $this->register( $name, $settings ); - $this->assertFalse( $result ); - $this->assertFalse( $this->registry->is_registered( $name ) ); ->>>>>>> trunk } } From f203e7dc60f99d4aac3d23671cb4fd95f5eb0d5a Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Tue, 23 Jun 2026 16:12:57 +0900 Subject: [PATCH 29/30] Icons: Reject foreign-namespaced in sanitizer The SVG root check matched on tag name alone, so an in a foreign namespace (e.g. the MathML-namespaced inside ) was treated as the icon root. Also require the element to be in the SVG namespace so such elements are rejected. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 2e6678521e4b0b..9ceef1c4c056eb 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -903,7 +903,8 @@ protected function sanitize_icon_content( $icon_content ) { return ''; } - if ( 'SVG' !== $processor->get_tag() ) { + // Require the SVG namespace to reject a foreign-namespaced ``. + if ( 'SVG' !== $processor->get_tag() || 'svg' !== $processor->get_namespace() ) { return ''; } From f4c78d9999d851004a984e8b704b1ae6efa51534 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Tue, 23 Jun 2026 16:54:56 +0900 Subject: [PATCH 30/30] Icons: Relax SVG extraction but reject multiple roots Locate the icon root via next_tag( 'SVG' ) and ignore content surrounding it (XML declaration, doctype, comments, sibling elements), so real-world SVG files that wrap the root are accepted. Surrounding content is only ignored, never emitted, so wp_kses still bounds what is output. A second top-level SVG is still rejected to keep the icon unambiguous, and the namespace check continues to reject a foreign-namespaced . Reorganize the sanitizer data provider so groups follow a single criterion (behavior, labeled by comment; element category in the key) and add cases for multiple/nested/foreign-namespaced roots and ignored surrounding content. Co-Authored-By: Claude --- lib/class-wp-icons-registry-gutenberg.php | 32 ++--- ...class-wp-icons-registry-gutenberg-test.php | 128 +++++++++--------- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 9ceef1c4c056eb..7c3f1b2dfb95ce 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -885,26 +885,9 @@ protected function sanitize_icon_content( $icon_content ) { return ''; } - // Skip leading comments, XML declarations, doctype, and whitespace to - // reach the root SVG element. - while ( $processor->next_token() ) { - $token_type = $processor->get_token_type(); - if ( '#tag' === $token_type ) { - break; - } - if ( - '#comment' === $token_type - || '#doctype' === $token_type - || ( '#text' === $token_type && '' === trim( $processor->get_modifiable_text() ) ) - ) { - continue; - } - // Any other leading token (e.g. non-whitespace text) is invalid. - return ''; - } - - // Require the SVG namespace to reject a foreign-namespaced ``. - if ( 'SVG' !== $processor->get_tag() || 'svg' !== $processor->get_namespace() ) { + // Find the first SVG root, ignoring surrounding content. The namespace + // check rejects a foreign-namespaced ``, such as in ``. + if ( ! $processor->next_tag( 'SVG' ) || 'svg' !== $processor->get_namespace() ) { return ''; } @@ -920,6 +903,15 @@ protected function sanitize_icon_content( $icon_content ) { return ''; } $svg .= ''; + + // Reject more than one top-level SVG. Nested SVGs were extracted above, + // so only sibling roots remain to be found. + while ( $processor->next_tag( 'SVG' ) ) { + if ( 'svg' === $processor->get_namespace() ) { + return ''; + } + } + return wp_kses( $svg, $allowed_tags ); } diff --git a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php index 1783a899991983..7fa3fa75a5a85b 100644 --- a/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/experimental/class-wp-icons-registry-gutenberg-test.php @@ -230,44 +230,43 @@ public function data_sanitize_icon_content() { : ''; return array( - 'extracts only first svg when multiple present' => array( + // Root selection: exactly one SVG element in the SVG namespace. + 'rejects multiple top-level svg elements' => array( '', - '', - ), - 'returns empty svg when html-like tags present' => array( - '

paragraph content

div content
', - '', + '', ), - 'handles xmlns:xlink namespace attribute' => array( - '', - '', + 'allows nested svg' => array( + '', + '', ), - // Dangerous content is stripped (wp_kses). - 'strips foreignObject but keeps text content' => array( - '

paragraph content

', - 'paragraph contentalert(1)', + 'rejects svg in a foreign namespace' => array( + '', + '', ), - 'strips script tags' => array( - '', - 'alert(1)', + + // Content surrounding the SVG root is ignored. + 'ignores content preceding the svg' => array( + '

before

', + '', ), - 'strips event handlers' => array( - '', - '', + 'ignores content following the svg' => array( + '

after

', + '', ), - 'strips javascript protocol in href' => array( - '', - '', + 'ignores an xml declaration before the svg' => array( + '', + '', ), - 'strips data protocol in href' => array( - '', - '', + 'ignores a comment before the svg' => array( + '', + '', ), - 'strips disallowed tags' => array( - '', + 'ignores whitespace before the svg' => array( + " \n\t", '', ), - // Returns empty string when input is not SVG. + + // Non-SVG or unparseable input returns an empty string. 'returns empty for empty string' => array( '', '', @@ -280,38 +279,62 @@ public function data_sanitize_icon_content() { 'plain text without svg', '', ), - 'returns empty for html without svg' => array( + 'returns empty for html without an svg' => array( '
not svg

content

', '', ), - 'returns empty when svg is not first element' => array( - '

before

', + 'returns empty for incomplete markup' => array( + ' array( - '', - '', + 'returns empty for unsupported markup' => array( + '
TEXT NOT SUPPORTED HERE!', + '', ), - 'extracts svg after leading comment' => array( - '', - '', + + // Disallowed or dangerous content is stripped (wp_kses). + 'strips html-like tags inside svg' => array( + '

paragraph content

div content
', + '', ), - 'extracts svg after leading whitespace' => array( - " \n\t", + 'strips foreignObject but keeps text content' => array( + '

paragraph content

', + 'paragraph contentalert(1)', + ), + 'strips script tags' => array( + '', + 'alert(1)', + ), + 'strips event handler attributes' => array( + '', + '', + ), + 'strips javascript protocol in href' => array( + '', + '', + ), + 'strips data protocol in href' => array( + '', + '', + ), + 'strips disallowed tags' => array( + '', '', ), - // Root SVG element. - 'preserves root svg element' => array( + + // Allowed SVG elements and attributes are preserved. + 'preserves the root svg element' => array( '', '', ), - // Basic shape elements. + 'preserves xmlns:xlink attribute' => array( + '', + '', + ), 'preserves basic shape elements' => array( '', '', ), - // Grouping and structural elements. 'preserves grouping and structural elements' => array( '', '', @@ -328,55 +351,38 @@ public function data_sanitize_icon_content() { '', '', ), - // Gradient elements. 'preserves gradient elements' => array( '', '', ), - // Pattern element. 'preserves pattern element' => array( '', '', ), - // Filter elements. 'preserves filter elements' => array( '', '', ), - // Text elements. 'preserves text elements' => array( 'ABpath', 'ABpath', ), - // Descriptive elements. 'preserves descriptive elements' => array( 'Icon titleDescription', 'Icon titleDescription', ), - // Image element. 'preserves image element' => array( '', '', ), - // Marker element. 'preserves marker element' => array( '', '', ), - // Animation elements. 'preserves animation elements' => array( '', '', ), - // Returns empty string when the processor cannot fully parse the SVG. - 'returns empty when paused on incomplete token' => array( - ' array( - '
TEXT NOT SUPPORTED HERE!', - '', - ), ); } }