From d71f29ff0899c6eb29bc037a7c521a966405cb35 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 10 Feb 2025 22:27:49 +0000 Subject: [PATCH] Global Styles: Improve sanitization of block variation styles. Fixes an issue where block style variations containing inner block type and element styles would have those inner styles stripped when the user attempting to save Global Styles does not have the `unfiltered_html` capability. Props aaronrobertshaw, mukesh27, andrewserong. Fixes #62372. git-svn-id: https://develop.svn.wordpress.org/trunk@59802 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-theme-json.php | 77 ++++++--- tests/phpunit/tests/theme/wpThemeJson.php | 184 ++++++++++++++++++++++ 2 files changed, 242 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 7d1216a4515d3..7f45040221af4 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3552,26 +3552,12 @@ public static function remove_insecure_properties( $theme_json, $origin = 'theme $variation_output = static::remove_insecure_styles( $variation_input ); - // Process a variation's elements and element pseudo selector styles. - if ( isset( $variation_input['elements'] ) ) { - foreach ( $valid_element_names as $element_name ) { - $element_input = $variation_input['elements'][ $element_name ] ?? null; - if ( $element_input ) { - $element_output = static::remove_insecure_styles( $element_input ); - - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { - if ( isset( $element_input[ $pseudo_selector ] ) ) { - $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); - } - } - } + if ( isset( $variation_input['blocks'] ) ) { + $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] ); + } - if ( ! empty( $element_output ) ) { - _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); - } - } - } + if ( isset( $variation_input['elements'] ) ) { + $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] ); } if ( ! empty( $variation_output ) ) { @@ -3609,6 +3595,59 @@ public static function remove_insecure_properties( $theme_json, $origin = 'theme return $theme_json; } + /** + * Remove insecure element styles within a variation or block. + * + * @since 6.8.0 + * + * @param array $elements The elements to process. + * @return array The sanitized elements styles. + */ + protected static function remove_insecure_element_styles( $elements ) { + $sanitized = array(); + $valid_element_names = array_keys( static::ELEMENTS ); + + foreach ( $valid_element_names as $element_name ) { + $element_input = $elements[ $element_name ] ?? null; + if ( $element_input ) { + $element_output = static::remove_insecure_styles( $element_input ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { + if ( isset( $element_input[ $pseudo_selector ] ) ) { + $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); + } + } + } + + $sanitized[ $element_name ] = $element_output; + } + } + return $sanitized; + } + + /** + * Remove insecure styles from inner blocks and their elements. + * + * @since 6.8.0 + * + * @param array $blocks The block styles to process. + * @return array Sanitized block type styles. + */ + protected static function remove_insecure_inner_block_styles( $blocks ) { + $sanitized = array(); + foreach ( $blocks as $block_type => $block_input ) { + $block_output = static::remove_insecure_styles( $block_input ); + + if ( isset( $block_input['elements'] ) ) { + $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] ); + } + + $sanitized[ $block_type ] = $block_output; + } + return $sanitized; + } + /** * Processes a setting node and returns the same node * without the insecure settings. diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 8fe8c7d5d0d65..f40df84a38fb2 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -4706,6 +4706,190 @@ public function test_block_style_variations_with_invalid_properties() { $this->assertSameSetsWithIndex( $expected, $actual ); } + /** + * Test ensures that inner block type styles and their element styles are + * preserved for block style variations when removing insecure properties. + * + * @ticket 62372 + */ + public function test_block_style_variations_with_inner_blocks_and_elements() { + wp_set_current_user( static::$administrator_id ); + register_block_style( + array( 'core/group' ), + array( + 'name' => 'custom-group', + 'label' => 'Custom Group', + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => 'blue', + ), + 'variations' => array( + 'custom-group' => array( + 'color' => array( + 'background' => 'purple', + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'red', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'blue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'green', + ), + ), + ), + ), + ), + 'core/heading' => array( + 'typography' => array( + 'fontSize' => '24px', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + ':hover' => array( + 'color' => array( + 'text' => 'orange', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON::remove_insecure_properties( $expected ); + + // The sanitization processes blocks in a specific order which might differ to the theme.json input. + $this->assertEqualsCanonicalizing( + $expected, + $actual, + 'Block style variations data does not match when inner blocks or element styles present' + ); + } + + /** + * Test ensures that inner block type styles and their element styles for block + * style variations have all unsafe values removed. + * + * @ticket 62372 + */ + public function test_block_style_variations_with_invalid_inner_block_or_element_styles() { + wp_set_current_user( static::$administrator_id ); + register_block_style( + array( 'core/group' ), + array( + 'name' => 'custom-group', + 'label' => 'Custom Group', + ) + ); + + $input = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'variations' => array( + 'custom-group' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'red', + ), + 'typography' => array( + 'fontSize' => 'alert(1)', // Should be removed. + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'blue', + ), + 'css' => 'unsafe-value', // Should be removed. + ), + ), + 'custom' => 'unsafe-value', // Should be removed. + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + 'javascript' => 'alert(1)', // Should be removed. + ), + ), + ), + ), + ), + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'variations' => array( + 'custom-group' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'red', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'blue', + ), + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + ), + ), + ), + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON::remove_insecure_properties( $input ); + + // The sanitization processes blocks in a specific order which might differ to the theme.json input. + $this->assertEqualsCanonicalizing( + $expected, + $actual, + 'Insecure properties were not removed from block style variation inner block types or elements' + ); + } + /** * Tests generating the spacing presets array based on the spacing scale provided. *