| @@ -1,6 +1,97 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | +/** | |
| 4 | + * Returns the SVG elements and attributes allowed for registered icons. | |
| 5 | + * | |
| 6 | + * @return array[] Allowed SVG elements and attributes. | |
| 7 | + * @phpstan-return array<non-falsy-string, array<non-falsy-string, true>> | |
| 8 | + */ | |
| 9 | +function gutenberg_get_allowed_icon_svg_tags(): array { | |
| 10 | + $allow_attributes = static function ( string ...$attribute_names ): array { | |
| 11 | + return array_fill_keys( $attribute_names, true ); | |
| 12 | + }; | |
| 13 | + | |
| 14 | + $stroke_attributes = $allow_attributes( | |
| 15 | + 'style', | |
| 16 | + 'stroke', | |
| 17 | + 'stroke-width', | |
| 18 | + 'stroke-linecap', | |
| 19 | + 'stroke-linejoin', | |
| 20 | + 'stroke-miterlimit', | |
| 21 | + 'vector-effect', | |
| 22 | + ); | |
| 23 | + | |
| 24 | + return array( | |
| 25 | + 'svg' => array_merge( | |
| 26 | + $allow_attributes( | |
| 27 | + 'class', | |
| 28 | + 'xmlns', | |
| 29 | + 'width', | |
| 30 | + 'height', | |
| 31 | + 'viewbox', | |
| 32 | + 'aria-hidden', | |
| 33 | + 'role', | |
| 34 | + 'focusable', | |
| 35 | + 'fill', | |
| 36 | + 'fill-rule', | |
| 37 | + 'clip-rule', | |
| 38 | + ), | |
| 39 | + $stroke_attributes | |
| 40 | + ), | |
| 41 | + 'path' => array_merge( | |
| 42 | + $allow_attributes( | |
| 43 | + 'fill', | |
| 44 | + 'fill-rule', | |
| 45 | + 'clip-rule', | |
| 46 | + 'd', | |
| 47 | + 'opacity', | |
| 48 | + 'transform', | |
| 49 | + ), | |
| 50 | + $stroke_attributes | |
| 51 | + ), | |
| 52 | + 'polygon' => array_merge( | |
| 53 | + $allow_attributes( | |
| 54 | + 'fill', | |
| 55 | + 'fill-rule', | |
| 56 | + 'clip-rule', | |
| 57 | + 'points', | |
| 58 | + 'transform', | |
| 59 | + 'focusable', | |
| 60 | + ), | |
| 61 | + $stroke_attributes | |
| 62 | + ), | |
| 63 | + 'rect' => array_merge( | |
| 64 | + $allow_attributes( | |
| 65 | + 'fill', | |
| 66 | + 'fill-rule', | |
| 67 | + 'clip-rule', | |
| 68 | + 'x', | |
| 69 | + 'y', | |
| 70 | + 'width', | |
| 71 | + 'height', | |
| 72 | + 'rx', | |
| 73 | + 'ry', | |
| 74 | + 'transform', | |
| 75 | + ), | |
| 76 | + $stroke_attributes | |
| 77 | + ), | |
| 78 | + 'circle' => array_merge( | |
| 79 | + $allow_attributes( | |
| 80 | + 'fill', | |
| 81 | + 'fill-rule', | |
| 82 | + 'clip-rule', | |
| 83 | + 'cx', | |
| 84 | + 'cy', | |
| 85 | + 'r', | |
| 86 | + 'transform', | |
| 87 | + ), | |
| 88 | + $stroke_attributes | |
| 89 | + ), | |
| 90 | + ); | |
| 91 | +} | |
| 92 | + | |
| 93 | + | |
| 3 | 94 | class WP_Icons_Registry_Gutenberg extends WP_Icons_Registry { |
| 4 | 95 | /** |
| 5 | 96 | * Overridden to skip the parent's core icon registration, which uses the |
| 6 | 97 | * core manifest path. Core icons are registered via |
| @@ -185,8 +276,25 @@ | ||
| 185 | 276 | return true; |
| 186 | 277 | } |
| 187 | 278 | |
| 188 | 279 | /** |
| 280 | + * Sanitizes the icon SVG content. | |
| 281 | + * | |
| 282 | + * Overrides the base class to allow the `rect` and `circle` shapes, plus the | |
| 283 | + * stroke-related attributes and inline styles required by stroke-based icons. | |
| 284 | + * | |
| 285 | + * The signature is intentionally left without type declarations to stay | |
| 286 | + * compatible with the parent WP_Icons_Registry::sanitize_icon_content() | |
| 287 | + * shipped in WordPress core, which declares none. | |
| 288 | + * | |
| 289 | + * @param string $icon_content The icon SVG content to sanitize. | |
| 290 | + * @return string The sanitized icon SVG content. | |
| 291 | + */ | |
| 292 | + protected function sanitize_icon_content( $icon_content ) { | |
| 293 | + return wp_kses( $icon_content, gutenberg_get_allowed_icon_svg_tags() ); | |
| 294 | + } | |
| 295 | + | |
| 296 | + /** | |
| 189 | 297 | * Retrieves the content of a registered icon. |
| 190 | 298 | * |
| 191 | 299 | * Overridden so that the file validation is applied even when the base |
| 192 | 300 | * `WP_Icons_Registry` is provided by WordPress core rather than the |
| @@ -255,9 +363,9 @@ | ||
| 255 | 363 | * |
| 256 | 364 | * The base `$instance` slot is intentionally not redefined, so both |
| 257 | 365 | * `WP_Icons_Registry::get_instance()` (used by core) and this method share |
| 258 | 366 | * one instance. An existing base registry is upgraded, replaying any |
| 259 | - * non-`core/` icons so they are not lost. | |
| 367 | + * non-`core/` and non-`core-admin/` icons so they are not lost. | |
| 260 | 368 | */ |
| 261 | 369 | public static function get_instance() { |
| 262 | 370 | if ( ! self::$instance instanceof self ) { |
| 263 | 371 | $original_registry = self::$instance; |
| @@ -264,9 +372,9 @@ | ||
| 264 | 372 | $gutenberg_registry = new self(); |
| 265 | 373 | |
| 266 | 374 | if ( null !== $original_registry ) { |
| 267 | 375 | foreach ( $original_registry->get_registered_icons() as $icon ) { |
| 268 | - if ( str_starts_with( $icon['name'], 'core/' ) ) { | |
| 376 | + if ( str_starts_with( $icon['name'], 'core/' ) || str_starts_with( $icon['name'], 'core-admin/' ) ) { | |
| 269 | 377 | continue; |
| 270 | 378 | } |
| 271 | 379 | $icon_properties = array( 'label' => $icon['label'] ); |
| 272 | 380 | if ( ! empty( $icon['content'] ) ) { |