Skip to content

Commit cfb3563

Browse files
committed
Editor: Remove theme.json preset items of unexpected types.
Preset leaves are untyped in `VALID_SETTINGS`, so a preset item that isn't an array, or whose `slug` isn't a scalar, reached code that reads `$preset['slug']` as a string and threw a fatal `TypeError`. `WP_Theme_JSON::sanitize()` now removes those items from plain preset lists and from lists keyed by origin, at the top level and per block. Filtering in `sanitize()` also covers `remove_insecure_properties()`, which the kses filter uses when global styles are saved. Scalar slugs are kept, so an integer `slug` still works. Backports WordPress/gutenberg#84534. See #66304.
1 parent 66e7c8d commit cfb3563

2 files changed

Lines changed: 132 additions & 8 deletions

File tree

‎src/wp-includes/class-wp-theme-json.php‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,32 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
15361536
$result['viewport'] = self::sanitize_viewport_settings( $input[ $subtree ]['viewport'] );
15371537
}
15381538

1539+
if ( 'settings' === $subtree ) {
1540+
// Preset leaves are untyped in VALID_SETTINGS, but consumers read `$preset['slug']` as a string.
1541+
$is_valid_preset = static function ( $preset ) {
1542+
return is_array( $preset ) && ( ! isset( $preset['slug'] ) || is_scalar( $preset['slug'] ) );
1543+
};
1544+
foreach ( static::get_setting_nodes( array( 'settings' => $result ) ) as $node ) {
1545+
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
1546+
$path = array_merge( array_slice( $node['path'], 1 ), $preset_metadata['path'] );
1547+
$presets = _wp_array_get( $result, $path, null );
1548+
if ( ! is_array( $presets ) ) {
1549+
continue;
1550+
}
1551+
if ( isset( $presets[0] ) ) {
1552+
$presets = array_values( array_filter( $presets, $is_valid_preset ) );
1553+
} else {
1554+
foreach ( static::VALID_ORIGINS as $origin ) {
1555+
if ( isset( $presets[ $origin ] ) && is_array( $presets[ $origin ] ) ) {
1556+
$presets[ $origin ] = array_values( array_filter( $presets[ $origin ], $is_valid_preset ) );
1557+
}
1558+
}
1559+
}
1560+
_wp_array_set( $result, $path, $presets );
1561+
}
1562+
}
1563+
}
1564+
15391565
if ( empty( $result ) ) {
15401566
unset( $output[ $subtree ] );
15411567
} else {

‎tests/phpunit/tests/theme/wpThemeJson.php‎

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5898,6 +5898,7 @@ public static function data_sanitize_with_invalid_style_variation() {
58985898

58995899
/**
59005900
* @ticket 66286
5901+
* @ticket 66304
59015902
*
59025903
* @dataProvider data_get_stylesheet_ignores_values_of_unexpected_types
59035904
*
@@ -5918,22 +5919,57 @@ public function test_get_stylesheet_ignores_values_of_unexpected_types( $theme_j
59185919
*/
59195920
public static function data_get_stylesheet_ignores_values_of_unexpected_types() {
59205921
return array(
5921-
'root custom css as an object' => array(
5922+
'root custom css as an object' => array(
59225923
array( 'styles' => array( 'css' => array( 'color' => 'red' ) ) ),
59235924
),
5924-
'element custom css as an object' => array(
5925+
'element custom css as an object' => array(
59255926
array( 'styles' => array( 'elements' => array( 'link' => array( 'css' => array( 'color' => 'red' ) ) ) ) ),
59265927
),
5927-
'block custom css as a list' => array(
5928+
'block custom css as a list' => array(
59285929
array( 'styles' => array( 'blocks' => array( 'core/paragraph' => array( 'css' => array( 'color: red;' ) ) ) ) ),
59295930
),
5930-
'ref as an array' => array(
5931+
'ref as an array' => array(
59315932
array( 'styles' => array( 'color' => array( 'text' => array( 'ref' => array( 'styles', 'color' ) ) ) ) ),
59325933
),
5933-
'spacing scale as a string' => array(
5934+
'palette as a list of strings' => array(
5935+
array( 'settings' => array( 'color' => array( 'palette' => array( '#000', '#fff' ) ) ) ),
5936+
),
5937+
'palette keyed by origin with a string' => array(
5938+
array( 'settings' => array( 'color' => array( 'palette' => array( 'custom' => array( '#000' ) ) ) ) ),
5939+
),
5940+
'palette mixing a list and an origin' => array(
5941+
array(
5942+
'settings' => array(
5943+
'color' => array(
5944+
'palette' => array(
5945+
0 => '#000',
5946+
'theme' => '#fff',
5947+
),
5948+
),
5949+
),
5950+
),
5951+
),
5952+
'preset slug as an array' => array(
5953+
array(
5954+
'settings' => array(
5955+
'typography' => array(
5956+
'fontSizes' => array(
5957+
array(
5958+
'slug' => array( 'small' ),
5959+
'size' => '1rem',
5960+
),
5961+
),
5962+
),
5963+
),
5964+
),
5965+
),
5966+
'font families as a list of strings' => array(
5967+
array( 'settings' => array( 'typography' => array( 'fontFamilies' => array( 'Inter' ) ) ) ),
5968+
),
5969+
'spacing scale as a string' => array(
59345970
array( 'settings' => array( 'spacing' => array( 'spacingScale' => '1.5' ) ) ),
59355971
),
5936-
'spacing scale unit as an array' => array(
5972+
'spacing scale unit as an array' => array(
59375973
array(
59385974
'settings' => array(
59395975
'spacing' => array(
@@ -5948,15 +5984,77 @@ public static function data_get_stylesheet_ignores_values_of_unexpected_types()
59485984
),
59495985
),
59505986
),
5951-
'shared style variation as a string' => array(
5987+
'shared style variation as a string' => array(
59525988
array( 'styles' => array( 'variations' => array( 'plain' => 'red' ) ) ),
59535989
),
5954-
'block types item as an array' => array(
5990+
'block types item as an array' => array(
59555991
array( 'blockTypes' => array( array( 'core/button' ) ) ),
59565992
),
59575993
);
59585994
}
59595995

5996+
/**
5997+
* @ticket 66304
5998+
*/
5999+
public function test_sanitize_removes_preset_items_of_unexpected_types() {
6000+
$valid_color = array(
6001+
'slug' => 'black',
6002+
'color' => '#000',
6003+
'name' => 'Black',
6004+
);
6005+
$int_slug = array(
6006+
'slug' => 50,
6007+
'color' => '#888',
6008+
'name' => 'Grey',
6009+
);
6010+
$invalid = array(
6011+
'#fff',
6012+
array(
6013+
'slug' => array( 'white' ),
6014+
'color' => '#fff',
6015+
),
6016+
);
6017+
6018+
$theme_json = new WP_Theme_JSON(
6019+
array(
6020+
'version' => WP_Theme_JSON::LATEST_SCHEMA,
6021+
'settings' => array(
6022+
'color' => array( 'palette' => array_merge( $invalid, array( $valid_color, $int_slug ) ) ),
6023+
'blocks' => array(
6024+
'core/paragraph' => array(
6025+
'color' => array( 'palette' => array( 'custom' => array_merge( $invalid, array( $valid_color, $int_slug ) ) ) ),
6026+
),
6027+
),
6028+
),
6029+
)
6030+
);
6031+
$settings = $theme_json->get_settings();
6032+
6033+
$this->assertSame( array( $valid_color, $int_slug ), $settings['color']['palette']['theme'], 'Top-level preset list' );
6034+
$this->assertSame( array( $valid_color, $int_slug ), $settings['blocks']['core/paragraph']['color']['palette']['custom'], 'Block-level presets keyed by origin' );
6035+
}
6036+
6037+
/**
6038+
* @ticket 66304
6039+
*/
6040+
public function test_remove_insecure_properties_removes_preset_items_of_unexpected_types() {
6041+
$valid_color = array(
6042+
'slug' => 'black',
6043+
'color' => '#000',
6044+
'name' => 'Black',
6045+
);
6046+
6047+
$sanitized = WP_Theme_JSON::remove_insecure_properties(
6048+
array(
6049+
'version' => WP_Theme_JSON::LATEST_SCHEMA,
6050+
'settings' => array( 'color' => array( 'palette' => array( 'custom' => array( '#fff', $valid_color ) ) ) ),
6051+
),
6052+
'custom'
6053+
);
6054+
6055+
$this->assertSame( array( $valid_color ), $sanitized['settings']['color']['palette']['custom'] );
6056+
}
6057+
59606058
/**
59616059
* @ticket 66286
59626060
*/

0 commit comments

Comments
 (0)