Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ class WP_Theme_JSON_6_2 extends WP_Theme_JSON_6_1 {
'box-shadow' => array( 'shadow' ),
);

/**
* Indirect metadata for style properties that are not directly output.
*
* Each element is a direct mapping from a CSS property name to the
* path to the value in theme.json & block attributes.
*
* Indirect properties are not output directly by `compute_style_properties`,
* but are used elsewhere in the processing of global styles. The indirect
* property is used to validate whether or not a style value is allowed.
*
* @since 6.2.0
* @var array
*/
const INDIRECT_PROPERTIES_METADATA = array(
'gap' => array( 'spacing', 'blockGap' ),
'column-gap' => array( 'spacing', 'blockGap', 'left' ),
'row-gap' => array( 'spacing', 'blockGap', 'top' ),
);

/**
* The valid properties under the settings key.
*
Expand Down Expand Up @@ -216,4 +235,46 @@ class WP_Theme_JSON_6_2 extends WP_Theme_JSON_6_1 {
'textTransform' => null,
),
);

/**
* Processes a style node and returns the same node
* without the insecure styles.
*
* @since 5.9.0
* @since 6.2.0 Allow indirect properties used outside of `compute_style_properties`.
*
* @param array $input Node to process.
* @return array
*/
protected static function remove_insecure_styles( $input ) {
$output = array();
$declarations = static::compute_style_properties( $input );

foreach ( $declarations as $declaration ) {
if ( static::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) {
$path = static::PROPERTIES_METADATA[ $declaration['name'] ];

// Check the value isn't an array before adding so as to not
// double up shorthand and longhand styles.
$value = _wp_array_get( $input, $path, array() );
if ( ! is_array( $value ) ) {
_wp_array_set( $output, $path, $value );
}
}
}

// Ensure indirect properties not handled by `compute_style_properties` are allowed.
foreach ( static::INDIRECT_PROPERTIES_METADATA as $property => $path ) {
$value = _wp_array_get( $input, $path, array() );
if (
isset( $value ) &&
! is_array( $value ) &&
static::is_safe_css_declaration( $property, $value )
) {
_wp_array_set( $output, $path, $value );
}
}

return $output;
}
}
66 changes: 66 additions & 0 deletions lib/experimental/kses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* Temporary compatibility shims for kses rules present in Gutenberg.
*
* The functions in this file should not be backported to core.
*
* @package gutenberg
*/

/**
* Sanitizes global styles user content removing unsafe rules.
*
* This function is identical to the core version, but called the
* Gutenberg version of the theme JSON class (`WP_Theme_JSON_Gutenberg`).
*
* This function should not be backported to core.
*
* @since 5.9.0
*
* @param string $data Post content to filter.
* @return string Filtered post content with unsafe rules removed.
*/
function gutenberg_filter_global_styles_post( $data ) {
$decoded_data = json_decode( wp_unslash( $data ), true );
$json_decoding_error = json_last_error();
if (
JSON_ERROR_NONE === $json_decoding_error &&
is_array( $decoded_data ) &&
isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
$decoded_data['isGlobalStylesUserThemeJSON']
) {
unset( $decoded_data['isGlobalStylesUserThemeJSON'] );

$data_to_encode = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $decoded_data );

$data_to_encode['isGlobalStylesUserThemeJSON'] = true;
return wp_slash( wp_json_encode( $data_to_encode ) );
}
return $data;
}

/**
* Override core's kses_init_filters hooks for global styles,
* and use Gutenberg's version instead. This ensures that
* Gutenberg's `remove_insecure_properties` function can be called.
*
* The hooks are only set if they are already added, which ensures
* that global styles is only filtered for users without the `unfiltered_html`
* capability.
*
* This function should not be backported to core.
*/
function gutenberg_override_core_kses_init_filters() {
if ( has_filter( 'content_save_pre', 'wp_filter_global_styles_post' ) ) {
remove_filter( 'content_save_pre', 'wp_filter_global_styles_post', 9 );
add_filter( 'content_save_pre', 'gutenberg_filter_global_styles_post', 9 );
}

if ( has_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post' ) ) {
remove_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 );
add_filter( 'content_filtered_save_pre', 'gutenberg_filter_global_styles_post', 9 );
}

}
add_action( 'init', 'gutenberg_override_core_kses_init_filters' );
add_action( 'set_current_user', 'gutenberg_override_core_kses_init_filters' );
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/webfonts.php';
require __DIR__ . '/experimental/navigation-theme-opt-in.php';
require __DIR__ . '/experimental/navigation-page.php';
require __DIR__ . '/experimental/kses.php';

// Plugin specific code.
require __DIR__ . '/blocks.php';
Expand Down
44 changes: 44 additions & 0 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,50 @@ public function test_get_stylesheet_with_block_support_feature_level_selectors()
$this->assertEquals( $expected, $theme_json->get_stylesheet() );
}

public function test_allow_indirect_properties() {
$actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties(
array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array(
'blocks' => array(
'core/social-links' => array(
'spacing' => array(
'blockGap' => array(
'top' => '1em',
'left' => '2em',
),
),
),
),
'spacing' => array(
'blockGap' => '3em',
),
),
)
);

$expected = array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array(
'blocks' => array(
'core/social-links' => array(
'spacing' => array(
'blockGap' => array(
'top' => '1em',
'left' => '2em',
),
),
),
),
'spacing' => array(
'blockGap' => '3em',
),
),
);

$this->assertEqualSetsWithIndex( $expected, $actual );
}

public function test_remove_invalid_element_pseudo_selectors() {
$actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties(
array(
Expand Down