Make WordPress Core

Changeset 57496


Ignore:
Timestamp:
01/31/2024 10:53:48 AM (4 months ago)
Author:
youknowriad
Message:

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.

Location:
trunk
Files:
2 edited

Legend:

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

    r57491 r57496  
    430430    );
    431431
     432    /*
     433     * The valid properties for fontFamilies under settings key.
     434     *
     435     * @since 6.5.0
     436     *
     437     * @var array
     438     */
     439    const FONT_FAMILY_SCHEMA = array(
     440        array(
     441            'fontFamily' => null,
     442            'name'       => null,
     443            'slug'       => null,
     444            'fontFace'   => array(
     445                array(
     446                    'ascentOverride'        => null,
     447                    'descentOverride'       => null,
     448                    'fontDisplay'           => null,
     449                    'fontFamily'            => null,
     450                    'fontFeatureSettings'   => null,
     451                    'fontStyle'             => null,
     452                    'fontStretch'           => null,
     453                    'fontVariationSettings' => null,
     454                    'fontWeight'            => null,
     455                    'lineGapOverride'       => null,
     456                    'sizeAdjust'            => null,
     457                    'src'                   => null,
     458                    'unicodeRange'          => null,
     459                ),
     460            ),
     461        ),
     462    );
     463
    432464    /**
    433465     * The valid properties under the styles key.
     
    558590
    559591    /**
     592     * Return the input schema at the root and per origin.
     593     *
     594     * @since 6.5.0
     595     *
     596     * @param array $schema The base schema.
     597     * @return array The schema at the root and per origin.
     598     *
     599     * Example:
     600     * schema_in_root_and_per_origin(
     601     *   array(
     602     *    'fontFamily' => null,
     603     *    'slug' => null,
     604     *   )
     605     * )
     606     *
     607     * Returns:
     608     * array(
     609     *  'fontFamily' => null,
     610     *  'slug' => null,
     611     *  'default' => array(
     612     *    'fontFamily' => null,
     613     *    'slug' => null,
     614     *  ),
     615     *  'blocks' => array(
     616     *    'fontFamily' => null,
     617     *    'slug' => null,
     618     *  ),
     619     *  'theme' => array(
     620     *     'fontFamily' => null,
     621     *     'slug' => null,
     622     *  ),
     623     *  'custom' => array(
     624     *     'fontFamily' => null,
     625     *     'slug' => null,
     626     *  ),
     627     * )
     628     */
     629    protected static function schema_in_root_and_per_origin( $schema ) {
     630        $schema_in_root_and_per_origin = $schema;
     631        foreach ( static::VALID_ORIGINS as $origin ) {
     632            $schema_in_root_and_per_origin[ $origin ] = $schema;
     633        }
     634        return $schema_in_root_and_per_origin;
     635    }
     636
     637    /**
    560638     * Returns a class name by an element name.
    561639     *
     
    798876        }
    799877
    800         $schema['styles']             = static::VALID_STYLES;
    801         $schema['styles']['blocks']   = $schema_styles_blocks;
    802         $schema['styles']['elements'] = $schema_styles_elements;
    803         $schema['settings']           = static::VALID_SETTINGS;
    804         $schema['settings']['blocks'] = $schema_settings_blocks;
     878        $schema['styles']                                 = static::VALID_STYLES;
     879        $schema['styles']['blocks']                       = $schema_styles_blocks;
     880        $schema['styles']['elements']                     = $schema_styles_elements;
     881        $schema['settings']                               = static::VALID_SETTINGS;
     882        $schema['settings']['blocks']                     = $schema_settings_blocks;
     883        $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA );
    805884
    806885        // Remove anything that's not present in the schema.
     
    9751054     */
    9761055    protected static function remove_keys_not_in_schema( $tree, $schema ) {
    977         $tree = array_intersect_key( $tree, $schema );
    978 
    979         foreach ( $schema as $key => $data ) {
    980             if ( ! isset( $tree[ $key ] ) ) {
     1056        if ( ! is_array( $tree ) ) {
     1057            return $tree;
     1058        }
     1059
     1060        foreach ( $tree as $key => $value ) {
     1061            // Remove keys not in the schema or with null/empty values.
     1062            if ( ! array_key_exists( $key, $schema ) ) {
     1063                unset( $tree[ $key ] );
    9811064                continue;
    9821065            }
    9831066
    984             if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) {
    985                 $tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] );
    986 
    987                 if ( empty( $tree[ $key ] ) ) {
    988                     unset( $tree[ $key ] );
     1067            // Check if the value is an array and requires further processing.
     1068            if ( is_array( $value ) && is_array( $schema[ $key ] ) ) {
     1069                // Determine if it is an associative or indexed array.
     1070                $schema_is_assoc = self::is_assoc( $value );
     1071
     1072                if ( $schema_is_assoc ) {
     1073                    // If associative, process as a single object.
     1074                    $tree[ $key ] = self::remove_keys_not_in_schema( $value, $schema[ $key ] );
     1075
     1076                    if ( empty( $tree[ $key ] ) ) {
     1077                        unset( $tree[ $key ] );
     1078                    }
     1079                } else {
     1080                    // If indexed, process each item in the array.
     1081                    foreach ( $value as $item_key => $item_value ) {
     1082                        if ( isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) ) {
     1083                            $tree[ $key ][ $item_key ] = self::remove_keys_not_in_schema( $item_value, $schema[ $key ][0] );
     1084                        } else {
     1085                            // If the schema does not define a further structure, keep the value as is.
     1086                            $tree[ $key ][ $item_key ] = $item_value;
     1087                        }
     1088                    }
    9891089                }
    9901090            } elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) {
     
    9941094
    9951095        return $tree;
     1096    }
     1097
     1098    /**
     1099     * Checks if the given array is associative.
     1100     *
     1101     * @since 6.5.0
     1102     * @param array $data The array to check.
     1103     * @return bool True if the array is associative, false otherwise.
     1104     */
     1105    protected static function is_assoc( $data ) {
     1106        if ( array() === $data ) {
     1107            return false;
     1108        }
     1109        return array_keys( $data ) !== range( 0, count( $data ) - 1 );
    9961110    }
    9971111
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r57491 r57496  
    49794979        );
    49804980    }
     4981
     4982    /**
     4983     * Tests that invalid properties are removed from the theme.json inside indexed arrays as settings.typography.fontFamilies.
     4984     *
     4985     * @ticket 60360
     4986     */
     4987    public function test_sanitize_indexed_arrays() {
     4988        $theme_json = new WP_Theme_JSON(
     4989            array(
     4990                'version'  => '2',
     4991                'badKey2'  => 'I am Evil!',
     4992                'settings' => array(
     4993                    'badKey3'    => 'I am Evil!',
     4994                    'typography' => array(
     4995                        'badKey4'      => 'I am Evil!',
     4996                        'fontFamilies' => array(
     4997                            'custom' => array(
     4998                                array(
     4999                                    'badKey4'    => 'I am Evil!',
     5000                                    'name'       => 'Arial',
     5001                                    'slug'       => 'arial',
     5002                                    'fontFamily' => 'Arial, sans-serif',
     5003                                ),
     5004                            ),
     5005                            'theme'  => array(
     5006                                array(
     5007                                    'badKey5'    => 'I am Evil!',
     5008                                    'name'       => 'Piazzolla',
     5009                                    'slug'       => 'piazzolla',
     5010                                    'fontFamily' => 'Piazzolla',
     5011                                    'fontFace'   => array(
     5012                                        array(
     5013                                            'badKey6'    => 'I am Evil!',
     5014                                            'fontFamily' => 'Piazzolla',
     5015                                            'fontStyle'  => 'italic',
     5016                                            'fontWeight' => '400',
     5017                                            'src'        => 'https://example.com/font.ttf',
     5018                                        ),
     5019                                        array(
     5020                                            'badKey7'    => 'I am Evil!',
     5021                                            'fontFamily' => 'Piazzolla',
     5022                                            'fontStyle'  => 'italic',
     5023                                            'fontWeight' => '400',
     5024                                            'src'        => 'https://example.com/font.ttf',
     5025                                        ),
     5026                                    ),
     5027                                ),
     5028                                array(
     5029                                    'badKey8'    => 'I am Evil!',
     5030                                    'name'       => 'Inter',
     5031                                    'slug'       => 'Inter',
     5032                                    'fontFamily' => 'Inter',
     5033                                    'fontFace'   => array(
     5034                                        array(
     5035                                            'badKey9'    => 'I am Evil!',
     5036                                            'fontFamily' => 'Inter',
     5037                                            'fontStyle'  => 'italic',
     5038                                            'fontWeight' => '400',
     5039                                            'src'        => 'https://example.com/font.ttf',
     5040                                        ),
     5041                                        array(
     5042                                            'badKey10'   => 'I am Evil!',
     5043                                            'fontFamily' => 'Inter',
     5044                                            'fontStyle'  => 'italic',
     5045                                            'fontWeight' => '400',
     5046                                            'src'        => 'https://example.com/font.ttf',
     5047                                        ),
     5048                                    ),
     5049                                ),
     5050                            ),
     5051                        ),
     5052                    ),
     5053                ),
     5054            )
     5055        );
     5056
     5057        $expected_sanitized   = array(
     5058            'version'  => '2',
     5059            'settings' => array(
     5060                'typography' => array(
     5061                    'fontFamilies' => array(
     5062                        'custom' => array(
     5063                            array(
     5064                                'name'       => 'Arial',
     5065                                'slug'       => 'arial',
     5066                                'fontFamily' => 'Arial, sans-serif',
     5067                            ),
     5068                        ),
     5069                        'theme'  => array(
     5070                            array(
     5071                                'name'       => 'Piazzolla',
     5072                                'slug'       => 'piazzolla',
     5073                                'fontFamily' => 'Piazzolla',
     5074                                'fontFace'   => array(
     5075                                    array(
     5076                                        'fontFamily' => 'Piazzolla',
     5077                                        'fontStyle'  => 'italic',
     5078                                        'fontWeight' => '400',
     5079                                        'src'        => 'https://example.com/font.ttf',
     5080                                    ),
     5081                                    array(
     5082                                        'fontFamily' => 'Piazzolla',
     5083                                        'fontStyle'  => 'italic',
     5084                                        'fontWeight' => '400',
     5085                                        'src'        => 'https://example.com/font.ttf',
     5086                                    ),
     5087                                ),
     5088                            ),
     5089                            array(
     5090                                'name'       => 'Inter',
     5091                                'slug'       => 'Inter',
     5092                                'fontFamily' => 'Inter',
     5093                                'fontFace'   => array(
     5094                                    array(
     5095                                        'fontFamily' => 'Inter',
     5096                                        'fontStyle'  => 'italic',
     5097                                        'fontWeight' => '400',
     5098                                        'src'        => 'https://example.com/font.ttf',
     5099                                    ),
     5100                                    array(
     5101                                        'fontFamily' => 'Inter',
     5102                                        'fontStyle'  => 'italic',
     5103                                        'fontWeight' => '400',
     5104                                        'src'        => 'https://example.com/font.ttf',
     5105                                    ),
     5106                                ),
     5107                            ),
     5108                        ),
     5109                    ),
     5110                ),
     5111            ),
     5112        );
     5113        $sanitized_theme_json = $theme_json->get_raw_data();
     5114        $this->assertSameSetsWithIndex( $expected_sanitized, $sanitized_theme_json, 'Sanitized theme.json does not match' );
     5115    }
    49815116}
Note: See TracChangeset for help on using the changeset viewer.