From 1a001f99b8f82d951e3f729c2087e0b1ffc7f405 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 15:41:48 +0900 Subject: [PATCH 1/3] Icons: Improve the SVG sanitizer in WP_Icons_Registry. Replace the minimal twentytwenty-derived allowlist in WP_Icons_Registry::sanitize_icon_content() with a comprehensive SVG-aware sanitizer. The content is first parsed with WP_HTML_Processor to extract the root SVG element in its entirety before wp_kses() runs. This prevents HTML tags such as

inside the markup from prematurely terminating the SVG element when parsed as HTML, and correctly handles self-closing tags and SVG structure. The allowlist now covers SVG-friendly elements and attributes including shapes, gradients, patterns, filters, text, markers, animations, and core/ARIA/presentation attributes, since core still lacks a dedicated SVG sanitization function. Adds unit tests covering extraction, sanitization of dangerous content, and preservation of valid SVG markup. Ported from Gutenberg PR 75550. Co-Authored-By: Claude --- src/wp-includes/class-wp-icons-registry.php | 920 +++++++++++++++++- tests/phpunit/tests/icons/wpIconsRegistry.php | 199 ++++ 2 files changed, 1094 insertions(+), 25 deletions(-) create mode 100644 tests/phpunit/tests/icons/wpIconsRegistry.php diff --git a/src/wp-includes/class-wp-icons-registry.php b/src/wp-includes/class-wp-icons-registry.php index f82739fc5d91d..a234a8b65cb31 100644 --- a/src/wp-includes/class-wp-icons-registry.php +++ b/src/wp-includes/class-wp-icons-registry.php @@ -186,8 +186,10 @@ protected function register( $icon_name, $icon_properties ) { /** * Sanitizes the icon SVG content. * - * Logic borrowed from twentytwenty. - * @see twentytwenty_get_theme_svg + * 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. * * @since 7.0.0 * @@ -195,32 +197,900 @@ 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. `data-*` is a wildcard + // supported by wp_kses() and matches any data attribute. + $core_attributes = array_fill_keys( + array( 'id', 'class', 'style', 'data-*' ), + true + ); + + /* + * 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_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_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_fill_keys( + array( 'marker-start', 'marker-mid', 'marker-end' ), + true + ); + + // Container attributes for grouping elements. + $container_attributes = array_fill_keys( + array( + 'transform', + 'clip-path', + 'mask', + 'filter', + 'visibility', + 'display', + '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. + * + * @link https://github.com/dmsnell/wordpress-develop/pull/20 + */ $allowed_tags = array( - 'svg' => array( - 'class' => true, - 'xmlns' => true, - 'width' => true, - 'height' => true, - 'viewbox' => true, - 'aria-hidden' => true, - 'role' => true, - 'focusable' => true, - ), - 'path' => array( - 'fill' => true, - 'fill-rule' => true, - 'd' => true, - 'transform' => true, - ), - 'polygon' => array( - 'fill' => true, - 'fill-rule' => true, - 'points' => true, - 'transform' => true, - 'focusable' => true, + // Root SVG element. + 'svg' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array_fill_keys( + array( + 'xmlns', + 'xmlns:xlink', + 'width', + 'height', + 'viewbox', + 'preserveaspectratio', + 'x', + 'y', + ), + true + ) + ), + // Basic shape elements (with markers). + 'path' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'd', + 'pathlength', + ), + true + ) + ), + 'circle' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'cx', + 'cy', + 'r', + ), + true + ) + ), + 'ellipse' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'cx', + 'cy', + 'rx', + 'ry', + ), + true + ) + ), + 'line' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'x1', + 'x2', + 'y1', + 'y2', + ), + true + ) + ), + 'polygon' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'points', + ), + true + ) + ), + 'polyline' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'points', + ), + true + ) + ), + 'rect' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + $marker_attributes, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'rx', + 'ry', + ), + true + ) + ), + // Grouping and structural elements. + 'g' => array_merge( + $core_attributes, + $aria_attributes, + $container_attributes + ), + 'defs' => $core_attributes, + 'view' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'viewbox', + 'preserveaspectratio', + 'zoomandpan', + 'viewtarget', + ), + true + ) + ), + 'symbol' => array_merge( + $core_attributes, + $aria_attributes, + $container_attributes, + array_fill_keys( + array( + 'viewbox', + 'preserveaspectratio', + 'x', + 'y', + 'width', + 'height', + ), + true + ) + ), + 'use' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array_fill_keys( + array( + 'href', + 'xlink:href', + 'x', + 'y', + 'width', + '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_fill_keys( + array( + 'href', + 'xlink:href', + 'target', + 'rel', + 'type', + ), + true + ) + ), + 'clippath' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'clippathunits', + 'transform', + ), + true + ) + ), + 'mask' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'maskunits', + 'maskcontentunits', + ), + true + ) + ), + // Gradient elements. + 'lineargradient' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'x1', + 'x2', + 'y1', + 'y2', + 'gradientunits', + 'gradienttransform', + 'spreadmethod', + 'href', + 'xlink:href', + ), + true + ) + ), + 'radialgradient' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'cx', + 'cy', + 'r', + 'fx', + 'fy', + 'fr', + 'gradientunits', + 'gradienttransform', + 'spreadmethod', + 'href', + 'xlink:href', + ), + true + ) + ), + 'stop' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'offset', + 'stop-color', + 'stop-opacity', + ), + true + ) + ), + // Pattern element. + 'pattern' => array_merge( + $core_attributes, + 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_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'filterunits', + 'primitiveunits', + ), + 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_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_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_fill_keys( + array( + 'href', + 'xlink:href', + 'startoffset', + 'method', + 'spacing', + 'text-anchor', + ), + true + ) + ), + // Descriptive elements. + 'title' => array(), + 'desc' => array(), + 'metadata' => array(), + // Image element. + 'image' => array_merge( + $core_attributes, + $aria_attributes, + $presentation_attributes, + array_fill_keys( + array( + 'x', + 'y', + 'width', + 'height', + 'href', + 'xlink:href', + 'preserveaspectratio', + ), + true + ) + ), + // Marker element. + 'marker' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'markerunits', + 'refx', + 'refy', + 'markerwidth', + 'markerheight', + 'orient', + 'preserveaspectratio', + 'viewbox', + ), + true + ) + ), + // Animation elements. + 'animate' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'attributename', + 'from', + 'to', + 'dur', + 'repeatcount', + 'begin', + 'end', + 'values', + 'keytimes', + 'keysplines', + 'calcmode', + 'additive', + 'accumulate', + ), + true + ) + ), + 'animatemotion' => array_merge( + $core_attributes, + 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_fill_keys( + array( + 'attributename', + 'type', + 'from', + 'to', + 'dur', + 'repeatcount', + 'begin', + 'end', + 'values', + 'keytimes', + 'keysplines', + 'calcmode', + 'additive', + 'accumulate', + ), + true + ) + ), + 'set' => array_merge( + $core_attributes, + array_fill_keys( + array( + 'attributename', + 'to', + 'begin', + 'dur', + 'end', + 'repeatcount', + ), + true + ) ), ); - return wp_kses( $icon_content, $allowed_tags ); + + $processor = WP_HTML_Processor::create_fragment( $icon_content ); + 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 ''; + } + + $svg = $processor->serialize_token(); + $depth = $processor->get_current_depth(); + 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/tests/phpunit/tests/icons/wpIconsRegistry.php b/tests/phpunit/tests/icons/wpIconsRegistry.php new file mode 100644 index 0000000000000..d4330e6b9d650 --- /dev/null +++ b/tests/phpunit/tests/icons/wpIconsRegistry.php @@ -0,0 +1,199 @@ +setAccessible( true ); + return $method->invoke( $registry, $icon_content ); + } + + /** + * @ticket 64651 + * + * @dataProvider data_sanitize_icon_content + * @covers ::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() { + $xlink = ' xmlns:xlink="http://www.w3.org/1999/xlink"'; + + return array( + 'extracts only first svg when multiple present' => array( + '', + '', + ), + 'returns empty svg when html-like tags present' => array( + '

paragraph content

div content
', + '', + ), + 'handles xmlns:xlink namespace attribute' => 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

', + '', + ), + // 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( + '', + '', + ), + // 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( + '', + '', + ), + // 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 cd370c7305d54b69e2eea06c7bc30f20abd5103c Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Wed, 17 Jun 2026 16:32:32 +0900 Subject: [PATCH 2/3] Icons: Avoid deprecated ReflectionMethod::setAccessible() in tests. Guard the setAccessible() call behind a PHP version check so it only runs on PHP < 8.1, where it is still required. Since PHP 8.1 protected methods can be invoked via reflection without it, and PHP 8.5 emits a deprecation notice for the now-ineffective call. Co-Authored-By: Claude --- tests/phpunit/tests/icons/wpIconsRegistry.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/icons/wpIconsRegistry.php b/tests/phpunit/tests/icons/wpIconsRegistry.php index d4330e6b9d650..63ca97286577f 100644 --- a/tests/phpunit/tests/icons/wpIconsRegistry.php +++ b/tests/phpunit/tests/icons/wpIconsRegistry.php @@ -20,7 +20,9 @@ class Tests_Icons_WpIconsRegistry extends WP_UnitTestCase { private function sanitize_icon_content( $icon_content ) { $registry = WP_Icons_Registry::get_instance(); $method = new ReflectionMethod( $registry, 'sanitize_icon_content' ); - $method->setAccessible( true ); + if ( PHP_VERSION_ID < 80100 ) { + $method->setAccessible( true ); + } return $method->invoke( $registry, $icon_content ); } From a1597c3677c9d6f60bde9a16e33f58eaef844310 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 19 Jun 2026 18:10:27 +0900 Subject: [PATCH 3/3] Icons: Refactor SVG sanitizer allowed-attribute lists. Introduce a private get_allowed_attribute_list() helper and rebuild the wp_kses() allow-list in sanitize_icon_content() through it, with each tag's attributes sorted alphabetically. This keeps the allowed tags and attributes identical while making the list far easier to scan and diff, and mirrors the canonical implementation in Gutenberg PR 75550 so the two stay in sync for backporting. Co-Authored-By: Claude --- src/wp-includes/class-wp-icons-registry.php | 1091 ++++++++----------- 1 file changed, 466 insertions(+), 625 deletions(-) diff --git a/src/wp-includes/class-wp-icons-registry.php b/src/wp-includes/class-wp-icons-registry.php index 4c79395f13eed..4702af653fa56 100644 --- a/src/wp-includes/class-wp-icons-registry.php +++ b/src/wp-includes/class-wp-icons-registry.php @@ -211,6 +211,18 @@ protected function register( $icon_name, $icon_properties ) { return true; } + /** + * Builds the allowed attribute list for wp_kses() from attribute names. + * + * @since 7.1.0 + * + * @param string ...$attribute_names Attribute names to allow. + * @return array Attribute names mapped to true. + */ + private function get_allowed_attribute_list( ...$attribute_names ) { + return array_fill_keys( $attribute_names, true ); + } + /** * Sanitizes the icon SVG content. * @@ -227,125 +239,110 @@ 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( 'id', 'class', 'style', 'data-*' ), - true - ); + $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. * - * @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( - '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 + $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( - '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 + $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-start', 'marker-mid', 'marker-end' ), - 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( - 'transform', - 'clip-path', - 'mask', - 'filter', - 'visibility', - 'display', - 'opacity', - ), - true + $container_attributes = $this->get_allowed_attribute_list( + 'clip-path', + 'display', + 'filter', + 'mask', + 'opacity', + 'transform', + 'visibility', ); /* @@ -361,18 +358,15 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'xmlns', - 'xmlns:xlink', - 'width', - 'height', - 'viewbox', - 'preserveaspectratio', - 'x', - 'y', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'preserveaspectratio', + 'viewbox', + 'width', + 'x', + 'xmlns', + 'xmlns:xlink', + 'y', ) ), // Basic shape elements (with markers). @@ -381,12 +375,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( @@ -394,13 +385,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( @@ -408,14 +396,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( @@ -423,14 +408,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( @@ -438,11 +420,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( @@ -450,11 +429,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( @@ -462,16 +438,13 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $marker_attributes, - array_fill_keys( - array( - 'x', - 'y', - 'width', - 'height', - 'rx', - 'ry', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'rx', + 'ry', + 'width', + 'x', + 'y', ) ), // Grouping and structural elements. @@ -483,46 +456,37 @@ protected function sanitize_icon_content( $icon_content ) { 'defs' => $core_attributes, 'view' => array_merge( $core_attributes, - array_fill_keys( - array( - 'viewbox', - 'preserveaspectratio', - 'zoomandpan', - 'viewtarget', - ), - true + $this->get_allowed_attribute_list( + 'preserveaspectratio', + 'viewbox', + 'viewtarget', + 'zoomandpan', ) ), 'symbol' => array_merge( $core_attributes, $aria_attributes, $container_attributes, - array_fill_keys( - array( - 'viewbox', - 'preserveaspectratio', - 'x', - 'y', - 'width', - 'height', - ), - 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( - 'href', - 'xlink:href', - 'x', - 'y', - 'width', - 'height', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'href', + 'width', + 'x', + 'xlink:href', + 'y', ) ), 'switch' => array_merge( @@ -536,425 +500,320 @@ protected function sanitize_icon_content( $icon_content ) { $aria_attributes, $presentation_attributes, $container_attributes, - array_fill_keys( - array( - 'href', - 'xlink:href', - 'target', - 'rel', - 'type', - ), - 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( - 'x', - 'y', - 'width', - 'height', - 'maskunits', - 'maskcontentunits', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'maskcontentunits', + 'maskunits', + 'width', + 'x', + 'y', ) ), // Gradient elements. 'lineargradient' => array_merge( $core_attributes, - array_fill_keys( - array( - 'x1', - 'x2', - 'y1', - 'y2', - 'gradientunits', - 'gradienttransform', - 'spreadmethod', - 'href', - 'xlink:href', - ), - 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', - 'r', - 'fx', - 'fy', - 'fr', - 'gradientunits', - 'gradienttransform', - 'spreadmethod', - 'href', - '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( - 'x', - 'y', - 'width', - 'height', - 'patternunits', - 'patterncontentunits', - 'patterntransform', - 'viewbox', - 'preserveaspectratio', - 'href', - 'xlink:href', - ), - true + $this->get_allowed_attribute_list( + 'height', + 'href', + 'patterncontentunits', + 'patterntransform', + 'patternunits', + 'preserveaspectratio', + 'viewbox', + 'width', + 'x', + 'xlink:href', + 'y', ) ), // Filter elements. 'filter' => array_merge( $core_attributes, - array_fill_keys( - array( - 'x', - 'y', - 'width', - 'height', - 'filterunits', - 'primitiveunits', - ), - true + $this->get_allowed_attribute_list( + 'filterunits', + 'height', + 'primitiveunits', + 'width', + 'x', + 'y', ) ), - 'feblend' => array_fill_keys( - array( - 'in', - 'in2', - 'mode', - 'result', - ), - true + 'feblend' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'mode', + 'result', ), - 'fecolormatrix' => array_fill_keys( - array( - 'in', - 'type', - 'values', - 'result', - ), - true + 'fecolormatrix' => $this->get_allowed_attribute_list( + 'in', + 'result', + 'type', + 'values', ), - 'fecomponenttransfer' => array_fill_keys( - array( - 'in', - 'result', - ), - true + 'fecomponenttransfer' => $this->get_allowed_attribute_list( + 'in', + 'result', ), - 'fecomposite' => array_fill_keys( - array( - 'in', - 'in2', - 'operator', - 'k1', - 'k2', - 'k3', - 'k4', - 'result', - ), - true + 'fecomposite' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'k1', + 'k2', + 'k3', + 'k4', + 'operator', + 'result', ), - 'feconvolvematrix' => array_fill_keys( - array( - 'in', - 'order', - 'kernelmatrix', - 'divisor', - 'bias', - 'targetx', - 'targety', - 'edgemode', - 'preservealpha', - 'result', - ), - true + 'feconvolvematrix' => $this->get_allowed_attribute_list( + 'bias', + 'divisor', + 'edgemode', + 'in', + 'kernelmatrix', + 'order', + 'preservealpha', + 'result', + 'targetx', + 'targety', ), - 'fediffuselighting' => array_fill_keys( - array( - 'in', - 'surfacescale', - 'diffuseconstant', - 'result', - ), - true + 'fediffuselighting' => $this->get_allowed_attribute_list( + 'diffuseconstant', + 'in', + 'result', + 'surfacescale', ), - 'fedisplacementmap' => array_fill_keys( - array( - 'in', - 'in2', - 'scale', - 'xchannelselector', - 'ychannelselector', - 'result', - ), - true + 'fedisplacementmap' => $this->get_allowed_attribute_list( + 'in', + 'in2', + 'result', + 'scale', + 'xchannelselector', + 'ychannelselector', ), - 'fedistantlight' => array_fill_keys( - array( - 'azimuth', - 'elevation', - ), - true + 'fedistantlight' => $this->get_allowed_attribute_list( + 'azimuth', + 'elevation', ), - 'feflood' => array_fill_keys( - array( - 'flood-color', - 'flood-opacity', - 'result', - ), - true + 'feflood' => $this->get_allowed_attribute_list( + 'flood-color', + 'flood-opacity', + 'result', ), - 'fegaussianblur' => array_fill_keys( - array( - 'in', - 'stddeviation', - 'edgemode', - 'result', - ), - true + 'fegaussianblur' => $this->get_allowed_attribute_list( + 'edgemode', + 'in', + 'result', + 'stddeviation', ), - 'feimage' => array_fill_keys( - array( - 'href', - 'xlink:href', - 'preserveaspectratio', - 'result', - ), - true + 'feimage' => $this->get_allowed_attribute_list( + 'href', + 'preserveaspectratio', + 'result', + 'xlink:href', ), - 'femerge' => array_fill_keys( - array( - 'result', - ), - true + 'femerge' => $this->get_allowed_attribute_list( + 'result', ), - 'femergenode' => array_fill_keys( - array( - 'in', - ), - true + 'femergenode' => $this->get_allowed_attribute_list( + 'in', ), - 'femorphology' => array_fill_keys( - array( - 'in', - 'operator', - 'radius', - 'result', - ), - true + 'femorphology' => $this->get_allowed_attribute_list( + 'in', + 'operator', + 'radius', + 'result', ), - 'feoffset' => array_fill_keys( - array( - 'in', - 'dx', - 'dy', - 'result', - ), - true + 'feoffset' => $this->get_allowed_attribute_list( + 'dx', + 'dy', + 'in', + 'result', ), - 'fepointlight' => array_fill_keys( - array( - 'x', - 'y', - 'z', - ), - true + 'fepointlight' => $this->get_allowed_attribute_list( + 'x', + 'y', + 'z', ), - 'fespecularlighting' => array_fill_keys( - array( - 'in', - 'surfacescale', - 'specularconstant', - 'specularexponent', - 'result', - ), - true + 'fespecularlighting' => $this->get_allowed_attribute_list( + 'in', + 'result', + 'specularconstant', + 'specularexponent', + 'surfacescale', ), - 'fespotlight' => array_fill_keys( - array( - 'x', - 'y', - 'z', - 'pointsatx', - 'pointsaty', - 'pointsatz', - 'specularexponent', - 'limitingconeangle', - ), - true + 'fespotlight' => $this->get_allowed_attribute_list( + 'limitingconeangle', + 'pointsatx', + 'pointsaty', + 'pointsatz', + 'specularexponent', + 'x', + 'y', + 'z', ), - 'fetile' => array_fill_keys( - array( - 'in', - 'result', - ), - true + 'fetile' => $this->get_allowed_attribute_list( + 'in', + 'result', ), - 'feturbulence' => array_fill_keys( - array( - 'basefrequency', - 'numoctaves', - 'seed', - 'stitchtiles', - 'type', - 'result', - ), - true + 'feturbulence' => $this->get_allowed_attribute_list( + 'basefrequency', + 'numoctaves', + 'result', + 'seed', + 'stitchtiles', + 'type', ), - 'fefunca' => array_fill_keys( - array( - 'type', - 'tablevalues', - 'slope', - 'intercept', - 'amplitude', - 'exponent', - 'offset', - ), - true + 'fefunca' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', ), - 'fefuncb' => array_fill_keys( - array( - 'type', - 'tablevalues', - 'slope', - 'intercept', - 'amplitude', - 'exponent', - 'offset', - ), - true + 'fefuncb' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', ), - 'fefuncg' => array_fill_keys( - array( - 'type', - 'tablevalues', - 'slope', - 'intercept', - 'amplitude', - 'exponent', - 'offset', - ), - true + 'fefuncg' => $this->get_allowed_attribute_list( + 'amplitude', + 'exponent', + 'intercept', + 'offset', + 'slope', + 'tablevalues', + 'type', ), - 'fefuncr' => array_fill_keys( - array( - 'type', - 'tablevalues', - 'slope', - 'intercept', - 'amplitude', - 'exponent', - 'offset', - ), - true + '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( - '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 + $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( - 'x', - 'y', - 'dx', - 'dy', - 'rotate', - 'textlength', - 'lengthadjust', - 'text-anchor', - 'font-family', - 'font-size', - 'font-weight', - 'font-style', - 'text-decoration', - ), - 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', - 'xlink:href', - 'startoffset', - 'method', - 'spacing', - 'text-anchor', - ), - true + $this->get_allowed_attribute_list( + 'href', + 'method', + 'spacing', + 'startoffset', + 'text-anchor', + 'xlink:href', ) ), // Descriptive elements. @@ -966,115 +825,97 @@ protected function sanitize_icon_content( $icon_content ) { $core_attributes, $aria_attributes, $presentation_attributes, - array_fill_keys( - array( - 'x', - 'y', - 'width', - 'height', - 'href', - 'xlink:href', - 'preserveaspectratio', - ), - 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( - 'markerunits', - 'refx', - 'refy', - 'markerwidth', - 'markerheight', - 'orient', - 'preserveaspectratio', - '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( - 'attributename', - 'from', - 'to', - 'dur', - 'repeatcount', - 'begin', - 'end', - 'values', - 'keytimes', - 'keysplines', - 'calcmode', - 'additive', - 'accumulate', - ), - 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( - 'path', - 'keypoints', - 'rotate', - 'keytimes', - 'keysplines', - 'calcmode', - 'from', - 'to', - 'values', - 'dur', - 'repeatcount', - 'begin', - 'end', - 'additive', - 'accumulate', - ), - 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( - 'attributename', - 'type', - 'from', - 'to', - 'dur', - 'repeatcount', - 'begin', - 'end', - 'values', - 'keytimes', - 'keysplines', - 'calcmode', - 'additive', - 'accumulate', - ), - 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', - 'to', - 'begin', - 'dur', - 'end', - 'repeatcount', - ), - true + $this->get_allowed_attribute_list( + 'attributename', + 'begin', + 'dur', + 'end', + 'repeatcount', + 'to', ) ), );