From 63d4af46ea2f52e04ea3eda3ef89399fc0c91248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 14 Sep 2026 13:53:09 +0100 Subject: [PATCH 1/6] allow rect and circle in icon registry sanitizer add a test running every library svg through wp_kses --- lib/class-wp-icons-registry-gutenberg.php | 31 +++++- ...class-wp-icons-registry-gutenberg-test.php | 94 +++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 9f989868e992f5..6a7850acb89e93 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -55,6 +55,33 @@ function gutenberg_get_allowed_icon_svg_tags(): array { ), $stroke_attributes ), + 'rect' => array_merge( + array( + 'fill' => true, + 'fill-rule' => true, + 'clip-rule' => true, + 'x' => true, + 'y' => true, + 'width' => true, + 'height' => true, + 'rx' => true, + 'ry' => true, + 'transform' => true, + ), + $stroke_attributes + ), + 'circle' => array_merge( + array( + 'fill' => true, + 'fill-rule' => true, + 'clip-rule' => true, + 'cx' => true, + 'cy' => true, + 'r' => true, + 'transform' => true, + ), + $stroke_attributes + ), ); } @@ -260,8 +287,8 @@ public function unregister( $icon_name ) { /** * Sanitizes the icon SVG content. * - * Overrides the base class to allow stroke-related attributes and inline - * styles required by stroke-based icons. + * Overrides the base class to allow the `rect` and `circle` shapes, plus the + * stroke-related attributes and inline styles required by stroke-based icons. * * The signature is intentionally left without type declarations to stay * compatible with the parent WP_Icons_Registry::sanitize_icon_content() diff --git a/phpunit/class-wp-icons-registry-gutenberg-test.php b/phpunit/class-wp-icons-registry-gutenberg-test.php index 5668bdcd763090..733f624c6b0576 100644 --- a/phpunit/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/class-wp-icons-registry-gutenberg-test.php @@ -468,6 +468,100 @@ public function test_clip_rule_survives_sanitization( bool $use_file_path ) { $this->assertStringContainsString( 'clip-rule="evenodd"', $icon['content'] ); } + /** + * Should preserve `rect` and `circle` shapes when sanitizing registered icons. + * + * @dataProvider data_icon_content_sources + * + * @param bool $use_file_path Whether to register the icon from a file path. + */ + public function test_rect_and_circle_survive_sanitization( bool $use_file_path ) { + $rect = ''; + $circle = ''; + $content = '' . $rect . $circle . ''; + $name = 'test-collection/shapes-icon'; + $settings = array( + 'label' => 'Shapes Icon', + ); + + if ( $use_file_path ) { + $settings['file_path'] = $this->create_temp_icon_file( $content ); + } else { + $settings['content'] = $content; + } + + $this->assertTrue( $this->register( $name, $settings ) ); + + $icon = $this->registry->get_registered_icon( $name ); + $this->assertStringContainsString( $rect, $icon['content'] ); + $this->assertStringContainsString( $circle, $icon['content'] ); + } + + /** + * Provides every SVG file shipped in the `@wordpress/icons` library. + * + * @return array Data sets of [ $file_path ], keyed by icon slug. + */ + public function data_library_icons(): array { + $data = array(); + foreach ( glob( gutenberg_dir_path() . 'packages/icons/src/library/*.svg' ) as $file_path ) { + $data[ basename( $file_path, '.svg' ) ] = array( $file_path ); + } + return $data; + } + + /** + * Should keep every element and attribute of a library icon through sanitization. + * + * @dataProvider data_library_icons + * + * @param string $file_path Absolute path to the library SVG file. + */ + public function test_library_icon_survives_sanitization( string $file_path ) { + $name = 'test-collection/' . basename( $file_path, '.svg' ); + + $this->assertTrue( + $this->register( + $name, + array( + 'label' => 'Library Icon', + 'file_path' => $file_path, + ) + ) + ); + + $icon = $this->registry->get_registered_icon( $name ); + $this->assertSame( + $this->get_svg_structure( file_get_contents( $file_path ) ), + $this->get_svg_structure( $icon['content'] ) + ); + } + + /** + * Lists the tags of an SVG document with their attributes, in document order. + * + * Tag names and attribute names are compared case-insensitively because + * `wp_kses()` lowercases attribute names such as `viewBox`. + * + * @param string $svg SVG markup. + * @return array}> Tag name and attributes per element. + */ + private function get_svg_structure( string $svg ): array { + $structure = array(); + $processor = new WP_HTML_Tag_Processor( $svg ); + + while ( $processor->next_tag() ) { + $attributes = array(); + foreach ( $processor->get_attribute_names_with_prefix( '' ) as $attribute_name ) { + $attributes[ $attribute_name ] = $processor->get_attribute( $attribute_name ); + } + ksort( $attributes ); + $structure[] = array( $processor->get_tag(), $attributes ); + } + + return $structure; + } + /** * Should fail to register an icon that provides both `content` and `file_path`. * From ac6b73e9ce9b69d422960ac266b6604c16cd0fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Mon, 14 Sep 2026 14:12:25 +0100 Subject: [PATCH 2/6] add backport changelog entry for #82846 --- backport-changelog/7.2/12197.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backport-changelog/7.2/12197.md b/backport-changelog/7.2/12197.md index c68f1fa3c31823..ebe15e4f274937 100644 --- a/backport-changelog/7.2/12197.md +++ b/backport-changelog/7.2/12197.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/12197 * https://github.com/WordPress/gutenberg/pull/78808 +* https://github.com/WordPress/gutenberg/pull/82846 From f2a148eb94ab2e380c9f72b80d385f80a93edee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 16 Sep 2026 08:25:20 +0100 Subject: [PATCH 3/6] add `opacity` prop to `path` --- lib/class-wp-icons-registry-gutenberg.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php index 6a7850acb89e93..ef4ad56f3d0ace 100644 --- a/lib/class-wp-icons-registry-gutenberg.php +++ b/lib/class-wp-icons-registry-gutenberg.php @@ -40,6 +40,7 @@ function gutenberg_get_allowed_icon_svg_tags(): array { 'fill-rule' => true, 'clip-rule' => true, 'd' => true, + 'opacity' => true, 'transform' => true, ), $stroke_attributes From 553201d4bdd70c3ae056348a39d49b7487a58177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 16 Sep 2026 11:14:19 +0100 Subject: [PATCH 4/6] fix formatting issue --- .../src/components/widget-footer/widget-footer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx b/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx index b0a8167bc9bcd4..682e4f79990907 100644 --- a/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx +++ b/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx @@ -25,7 +25,7 @@ function IconAction( { action }: IconActionProps ): React.ReactNode { /* translators: %s: action label. */ __( '%s (opens in a new tab)' ), action.label - ) + ) : action.label; return ( @@ -46,7 +46,7 @@ function IconAction( { action }: IconActionProps ): React.ReactNode { ? { target: '_blank', rel: 'noopener noreferrer', - } + } : {} ) } /> } From 336db81d6eb6e2ea49dc695347757fc04d6a1cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 16 Sep 2026 11:30:50 +0100 Subject: [PATCH 5/6] remove potential duplicated tests The test will be added in https://github.com/WordPress/gutenberg/pull/83008 --- ...class-wp-icons-registry-gutenberg-test.php | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/phpunit/class-wp-icons-registry-gutenberg-test.php b/phpunit/class-wp-icons-registry-gutenberg-test.php index 733f624c6b0576..72b7ce84737a06 100644 --- a/phpunit/class-wp-icons-registry-gutenberg-test.php +++ b/phpunit/class-wp-icons-registry-gutenberg-test.php @@ -497,71 +497,6 @@ public function test_rect_and_circle_survive_sanitization( bool $use_file_path ) $this->assertStringContainsString( $circle, $icon['content'] ); } - /** - * Provides every SVG file shipped in the `@wordpress/icons` library. - * - * @return array Data sets of [ $file_path ], keyed by icon slug. - */ - public function data_library_icons(): array { - $data = array(); - foreach ( glob( gutenberg_dir_path() . 'packages/icons/src/library/*.svg' ) as $file_path ) { - $data[ basename( $file_path, '.svg' ) ] = array( $file_path ); - } - return $data; - } - - /** - * Should keep every element and attribute of a library icon through sanitization. - * - * @dataProvider data_library_icons - * - * @param string $file_path Absolute path to the library SVG file. - */ - public function test_library_icon_survives_sanitization( string $file_path ) { - $name = 'test-collection/' . basename( $file_path, '.svg' ); - - $this->assertTrue( - $this->register( - $name, - array( - 'label' => 'Library Icon', - 'file_path' => $file_path, - ) - ) - ); - - $icon = $this->registry->get_registered_icon( $name ); - $this->assertSame( - $this->get_svg_structure( file_get_contents( $file_path ) ), - $this->get_svg_structure( $icon['content'] ) - ); - } - - /** - * Lists the tags of an SVG document with their attributes, in document order. - * - * Tag names and attribute names are compared case-insensitively because - * `wp_kses()` lowercases attribute names such as `viewBox`. - * - * @param string $svg SVG markup. - * @return array}> Tag name and attributes per element. - */ - private function get_svg_structure( string $svg ): array { - $structure = array(); - $processor = new WP_HTML_Tag_Processor( $svg ); - - while ( $processor->next_tag() ) { - $attributes = array(); - foreach ( $processor->get_attribute_names_with_prefix( '' ) as $attribute_name ) { - $attributes[ $attribute_name ] = $processor->get_attribute( $attribute_name ); - } - ksort( $attributes ); - $structure[] = array( $processor->get_tag(), $attributes ); - } - - return $structure; - } - /** * Should fail to register an icon that provides both `content` and `file_path`. * From 0c820ea49399f079efa12e2b7d9bd3eebf08347c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 16 Sep 2026 11:38:31 +0100 Subject: [PATCH 6/6] linting issues --- .../src/components/widget-footer/widget-footer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx b/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx index 682e4f79990907..b0a8167bc9bcd4 100644 --- a/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx +++ b/packages/widget-dashboard/src/components/widget-footer/widget-footer.tsx @@ -25,7 +25,7 @@ function IconAction( { action }: IconActionProps ): React.ReactNode { /* translators: %s: action label. */ __( '%s (opens in a new tab)' ), action.label - ) + ) : action.label; return ( @@ -46,7 +46,7 @@ function IconAction( { action }: IconActionProps ): React.ReactNode { ? { target: '_blank', rel: 'noopener noreferrer', - } + } : {} ) } /> }