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. *