diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php index 7dd2c94bf3088..2e271be545f57 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php @@ -96,7 +96,7 @@ class WP_Theme_JSON_Gutenberg { const PRESETS_METADATA = array( array( 'path' => array( 'color', 'palette' ), - 'override' => false, + 'override' => array( 'color', 'defaultPalette' ), 'value_key' => 'color', 'css_vars' => '--wp--preset--color--$slug', 'classes' => array( @@ -108,7 +108,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'color', 'gradients' ), - 'override' => false, + 'override' => array( 'color', 'defaultGradients' ), 'value_key' => 'gradient', 'css_vars' => '--wp--preset--gradient--$slug', 'classes' => array( '.has-$slug-gradient-background' => 'background' ), @@ -1412,9 +1412,9 @@ public function merge( $incoming ) { * we remove it from the theme presets. */ $nodes = self::get_setting_nodes( $incoming_data ); - $slugs_global = self::get_slugs_not_to_override( $this->theme_json ); + $slugs_global = self::get_default_slugs( $this->theme_json, array( 'settings' ) ); foreach ( $nodes as $node ) { - $slugs_node = self::get_slugs_not_to_override( $this->theme_json, $node['path'] ); + $slugs_node = self::get_default_slugs( $this->theme_json, $node['path'] ); $slugs = array_merge_recursive( $slugs_global, $slugs_node ); // Replace the spacing.units. @@ -1426,6 +1426,8 @@ public function merge( $incoming ) { // Replace the presets. foreach ( self::PRESETS_METADATA as $preset ) { + $override_preset = self::should_override_preset( $this->theme_json, $node['path'], $preset['override'] ); + foreach ( self::VALID_ORIGINS as $origin ) { $path = array_merge( $node['path'], $preset['path'], array( $origin ) ); $content = _wp_array_get( $incoming_data, $path, null ); @@ -1435,13 +1437,12 @@ public function merge( $incoming ) { if ( ( 'theme' !== $origin ) || - ( 'theme' === $origin && $preset['override'] ) + ( 'theme' === $origin && $override_preset ) ) { _wp_array_set( $this->theme_json, $path, $content ); - } - - if ( 'theme' === $origin && ! $preset['override'] ) { - $content = self::filter_slugs( $content, $preset['path'], $slugs ); + } else { + $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); + $content = self::filter_slugs( $content, $slugs_for_preset ); _wp_array_set( $this->theme_json, $path, $content ); } } @@ -1450,38 +1451,73 @@ public function merge( $incoming ) { } /** - * Returns the slugs for all the presets that cannot be overriden - * in the given path. It returns an associative array + * Returns whether a presets should be overriden or not. + * + * @param array $theme_json The theme.json like structure to inspect. + * @param array $path Path to inspect. + * @param bool|array $override Data to compute whether to override the preset. + * @return boolean + */ + private static function should_override_preset( $theme_json, $path, $override ) { + if ( is_bool( $override ) ) { + return $override; + } + + // The relationship between whether to override the defaults + // and whether the defaults are enabled is inverse: + // + // - If defaults are enabled => theme presets should not be overriden + // - If defaults are disabled => theme presets should be overriden + // + // For example, a theme sets defaultPalette to false, + // making the default palette hidden from the user. + // In that case, we want all the theme presets to be present, + // so they should override the defaults. + if ( is_array( $override ) ) { + $value = _wp_array_get( $theme_json, array_merge( $path, $override ) ); + if ( isset( $value ) ) { + return ! $value; + } + + // Search the top-level key if none was found for this node. + $value = _wp_array_get( $theme_json, array_merge( array( 'settings' ), $override ) ); + if ( isset( $value ) ) { + return ! $value; + } + + return true; + } + } + + /** + * Returns the default slugs for all the presets in an associative array * whose keys are the preset paths and the leafs is the list of slugs. * * For example: * - * array( + * array( * 'color' => array( * 'palette' => array( 'slug-1', 'slug-2' ), * 'gradients' => array( 'slug-3', 'slug-4' ), * ), * ) * - * @param array $data A theme.json like structure to inspect. + * @param array $data A theme.json like structure. * @param array $node_path The path to inspect. It's 'settings' by default. * - * @return array An associative array containing the slugs for the given path. + * @return array */ - private static function get_slugs_not_to_override( $data, $node_path = array( 'settings' ) ) { + private static function get_default_slugs( $data, $node_path ) { $slugs = array(); - foreach ( self::PRESETS_METADATA as $metadata ) { - if ( $metadata['override'] ) { - continue; - } - $slugs_for_preset = array(); - $path = array_merge( $node_path, $metadata['path'], array( 'default' ) ); - $preset = _wp_array_get( $data, $path, null ); + foreach ( self::PRESETS_METADATA as $metadata ) { + $path = array_merge( $node_path, $metadata['path'], array( 'default' ) ); + $preset = _wp_array_get( $data, $path, null ); if ( ! isset( $preset ) ) { continue; } + $slugs_for_preset = array(); $slugs_for_preset = array_map( function( $value ) { return isset( $value['slug'] ) ? $value['slug'] : null; @@ -1498,20 +1534,18 @@ function( $value ) { * Removes the preset values whose slug is equal to any of given slugs. * * @param array $node The node with the presets to validate. - * @param array $path The path to the preset type to inspect. * @param array $slugs The slugs that should not be overriden. * * @return array The new node */ - private static function filter_slugs( $node, $path, $slugs ) { - $slugs_for_preset = _wp_array_get( $slugs, $path, array() ); - if ( empty( $slugs_for_preset ) ) { + private static function filter_slugs( $node, $slugs ) { + if ( empty( $slugs ) ) { return $node; } $new_node = array(); foreach ( $node as $value ) { - if ( isset( $value['slug'] ) && ! in_array( $value['slug'], $slugs_for_preset, true ) ) { + if ( isset( $value['slug'] ) && ! in_array( $value['slug'], $slugs, true ) ) { $new_node[] = $value; } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index c398386cd9d81..21ba51cc37375 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -1081,13 +1081,14 @@ public function test_merge_incoming_data_null_presets() { $this->assertEqualSetsWithIndex( $expected, $actual ); } - public function test_merge_incoming_data_removes_theme_presets_with_slugs_as_default_presets() { + public function test_merge_incoming_data_color_presets_with_same_slugs_as_default_are_removed() { $defaults = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( - 'palette' => array( + 'defaultPalette' => true, + 'palette' => array( array( 'slug' => 'red', 'color' => 'red', @@ -1166,7 +1167,7 @@ public function test_merge_incoming_data_removes_theme_presets_with_slugs_as_def 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( - 'palette' => array( + 'palette' => array( 'default' => array( array( 'slug' => 'red', @@ -1187,6 +1188,7 @@ public function test_merge_incoming_data_removes_theme_presets_with_slugs_as_def ), ), ), + 'defaultPalette' => true, ), 'blocks' => array( 'core/paragraph' => array( @@ -1219,6 +1221,162 @@ public function test_merge_incoming_data_removes_theme_presets_with_slugs_as_def $this->assertEqualSetsWithIndex( $expected, $actual ); } + public function test_merge_incoming_data_color_presets_with_same_slugs_as_default_are_not_removed_if_defaults_are_disabled() { + $defaults = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => true, // Emulate the defaults from core theme.json. + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', + ), + ), + ), + ), + ), + ), + ), + 'default' + ); + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => false, + 'palette' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Greenish', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Bluish', + ), + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Block Green', + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'defaultPalette' => false, + 'palette' => array( + 'default' => array( + array( + 'slug' => 'red', + 'color' => 'red', + 'name' => 'Red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Green', + ), + ), + 'theme' => array( + array( + 'slug' => 'pink', + 'color' => 'pink', + 'name' => 'Pink', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Greenish', + ), + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'default' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Blue', + ), + ), + 'theme' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + 'name' => 'Bluish', + ), + array( + 'slug' => 'yellow', + 'color' => 'yellow', + 'name' => 'Yellow', + ), + array( + 'slug' => 'green', + 'color' => 'green', + 'name' => 'Block Green', + ), + ), + ), + ), + ), + ), + ), + ); + + $defaults->merge( $theme ); + $actual = $defaults->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + function test_remove_insecure_properties_removes_unsafe_styles() { $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( array(