mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2025-10-16 12:05:38 +00:00
Editor: Sanitize nested array in theme.json properly.
WP_Theme_JSON sanitization is now able to sanitize data contained on indexed arrays. So certain data from theme.json, for example, settings.typography.fontFamilies which is a JSON array will be sanitized. Props mmaattiiaass, mukesh27. Fixes #60360. git-svn-id: https://develop.svn.wordpress.org/trunk@57496 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
4472385b88
commit
b999860bb6
@ -429,6 +429,38 @@ class WP_Theme_JSON {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The valid properties for fontFamilies under settings key.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
const FONT_FAMILY_SCHEMA = array(
|
||||||
|
array(
|
||||||
|
'fontFamily' => null,
|
||||||
|
'name' => null,
|
||||||
|
'slug' => null,
|
||||||
|
'fontFace' => array(
|
||||||
|
array(
|
||||||
|
'ascentOverride' => null,
|
||||||
|
'descentOverride' => null,
|
||||||
|
'fontDisplay' => null,
|
||||||
|
'fontFamily' => null,
|
||||||
|
'fontFeatureSettings' => null,
|
||||||
|
'fontStyle' => null,
|
||||||
|
'fontStretch' => null,
|
||||||
|
'fontVariationSettings' => null,
|
||||||
|
'fontWeight' => null,
|
||||||
|
'lineGapOverride' => null,
|
||||||
|
'sizeAdjust' => null,
|
||||||
|
'src' => null,
|
||||||
|
'unicodeRange' => null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The valid properties under the styles key.
|
* The valid properties under the styles key.
|
||||||
*
|
*
|
||||||
@ -556,6 +588,52 @@ class WP_Theme_JSON {
|
|||||||
'typography' => 'typography',
|
'typography' => 'typography',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the input schema at the root and per origin.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
|
*
|
||||||
|
* @param array $schema The base schema.
|
||||||
|
* @return array The schema at the root and per origin.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* schema_in_root_and_per_origin(
|
||||||
|
* array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* 'default' => array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* ),
|
||||||
|
* 'blocks' => array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* ),
|
||||||
|
* 'theme' => array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* ),
|
||||||
|
* 'custom' => array(
|
||||||
|
* 'fontFamily' => null,
|
||||||
|
* 'slug' => null,
|
||||||
|
* ),
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
protected static function schema_in_root_and_per_origin( $schema ) {
|
||||||
|
$schema_in_root_and_per_origin = $schema;
|
||||||
|
foreach ( static::VALID_ORIGINS as $origin ) {
|
||||||
|
$schema_in_root_and_per_origin[ $origin ] = $schema;
|
||||||
|
}
|
||||||
|
return $schema_in_root_and_per_origin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a class name by an element name.
|
* Returns a class name by an element name.
|
||||||
*
|
*
|
||||||
@ -797,11 +875,12 @@ class WP_Theme_JSON {
|
|||||||
$schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations;
|
$schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations;
|
||||||
}
|
}
|
||||||
|
|
||||||
$schema['styles'] = static::VALID_STYLES;
|
$schema['styles'] = static::VALID_STYLES;
|
||||||
$schema['styles']['blocks'] = $schema_styles_blocks;
|
$schema['styles']['blocks'] = $schema_styles_blocks;
|
||||||
$schema['styles']['elements'] = $schema_styles_elements;
|
$schema['styles']['elements'] = $schema_styles_elements;
|
||||||
$schema['settings'] = static::VALID_SETTINGS;
|
$schema['settings'] = static::VALID_SETTINGS;
|
||||||
$schema['settings']['blocks'] = $schema_settings_blocks;
|
$schema['settings']['blocks'] = $schema_settings_blocks;
|
||||||
|
$schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA );
|
||||||
|
|
||||||
// Remove anything that's not present in the schema.
|
// Remove anything that's not present in the schema.
|
||||||
foreach ( array( 'styles', 'settings' ) as $subtree ) {
|
foreach ( array( 'styles', 'settings' ) as $subtree ) {
|
||||||
@ -974,18 +1053,39 @@ class WP_Theme_JSON {
|
|||||||
* @return array The modified $tree.
|
* @return array The modified $tree.
|
||||||
*/
|
*/
|
||||||
protected static function remove_keys_not_in_schema( $tree, $schema ) {
|
protected static function remove_keys_not_in_schema( $tree, $schema ) {
|
||||||
$tree = array_intersect_key( $tree, $schema );
|
if ( ! is_array( $tree ) ) {
|
||||||
|
return $tree;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ( $schema as $key => $data ) {
|
foreach ( $tree as $key => $value ) {
|
||||||
if ( ! isset( $tree[ $key ] ) ) {
|
// Remove keys not in the schema or with null/empty values.
|
||||||
|
if ( ! array_key_exists( $key, $schema ) ) {
|
||||||
|
unset( $tree[ $key ] );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) {
|
// Check if the value is an array and requires further processing.
|
||||||
$tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] );
|
if ( is_array( $value ) && is_array( $schema[ $key ] ) ) {
|
||||||
|
// Determine if it is an associative or indexed array.
|
||||||
|
$schema_is_assoc = self::is_assoc( $value );
|
||||||
|
|
||||||
if ( empty( $tree[ $key ] ) ) {
|
if ( $schema_is_assoc ) {
|
||||||
unset( $tree[ $key ] );
|
// If associative, process as a single object.
|
||||||
|
$tree[ $key ] = self::remove_keys_not_in_schema( $value, $schema[ $key ] );
|
||||||
|
|
||||||
|
if ( empty( $tree[ $key ] ) ) {
|
||||||
|
unset( $tree[ $key ] );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If indexed, process each item in the array.
|
||||||
|
foreach ( $value as $item_key => $item_value ) {
|
||||||
|
if ( isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) ) {
|
||||||
|
$tree[ $key ][ $item_key ] = self::remove_keys_not_in_schema( $item_value, $schema[ $key ][0] );
|
||||||
|
} else {
|
||||||
|
// If the schema does not define a further structure, keep the value as is.
|
||||||
|
$tree[ $key ][ $item_key ] = $item_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) {
|
} elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) {
|
||||||
unset( $tree[ $key ] );
|
unset( $tree[ $key ] );
|
||||||
@ -995,6 +1095,20 @@ class WP_Theme_JSON {
|
|||||||
return $tree;
|
return $tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given array is associative.
|
||||||
|
*
|
||||||
|
* @since 6.5.0
|
||||||
|
* @param array $data The array to check.
|
||||||
|
* @return bool True if the array is associative, false otherwise.
|
||||||
|
*/
|
||||||
|
protected static function is_assoc( $data ) {
|
||||||
|
if ( array() === $data ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return array_keys( $data ) !== range( 0, count( $data ) - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the existing settings for each block.
|
* Returns the existing settings for each block.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -4978,4 +4978,139 @@ class Tests_Theme_wpThemeJson extends WP_UnitTestCase {
|
|||||||
$actual
|
$actual
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that invalid properties are removed from the theme.json inside indexed arrays as settings.typography.fontFamilies.
|
||||||
|
*
|
||||||
|
* @ticket 60360
|
||||||
|
*/
|
||||||
|
public function test_sanitize_indexed_arrays() {
|
||||||
|
$theme_json = new WP_Theme_JSON(
|
||||||
|
array(
|
||||||
|
'version' => '2',
|
||||||
|
'badKey2' => 'I am Evil!',
|
||||||
|
'settings' => array(
|
||||||
|
'badKey3' => 'I am Evil!',
|
||||||
|
'typography' => array(
|
||||||
|
'badKey4' => 'I am Evil!',
|
||||||
|
'fontFamilies' => array(
|
||||||
|
'custom' => array(
|
||||||
|
array(
|
||||||
|
'badKey4' => 'I am Evil!',
|
||||||
|
'name' => 'Arial',
|
||||||
|
'slug' => 'arial',
|
||||||
|
'fontFamily' => 'Arial, sans-serif',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'theme' => array(
|
||||||
|
array(
|
||||||
|
'badKey5' => 'I am Evil!',
|
||||||
|
'name' => 'Piazzolla',
|
||||||
|
'slug' => 'piazzolla',
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontFace' => array(
|
||||||
|
array(
|
||||||
|
'badKey6' => 'I am Evil!',
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'badKey7' => 'I am Evil!',
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'badKey8' => 'I am Evil!',
|
||||||
|
'name' => 'Inter',
|
||||||
|
'slug' => 'Inter',
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontFace' => array(
|
||||||
|
array(
|
||||||
|
'badKey9' => 'I am Evil!',
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'badKey10' => 'I am Evil!',
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$expected_sanitized = array(
|
||||||
|
'version' => '2',
|
||||||
|
'settings' => array(
|
||||||
|
'typography' => array(
|
||||||
|
'fontFamilies' => array(
|
||||||
|
'custom' => array(
|
||||||
|
array(
|
||||||
|
'name' => 'Arial',
|
||||||
|
'slug' => 'arial',
|
||||||
|
'fontFamily' => 'Arial, sans-serif',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'theme' => array(
|
||||||
|
array(
|
||||||
|
'name' => 'Piazzolla',
|
||||||
|
'slug' => 'piazzolla',
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontFace' => array(
|
||||||
|
array(
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'fontFamily' => 'Piazzolla',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'Inter',
|
||||||
|
'slug' => 'Inter',
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontFace' => array(
|
||||||
|
array(
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'fontFamily' => 'Inter',
|
||||||
|
'fontStyle' => 'italic',
|
||||||
|
'fontWeight' => '400',
|
||||||
|
'src' => 'https://example.com/font.ttf',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$sanitized_theme_json = $theme_json->get_raw_data();
|
||||||
|
$this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user