Make WordPress Core

Changeset 64309


Ignore:
Timestamp:
10/12/2026 08:18:48 AM (27 hours ago)
Author:
ramonopoly
Message:

Editor: Ignore theme.json values of unexpected types

This commit prevents WP_Theme_JSON from throwing fatal TypeErrors when theme.json or global styles data contains values of the wrong type.

Those values could be user- or AI-generated.

The checks in this commit cover spacing scale arrays, block variation selectors, and custom block CSS processing.

The motivation arose from a real-life PHP fatal where WP_Theme_JSON::process_blocks_custom_css attempted to explode a non-string.

Developed in: ​https://github.com/WordPress/wordpress-develop/pull/14190

Props ramonopoly, aaronrobertshaw, andrewserong.

Fixes #66286.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-theme-json.php

    r64304 r64309  
    11411141                $scale_path    = array( 'settings', 'spacing', 'spacingScale' );
    11421142                $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null );
    1143                 if ( null !== $spacing_scale ) {
     1143                if ( is_array( $spacing_scale ) ) {
    11441144                        // If the spacingScale is not already keyed by origin.
    11451145                        if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) {
    … …  
    12151215                                $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array();
    12161216                                $top_level_data   = $variations[ $variation_name ] ?? array();
    1217                                 $merged_data      = array_replace_recursive( $top_level_data, $block_level_data );
     1217                                if ( ! is_array( $block_level_data ) || ! is_array( $top_level_data ) ) {
     1218                                        continue;
     1219                                }
     1220                                $merged_data = array_replace_recursive( $top_level_data, $block_level_data );
    12181221                                if ( ! empty( $merged_data ) ) {
    12191222                                        _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data );
    … …  
    14941497                        $variation_pseudo_selectors = array();
    14951498                        foreach ( $input['blockTypes'] as $variation_block_type ) {
    1496                                 if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $variation_block_type ] ) ) {
     1499                                if ( is_string( $variation_block_type ) && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $variation_block_type ] ) ) {
    14971500                                        $variation_pseudo_selectors = array_merge(
    14981501                                                $variation_pseudo_selectors,
    … …  
    21022105                if ( in_array( 'custom-css', $types, true ) ) {
    21032106                        // Add the global styles root CSS.
    2104                         $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) );
     2107                        $root_css = $this->theme_json['styles']['css'] ?? '';
     2108                        if ( is_string( $root_css ) ) {
     2109                                $stylesheet .= $root_css;
     2110                        }
    21052111                }
    21062112
    … …  
    21222128                $processed_css = '';
    21232129
    2124                 if ( empty( $css ) ) {
     2130                if ( empty( $css ) || ! is_string( $css ) ) {
    21252131                        return $processed_css;
    21262132                }
    … …  
    32423248                 * the URL takes precedence and the ref is ignored.
    32433249                 */
    3244                 if ( is_array( $value ) && isset( $value['ref'] ) ) {
     3250                if ( is_array( $value ) && isset( $value['ref'] ) && is_string( $value['ref'] ) ) {
    32453251                        $value_path = explode( '.', $value['ref'] );
    32463252                        $ref_value  = _wp_array_get( $theme_json, $value_path );
    … …  
    44324438
    44334439                        // Apply the base spacing scale to the current layer.
    4434                         $base_spacing_scale      = _wp_array_get( $this->theme_json, $scale_path, array() );
    4435                         $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale );
     4440                        $base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() );
     4441                        if ( is_array( $base_spacing_scale ) ) {
     4442                                $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale );
     4443                        }
    44364444
    44374445                        $spacing_scale = _wp_array_get( $incoming_data, $scale_path, null );
    4438                         if ( ! isset( $spacing_scale ) ) {
     4446                        if ( ! is_array( $spacing_scale ) ) {
    44394447                                continue;
    44404448                        }
    … …  
    55705578                        ! is_numeric( $spacing_scale['mediumStep'] ) ||
    55715579                        ! isset( $spacing_scale['unit'] ) ||
     5580                        ! is_string( $spacing_scale['unit'] ) ||
    55725581                        ! isset( $spacing_scale['operator'] ) ||
    55735582                        ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ||
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r64238 r64309  
    58985898
    58995899        /**
     5900         * @ticket 66286
     5901         *
     5902         * @dataProvider data_get_stylesheet_ignores_values_of_unexpected_types
     5903         *
     5904         * @param array $theme_json_data The theme.json data, without the version.
     5905         */
     5906        public function test_get_stylesheet_ignores_values_of_unexpected_types( $theme_json_data ) {
     5907                $theme_json = new WP_Theme_JSON(
     5908                        array_merge( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ), $theme_json_data )
     5909                );
     5910
     5911                $this->assertIsString( $theme_json->get_stylesheet( array( 'variables', 'styles', 'presets', 'custom-css' ) ) );
     5912        }
     5913
     5914        /**
     5915         * Data provider.
     5916         *
     5917         * @return array
     5918         */
     5919        public static function data_get_stylesheet_ignores_values_of_unexpected_types() {
     5920                return array(
     5921                        'root custom css as an object'       => array(
     5922                                array( 'styles' => array( 'css' => array( 'color' => 'red' ) ) ),
     5923                        ),
     5924                        'element custom css as an object'    => array(
     5925                                array( 'styles' => array( 'elements' => array( 'link' => array( 'css' => array( 'color' => 'red' ) ) ) ) ),
     5926                        ),
     5927                        'block custom css as a list'         => array(
     5928                                array( 'styles' => array( 'blocks' => array( 'core/paragraph' => array( 'css' => array( 'color: red;' ) ) ) ) ),
     5929                        ),
     5930                        'ref as an array'                    => array(
     5931                                array( 'styles' => array( 'color' => array( 'text' => array( 'ref' => array( 'styles', 'color' ) ) ) ) ),
     5932                        ),
     5933                        'spacing scale as a string'          => array(
     5934                                array( 'settings' => array( 'spacing' => array( 'spacingScale' => '1.5' ) ) ),
     5935                        ),
     5936                        'spacing scale unit as an array'     => array(
     5937                                array(
     5938                                        'settings' => array(
     5939                                                'spacing' => array(
     5940                                                        'spacingScale' => array(
     5941                                                                'operator'   => '*',
     5942                                                                'increment'  => 1.5,
     5943                                                                'steps'      => 7,
     5944                                                                'mediumStep' => 1.5,
     5945                                                                'unit'       => array( 'rem' ),
     5946                                                        ),
     5947                                                ),
     5948                                        ),
     5949                                ),
     5950                        ),
     5951                        'shared style variation as a string' => array(
     5952                                array( 'styles' => array( 'variations' => array( 'plain' => 'red' ) ) ),
     5953                        ),
     5954                        'block types item as an array'       => array(
     5955                                array( 'blockTypes' => array( array( 'core/button' ) ) ),
     5956                        ),
     5957                );
     5958        }
     5959
     5960        /**
     5961         * @ticket 66286
     5962         */
     5963        public function test_merge_does_not_generate_spacing_sizes_from_a_non_array_per_origin_spacing_scale() {
     5964                $theme_json = new WP_Theme_JSON(
     5965                        array(
     5966                                'version'  => WP_Theme_JSON::LATEST_SCHEMA,
     5967                                'settings' => array(
     5968                                        'spacing' => array(
     5969                                                'spacingScale' => array(
     5970                                                        'operator'   => '*',
     5971                                                        'increment'  => 1.5,
     5972                                                        'steps'      => 3,
     5973                                                        'mediumStep' => 1.5,
     5974                                                        'unit'       => 'rem',
     5975                                                ),
     5976                                        ),
     5977                                ),
     5978                        ),
     5979                        'default'
     5980                );
     5981                $theme_json->merge(
     5982                        new WP_Theme_JSON(
     5983                                array(
     5984                                        'version'  => WP_Theme_JSON::LATEST_SCHEMA,
     5985                                        'settings' => array( 'spacing' => array( 'spacingScale' => array( 'theme' => '1.5' ) ) ),
     5986                                )
     5987                        )
     5988                );
     5989                $spacing_sizes = $theme_json->get_settings()['spacing']['spacingSizes'];
     5990
     5991                $this->assertSame( array(), $spacing_sizes['theme'], 'The theme layer with a non-array spacing scale should generate no sizes.' );
     5992                $this->assertSame(
     5993                        array(
     5994                                array(
     5995                                        'name' => 'Small',
     5996                                        'slug' => '40',
     5997                                        'size' => '1rem',
     5998                                ),
     5999                                array(
     6000                                        'name' => 'Medium',
     6001                                        'slug' => '50',
     6002                                        'size' => '1.5rem',
     6003                                ),
     6004                                array(
     6005                                        'name' => 'Large',
     6006                                        'slug' => '60',
     6007                                        'size' => '2.25rem',
     6008                                ),
     6009                        ),
     6010                        $spacing_sizes['default'],
     6011                        'The default layer sizes should be unaffected.'
     6012                );
     6013        }
     6014
     6015        /**
    59006016         * @ticket 57583
    59016017         * @ticket 61165
    … …  
    74127528         * @ticket 61165
    74137529         * @ticket 61769
     7530         * @ticket 66286
    74147531         *
    74157532         * @dataProvider data_process_blocks_custom_css
    … …  
    74457562                                        'selector' => '.foo',
    74467563                                        'css'      => '',
     7564                                ),
     7565                                'expected' => '',
     7566                        ),
     7567                        'css as an object'             => array(
     7568                                'input'    => array(
     7569                                        'selector' => '.foo',
     7570                                        'css'      => array( 'color' => 'red' ),
     7571                                ),
     7572                                'expected' => '',
     7573                        ),
     7574                        'css as a list of strings'     => array(
     7575                                'input'    => array(
     7576                                        'selector' => '.foo',
     7577                                        'css'      => array( 'color: red;' ),
    74477578                                ),
    74487579                                'expected' => '',
Note: See TracChangeset for help on using the changeset viewer.