From c77842cc2bea0fc1fc1e69dea57dd39de29dc856 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 20 Nov 2025 16:33:51 +1100 Subject: [PATCH 1/9] Introduce SAFE_SETTINGS constant in WP_Theme_JSON to preserve non-preset, non-CSS settings during property removal. Added unit tests to validate the functionality of safe settings and their existence in VALID_SETTINGS. --- src/wp-includes/class-wp-theme-json.php | 47 ++++++++++++++++ tests/phpunit/tests/theme/wpThemeJson.php | 66 +++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index e48bdfa1541dd..bf6a5692e0ddc 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -482,6 +482,26 @@ class WP_Theme_JSON { ), ); + /** + * Safe settings that should be preserved by ::remove_insecure_settings(). + * + * These are non-preset, non-CSS settings that control behavior rather than styling. + * Each entry defines the setting path and its expected type for validation. + * Each entry should also be present in ::VALID_SETTINGS. + * + * @since 7.0.0 + */ + const SAFE_SETTINGS = array( + array( + 'path' => array( 'lightbox', 'allowEditing' ), + 'type' => 'boolean', + ), + array( + 'path' => array( 'lightbox', 'enabled' ), + 'type' => 'boolean', + ), + ); + /** * The valid properties for fontFamilies under settings key. * @@ -3732,6 +3752,33 @@ protected static function remove_insecure_settings( $input ) { // Ensure indirect properties not included in any `PRESETS_METADATA` value are allowed. static::remove_indirect_properties( $input, $output ); + // Preserve all valid settings that aren't presets or indirect properties. + foreach ( static::SAFE_SETTINGS as $safe_setting ) { + $path = $safe_setting['path']; + $type = $safe_setting['type']; + + // Extract the value from input using the path. + $value = _wp_array_get( $input, $path, null ); + + // Skip if the setting is not present in the input. + if ( null === $value ) { + continue; + } + + // Validate the type. + $is_valid_type = false; + switch ( $type ) { + case 'boolean': + $is_valid_type = is_bool( $value ); + break; + } + + // If the type is valid, set it in the output using the path. + if ( $is_valid_type ) { + _wp_array_set( $output, $path, $value ); + } + } + return $output; } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 2bf0e7d84f266..3bdfe2c63f44b 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6623,4 +6623,70 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + + /** + * @covers WP_Theme_JSON::remove_insecure_properties + */ + public function test_remove_insecure_properties_should_allow_safe_settings() { + $actual = WP_Theme_JSON::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/image' => array( + 'lightbox' => array( + 'enabled' => false, + 'allowEditing' => true, + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/image' => array( + 'lightbox' => array( + 'enabled' => false, + 'allowEditing' => true, + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + /** + * @covers WP_Theme_JSON::remove_insecure_properties + */ + public function test_safe_settings_paths_should_exist_in_valid_settings() { + // Verify all paths in SAFE_SETTINGS exist in VALID_SETTINGS. + foreach ( WP_Theme_JSON::SAFE_SETTINGS as $safe_setting ) { + $path = $safe_setting['path']; + $data = WP_Theme_JSON::VALID_SETTINGS; + + // Check if path exists by traversing the nested structure. + $exists = true; + foreach ( $path as $key ) { + if ( ! is_array( $data ) || ! array_key_exists( $key, $data ) ) { + $exists = false; + break; + } + $data = $data[ $key ]; + } + + $this->assertTrue( + $exists, + sprintf( + 'Path %s from SAFE_SETTINGS should exist in VALID_SETTINGS', + implode( '.', $path ) + ) + ); + } + } } From 83c9c9ef8127c7a7b420932bdcf1f70bdd7e102b Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 20 Nov 2025 16:40:51 +1100 Subject: [PATCH 2/9] Add ticket reference to unit tests for `WP_Theme_JSON::remove_insecure_properties`. This update includes the ticket number 64280 in the documentation for the tests that validate safe settings and their paths in valid settings. --- tests/phpunit/tests/theme/wpThemeJson.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 3bdfe2c63f44b..3b910f0a27b3b 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6626,6 +6626,8 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { /** * @covers WP_Theme_JSON::remove_insecure_properties + * + * @ticket 64280 */ public function test_remove_insecure_properties_should_allow_safe_settings() { $actual = WP_Theme_JSON::remove_insecure_properties( @@ -6663,6 +6665,8 @@ public function test_remove_insecure_properties_should_allow_safe_settings() { /** * @covers WP_Theme_JSON::remove_insecure_properties + * + * @ticket 64280 */ public function test_safe_settings_paths_should_exist_in_valid_settings() { // Verify all paths in SAFE_SETTINGS exist in VALID_SETTINGS. From 503714d2027315f17eac0b2206d1e3a5f226ad0e Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 21 Nov 2025 14:14:49 +1100 Subject: [PATCH 3/9] Refactor SAFE_SETTINGS in WP_Theme_JSON to be private, ensuring it is used only internally. Update unit tests to validate the removal of insecure properties and ensure safe settings are not allowed to be unsafe. --- src/wp-includes/class-wp-theme-json.php | 6 ++- tests/phpunit/tests/theme/wpThemeJson.php | 65 +++++++++++++---------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index bf6a5692e0ddc..c7096cd74a380 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -487,11 +487,13 @@ class WP_Theme_JSON { * * These are non-preset, non-CSS settings that control behavior rather than styling. * Each entry defines the setting path and its expected type for validation. - * Each entry should also be present in ::VALID_SETTINGS. + * + * The constant is deliberately private to prevent external usage by plugins. + * Like the class itself, it is intended for internal core usage. * * @since 7.0.0 */ - const SAFE_SETTINGS = array( + private const SAFE_SETTINGS = array( array( 'path' => array( 'lightbox', 'allowEditing' ), 'type' => 'boolean', diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 3b910f0a27b3b..409f5c0c777f8 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6630,16 +6630,17 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { * @ticket 64280 */ public function test_remove_insecure_properties_should_allow_safe_settings() { - $actual = WP_Theme_JSON::remove_insecure_properties( + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( - 'lightbox' => array( + 'lightbox' => array( 'enabled' => false, 'allowEditing' => true, ), + 'unsupported' => 'value', ), ), ), @@ -6647,7 +6648,7 @@ public function test_remove_insecure_properties_should_allow_safe_settings() { ); $expected = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( @@ -6662,35 +6663,41 @@ public function test_remove_insecure_properties_should_allow_safe_settings() { $this->assertEqualSetsWithIndex( $expected, $actual ); } - /** * @covers WP_Theme_JSON::remove_insecure_properties * * @ticket 64280 */ - public function test_safe_settings_paths_should_exist_in_valid_settings() { - // Verify all paths in SAFE_SETTINGS exist in VALID_SETTINGS. - foreach ( WP_Theme_JSON::SAFE_SETTINGS as $safe_setting ) { - $path = $safe_setting['path']; - $data = WP_Theme_JSON::VALID_SETTINGS; - - // Check if path exists by traversing the nested structure. - $exists = true; - foreach ( $path as $key ) { - if ( ! is_array( $data ) || ! array_key_exists( $key, $data ) ) { - $exists = false; - break; - } - $data = $data[ $key ]; - } - - $this->assertTrue( - $exists, - sprintf( - 'Path %s from SAFE_SETTINGS should exist in VALID_SETTINGS', - implode( '.', $path ) - ) - ); - } + public function test_remove_insecure_properties_should_not_allow_unsafe_settings() { + $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/image' => array( + 'lightbox' => array( + 'enabled' => 'false', + 'allowEditing' => true, + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/image' => array( + 'lightbox' => array( + 'allowEditing' => true, + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); } } From f481c65533d477eb4192d3aca82cc66c25ea6c02 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 21 Nov 2025 14:31:19 +1100 Subject: [PATCH 4/9] Whoops. There is no WP_Theme_JSON_Gutenberg --- tests/phpunit/tests/theme/wpThemeJson.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 409f5c0c777f8..7bdcb9dfa7538 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6630,9 +6630,9 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { * @ticket 64280 */ public function test_remove_insecure_properties_should_allow_safe_settings() { - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + $actual = WP_Theme_JSON::remove_insecure_properties( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( @@ -6648,7 +6648,7 @@ public function test_remove_insecure_properties_should_allow_safe_settings() { ); $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( @@ -6669,9 +6669,9 @@ public function test_remove_insecure_properties_should_allow_safe_settings() { * @ticket 64280 */ public function test_remove_insecure_properties_should_not_allow_unsafe_settings() { - $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( + $actual = WP_Theme_JSON::remove_insecure_properties( array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( @@ -6686,7 +6686,7 @@ public function test_remove_insecure_properties_should_not_allow_unsafe_settings ); $expected = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( From 4c55853a4bcca28e1351cd20aad03a240cd8b0fb Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 23 Nov 2025 21:17:21 +1100 Subject: [PATCH 5/9] Enhance WP_Theme_JSON to include type validation for boolean values in settings. Introduce a new method to preserve valid typed settings based on schema markers. Update unit tests to ensure correct behavior for boolean values and their validation against the schema. --- src/wp-includes/class-wp-theme-json.php | 96 ++++++++-------- tests/phpunit/tests/theme/wpThemeJson.php | 127 +++++++++++++++------- 2 files changed, 135 insertions(+), 88 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index c7096cd74a380..2766e2b2bc501 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -395,6 +395,7 @@ class WP_Theme_JSON { * @since 6.6.0 Added support for 'dimensions.aspectRatios', 'dimensions.defaultAspectRatios', * 'typography.defaultFontSizes', and 'spacing.defaultSpacingSizes'. * @since 6.9.0 Added support for `border.radiusSizes`. + * @since 7.0.0 Added type markers to the schema for boolean values. * @var array */ const VALID_SETTINGS = array( @@ -442,8 +443,8 @@ class WP_Theme_JSON { 'allowCustomContentAndWideSize' => null, ), 'lightbox' => array( - 'enabled' => null, - 'allowEditing' => null, + 'enabled' => true, + 'allowEditing' => true, ), 'position' => array( 'fixed' => null, @@ -482,28 +483,6 @@ class WP_Theme_JSON { ), ); - /** - * Safe settings that should be preserved by ::remove_insecure_settings(). - * - * These are non-preset, non-CSS settings that control behavior rather than styling. - * Each entry defines the setting path and its expected type for validation. - * - * The constant is deliberately private to prevent external usage by plugins. - * Like the class itself, it is intended for internal core usage. - * - * @since 7.0.0 - */ - private const SAFE_SETTINGS = array( - array( - 'path' => array( 'lightbox', 'allowEditing' ), - 'type' => 'boolean', - ), - array( - 'path' => array( 'lightbox', 'enabled' ), - 'type' => 'boolean', - ), - ); - /** * The valid properties for fontFamilies under settings key. * @@ -1268,6 +1247,7 @@ protected static function get_blocks_metadata() { * It is recursive and modifies the input in-place. * * @since 5.8.0 + * @since 7.0.0 Added type validation for boolean values. * * @param array $tree Input to process. * @param array $schema Schema to adhere to. @@ -1285,6 +1265,17 @@ protected static function remove_keys_not_in_schema( $tree, $schema ) { continue; } + // Validate type if schema specifies a boolean marker. + if ( is_bool( $schema[ $key ] ) ) { + // Schema expects a boolean value - validate the input matches. + if ( ! is_bool( $value ) ) { + unset( $tree[ $key ] ); + continue; + } + // Type matches, keep the value and continue to next key. + continue; + } + if ( is_array( $schema[ $key ] ) ) { if ( ! is_array( $value ) ) { unset( $tree[ $key ] ); @@ -3695,6 +3686,35 @@ protected static function remove_insecure_inner_block_styles( $blocks ) { return $sanitized; } + /** + * Preserves valid typed settings from input to output based on type markers in schema. + * + * Recursively iterates through the schema and validates/preserves settings + * that have type markers (e.g., boolean) in VALID_SETTINGS. + * + * @since 7.0.0 + * + * @param array $input Input settings to process. + * @param array $output Output settings array (passed by reference). + * @param array $schema Schema to validate against (typically VALID_SETTINGS). + * @param array $path Current path in the schema (for recursive calls). + */ + private static function preserve_valid_typed_settings( $input, &$output, $schema, $path = array() ) { + foreach ( $schema as $key => $schema_value ) { + $current_path = array_merge( $path, array( $key ) ); + + // Validate boolean type markers. + if ( is_bool( $schema_value ) ) { + $value = _wp_array_get( $input, $current_path, null ); + if ( null !== $value && is_bool( $value ) ) { + _wp_array_set( $output, $current_path, $value ); // Preserve boolean value. + } + } elseif ( is_array( $schema_value ) ) { + static::preserve_valid_typed_settings( $input, $output, $schema_value, $current_path ); // Recurse into nested structure. + } + } + } + /** * Processes a setting node and returns the same node * without the insecure settings. @@ -3754,32 +3774,8 @@ protected static function remove_insecure_settings( $input ) { // Ensure indirect properties not included in any `PRESETS_METADATA` value are allowed. static::remove_indirect_properties( $input, $output ); - // Preserve all valid settings that aren't presets or indirect properties. - foreach ( static::SAFE_SETTINGS as $safe_setting ) { - $path = $safe_setting['path']; - $type = $safe_setting['type']; - - // Extract the value from input using the path. - $value = _wp_array_get( $input, $path, null ); - - // Skip if the setting is not present in the input. - if ( null === $value ) { - continue; - } - - // Validate the type. - $is_valid_type = false; - switch ( $type ) { - case 'boolean': - $is_valid_type = is_bool( $value ); - break; - } - - // If the type is valid, set it in the output using the path. - if ( $is_valid_type ) { - _wp_array_set( $output, $path, $value ); - } - } + // Preserve all valid settings that have type markers in VALID_SETTINGS. + static::preserve_valid_typed_settings( $input, $output, static::VALID_SETTINGS ); return $output; } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 7bdcb9dfa7538..a3df324e96d42 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -5999,8 +5999,8 @@ public function test_internal_syntax_is_converted_to_css_variables() { * @ticket 58588 * @ticket 60613 * - * @covers WP_Theme_JSON_Gutenberg::resolve_variables - * @covers WP_Theme_JSON_Gutenberg::convert_variables_to_value + * @covers WP_Theme_JSON::resolve_variables + * @covers WP_Theme_JSON::convert_variables_to_value */ public function test_resolve_variables() { $primary_color = '#9DFF20'; @@ -6625,59 +6625,69 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { } /** - * @covers WP_Theme_JSON::remove_insecure_properties + * @covers WP_Theme_JSON::sanitize + * @covers WP_Theme_JSON::remove_keys_not_in_schema * * @ticket 64280 */ - public function test_remove_insecure_properties_should_allow_safe_settings() { - $actual = WP_Theme_JSON::remove_insecure_properties( + public function test_sanitize_preserves_boolean_values_when_schema_expects_boolean() { + $theme_json = new WP_Theme_JSON( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'blocks' => array( - 'core/image' => array( - 'lightbox' => array( - 'enabled' => false, - 'allowEditing' => true, - ), - 'unsupported' => 'value', - ), + 'lightbox' => array( + 'enabled' => true, + 'allowEditing' => false, ), ), ) ); - $expected = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'settings' => array( - 'blocks' => array( - 'core/image' => array( - 'lightbox' => array( - 'enabled' => false, - 'allowEditing' => true, - ), + $settings = $theme_json->get_settings(); + $this->assertTrue( $settings['lightbox']['enabled'] ); + $this->assertFalse( $settings['lightbox']['allowEditing'] ); + } + + /** + * @covers WP_Theme_JSON::sanitize + * @covers WP_Theme_JSON::remove_keys_not_in_schema + * + * @ticket 64280 + */ + public function test_sanitize_removes_non_boolean_values_when_schema_expects_boolean() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'lightbox' => array( + 'enabled' => 'not-a-boolean', + 'allowEditing' => 123, ), ), - ), + ) ); - $this->assertEqualSetsWithIndex( $expected, $actual ); + $settings = $theme_json->get_settings(); + $this->assertArrayNotHasKey( 'enabled', $settings['lightbox'] ?? array() ); + $this->assertArrayNotHasKey( 'allowEditing', $settings['lightbox'] ?? array() ); } + /** - * @covers WP_Theme_JSON::remove_insecure_properties + * @covers WP_Theme_JSON::sanitize + * @covers WP_Theme_JSON::remove_keys_not_in_schema * * @ticket 64280 */ - public function test_remove_insecure_properties_should_not_allow_unsafe_settings() { - $actual = WP_Theme_JSON::remove_insecure_properties( + public function test_sanitize_preserves_boolean_values_in_block_settings() { + $theme_json = new WP_Theme_JSON( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( 'blocks' => array( 'core/image' => array( 'lightbox' => array( - 'enabled' => 'false', - 'allowEditing' => true, + 'enabled' => true, + 'allowEditing' => false, ), ), ), @@ -6685,19 +6695,60 @@ public function test_remove_insecure_properties_should_not_allow_unsafe_settings ) ); - $expected = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'settings' => array( - 'blocks' => array( - 'core/image' => array( - 'lightbox' => array( - 'allowEditing' => true, + $settings = $theme_json->get_settings(); + $this->assertTrue( $settings['blocks']['core/image']['lightbox']['enabled'] ); + $this->assertFalse( $settings['blocks']['core/image']['lightbox']['allowEditing'] ); + } + + /** + * @covers WP_Theme_JSON::sanitize + * @covers WP_Theme_JSON::remove_keys_not_in_schema + * + * @ticket 64280 + */ + public function test_sanitize_removes_non_boolean_values_in_block_settings() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/image' => array( + 'lightbox' => array( + 'enabled' => 'string-value', + 'allowEditing' => array( 'not', 'a', 'boolean' ), + ), ), ), ), - ), + ) ); - $this->assertEqualSetsWithIndex( $expected, $actual ); + $settings = $theme_json->get_settings(); + $lightbox = $settings['blocks']['core/image']['lightbox'] ?? array(); + $this->assertArrayNotHasKey( 'enabled', $lightbox ); + $this->assertArrayNotHasKey( 'allowEditing', $lightbox ); + } + + /** + * @covers WP_Theme_JSON::sanitize + * @covers WP_Theme_JSON::remove_keys_not_in_schema + * + * @ticket 64280 + */ + public function test_sanitize_preserves_null_schema_behavior() { + // Test that settings with null in schema (no type validation) still accept any type. + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'appearanceTools' => 'string-value', // null in schema, should accept any type. + 'custom' => array( 'nested' => 'value' ), // null in schema, should accept any type. + ), + ) + ); + + $settings = $theme_json->get_settings(); + $this->assertSame( 'string-value', $settings['appearanceTools'] ); + $this->assertSame( array( 'nested' => 'value' ), $settings['custom'] ); } } From bdfae6dbbe549441b074b96f3349edc11b3d14b8 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 23 Nov 2025 21:18:38 +1100 Subject: [PATCH 6/9] Enhance unit tests for WP_Theme_JSON by adding descriptive messages to assertions. This improves clarity on expected outcomes for lightbox settings and appearance tools, ensuring better maintainability and understanding of test intentions. --- tests/phpunit/tests/theme/wpThemeJson.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index a3df324e96d42..aeb89f323e77a 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6644,8 +6644,8 @@ public function test_sanitize_preserves_boolean_values_when_schema_expects_boole ); $settings = $theme_json->get_settings(); - $this->assertTrue( $settings['lightbox']['enabled'] ); - $this->assertFalse( $settings['lightbox']['allowEditing'] ); + $this->assertTrue( $settings['lightbox']['enabled'], 'Enabled should be true' ); + $this->assertFalse( $settings['lightbox']['allowEditing'], 'Allow editing should be false' ); } /** @@ -6668,8 +6668,8 @@ public function test_sanitize_removes_non_boolean_values_when_schema_expects_boo ); $settings = $theme_json->get_settings(); - $this->assertArrayNotHasKey( 'enabled', $settings['lightbox'] ?? array() ); - $this->assertArrayNotHasKey( 'allowEditing', $settings['lightbox'] ?? array() ); + $this->assertArrayNotHasKey( 'enabled', $settings['lightbox'] ?? array(), 'Enabled should be removed' ); + $this->assertArrayNotHasKey( 'allowEditing', $settings['lightbox'] ?? array(), 'Allow editing should be removed' ); } /** @@ -6696,8 +6696,8 @@ public function test_sanitize_preserves_boolean_values_in_block_settings() { ); $settings = $theme_json->get_settings(); - $this->assertTrue( $settings['blocks']['core/image']['lightbox']['enabled'] ); - $this->assertFalse( $settings['blocks']['core/image']['lightbox']['allowEditing'] ); + $this->assertTrue( $settings['blocks']['core/image']['lightbox']['enabled'], 'Enabled should be true' ); + $this->assertFalse( $settings['blocks']['core/image']['lightbox']['allowEditing'], 'Allow editing should be false' ); } /** @@ -6725,8 +6725,8 @@ public function test_sanitize_removes_non_boolean_values_in_block_settings() { $settings = $theme_json->get_settings(); $lightbox = $settings['blocks']['core/image']['lightbox'] ?? array(); - $this->assertArrayNotHasKey( 'enabled', $lightbox ); - $this->assertArrayNotHasKey( 'allowEditing', $lightbox ); + $this->assertArrayNotHasKey( 'enabled', $lightbox, 'Enabled should be removed' ); + $this->assertArrayNotHasKey( 'allowEditing', $lightbox, 'Allow editing should be removed' ); } /** @@ -6748,7 +6748,7 @@ public function test_sanitize_preserves_null_schema_behavior() { ); $settings = $theme_json->get_settings(); - $this->assertSame( 'string-value', $settings['appearanceTools'] ); - $this->assertSame( array( 'nested' => 'value' ), $settings['custom'] ); + $this->assertSame( 'string-value', $settings['appearanceTools'], 'Appearance tools should be string value' ); + $this->assertSame( array( 'nested' => 'value' ), $settings['custom'], 'Custom should be array value' ); } } From 311eafbc4448ff242e6161c45347b08c2a91420b Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 26 Nov 2025 14:10:23 +1100 Subject: [PATCH 7/9] Refactor WP_Theme_JSON to use `self` instead of `static` for method calls in `preserve_valid_typed_settings`. This change enhances consistency in method referencing within the class. --- src/wp-includes/class-wp-theme-json.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 2766e2b2bc501..492148b9bfcc2 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3710,7 +3710,7 @@ private static function preserve_valid_typed_settings( $input, &$output, $schema _wp_array_set( $output, $current_path, $value ); // Preserve boolean value. } } elseif ( is_array( $schema_value ) ) { - static::preserve_valid_typed_settings( $input, $output, $schema_value, $current_path ); // Recurse into nested structure. + self::preserve_valid_typed_settings( $input, $output, $schema_value, $current_path ); // Recurse into nested structure. } } } @@ -3775,7 +3775,7 @@ protected static function remove_insecure_settings( $input ) { static::remove_indirect_properties( $input, $output ); // Preserve all valid settings that have type markers in VALID_SETTINGS. - static::preserve_valid_typed_settings( $input, $output, static::VALID_SETTINGS ); + self::preserve_valid_typed_settings( $input, $output, static::VALID_SETTINGS ); return $output; } From 82fd70a5fa0ee82f88652ac4af1e38bca20ac921 Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 26 Nov 2025 14:20:57 +1100 Subject: [PATCH 8/9] Update PHP docblock in WP_Theme_JSON to include specific type annotations for parameters in the preserve_valid_typed_settings method, enhancing clarity and type safety. --- src/wp-includes/class-wp-theme-json.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 492148b9bfcc2..cac5eaf2ba80a 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3694,10 +3694,10 @@ protected static function remove_insecure_inner_block_styles( $blocks ) { * * @since 7.0.0 * - * @param array $input Input settings to process. - * @param array $output Output settings array (passed by reference). - * @param array $schema Schema to validate against (typically VALID_SETTINGS). - * @param array $path Current path in the schema (for recursive calls). + * @param array $input Input settings to process. + * @param array $output Output settings array (passed by reference). + * @param array $schema Schema to validate against (typically VALID_SETTINGS). + * @param array $path Current path in the schema (for recursive calls). */ private static function preserve_valid_typed_settings( $input, &$output, $schema, $path = array() ) { foreach ( $schema as $key => $schema_value ) { From 0e46e43f8c4f6a37a489bc6ca5e28f900406220b Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 26 Nov 2025 14:55:17 +1100 Subject: [PATCH 9/9] Update src/wp-includes/class-wp-theme-json.php Co-authored-by: Weston Ruter --- src/wp-includes/class-wp-theme-json.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index cac5eaf2ba80a..c1bc6209f306d 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -3706,7 +3706,7 @@ private static function preserve_valid_typed_settings( $input, &$output, $schema // Validate boolean type markers. if ( is_bool( $schema_value ) ) { $value = _wp_array_get( $input, $current_path, null ); - if ( null !== $value && is_bool( $value ) ) { + if ( is_bool( $value ) ) { _wp_array_set( $output, $current_path, $value ); // Preserve boolean value. } } elseif ( is_array( $schema_value ) ) {