From 514e7b988bfa2b20c5d1a9e02c9a0906614c5115 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 1 May 2024 15:35:01 +1000 Subject: [PATCH 01/25] This initial commit: - removes requirement for a `source` property in theme.json as the assumption is that, for now, all paths are paths to image files, whether absolute or relative - checks for existence of "host" in URL and then tries to resolve background image url using get_theme_file_uri - Adds a new public method WP_Theme_JSON_Gutenberg::resolve_theme_file_uris to allow theme devs to optionally resolve relative paths in theme.json to a theme. --- lib/class-wp-theme-json-gutenberg.php | 31 +++++++++++++++++++++++++++ lib/global-styles-and-settings.php | 2 +- phpunit/bootstrap.php | 12 +++++++++++ phpunit/class-wp-theme-json-test.php | 26 ++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 1dec7b164d880b..f347192e5f247f 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -4096,4 +4096,35 @@ protected static function get_valid_block_style_variations() { return $valid_variations; } + + /** + * Resolves relative paths in theme.json styles to theme absolute paths. + * + * @since 6.6.0 + * + * @return WP_Theme_JSON_Gutenberg The current instance of $this. + */ + public function resolve_theme_file_uris() { + if ( empty( $this->theme_json ) ) { + return $this; + } + + /* + * Styles backgrounds. + * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. + * Blocks, elements, and block variations are not yet supported. + */ + if ( + isset( $this->theme_json['styles']['background']['backgroundImage']['url'] ) && + is_string( $this->theme_json['styles']['background']['backgroundImage']['url'] ) && + ! isset( wp_parse_url( $this->theme_json['styles']['background']['backgroundImage']['url'] )['host'] ) ) { + _wp_array_set( + $this->theme_json, + array( 'styles', 'background', 'backgroundImage', 'url' ), + esc_url( get_theme_file_uri( $this->theme_json['styles']['background']['backgroundImage']['url'] ) ) + ); + } + + return $this; + } } diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index 70b3f7078e62ac..d65aaf2e8fc548 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -27,7 +27,7 @@ function gutenberg_get_global_stylesheet( $types = array() ) { return $cached; } } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->resolve_theme_file_uris(); $supports_theme_json = wp_theme_has_theme_json(); if ( empty( $types ) && ! $supports_theme_json ) { diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index acc7cfde89dbd4..54cacec08c0309 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -186,6 +186,18 @@ function gutenberg_register_test_block_for_feature_selectors() { } tests_add_filter( 'init', 'gutenberg_register_test_block_for_feature_selectors' ); +// Used in class-wp-theme-json-test.php to mock theme file URIs. +function gutenberg_tests_filter_theme_file_uri( $file ) { + // Tests can pass a file path with `example/` to return a dummy absolute theme file URI. + if ( strpos( $file, 'example/' ) ) { + $file_name = explode( 'example/', $file )[1]; + return 'https://example.org/wp-content/themes/example-theme/example/' . $file_name; + } + // Default active theme URI. + return get_stylesheet_directory_uri() . '/' . $file; +} +tests_add_filter( 'theme_file_uri', 'gutenberg_tests_filter_theme_file_uri' ); + // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 00799b7ba0b478..1445442c8c4349 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -4855,6 +4855,32 @@ public function test_get_top_level_background_image_styles() { $expected_styles = ":where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; $this->assertSame( $expected_styles, $theme_json->get_stylesheet(), 'Styles returned from "::get_stylesheet()" with top-level background image as string type does not match expectations' ); + + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'example/img/image.png', + ), + ), + ), + ) + ); + $expected_data = array( + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', + ), + ), + ), + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + ); + $expected_styles = "body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('https://example.org/wp-content/themes/example-theme/example/img/image.png');}"; + $this->assertSame( $expected_styles, $theme_json->resolve_theme_file_uris()->get_stylesheet(), 'Styles returned from "::resolve_theme_file_uris()->get_stylesheet()" with resolved top-level theme background images do not match expected string' ); + $this->assertSame( $expected_data, $theme_json->get_data(), 'Styles returned from "::get_data()" with resolved top-level theme background images do not match expected array' ); } public function test_get_custom_css_handles_global_custom_css() { From fa9e75f29212cc4370f7f52c90706c73a2d5b771 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 1 May 2024 15:54:26 +1000 Subject: [PATCH 02/25] For testing purposes, resolve in get_merged_data - should it be optional? That is, done in the global themes controller and wherever a stylesheet is generated? --- ...class-wp-theme-json-resolver-gutenberg.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index dcc0bf8b099c3b..d49edb795bcba0 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -615,11 +615,13 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { $result->set_spacing_sizes(); + $result->resolve_theme_file_uris(); return $result; } $result->merge( static::get_user_data() ); $result->set_spacing_sizes(); + $result->resolve_theme_file_uris(); return $result; } @@ -760,4 +762,34 @@ public static function get_style_variations() { } return $variations; } + + /** + * Resolves relative paths in theme.json styles to theme absolute paths. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @return WP_Theme_JSON_Gutenberg A theme json instance with resolved paths. + */ + protected static function resolve_theme_file_uris( $theme_json ) { + if ( empty( $theme_json ) ) { + return; + } + + /* + * Styles backgrounds. + * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. + * Blocks, elements, and block variations are not yet supported. + */ + if ( + isset( $theme_json['styles']['background']['backgroundImage']['url'] ) && + is_string( $theme_json['styles']['background']['backgroundImage']['url'] ) && + ! isset( wp_parse_url( $theme_json['styles']['background']['backgroundImage']['url'] )['host'] ) ) { + _wp_array_set( + $theme_json, + array( 'styles', 'background', 'backgroundImage', 'url' ), + esc_url( get_theme_file_uri( $theme_json['styles']['background']['backgroundImage']['url'] ) ) + ); + } + } } From 2ba6cfd9cf6615e19a8735bda01ca93feb4e4623 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 1 May 2024 16:33:42 +1000 Subject: [PATCH 03/25] Rollback test of imperative method in resolver --- ...class-wp-theme-json-resolver-gutenberg.php | 33 ++----------------- lib/global-styles-and-settings.php | 2 +- phpunit/bootstrap.php | 12 ------- phpunit/class-wp-theme-json-test.php | 15 ++++++++- 4 files changed, 17 insertions(+), 45 deletions(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index d49edb795bcba0..e4fd3bac96ad91 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -604,6 +604,7 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { $result->set_spacing_sizes(); + $result->resolve_theme_file_uris(); return $result; } @@ -753,7 +754,7 @@ public static function get_style_variations() { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); + $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->resolve_theme_file_uris()->get_raw_data(); if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } @@ -762,34 +763,4 @@ public static function get_style_variations() { } return $variations; } - - /** - * Resolves relative paths in theme.json styles to theme absolute paths. - * - * @since 6.6.0 - * - * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. - * @return WP_Theme_JSON_Gutenberg A theme json instance with resolved paths. - */ - protected static function resolve_theme_file_uris( $theme_json ) { - if ( empty( $theme_json ) ) { - return; - } - - /* - * Styles backgrounds. - * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - * Blocks, elements, and block variations are not yet supported. - */ - if ( - isset( $theme_json['styles']['background']['backgroundImage']['url'] ) && - is_string( $theme_json['styles']['background']['backgroundImage']['url'] ) && - ! isset( wp_parse_url( $theme_json['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - _wp_array_set( - $theme_json, - array( 'styles', 'background', 'backgroundImage', 'url' ), - esc_url( get_theme_file_uri( $theme_json['styles']['background']['backgroundImage']['url'] ) ) - ); - } - } } diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index d65aaf2e8fc548..70b3f7078e62ac 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -27,7 +27,7 @@ function gutenberg_get_global_stylesheet( $types = array() ) { return $cached; } } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->resolve_theme_file_uris(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); $supports_theme_json = wp_theme_has_theme_json(); if ( empty( $types ) && ! $supports_theme_json ) { diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index 54cacec08c0309..acc7cfde89dbd4 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -186,18 +186,6 @@ function gutenberg_register_test_block_for_feature_selectors() { } tests_add_filter( 'init', 'gutenberg_register_test_block_for_feature_selectors' ); -// Used in class-wp-theme-json-test.php to mock theme file URIs. -function gutenberg_tests_filter_theme_file_uri( $file ) { - // Tests can pass a file path with `example/` to return a dummy absolute theme file URI. - if ( strpos( $file, 'example/' ) ) { - $file_name = explode( 'example/', $file )[1]; - return 'https://example.org/wp-content/themes/example-theme/example/' . $file_name; - } - // Default active theme URI. - return get_stylesheet_directory_uri() . '/' . $file; -} -tests_add_filter( 'theme_file_uri', 'gutenberg_tests_filter_theme_file_uri' ); - // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 1445442c8c4349..0b38194e8aeb2a 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -4878,8 +4878,21 @@ public function test_get_top_level_background_image_styles() { ), 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, ); - $expected_styles = "body { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('https://example.org/wp-content/themes/example-theme/example/img/image.png');}"; + $expected_styles = ":where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('https://example.org/wp-content/themes/example-theme/example/img/image.png');}"; + + /* + * This filter callback normalizes the return value from `get_theme_file_uri` + * to guard against changes in test environments. + * The test suite otherwise returns full system dir path, e.g., + * /wordpress-phpunit/includes/../data/themedir1/default/example/img/image.png + */ + $filter_theme_file_uri_callback = function ( $file ) { + return 'https://example.org/wp-content/themes/example-theme/example/' . explode( 'example/', $file )[1]; + }; + add_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); $this->assertSame( $expected_styles, $theme_json->resolve_theme_file_uris()->get_stylesheet(), 'Styles returned from "::resolve_theme_file_uris()->get_stylesheet()" with resolved top-level theme background images do not match expected string' ); + remove_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); + $this->assertSame( $expected_data, $theme_json->get_data(), 'Styles returned from "::get_data()" with resolved top-level theme background images do not match expected array' ); } From 17048c3d18a655d4723a8ed0ca9535323fae192e Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 7 May 2024 14:54:59 +1000 Subject: [PATCH 04/25] Moves resolution of file paths back to theme_json resolver --- lib/class-wp-theme-json-gutenberg.php | 31 ------------ ...class-wp-theme-json-resolver-gutenberg.php | 49 +++++++++++++++++-- phpunit/class-wp-theme-json-resolver-test.php | 49 +++++++++++++++++++ phpunit/class-wp-theme-json-test.php | 39 --------------- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index f347192e5f247f..1dec7b164d880b 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -4096,35 +4096,4 @@ protected static function get_valid_block_style_variations() { return $valid_variations; } - - /** - * Resolves relative paths in theme.json styles to theme absolute paths. - * - * @since 6.6.0 - * - * @return WP_Theme_JSON_Gutenberg The current instance of $this. - */ - public function resolve_theme_file_uris() { - if ( empty( $this->theme_json ) ) { - return $this; - } - - /* - * Styles backgrounds. - * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - * Blocks, elements, and block variations are not yet supported. - */ - if ( - isset( $this->theme_json['styles']['background']['backgroundImage']['url'] ) && - is_string( $this->theme_json['styles']['background']['backgroundImage']['url'] ) && - ! isset( wp_parse_url( $this->theme_json['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - _wp_array_set( - $this->theme_json, - array( 'styles', 'background', 'backgroundImage', 'url' ), - esc_url( get_theme_file_uri( $this->theme_json['styles']['background']['backgroundImage']['url'] ) ) - ); - } - - return $this; - } } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index e4fd3bac96ad91..40181be2a72875 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -604,7 +604,7 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { $result->set_spacing_sizes(); - $result->resolve_theme_file_uris(); + $result = static::resolve_theme_file_uris( $result ); return $result; } @@ -616,13 +616,13 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { $result->set_spacing_sizes(); - $result->resolve_theme_file_uris(); + $result = static::resolve_theme_file_uris( $result ); return $result; } $result->merge( static::get_user_data() ); $result->set_spacing_sizes(); - $result->resolve_theme_file_uris(); + $result = static::resolve_theme_file_uris( $result ); return $result; } @@ -753,8 +753,9 @@ public static function get_style_variations() { foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { - $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->resolve_theme_file_uris()->get_raw_data(); + $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); + $with_resolved_paths = static::resolve_theme_file_uris( new WP_Theme_JSON_Gutenberg( $translated ) ); + $variation = $with_resolved_paths->get_raw_data(); if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } @@ -763,4 +764,42 @@ public static function get_style_variations() { } return $variations; } + + + /** + * Resolves relative paths in theme.json styles to theme absolute paths. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @return WP_Theme_JSON_Gutenberg The theme json instance with merged resolved paths. + */ + protected static function resolve_theme_file_uris( $theme_json ) { + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { + return $theme_json; + } + + $theme_json_data = $theme_json->get_raw_data(); + $resolved_theme_json_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + ); + + /* + * Styles backgrounds. + * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. + * Blocks, elements, and block variations are not yet supported. + */ + if ( + isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { + $resolved_theme_json_data['styles']['background']['backgroundImage']['url'] = esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ); + } + + if ( ! empty( $resolved_theme_json_data ) ) { + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ) ); + } + + return $theme_json; + } } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index c842376b53e300..305df6b1b4d88d 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1139,4 +1139,53 @@ public function test_shadow_default_presets_value_for_block_and_classic_themes() $default_presets_for_block = $theme_json->get_settings()['shadow']['defaultPresets']; $this->assertTrue( $default_presets_for_block ); } + + /** + * Tests that classic themes still get core default settings such as color palette and duotone. + */ + public function test_resolve_theme_file_uris() { + $theme_json_resolver = new ReflectionClass( 'WP_Theme_JSON_Resolver_Gutenberg' ); + $func = $theme_json_resolver->getMethod( 'resolve_theme_file_uris' ); + + $func->setAccessible( true ); + + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'example/img/image.png', + ), + ), + ), + ) + ); + + $expected_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', + ), + ), + ), + ); + + /* + * This filter callback normalizes the return value from `get_theme_file_uri` + * to guard against changes in test environments. + * The test suite otherwise returns full system dir path, e.g., + * /wordpress-phpunit/includes/../data/themedir1/default/example/img/image.png + */ + $filter_theme_file_uri_callback = function ( $file ) { + return 'https://example.org/wp-content/themes/example-theme/example/' . explode( 'example/', $file )[1]; + }; + add_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); + $actual = $func->invoke( null, $theme_json ); + remove_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); + + $this->assertSame( $expected_data, $actual->get_raw_data() ); + } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 0b38194e8aeb2a..00799b7ba0b478 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -4855,45 +4855,6 @@ public function test_get_top_level_background_image_styles() { $expected_styles = ":where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; $this->assertSame( $expected_styles, $theme_json->get_stylesheet(), 'Styles returned from "::get_stylesheet()" with top-level background image as string type does not match expectations' ); - - $theme_json = new WP_Theme_JSON_Gutenberg( - array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - 'styles' => array( - 'background' => array( - 'backgroundImage' => array( - 'url' => 'example/img/image.png', - ), - ), - ), - ) - ); - $expected_data = array( - 'styles' => array( - 'background' => array( - 'backgroundImage' => array( - 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', - ), - ), - ), - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, - ); - $expected_styles = ":where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:where(body){background-image: url('https://example.org/wp-content/themes/example-theme/example/img/image.png');}"; - - /* - * This filter callback normalizes the return value from `get_theme_file_uri` - * to guard against changes in test environments. - * The test suite otherwise returns full system dir path, e.g., - * /wordpress-phpunit/includes/../data/themedir1/default/example/img/image.png - */ - $filter_theme_file_uri_callback = function ( $file ) { - return 'https://example.org/wp-content/themes/example-theme/example/' . explode( 'example/', $file )[1]; - }; - add_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); - $this->assertSame( $expected_styles, $theme_json->resolve_theme_file_uris()->get_stylesheet(), 'Styles returned from "::resolve_theme_file_uris()->get_stylesheet()" with resolved top-level theme background images do not match expected string' ); - remove_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); - - $this->assertSame( $expected_data, $theme_json->get_data(), 'Styles returned from "::get_data()" with resolved top-level theme background images do not match expected array' ); } public function test_get_custom_css_handles_global_custom_css() { From 88aac469c4c071e542a90517e529d0127f3c97c3 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 8 May 2024 15:25:58 +1000 Subject: [PATCH 05/25] Backend resolution of theme file URIs for global styles. --- ...est-global-styles-controller-gutenberg.php | 17 +++++-- ...class-wp-theme-json-resolver-gutenberg.php | 50 +++++++++++++++---- lib/global-styles-and-settings.php | 1 + .../global-styles/use-get-theme-file-uris.js | 38 ++++++++++++++ .../global-styles/use-global-styles-output.js | 13 +++-- .../global-styles/variations/variation.js | 3 ++ 6 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 79cb8af59df4f9..73498589ff5d3d 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -607,17 +607,19 @@ public function get_theme_item( $request ) { ); } - $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); - $data = array(); - $fields = $this->get_fields_for_response( $request ); + $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); + $data = array(); + $fields = $this->get_fields_for_response( $request ); + $resolved_theme_uris = array(); if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = $theme->get_settings(); } if ( rest_is_field_included( 'styles', $fields ) ) { - $raw_data = $theme->get_raw_data(); - $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); + $raw_data = $theme->get_raw_data(); + $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; @@ -632,6 +634,11 @@ public function get_theme_item( $request ) { 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ), ), ); + + if ( ! empty( $resolved_theme_uris ) ) { + $links['theme_file_uris'] = $resolved_theme_uris; + } + $response->add_links( $links ); } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 40181be2a72875..6102fc498a1be9 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -604,7 +604,6 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { $result->set_spacing_sizes(); - $result = static::resolve_theme_file_uris( $result ); return $result; } @@ -616,13 +615,11 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { $result->set_spacing_sizes(); - $result = static::resolve_theme_file_uris( $result ); return $result; } $result->merge( static::get_user_data() ); $result->set_spacing_sizes(); - $result = static::resolve_theme_file_uris( $result ); return $result; } @@ -753,12 +750,16 @@ public static function get_style_variations() { foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { - $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $with_resolved_paths = static::resolve_theme_file_uris( new WP_Theme_JSON_Gutenberg( $translated ) ); - $variation = $with_resolved_paths->get_raw_data(); + $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $translated ); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $variation_theme_json ); + $variation = $variation_theme_json->get_raw_data(); if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } + if ( ! empty( $resolved_theme_uris ) ) { + $variation['_links']['theme_file_uris'] = $resolved_theme_uris; + } $variations[] = $variation; } } @@ -767,14 +768,43 @@ public static function get_style_variations() { /** - * Resolves relative paths in theme.json styles to theme absolute paths. + * Resolves relative paths in theme.json styles to theme absolute paths + * and returns them in an array that can be embedded in a REST response. * * @since 6.6.0 * * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. - * @return WP_Theme_JSON_Gutenberg The theme json instance with merged resolved paths. + * @return array An array of resolved paths. */ - protected static function resolve_theme_file_uris( $theme_json ) { + public static function get_resolved_theme_uris( $theme_json ) { + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { + return $theme_json; + } + + $theme_json_data = $theme_json->get_raw_data(); + $resolved_theme_uris = array(); + + /* + * Styles backgrounds. + * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. + * Blocks, elements, and block variations are not yet supported. + */ + if ( + isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { + $resolved_theme_uris[] = array( + 'file' => $theme_json_data['styles']['background']['backgroundImage']['url'], + 'href' => esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ), + ); + } + + return $resolved_theme_uris; + } + + // @TODO used in gutenberg_get_global_stylesheet to ensure global stylesheet URIs are resolved. + // Try to harmonize with the above function + public static function resolve_theme_file_uris( $theme_json ) { if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { return $theme_json; } @@ -793,7 +823,7 @@ protected static function resolve_theme_file_uris( $theme_json ) { isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - $resolved_theme_json_data['styles']['background']['backgroundImage']['url'] = esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ); + $resolved_theme_json_data['styles']['background']['backgroundImage']['url'] = esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ); } if ( ! empty( $resolved_theme_json_data ) ) { diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index 70b3f7078e62ac..4ceade6c7125b1 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -28,6 +28,7 @@ function gutenberg_get_global_stylesheet( $types = array() ) { } } $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::resolve_theme_file_uris( $tree ); $supports_theme_json = wp_theme_has_theme_json(); if ( empty( $types ) && ! $supports_theme_json ) { diff --git a/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js b/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js new file mode 100644 index 00000000000000..e034ccf7eaf9c4 --- /dev/null +++ b/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { isURL, isValidPath } from '@wordpress/url'; + +function isRelativePath( url ) { + return isValidPath( url ) && ! isURL( url ); +} + +function getThemeFileURI( file, themeFileURIs = [] ) { + if ( ! isRelativePath( file ) ) { + return; + } + + return themeFileURIs.find( ( themeFileUri ) => themeFileUri.file === file ); +} + +function setUnresolvedThemeFilePaths( config ) { + const themeFileURIs = config._links.theme_file_uris; + const backgroundImageUrl = getThemeFileURI( + config?.styles?.background?.backgroundImage?.url, + themeFileURIs + ); + + // Top level styles. + if ( !! backgroundImageUrl?.href ) { + config.styles.background.backgroundImage.url = backgroundImageUrl.href; + } + + return config; +} + +export default function useGetThemeFileURIs( mergedConfig ) { + if ( ! mergedConfig?.styles || ! mergedConfig?._links?.theme_file_uris ) { + return mergedConfig; + } + return setUnresolvedThemeFilePaths( mergedConfig ); +} diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 23bb88efc9991e..16ce7a14469907 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -35,6 +35,7 @@ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; import BlockContext from '../block-context'; import { unlock } from '../../lock-unlock'; +import useGetThemeFileUris from './use-get-theme-file-uris'; // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. @@ -1217,6 +1218,7 @@ export function processCSSNesting( css, blockSelector ) { */ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); + const withResolvedThemeURIs = useGetThemeFileUris( mergedConfig ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. const disableLayoutStyles = useSelect( ( select ) => { @@ -1231,10 +1233,15 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const { getBlockStyles } = useSelect( blocksStore ); return useMemo( () => { - if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) { + if ( + ! withResolvedThemeURIs?.styles || + ! withResolvedThemeURIs?.settings + ) { return []; } - const updatedConfig = updateConfigWithSeparator( mergedConfig ); + const updatedConfig = updateConfigWithSeparator( + withResolvedThemeURIs + ); const blockSelectors = getBlockSelectors( getBlockTypes(), @@ -1297,7 +1304,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { }, [ hasBlockGapSupport, hasFallbackGapSupport, - mergedConfig, + withResolvedThemeURIs, disableLayoutStyles, isTemplate, getBlockStyles, diff --git a/packages/edit-site/src/components/global-styles/variations/variation.js b/packages/edit-site/src/components/global-styles/variations/variation.js index 26f5235dece6fc..a1055f18e05bc8 100644 --- a/packages/edit-site/src/components/global-styles/variations/variation.js +++ b/packages/edit-site/src/components/global-styles/variations/variation.js @@ -23,6 +23,9 @@ const { GlobalStylesContext, areGlobalStyleConfigsEqual } = unlock( ); export default function Variation( { variation, children, isPill } ) { + // @TODO _links need to be merged as well somehow so that + // they are always returned in "mergedConfig" in the global-styles-provider. + // console.log( 'variation', variation ); const [ isFocused, setIsFocused ] = useState( false ); const { base, user, setUserConfig } = useContext( GlobalStylesContext ); const context = useMemo( From 4137b37c78e85668ec2875c3276aea5da90a9b46 Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 9 May 2024 15:10:00 +1000 Subject: [PATCH 06/25] Working on revisions Backend resolution of theme file URIs for global styles revisions Ensuring links are preserved when updating global styles. --- ...est-global-styles-controller-gutenberg.php | 28 +++--- ...class-wp-theme-json-resolver-gutenberg.php | 18 ++-- lib/compat/wordpress-6.5/rest-api.php | 10 -- ...global-styles-revisions-controller-6-6.php | 96 +++++++++++++++++++ lib/compat/wordpress-6.6/rest-api.php | 11 +++ lib/load.php | 1 + .../global-styles/background-panel.js | 10 +- .../src/components/global-styles/hooks.js | 9 +- .../global-styles/set-theme-file-uris.js | 72 ++++++++++++++ .../global-styles/use-get-theme-file-uris.js | 38 -------- .../global-styles/use-global-styles-output.js | 18 ++-- .../global-styles/background-panel.js | 12 ++- .../global-styles/screen-revisions/index.js | 6 ++ .../use-global-styles-revisions.js | 1 + .../global-styles/variations/variation.js | 5 +- .../global-styles-provider/index.js | 11 ++- 16 files changed, 254 insertions(+), 92 deletions(-) create mode 100644 lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php create mode 100644 packages/block-editor/src/components/global-styles/set-theme-file-uris.js delete mode 100644 packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 73498589ff5d3d..c5f7fbb885a36b 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -366,8 +366,10 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V $raw_config = json_decode( $post->post_content, true ); $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON']; $config = array(); + $theme_json = array(); if ( $is_global_styles_user_theme_json ) { - $config = ( new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ) )->get_raw_data(); + $theme_json = new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ); + $config = $theme_json->get_raw_data(); } // Base fields for every post. @@ -409,6 +411,13 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $post->ID ); + // Only return resolved URIs for get requests to user theme JSON. + if ( $is_global_styles_user_theme_json ) { + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); + if ( ! empty( $resolved_theme_uris ) ) { + $links['theme_file_uris'] = $resolved_theme_uris; + } + } $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { $actions = $this->get_available_actions(); @@ -607,34 +616,31 @@ public function get_theme_item( $request ) { ); } - $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); - $data = array(); - $fields = $this->get_fields_for_response( $request ); - $resolved_theme_uris = array(); + $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' ); + $data = array(); + $fields = $this->get_fields_for_response( $request ); if ( rest_is_field_included( 'settings', $fields ) ) { $data['settings'] = $theme->get_settings(); } if ( rest_is_field_included( 'styles', $fields ) ) { - $raw_data = $theme->get_raw_data(); - $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); - $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); + $raw_data = $theme->get_raw_data(); + $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); - $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { - $links = array( + $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ), ), ); - + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); if ( ! empty( $resolved_theme_uris ) ) { $links['theme_file_uris'] = $resolved_theme_uris; } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 6102fc498a1be9..bd90873ea4925d 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -777,21 +777,19 @@ public static function get_style_variations() { * @return array An array of resolved paths. */ public static function get_resolved_theme_uris( $theme_json ) { - if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { - return $theme_json; + $resolved_theme_uris = array(); + + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { + return $resolved_theme_uris; } - $theme_json_data = $theme_json->get_raw_data(); - $resolved_theme_uris = array(); + $theme_json_data = $theme_json->get_raw_data(); - /* - * Styles backgrounds. - * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - * Blocks, elements, and block variations are not yet supported. - */ + // Top level styles. if ( isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + // Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { $resolved_theme_uris[] = array( 'file' => $theme_json_data['styles']['background']['backgroundImage']['url'], @@ -805,7 +803,7 @@ public static function get_resolved_theme_uris( $theme_json ) { // @TODO used in gutenberg_get_global_stylesheet to ensure global stylesheet URIs are resolved. // Try to harmonize with the above function public static function resolve_theme_file_uris( $theme_json ) { - if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { return $theme_json; } diff --git a/lib/compat/wordpress-6.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index 12d789fb58b869..d18756844cc91a 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -10,16 +10,6 @@ die( 'Silence is golden.' ); } -/** - * Registers the Global Styles Revisions REST API routes. - */ -function gutenberg_register_global_styles_revisions_endpoints() { - $global_styles_revisions_controller = new Gutenberg_REST_Global_Styles_Revisions_Controller_6_5(); - $global_styles_revisions_controller->register_routes(); -} - -add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); - /** * Registers additional fields for wp_template and wp_template_part rest api. * diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php new file mode 100644 index 00000000000000..1401c6608ea435 --- /dev/null +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -0,0 +1,96 @@ +get_parent( $request['parent'] ); + $global_styles_config = $this->get_decoded_global_styles_json( $post->post_content ); + + if ( is_wp_error( $global_styles_config ) ) { + return $global_styles_config; + } + + $fields = $this->get_fields_for_response( $request ); + $data = array(); + $theme_json = array(); + + if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) { + $theme_json = new WP_Theme_JSON_Gutenberg( $global_styles_config, 'custom' ); + $global_styles_config = ( $theme_json )->get_raw_data(); + + if ( rest_is_field_included( 'settings', $fields ) ) { + $data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass(); + } + if ( rest_is_field_included( 'styles', $fields ) ) { + $data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass(); + } + } + + if ( rest_is_field_included( 'author', $fields ) ) { + $data['author'] = (int) $post->post_author; + } + + if ( rest_is_field_included( 'date', $fields ) ) { + $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); + } + + if ( rest_is_field_included( 'date_gmt', $fields ) ) { + $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); + } + + if ( rest_is_field_included( 'id', $fields ) ) { + $data['id'] = (int) $post->ID; + } + + if ( rest_is_field_included( 'modified', $fields ) ) { + $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); + } + + if ( rest_is_field_included( 'modified_gmt', $fields ) ) { + $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); + } + + if ( rest_is_field_included( 'parent', $fields ) ) { + $data['parent'] = (int) $parent->ID; + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + + $response = rest_ensure_response( $data ); + + // Add resolved URIs to the response. + $links = array(); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); + if ( ! empty( $resolved_theme_uris ) ) { + $links['theme_file_uris'] = $resolved_theme_uris; + } + $response->add_links( $links ); + + return $response; + } +} diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index 8526093dc99ddb..54796685f45ab8 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -76,3 +76,14 @@ function gutenberg_add_class_list_to_public_post_types() { } } add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' ); + + +/** + * Registers the Global Styles Revisions REST API routes. + */ +function gutenberg_register_global_styles_revisions_endpoints() { + $global_styles_revisions_controller = new Gutenberg_REST_Global_Styles_Revisions_Controller_6_6(); + $global_styles_revisions_controller->register_routes(); +} + +add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); diff --git a/lib/load.php b/lib/load.php index 3450dbf2b24026..b00c024778b5ff 100644 --- a/lib/load.php +++ b/lib/load.php @@ -47,6 +47,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php'; // WordPress 6.6 compat. + require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index a975de53a99d92..ed925a6049bd44 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -38,6 +38,7 @@ import { TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; +import { getThemeFileURI } from './set-theme-file-uris'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { @@ -191,6 +192,7 @@ function BackgroundImageToolsPanelItem( { onChange, style, inheritedValue, + themeFileURIs, } ) { const mediaUpload = useSelect( ( select ) => select( blockEditorStore ).getSettings().mediaUpload, @@ -301,7 +303,7 @@ function BackgroundImageToolsPanelItem( { } variant="secondary" @@ -340,6 +342,7 @@ function BackgroundSizeToolsPanelItem( { style, inheritedValue, defaultValues, + themeFileURIs, } ) { const sizeValue = style?.background?.backgroundSize || @@ -468,7 +471,7 @@ function BackgroundSizeToolsPanelItem( { @@ -553,6 +556,7 @@ export default function BackgroundPanel( { defaultControls = DEFAULT_CONTROLS, defaultValues = {}, headerLabel = __( 'Background image' ), + themeFileURIs, } ) { const resetAllFilter = useCallback( ( previousValue ) => { return { @@ -577,6 +581,7 @@ export default function BackgroundPanel( { isShownByDefault={ defaultControls.backgroundImage } style={ value } inheritedValue={ inheritedValue } + themeFileURIs={ themeFileURIs } /> { shouldShowBackgroundSizeControls && ( ) } diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index e0de34cf2280e2..4516d6ed57e6c8 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -178,23 +178,26 @@ export function useGlobalStyle( ) ); }; - - let rawResult, result; + // @TODO _links isn't a great name. styleMeta? themeMeta? + let rawResult, result, _links; switch ( source ) { case 'all': rawResult = getValueFromObjectPath( mergedConfig, finalPath ); + _links = mergedConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( mergedConfig, blockName, rawResult ) : rawResult; break; case 'user': rawResult = getValueFromObjectPath( userConfig, finalPath ); + _links = userConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( mergedConfig, blockName, rawResult ) : rawResult; break; case 'base': rawResult = getValueFromObjectPath( baseConfig, finalPath ); + _links = baseConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( baseConfig, blockName, rawResult ) : rawResult; @@ -203,7 +206,7 @@ export function useGlobalStyle( throw 'Unsupported source'; } - return [ result, setStyle ]; + return [ result, setStyle, _links ]; } /** diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js new file mode 100644 index 00000000000000..56feb1757cbc4a --- /dev/null +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { isURL, isValidPath } from '@wordpress/url'; + +function isRelativePath( url ) { + return isValidPath( url ) && ! isURL( url ); +} + +/** + * Looks up a theme file URI based on a relative path. + * + * @param {string} file A relative path. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection. + */ +export function getThemeFileURI( file, themeFileURIs = [] ) { + if ( ! isRelativePath( file ) ) { + return file; + } + + const uri = themeFileURIs.find( + ( themeFileUri ) => themeFileUri.file === file + ); + + return uri?.href; +} + +/** + * Houses logic of where to look for unresolved theme file paths. + * + * @param {Object} styles A styles object. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {Object} Returns mutated styles object. + */ +function setUnresolvedThemeFilePaths( styles, themeFileURIs ) { + // Top level styles. + if ( + !! styles?.background?.backgroundImage?.url && + isRelativePath( styles?.background?.backgroundImage?.url ) + ) { + const backgroundImageUrl = getThemeFileURI( + styles?.background?.backgroundImage?.url, + themeFileURIs + ); + if ( backgroundImageUrl ) { + styles.background.backgroundImage.url = backgroundImageUrl; + } + } + + return styles; +} + +/** + * Resolves any relative paths if a corresponding theme file URI is available. + * Note: this function mutates the object and is specifically to be used in + * an async styles build context in useGlobalStylesOutput + * + * @param {Object} themeJson Theme.json/Global styles tree. + * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. + * @return {Object} Returns mutated object. + */ +export default function setThemeFileUris( themeJson, themeFileURIs ) { + if ( ! themeJson?.styles || ! themeFileURIs ) { + return themeJson; + } + + // Mutating function. + setUnresolvedThemeFilePaths( themeJson.styles, themeFileURIs ); + + return themeJson; +} diff --git a/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js b/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js deleted file mode 100644 index e034ccf7eaf9c4..00000000000000 --- a/packages/block-editor/src/components/global-styles/use-get-theme-file-uris.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * WordPress dependencies - */ -import { isURL, isValidPath } from '@wordpress/url'; - -function isRelativePath( url ) { - return isValidPath( url ) && ! isURL( url ); -} - -function getThemeFileURI( file, themeFileURIs = [] ) { - if ( ! isRelativePath( file ) ) { - return; - } - - return themeFileURIs.find( ( themeFileUri ) => themeFileUri.file === file ); -} - -function setUnresolvedThemeFilePaths( config ) { - const themeFileURIs = config._links.theme_file_uris; - const backgroundImageUrl = getThemeFileURI( - config?.styles?.background?.backgroundImage?.url, - themeFileURIs - ); - - // Top level styles. - if ( !! backgroundImageUrl?.href ) { - config.styles.background.backgroundImage.url = backgroundImageUrl.href; - } - - return config; -} - -export default function useGetThemeFileURIs( mergedConfig ) { - if ( ! mergedConfig?.styles || ! mergedConfig?._links?.theme_file_uris ) { - return mergedConfig; - } - return setUnresolvedThemeFilePaths( mergedConfig ); -} diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 16ce7a14469907..0032aecf984f93 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -35,7 +35,7 @@ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; import BlockContext from '../block-context'; import { unlock } from '../../lock-unlock'; -import useGetThemeFileUris from './use-get-theme-file-uris'; +import setThemeFileUris from './set-theme-file-uris'; // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. @@ -1218,7 +1218,10 @@ export function processCSSNesting( css, blockSelector ) { */ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); - const withResolvedThemeURIs = useGetThemeFileUris( mergedConfig ); + mergedConfig = setThemeFileUris( + mergedConfig, + mergedConfig?._links?.theme_file_uris + ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. const disableLayoutStyles = useSelect( ( select ) => { @@ -1233,15 +1236,10 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const { getBlockStyles } = useSelect( blocksStore ); return useMemo( () => { - if ( - ! withResolvedThemeURIs?.styles || - ! withResolvedThemeURIs?.settings - ) { + if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) { return []; } - const updatedConfig = updateConfigWithSeparator( - withResolvedThemeURIs - ); + const updatedConfig = updateConfigWithSeparator( mergedConfig ); const blockSelectors = getBlockSelectors( getBlockTypes(), @@ -1304,7 +1302,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { }, [ hasBlockGapSupport, hasFallbackGapSupport, - withResolvedThemeURIs, + mergedConfig, disableLayoutStyles, isTemplate, getBlockStyles, diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js index 65dd9738b2b4fb..0d4aa5e96f435a 100644 --- a/packages/edit-site/src/components/global-styles/background-panel.js +++ b/packages/edit-site/src/components/global-styles/background-panel.js @@ -39,9 +39,14 @@ export default function BackgroundPanel() { const [ style ] = useGlobalStyle( '', undefined, 'user', { shouldDecodeEncode: false, } ); - const [ inheritedStyle, setStyle ] = useGlobalStyle( '', undefined, 'all', { - shouldDecodeEncode: false, - } ); + const [ inheritedStyle, setStyle, _links ] = useGlobalStyle( + '', + undefined, + 'all', + { + shouldDecodeEncode: false, + } + ); const [ settings ] = useGlobalSetting( '' ); const defaultControls = { @@ -60,6 +65,7 @@ export default function BackgroundPanel() { headerLabel={ __( 'Image' ) } defaultValues={ BACKGROUND_DEFAULT_VALUES } defaultControls={ defaultControls } + themeFileURIs={ _links?.theme_file_uris } /> ); } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/index.js b/packages/edit-site/src/components/global-styles/screen-revisions/index.js index 6a20e23c916746..e4ffb209964187 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/index.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/index.js @@ -84,6 +84,7 @@ function ScreenRevisions() { setUserConfig( () => ( { styles: revision?.styles, settings: revision?.settings, + _links: revision?._links, } ) ); setIsLoadingRevisionWithUnsavedChanges( false ); onCloseRevisions(); @@ -91,8 +92,13 @@ function ScreenRevisions() { const selectRevision = ( revision ) => { setCurrentlySelectedRevision( { + /* + * The default must be an empty object so that + * `mergeBaseAndUserConfigs()` can merge them correctly. + */ styles: revision?.styles || {}, settings: revision?.settings || {}, + _links: revision?._links || {}, id: revision?.id, } ); }; diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js index 2cd0b3b7bdea60..024fb74ef97cf2 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js @@ -119,6 +119,7 @@ export default function useGlobalStylesRevisions( { query } = {} ) { id: 'unsaved', styles: userConfig?.styles, settings: userConfig?.settings, + _links: userConfig?._links, author: { name: currentUser?.name, avatar_urls: currentUser?.avatar_urls, diff --git a/packages/edit-site/src/components/global-styles/variations/variation.js b/packages/edit-site/src/components/global-styles/variations/variation.js index a1055f18e05bc8..a3214149edec33 100644 --- a/packages/edit-site/src/components/global-styles/variations/variation.js +++ b/packages/edit-site/src/components/global-styles/variations/variation.js @@ -23,9 +23,6 @@ const { GlobalStylesContext, areGlobalStyleConfigsEqual } = unlock( ); export default function Variation( { variation, children, isPill } ) { - // @TODO _links need to be merged as well somehow so that - // they are always returned in "mergedConfig" in the global-styles-provider. - // console.log( 'variation', variation ); const [ isFocused, setIsFocused ] = useState( false ); const { base, user, setUserConfig } = useContext( GlobalStylesContext ); const context = useMemo( @@ -33,6 +30,7 @@ export default function Variation( { variation, children, isPill } ) { user: { settings: variation.settings ?? {}, styles: variation.styles ?? {}, + _links: variation._links ?? {}, }, base, merged: mergeBaseAndUserConfigs( base, variation ), @@ -45,6 +43,7 @@ export default function Variation( { variation, children, isPill } ) { setUserConfig( () => ( { settings: variation.settings, styles: variation.styles, + _links: variation._links, } ) ); }; diff --git a/packages/editor/src/components/global-styles-provider/index.js b/packages/editor/src/components/global-styles-provider/index.js index 566e390b26a57d..9e4ba24e7311fe 100644 --- a/packages/editor/src/components/global-styles-provider/index.js +++ b/packages/editor/src/components/global-styles-provider/index.js @@ -31,7 +31,7 @@ export function mergeBaseAndUserConfigs( base, user ) { } function useGlobalStylesUserConfig() { - const { globalStylesId, isReady, settings, styles } = useSelect( + const { globalStylesId, isReady, settings, styles, _links } = useSelect( ( select ) => { const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); @@ -65,6 +65,7 @@ function useGlobalStylesUserConfig() { isReady: hasResolved, settings: record?.settings, styles: record?.styles, + _links: record?._links, }; }, [] @@ -76,8 +77,9 @@ function useGlobalStylesUserConfig() { return { settings: settings ?? {}, styles: styles ?? {}, + _links: _links ?? {}, }; - }, [ settings, styles ] ); + }, [ settings, styles, _links ] ); const setConfig = useCallback( ( callback, options = {} ) => { @@ -86,11 +88,14 @@ function useGlobalStylesUserConfig() { 'globalStyles', globalStylesId ); + const currentConfig = { styles: record?.styles ?? {}, settings: record?.settings ?? {}, + _links: record?._links ?? {}, }; const updatedConfig = callback( currentConfig ); + editEntityRecord( 'root', 'globalStyles', @@ -98,6 +103,7 @@ function useGlobalStylesUserConfig() { { styles: cleanEmptyObject( updatedConfig.styles ) || {}, settings: cleanEmptyObject( updatedConfig.settings ) || {}, + _links: cleanEmptyObject( updatedConfig._links ) || {}, }, options ); @@ -128,6 +134,7 @@ export function useGlobalStylesContext() { } return mergeBaseAndUserConfigs( baseConfig, userConfig ); }, [ userConfig, baseConfig ] ); + const context = useMemo( () => { return { isReady: isUserConfigReady && isBaseConfigReady, From 57cd28ca37f97ebfe2b46d30ddb5a56a60d9dae8 Mon Sep 17 00:00:00 2001 From: ramon Date: Fri, 10 May 2024 16:50:35 +1000 Subject: [PATCH 07/25] So my linter is working again --- ...est-global-styles-controller-gutenberg.php | 6 +-- ...class-wp-theme-json-resolver-gutenberg.php | 40 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index c5f7fbb885a36b..d13aec4062cd46 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -629,9 +629,9 @@ public function get_theme_item( $request ) { $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array(); } - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index bd90873ea4925d..d65b4441394b7e 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -775,8 +775,9 @@ public static function get_style_variations() { * * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. * @return array An array of resolved paths. + * @return array Options. */ - public static function get_resolved_theme_uris( $theme_json ) { + public static function get_resolved_theme_uris( $theme_json, $options = array() ) { $resolved_theme_uris = array(); if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { @@ -791,19 +792,36 @@ public static function get_resolved_theme_uris( $theme_json ) { is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && // Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - $resolved_theme_uris[] = array( + $resolved_theme_uri = array( 'file' => $theme_json_data['styles']['background']['backgroundImage']['url'], 'href' => esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ), ); + if ( ! empty( $options['include_paths'] ) ) { + $resolved_theme_uri['path'] = array( 'styles', 'background', 'backgroundImage', 'url' ); + } + $resolved_theme_uris[] = $resolved_theme_uri; } return $resolved_theme_uris; } - // @TODO used in gutenberg_get_global_stylesheet to ensure global stylesheet URIs are resolved. - // Try to harmonize with the above function + /** + * Resolves relative paths in theme.json styles to theme absolute paths + * and merges them with incoming theme JSON. + * + * @since 6.6.0 + * + * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. + * @return WP_Theme_JSON_Gutenberg Theme merged with resolved paths, if any found. + */ public static function resolve_theme_file_uris( $theme_json ) { - if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { + $resolved_urls = static::get_resolved_theme_uris( + $theme_json, + array( + 'include_paths' => true, + ) + ); + if ( empty( $resolved_urls ) ) { return $theme_json; } @@ -812,16 +830,8 @@ public static function resolve_theme_file_uris( $theme_json ) { 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, ); - /* - * Styles backgrounds. - * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - * Blocks, elements, and block variations are not yet supported. - */ - if ( - isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && - is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && - ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - $resolved_theme_json_data['styles']['background']['backgroundImage']['url'] = esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ); + foreach ( $resolved_urls as $resolved_url ) { + _wp_array_set( $resolved_theme_json_data, $resolved_url['path'], $resolved_url['href'] ); } if ( ! empty( $resolved_theme_json_data ) ) { From 06c2bb077e16a112245c41d71dfaa6c5daf715ae Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 13 May 2024 14:56:38 +1000 Subject: [PATCH 08/25] Changed the relative link to `wp:theme-file-uris` Always adding path to the link object so that it can dynamically resolved. --- ...est-global-styles-controller-gutenberg.php | 8 ++- ...class-wp-theme-json-resolver-gutenberg.php | 20 ++----- ...global-styles-revisions-controller-6-6.php | 6 +- .../global-styles/background-panel.js | 9 ++- .../global-styles/set-theme-file-uris.js | 58 ++++++++++++------- .../global-styles/use-global-styles-output.js | 2 +- .../global-styles/background-panel.js | 2 +- 7 files changed, 61 insertions(+), 44 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index d13aec4062cd46..e901478d643861 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -415,7 +415,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( $is_global_styles_user_theme_json ) { $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - $links['theme_file_uris'] = $resolved_theme_uris; + $links['wp:theme-file-uris'] = $resolved_theme_uris; } } $response->add_links( $links ); @@ -642,7 +642,11 @@ public function get_theme_item( $request ) { ); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); if ( ! empty( $resolved_theme_uris ) ) { - $links['theme_file_uris'] = $resolved_theme_uris; + /* + * @TODO this needs to be added to the WP REST API schema. + * E.g., $links['https://api.w.org/theme-file-uris']. + */ + $links['wp:theme-file-uris'] = $resolved_theme_uris; } $response->add_links( $links ); diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index d65b4441394b7e..9be887a0f5ed96 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -758,7 +758,7 @@ public static function get_style_variations() { $variation['title'] = basename( $path, '.json' ); } if ( ! empty( $resolved_theme_uris ) ) { - $variation['_links']['theme_file_uris'] = $resolved_theme_uris; + $variation['_links']['wp:theme-file-uris'] = $resolved_theme_uris; } $variations[] = $variation; } @@ -775,9 +775,8 @@ public static function get_style_variations() { * * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. * @return array An array of resolved paths. - * @return array Options. */ - public static function get_resolved_theme_uris( $theme_json, $options = array() ) { + public static function get_resolved_theme_uris( $theme_json ) { $resolved_theme_uris = array(); if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { @@ -792,13 +791,11 @@ public static function get_resolved_theme_uris( $theme_json, $options = array() is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && // Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - $resolved_theme_uri = array( - 'file' => $theme_json_data['styles']['background']['backgroundImage']['url'], + $resolved_theme_uri = array( + 'name' => $theme_json_data['styles']['background']['backgroundImage']['url'], 'href' => esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ), + 'path' => array( 'styles', 'background', 'backgroundImage', 'url' ), ); - if ( ! empty( $options['include_paths'] ) ) { - $resolved_theme_uri['path'] = array( 'styles', 'background', 'backgroundImage', 'url' ); - } $resolved_theme_uris[] = $resolved_theme_uri; } @@ -815,12 +812,7 @@ public static function get_resolved_theme_uris( $theme_json, $options = array() * @return WP_Theme_JSON_Gutenberg Theme merged with resolved paths, if any found. */ public static function resolve_theme_file_uris( $theme_json ) { - $resolved_urls = static::get_resolved_theme_uris( - $theme_json, - array( - 'include_paths' => true, - ) - ); + $resolved_urls = static::get_resolved_theme_uris( $theme_json ); if ( empty( $resolved_urls ) ) { return $theme_json; } diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php index 1401c6608ea435..e71e463a975288 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -87,7 +87,11 @@ public function prepare_item_for_response( $post, $request ) { $links = array(); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - $links['theme_file_uris'] = $resolved_theme_uris; + /* + * @TODO this needs to be added to the WP REST API schema. + * E.g., $links['https://api.w.org/theme-file-uris']. + */ + $links['wp:theme-file-uris'] = $resolved_theme_uris; } $response->add_links( $links ); diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index ed925a6049bd44..1960ee19bc2401 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -38,7 +38,7 @@ import { TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; -import { getThemeFileURI } from './set-theme-file-uris'; +import { getResolvedThemeFilePath } from './set-theme-file-uris'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { @@ -303,7 +303,10 @@ function BackgroundImageToolsPanelItem( { } variant="secondary" @@ -471,7 +474,7 @@ function BackgroundSizeToolsPanelItem( { diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js index 56feb1757cbc4a..7d5d778a853712 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -3,6 +3,11 @@ */ import { isURL, isValidPath } from '@wordpress/url'; +/** + * Internal dependencies + */ +import { getValueFromObjectPath } from '../../utils/object'; + function isRelativePath( url ) { return isValidPath( url ) && ! isURL( url ); } @@ -14,41 +19,39 @@ function isRelativePath( url ) { * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection. */ -export function getThemeFileURI( file, themeFileURIs = [] ) { +export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { if ( ! isRelativePath( file ) ) { return file; } const uri = themeFileURIs.find( - ( themeFileUri ) => themeFileUri.file === file + ( themeFileUri ) => themeFileUri.name === file ); return uri?.href; } /** - * Houses logic of where to look for unresolved theme file paths. + * Mutates an object by settings a value at the provided path. * - * @param {Object} styles A styles object. - * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. - * @return {Object} Returns mutated styles object. + * @param {Object} object Object to set a value in. + * @param {number|string|Array} path Path in the object to modify. + * @param {*} value New value to set. + * @return {Object} Object with the new value set. */ -function setUnresolvedThemeFilePaths( styles, themeFileURIs ) { - // Top level styles. - if ( - !! styles?.background?.backgroundImage?.url && - isRelativePath( styles?.background?.backgroundImage?.url ) - ) { - const backgroundImageUrl = getThemeFileURI( - styles?.background?.backgroundImage?.url, - themeFileURIs - ); - if ( backgroundImageUrl ) { - styles.background.backgroundImage.url = backgroundImageUrl; - } +function setMutably( object, path, value ) { + path = Array.isArray( path ) ? [ ...path ] : [ path ]; + const finalValueKey = path.pop(); + let prev = object; + + for ( const key of path ) { + const current = prev[ key ]; + prev = current; } - return styles; + prev[ finalValueKey ] = value; + + return object; } /** @@ -65,8 +68,19 @@ export default function setThemeFileUris( themeJson, themeFileURIs ) { return themeJson; } - // Mutating function. - setUnresolvedThemeFilePaths( themeJson.styles, themeFileURIs ); + themeFileURIs.forEach( ( { name, href, path } ) => { + const value = getValueFromObjectPath( themeJson, path ); + if ( value === name ) { + /* + * The object must not be updated immutably here because the + * themeJson is a reference to the global styles tree used as a dependency in the + * useGlobalStylesOutputWithConfig() hook. If we mutate the object, + * the hook will detect the change and re-render the component, resulting + * in a maximum depth exceeded error. + */ + themeJson = setMutably( themeJson, path, href ); + } + } ); return themeJson; } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 0032aecf984f93..56244b24b61ca3 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -1220,7 +1220,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); mergedConfig = setThemeFileUris( mergedConfig, - mergedConfig?._links?.theme_file_uris + mergedConfig?._links?.[ 'wp:theme-file-uris' ] ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js index 0d4aa5e96f435a..0b933aaa28753c 100644 --- a/packages/edit-site/src/components/global-styles/background-panel.js +++ b/packages/edit-site/src/components/global-styles/background-panel.js @@ -65,7 +65,7 @@ export default function BackgroundPanel() { headerLabel={ __( 'Image' ) } defaultValues={ BACKGROUND_DEFAULT_VALUES } defaultControls={ defaultControls } - themeFileURIs={ _links?.theme_file_uris } + themeFileURIs={ _links?.[ 'wp:theme-file-uris' ] } /> ); } From 3e283e022f4c3057475c837a51c58048994ba289 Mon Sep 17 00:00:00 2001 From: ramon Date: Mon, 13 May 2024 15:50:30 +1000 Subject: [PATCH 09/25] Added some explanatory TODOs --- ...ass-wp-rest-global-styles-controller-gutenberg.php | 1 + lib/class-wp-theme-json-resolver-gutenberg.php | 11 +++++++++++ ...rg-rest-global-styles-revisions-controller-6-6.php | 7 +++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index e901478d643861..e88d64042795e6 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -357,6 +357,7 @@ protected function prepare_item_for_database( $request ) { * * @since 5.9.0 * @since 6.2.0 Handling of style.css was added to WP_Theme_JSON. + * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @param WP_Post $post Global Styles post object. * @param WP_REST_Request $request Request object. diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 9be887a0f5ed96..a3a4fb81d18cb5 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -723,6 +723,7 @@ private static function recursively_iterate_json( $dir ) { * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. + * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @return array */ @@ -757,6 +758,16 @@ public static function get_style_variations() { if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } + /* + * @TODO this needs to be added to the WP REST API schema. + * E.g., $links['https://api.w.org/theme-file-uris']. ?? + */ + /* + * @TODO I'm not sure if we can/should add `_links` to collection items. + * It should be part of the response object, but given that + * WP_REST_Global_Styles_Controller_Gutenberg::get_theme_items + * returns a collection ([{}]) and not a response object ({}) it's not possible. + */ if ( ! empty( $resolved_theme_uris ) ) { $variation['_links']['wp:theme-file-uris'] = $resolved_theme_uris; } diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php index e71e463a975288..c2c176b4eb321e 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -11,6 +11,7 @@ * Core class used to access global styles revisions via the REST API. * * @since 6.3.0 + * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @see WP_REST_Controller */ @@ -88,8 +89,10 @@ public function prepare_item_for_response( $post, $request ) { $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { /* - * @TODO this needs to be added to the WP REST API schema. - * E.g., $links['https://api.w.org/theme-file-uris']. + * @TODO I'm not sure if we can/should add `_links` to collection items. + * It should be part of the response object, but given that + * WP_REST_Global_Styles_Revisions_Controller::get_items + * returns a collection ([{}]) and not a response object ({}) it's not possible. */ $links['wp:theme-file-uris'] = $resolved_theme_uris; } From 17528587d057d612fee9ad3a6afb6c368bf1e9b8 Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 14 May 2024 13:27:45 +1000 Subject: [PATCH 10/25] Adding valid link attributes to the _link object. Updated tests --- ...est-global-styles-controller-gutenberg.php | 8 +-- ...class-wp-theme-json-resolver-gutenberg.php | 28 ++++++---- ...global-styles-revisions-controller-6-6.php | 8 +-- .../global-styles/set-theme-file-uris.js | 8 +-- phpunit/class-wp-theme-json-resolver-test.php | 52 ++++++++++++++++--- 5 files changed, 69 insertions(+), 35 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index e88d64042795e6..84df658bd3028f 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -416,7 +416,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( $is_global_styles_user_theme_json ) { $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - $links['wp:theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; } } $response->add_links( $links ); @@ -643,11 +643,7 @@ public function get_theme_item( $request ) { ); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); if ( ! empty( $resolved_theme_uris ) ) { - /* - * @TODO this needs to be added to the WP REST API schema. - * E.g., $links['https://api.w.org/theme-file-uris']. - */ - $links['wp:theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; } $response->add_links( $links ); diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index a3a4fb81d18cb5..1f969e2eddd82d 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -780,7 +780,8 @@ public static function get_style_variations() { /** * Resolves relative paths in theme.json styles to theme absolute paths - * and returns them in an array that can be embedded in a REST response. + * and returns them in an array that can be embedded + * as the value of `_link` object in REST API responses. * * @since 6.6.0 * @@ -794,19 +795,24 @@ public static function get_resolved_theme_uris( $theme_json ) { return $resolved_theme_uris; } - $theme_json_data = $theme_json->get_raw_data(); + $theme_json_data = $theme_json->get_raw_data(); + $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; // Top level styles. if ( - isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && - is_string( $theme_json_data['styles']['background']['backgroundImage']['url'] ) && + isset( $background_image_url ) && + is_string( $background_image_url ) && // Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - ! isset( wp_parse_url( $theme_json_data['styles']['background']['backgroundImage']['url'] )['host'] ) ) { - $resolved_theme_uri = array( - 'name' => $theme_json_data['styles']['background']['backgroundImage']['url'], - 'href' => esc_url( get_theme_file_uri( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ), - 'path' => array( 'styles', 'background', 'backgroundImage', 'url' ), + ! isset( wp_parse_url( $background_image_url )['host'] ) ) { + $file_type = wp_check_filetype( $background_image_url ); + $resolved_theme_uri = array( + 'name' => $background_image_url, + 'href' => esc_url( get_theme_file_uri( $background_image_url ) ), + 'target' => 'styles.background.backgroundImage.url', ); + if ( isset( $file_type['type'] ) ) { + $resolved_theme_uri['type'] = $file_type['type']; + } $resolved_theme_uris[] = $resolved_theme_uri; } @@ -828,13 +834,13 @@ public static function resolve_theme_file_uris( $theme_json ) { return $theme_json; } - $theme_json_data = $theme_json->get_raw_data(); $resolved_theme_json_data = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, ); foreach ( $resolved_urls as $resolved_url ) { - _wp_array_set( $resolved_theme_json_data, $resolved_url['path'], $resolved_url['href'] ); + $path = explode( '.', $resolved_url['target'] ); + _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); } if ( ! empty( $resolved_theme_json_data ) ) { diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php index c2c176b4eb321e..5888287395ca7f 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -88,13 +88,7 @@ public function prepare_item_for_response( $post, $request ) { $links = array(); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - /* - * @TODO I'm not sure if we can/should add `_links` to collection items. - * It should be part of the response object, but given that - * WP_REST_Global_Styles_Revisions_Controller::get_items - * returns a collection ([{}]) and not a response object ({}) it's not possible. - */ - $links['wp:theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; } $response->add_links( $links ); diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js index 7d5d778a853712..2c6679bcefae6e 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -40,7 +40,7 @@ export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { * @return {Object} Object with the new value set. */ function setMutably( object, path, value ) { - path = Array.isArray( path ) ? [ ...path ] : [ path ]; + path = path.split( '.' ); const finalValueKey = path.pop(); let prev = object; @@ -68,8 +68,8 @@ export default function setThemeFileUris( themeJson, themeFileURIs ) { return themeJson; } - themeFileURIs.forEach( ( { name, href, path } ) => { - const value = getValueFromObjectPath( themeJson, path ); + themeFileURIs.forEach( ( { name, href, target } ) => { + const value = getValueFromObjectPath( themeJson, target ); if ( value === name ) { /* * The object must not be updated immutably here because the @@ -78,7 +78,7 @@ export default function setThemeFileUris( themeJson, themeFileURIs ) { * the hook will detect the change and re-render the component, resulting * in a maximum depth exceeded error. */ - themeJson = setMutably( themeJson, path, href ); + themeJson = setMutably( themeJson, target, href ); } } ); diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 305df6b1b4d88d..92ca37c8df7c1b 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1141,14 +1141,9 @@ public function test_shadow_default_presets_value_for_block_and_classic_themes() } /** - * Tests that classic themes still get core default settings such as color palette and duotone. + * Tests that relative paths are resolved and merged into the theme.json data. */ public function test_resolve_theme_file_uris() { - $theme_json_resolver = new ReflectionClass( 'WP_Theme_JSON_Resolver_Gutenberg' ); - $func = $theme_json_resolver->getMethod( 'resolve_theme_file_uris' ); - - $func->setAccessible( true ); - $theme_json = new WP_Theme_JSON_Gutenberg( array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, @@ -1183,9 +1178,52 @@ public function test_resolve_theme_file_uris() { return 'https://example.org/wp-content/themes/example-theme/example/' . explode( 'example/', $file )[1]; }; add_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); - $actual = $func->invoke( null, $theme_json ); + $actual = WP_Theme_JSON_Resolver_Gutenberg::resolve_theme_file_uris( $theme_json ); remove_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); $this->assertSame( $expected_data, $actual->get_raw_data() ); } + + /** + * Tests that them uris are resolved and bundled with other metadata + * in an array. + */ + public function test_get_resolved_theme_uris() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'example/img/image.png', + ), + ), + ), + ) + ); + + $expected_data = array( + array( + 'name' => 'example/img/image.png', + 'href' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', + 'target' => 'styles.background.backgroundImage.url', + 'type' => 'image/png', + ), + ); + + /* + * This filter callback normalizes the return value from `get_theme_file_uri` + * to guard against changes in test environments. + * The test suite otherwise returns full system dir path, e.g., + * /wordpress-phpunit/includes/../data/themedir1/default/example/img/image.png + */ + $filter_theme_file_uri_callback = function ( $file ) { + return 'https://example.org/wp-content/themes/example-theme/example/' . explode( 'example/', $file )[1]; + }; + add_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); + $actual = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); + remove_filter( 'theme_file_uri', $filter_theme_file_uri_callback ); + + $this->assertSame( $expected_data, $actual ); + } } From 98de1c6d6e553a4acb197d65bb5758e6937ed0b5 Mon Sep 17 00:00:00 2001 From: ramon Date: Tue, 14 May 2024 13:57:54 +1000 Subject: [PATCH 11/25] Added some unit tests for utils --- .../src/components/global-styles/set-theme-file-uris.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js index 2c6679bcefae6e..9b85fb4d0a1350 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -28,6 +28,10 @@ export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { ( themeFileUri ) => themeFileUri.name === file ); + if ( ! uri?.href ) { + return file; + } + return uri?.href; } From 70c26bac0d1a364b460b4007bd43934e375334e5 Mon Sep 17 00:00:00 2001 From: ramon Date: Thu, 16 May 2024 11:17:58 -0700 Subject: [PATCH 12/25] Switching to using file: prefix, which is an established theme.json convention for relative paths to theme assets. E.g., web fonts Adding test image to empty theme Added theme JSON schema update Unit tests for JS helper Using response methods to add links to response collection --- ...est-global-styles-controller-gutenberg.php | 19 ++++- ...class-wp-theme-json-resolver-gutenberg.php | 35 +++------- .../global-styles/set-theme-file-uris.js | 13 ---- .../global-styles/test/set-theme-file-uris.js | 65 ++++++++++++++++++ ...lobal-styles-controller-gutenberg-test.php | 19 ++++- phpunit/class-wp-theme-json-resolver-test.php | 6 +- schemas/json/theme.json | 2 +- .../img/1024x768_emptytheme_test_image.jpg | Bin 0 -> 22133 bytes test/emptytheme/styles/variation.json | 5 ++ 9 files changed, 120 insertions(+), 44 deletions(-) create mode 100644 packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js create mode 100644 test/emptytheme/img/1024x768_emptytheme_test_image.jpg diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 84df658bd3028f..e48327360eac08 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -685,6 +685,7 @@ public function get_theme_items_permissions_check( $request ) { // phpcs:ignore * @since 6.0.0 * @since 6.2.0 Returns parent theme variations, if they exist. * @since 6.4.0 Removed unnecessary local variable. + * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item. * * @param WP_REST_Request $request The request instance. * @@ -700,9 +701,25 @@ public function get_theme_items( $request ) { ); } + $response = array(); $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); - return rest_ensure_response( $variations ); + // Add resolved theme asset links. + foreach ( $variations as $variation ) { + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $variation ); + $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $variation_theme_json ); + $data = rest_ensure_response( $variation ); + if ( ! empty( $resolved_theme_uris ) ) { + $data->add_links( + array( + 'https://api.w.org/theme-file-uris' => $resolved_theme_uris, + ) + ); + } + $response[] = $this->prepare_response_for_collection( $data ); + } + + return rest_ensure_response( $response ); } /** diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 1f969e2eddd82d..fe3f7aa4314f24 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -723,7 +723,6 @@ private static function recursively_iterate_json( $dir ) { * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. - * @since 6.6.0 Added custom relative theme file URIs to `_links`. * * @return array */ @@ -751,26 +750,11 @@ public static function get_style_variations() { foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { - $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $variation_theme_json = new WP_Theme_JSON_Gutenberg( $translated ); - $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $variation_theme_json ); - $variation = $variation_theme_json->get_raw_data(); + $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); + $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); if ( empty( $variation['title'] ) ) { $variation['title'] = basename( $path, '.json' ); } - /* - * @TODO this needs to be added to the WP REST API schema. - * E.g., $links['https://api.w.org/theme-file-uris']. ?? - */ - /* - * @TODO I'm not sure if we can/should add `_links` to collection items. - * It should be part of the response object, but given that - * WP_REST_Global_Styles_Controller_Gutenberg::get_theme_items - * returns a collection ([{}]) and not a response object ({}) it's not possible. - */ - if ( ! empty( $resolved_theme_uris ) ) { - $variation['_links']['wp:theme-file-uris'] = $resolved_theme_uris; - } $variations[] = $variation; } } @@ -795,19 +779,22 @@ public static function get_resolved_theme_uris( $theme_json ) { return $resolved_theme_uris; } - $theme_json_data = $theme_json->get_raw_data(); - $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; + $theme_json_data = $theme_json->get_raw_data(); // Top level styles. + $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; + // Using the same file convention when registering web fonts. See: WP_Font_Face_Resolver:: to_theme_file_uri. + $placeholder = 'file:./'; if ( isset( $background_image_url ) && is_string( $background_image_url ) && - // Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory. - ! isset( wp_parse_url( $background_image_url )['host'] ) ) { - $file_type = wp_check_filetype( $background_image_url ); + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + str_starts_with( $background_image_url, $placeholder ) ) { + $file_type = wp_check_filetype( $background_image_url ); + $src_url = str_replace( $placeholder, '', $background_image_url ); $resolved_theme_uri = array( 'name' => $background_image_url, - 'href' => esc_url( get_theme_file_uri( $background_image_url ) ), + 'href' => esc_url( get_theme_file_uri( $src_url ) ), 'target' => 'styles.background.backgroundImage.url', ); if ( isset( $file_type['type'] ) ) { diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js index 9b85fb4d0a1350..468386ad414624 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -1,17 +1,8 @@ -/** - * WordPress dependencies - */ -import { isURL, isValidPath } from '@wordpress/url'; - /** * Internal dependencies */ import { getValueFromObjectPath } from '../../utils/object'; -function isRelativePath( url ) { - return isValidPath( url ) && ! isURL( url ); -} - /** * Looks up a theme file URI based on a relative path. * @@ -20,10 +11,6 @@ function isRelativePath( url ) { * @return {string?} A resolved theme file URI, if one is found in the themeFileURIs collection. */ export function getResolvedThemeFilePath( file, themeFileURIs = [] ) { - if ( ! isRelativePath( file ) ) { - return file; - } - const uri = themeFileURIs.find( ( themeFileUri ) => themeFileUri.name === file ); diff --git a/packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js new file mode 100644 index 00000000000000..db01af89da8304 --- /dev/null +++ b/packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js @@ -0,0 +1,65 @@ +/** + * Internal dependencies + */ +import setThemeFileUris, { + getResolvedThemeFilePath, +} from '../set-theme-file-uris'; + +const themeFileURIs = [ + { + name: 'file:./assets/image.jpg', + href: 'https://wordpress.org/assets/image.jpg', + target: 'styles.background.backgroundImage.url', + }, + { + name: 'file:./assets/other/image.jpg', + href: 'https://wordpress.org/assets/other/image.jpg', + target: "styles.blocks.['core/group].background.backgroundImage.url", + }, +]; + +describe( 'setThemeFileUris()', () => { + const themeJson = { + styles: { + background: { + backgroundImage: { + url: 'file:./assets/image.jpg', + }, + }, + }, + }; + + it( 'should replace relative paths with resolved URIs if found in themeFileURIs', () => { + const newThemeJson = setThemeFileUris( themeJson, themeFileURIs ); + expect( + newThemeJson.styles.background.backgroundImage.url === + 'https://wordpress.org/assets/image.jpg' + ).toBe( true ); + // Object reference should be the same as the function is mutating the object. + expect( newThemeJson ).toEqual( themeJson ); + } ); +} ); + +describe( 'getResolvedThemeFilePath()', () => { + it.each( [ + [ + 'file:./assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should return absolute URL if found in themeFileURIs', + ], + [ + 'file:./misc/image.jpg', + 'file:./misc/image.jpg', + 'Should return value if not found in themeFileURIs', + ], + [ + 'https://wordpress.org/assets/image.jpg', + 'https://wordpress.org/assets/image.jpg', + 'Should not match absolute URLs', + ], + ] )( 'Given file %s and return value %s: %s', ( file, returnedValue ) => { + expect( + getResolvedThemeFilePath( file, themeFileURIs ) === returnedValue + ).toBe( true ); + } ); +} ); diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 563037f41db9dc..37e011439e1332 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -136,8 +136,23 @@ public function test_get_theme_items() { ), ), ), + '_links' => array( + 'wp:theme-file-uris' => array( + array( + 'href' => 'http://localhost:8889/wp-content/themes/emptytheme/img/1024x768_emptytheme_test_image.jpg', + 'name' => 'file:./img/1024x768_emptytheme_test_image.jpg', + 'target' => 'styles.background.backgroundImage.url', + 'type' => 'image/jpeg', + ), + ), + ), 'styles' => array( - 'blocks' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./img/1024x768_emptytheme_test_image.jpg', + ), + ), + 'blocks' => array( 'core/post-title' => array( 'typography' => array( 'fontWeight' => '700', @@ -152,7 +167,7 @@ public function test_get_theme_items() { wp_recursive_ksort( $data ); wp_recursive_ksort( $expected ); - $this->assertSameSets( $expected, $data ); + $this->assertSameSets( $expected, $data, 'Theme item should match expected schema' ); } /** diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 92ca37c8df7c1b..9ba170cd785d22 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1150,7 +1150,7 @@ public function test_resolve_theme_file_uris() { 'styles' => array( 'background' => array( 'backgroundImage' => array( - 'url' => 'example/img/image.png', + 'url' => 'file:./example/img/image.png', ), ), ), @@ -1195,7 +1195,7 @@ public function test_get_resolved_theme_uris() { 'styles' => array( 'background' => array( 'backgroundImage' => array( - 'url' => 'example/img/image.png', + 'url' => 'file:./example/img/image.png', ), ), ), @@ -1204,7 +1204,7 @@ public function test_get_resolved_theme_uris() { $expected_data = array( array( - 'name' => 'example/img/image.png', + 'name' => 'file:./example/img/image.png', 'href' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', 'target' => 'styles.background.backgroundImage.url', 'type' => 'image/png', diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 46f0c671fdd65f..981df807fd5ce4 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2351,7 +2351,7 @@ "description": "Sets the `background-image` CSS property.", "oneOf": [ { - "description": "A valid CSS value for the background-image property.", + "description": "A valid CSS value for the background-image property, or a path to a file relative to the theme.json file in the theme and prefixed with `file:`, e.g., 'file:./path/to/file.png'.", "type": "string" }, { diff --git a/test/emptytheme/img/1024x768_emptytheme_test_image.jpg b/test/emptytheme/img/1024x768_emptytheme_test_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..694e710f77c5270a8acfbe813af4934a2bbbe233 GIT binary patch literal 22133 zcmeIzH&7Kp9LMqh-tN7m1xGS84i6BtBN!V7&?meOjMxDt?7)CJpAU(GC<- zC5obU7zvFC#tO{Roc9D}#>x@P@Bijzi`)6n{C4;D<94|(BrX~$j)=096nT%hy;3M@ zoE2*_&K63!x5No(F`|_tmZP+z+;*8JqU@30GFr;g#`gIG!O#S6LTj8@N^8q#+cw6V zz1{mR#MivznM;9+yx@`H1vXzyqRSo%Xx;I_^Js_~`MIr_Y{ub@%kW>>qgb z`pw&S?}t8o{Pg+D*Kgm4W4V-<^H+@o`y-dP2+A@>8-Fa9vTD6oJH}4Q^2O(u_{*yk z!r66!#Da##=C)vJPU&D$MNMZYIW4y@eJB>~XR^NwR{u}Qeg_-N)gu$N@-|O9k|(a@ z4NIn+vUY?RSOQJ~KLU3F*C8UHAsV6~8loW@q9GchAsV6~8loW@q9GchAsV6~8loW@ zq9GchAsV6~8loW@q9GchAsV6~8loW@q9GchAsV6~8loW@q9GchAsV6~8loW@q9Gch nAsV6~8loW@q9GchAsV6~8loW@q9GchAsV6~8vcI`#qIt9PFk%E literal 0 HcmV?d00001 diff --git a/test/emptytheme/styles/variation.json b/test/emptytheme/styles/variation.json index 06f672f6fd25d7..7210001d22e820 100644 --- a/test/emptytheme/styles/variation.json +++ b/test/emptytheme/styles/variation.json @@ -12,6 +12,11 @@ } }, "styles": { + "background": { + "backgroundImage": { + "url": "file:./img/1024x768_emptytheme_test_image.jpg" + } + }, "blocks": { "core/post-title": { "typography": { From b94a512eafc1afce732ae2469e26720da01b9388 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Sat, 18 May 2024 13:19:08 -0700 Subject: [PATCH 13/25] Fix linting --- lib/class-wp-theme-json-resolver-gutenberg.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index fe3f7aa4314f24..b2c6f25aaf9c61 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -784,14 +784,14 @@ public static function get_resolved_theme_uris( $theme_json ) { // Top level styles. $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; // Using the same file convention when registering web fonts. See: WP_Font_Face_Resolver:: to_theme_file_uri. - $placeholder = 'file:./'; + $placeholder = 'file:./'; if ( isset( $background_image_url ) && is_string( $background_image_url ) && // Skip if the src doesn't start with the placeholder, as there's nothing to replace. str_starts_with( $background_image_url, $placeholder ) ) { - $file_type = wp_check_filetype( $background_image_url ); - $src_url = str_replace( $placeholder, '', $background_image_url ); + $file_type = wp_check_filetype( $background_image_url ); + $src_url = str_replace( $placeholder, '', $background_image_url ); $resolved_theme_uri = array( 'name' => $background_image_url, 'href' => esc_url( get_theme_file_uri( $src_url ) ), From efae7f2098075677741095c97f46d65b9d54bb77 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 13:21:34 -0700 Subject: [PATCH 14/25] Remove TODO --- packages/block-editor/src/components/global-styles/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 4516d6ed57e6c8..5d420b496e27ab 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -178,7 +178,7 @@ export function useGlobalStyle( ) ); }; - // @TODO _links isn't a great name. styleMeta? themeMeta? + let rawResult, result, _links; switch ( source ) { case 'all': From eb4ab0f0b2cf075184dd4487de3679c070c0b5d8 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 14:00:03 -0700 Subject: [PATCH 15/25] Update tests --- ...lobal-styles-controller-gutenberg-test.php | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 37e011439e1332..f0d34b3c431fac 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -122,6 +122,23 @@ public function test_get_theme_items() { $data = $response->get_data(); $expected = array( array( + '_links' => array( + 'curies' => array( + array( + 'name' => 'wp', + 'href' => 'https://api.w.org/{rel}', + 'templated' => true, + ), + ), + 'wp:theme-file-uris' => array( + array( + 'href' => 'http://localhost:8889/wp-content/themes/emptytheme/img/1024x768_emptytheme_test_image.jpg', + 'name' => 'file:./img/1024x768_emptytheme_test_image.jpg', + 'target' => 'styles.background.backgroundImage.url', + 'type' => 'image/jpeg', + ), + ), + ), 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, 'settings' => array( 'color' => array( @@ -136,16 +153,6 @@ public function test_get_theme_items() { ), ), ), - '_links' => array( - 'wp:theme-file-uris' => array( - array( - 'href' => 'http://localhost:8889/wp-content/themes/emptytheme/img/1024x768_emptytheme_test_image.jpg', - 'name' => 'file:./img/1024x768_emptytheme_test_image.jpg', - 'target' => 'styles.background.backgroundImage.url', - 'type' => 'image/jpeg', - ), - ), - ), 'styles' => array( 'background' => array( 'backgroundImage' => array( @@ -166,7 +173,7 @@ public function test_get_theme_items() { wp_recursive_ksort( $data ); wp_recursive_ksort( $expected ); - + var_dump( $data ); $this->assertSameSets( $expected, $data, 'Theme item should match expected schema' ); } From b0fb93749021a5c13e09844735f3eb81b02bfd51 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 15:17:49 -0700 Subject: [PATCH 16/25] dump var_dump --- .../class-wp-rest-global-styles-controller-gutenberg-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index f0d34b3c431fac..09108a54a68fb0 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -173,7 +173,7 @@ public function test_get_theme_items() { wp_recursive_ksort( $data ); wp_recursive_ksort( $expected ); - var_dump( $data ); + $this->assertSameSets( $expected, $data, 'Theme item should match expected schema' ); } From 7c083bcad5dd368173763ee5a72bd76895a287c6 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 15:19:29 -0700 Subject: [PATCH 17/25] Check for $theme_json before resolving --- lib/class-wp-rest-global-styles-controller-gutenberg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index e48327360eac08..1b527162688666 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -367,7 +367,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V $raw_config = json_decode( $post->post_content, true ); $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON']; $config = array(); - $theme_json = array(); + $theme_json = null; if ( $is_global_styles_user_theme_json ) { $theme_json = new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ); $config = $theme_json->get_raw_data(); @@ -413,7 +413,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $links = $this->prepare_links( $post->ID ); // Only return resolved URIs for get requests to user theme JSON. - if ( $is_global_styles_user_theme_json ) { + if ( $theme_json ) { $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; From 17d51bde760bc47ff3f0639e3ef52da8efbd0ac4 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 15:36:53 -0700 Subject: [PATCH 18/25] be explicit about the background value file:./ --- schemas/json/theme.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 981df807fd5ce4..1443685ff83cb6 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2351,7 +2351,7 @@ "description": "Sets the `background-image` CSS property.", "oneOf": [ { - "description": "A valid CSS value for the background-image property, or a path to a file relative to the theme.json file in the theme and prefixed with `file:`, e.g., 'file:./path/to/file.png'.", + "description": "A valid CSS value for the background-image property, or a path to a file relative to the theme root directory, and prefixed with `file:`, e.g., 'file:./path/to/file.png'.", "type": "string" }, { From 60c8bb140c84cd543416e8b004e1e5e6050798ad Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 15:46:57 -0700 Subject: [PATCH 19/25] Remove unnecessary empty check --- lib/class-wp-theme-json-resolver-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index b2c6f25aaf9c61..258fba520ffb0a 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -775,7 +775,7 @@ public static function get_style_variations() { public static function get_resolved_theme_uris( $theme_json ) { $resolved_theme_uris = array(); - if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg || empty( $theme_json ) ) { + if ( ! $theme_json instanceof WP_Theme_JSON_Gutenberg ) { return $resolved_theme_uris; } From dd075575c32634bd76f7671e313e79506368c191 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 16:52:04 -0700 Subject: [PATCH 20/25] Abstracting getting any resolved URI to separate hook Updating comments --- .../src/components/global-styles/hooks.js | 5 +++++ .../src/components/global-styles/index.js | 1 + .../components/global-styles/set-theme-file-uris.js | 2 +- .../components/global-styles/background-panel.js | 13 +++++-------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 5d420b496e27ab..ae5a9bbb58b864 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -209,6 +209,11 @@ export function useGlobalStyle( return [ result, setStyle, _links ]; } +export function useGlobalStyleLinks() { + const { merged: mergedConfig } = useContext( GlobalStylesContext ); + return mergedConfig?._links; +} + /** * React hook that overrides a global settings object with block and element specific settings. * diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js index 7ad192fac9b4f5..0e9aeb4c9c84ec 100644 --- a/packages/block-editor/src/components/global-styles/index.js +++ b/packages/block-editor/src/components/global-styles/index.js @@ -3,6 +3,7 @@ export { useGlobalSetting, useGlobalStyle, useSettingsForBlockElement, + useGlobalStyleLinks, } from './hooks'; export { getBlockCSSSelector } from './get-block-css-selector'; export { diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js index 468386ad414624..a6ae86da4fb3e9 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/set-theme-file-uris.js @@ -65,7 +65,7 @@ export default function setThemeFileUris( themeJson, themeFileURIs ) { /* * The object must not be updated immutably here because the * themeJson is a reference to the global styles tree used as a dependency in the - * useGlobalStylesOutputWithConfig() hook. If we mutate the object, + * useGlobalStylesOutputWithConfig() hook. If we don't mutate the object, * the hook will detect the change and re-render the component, resulting * in a maximum depth exceeded error. */ diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js index 0b933aaa28753c..a16b5452140d3f 100644 --- a/packages/edit-site/src/components/global-styles/background-panel.js +++ b/packages/edit-site/src/components/global-styles/background-panel.js @@ -17,6 +17,7 @@ const BACKGROUND_DEFAULT_VALUES = { const { useGlobalStyle, useGlobalSetting, + useGlobalStyleLinks, BackgroundPanel: StylesBackgroundPanel, } = unlock( blockEditorPrivateApis ); @@ -39,14 +40,10 @@ export default function BackgroundPanel() { const [ style ] = useGlobalStyle( '', undefined, 'user', { shouldDecodeEncode: false, } ); - const [ inheritedStyle, setStyle, _links ] = useGlobalStyle( - '', - undefined, - 'all', - { - shouldDecodeEncode: false, - } - ); + const [ inheritedStyle, setStyle ] = useGlobalStyle( '', undefined, 'all', { + shouldDecodeEncode: false, + } ); + const _links = useGlobalStyleLinks(); const [ settings ] = useGlobalSetting( '' ); const defaultControls = { From 26866fc6bf4ab42fbd0787e4a74d8f62dd76eeb8 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Sat, 18 May 2024 16:12:58 -0700 Subject: [PATCH 21/25] Update lib/class-wp-theme-json-resolver-gutenberg.php --- lib/class-wp-theme-json-resolver-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 258fba520ffb0a..f6d60ac3d1795f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -794,7 +794,7 @@ public static function get_resolved_theme_uris( $theme_json ) { $src_url = str_replace( $placeholder, '', $background_image_url ); $resolved_theme_uri = array( 'name' => $background_image_url, - 'href' => esc_url( get_theme_file_uri( $src_url ) ), + 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 'target' => 'styles.background.backgroundImage.url', ); if ( isset( $file_type['type'] ) ) { From f01dcdf51e57ea950efb780f1bd566ba6710d30d Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Sat, 18 May 2024 16:18:37 -0700 Subject: [PATCH 22/25] Update lib/class-wp-theme-json-resolver-gutenberg.php --- lib/class-wp-theme-json-resolver-gutenberg.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index f6d60ac3d1795f..5aaa2ea7e3eac7 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -830,9 +830,7 @@ public static function resolve_theme_file_uris( $theme_json ) { _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); } - if ( ! empty( $resolved_theme_json_data ) ) { - $theme_json->merge( new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ) ); - } + $theme_json->merge( new WP_Theme_JSON_Gutenberg( $resolved_theme_json_data ) ); return $theme_json; } From 25f8754ff332d14690a1f116bb9ca81d9f165ccf Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 17:09:59 -0700 Subject: [PATCH 23/25] Revert useGlobalStyle changes - no longer required given the new hook --- .../src/components/global-styles/hooks.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index ae5a9bbb58b864..e0de34cf2280e2 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -179,25 +179,22 @@ export function useGlobalStyle( ); }; - let rawResult, result, _links; + let rawResult, result; switch ( source ) { case 'all': rawResult = getValueFromObjectPath( mergedConfig, finalPath ); - _links = mergedConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( mergedConfig, blockName, rawResult ) : rawResult; break; case 'user': rawResult = getValueFromObjectPath( userConfig, finalPath ); - _links = userConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( mergedConfig, blockName, rawResult ) : rawResult; break; case 'base': rawResult = getValueFromObjectPath( baseConfig, finalPath ); - _links = baseConfig?._links; result = shouldDecodeEncode ? getValueFromVariable( baseConfig, blockName, rawResult ) : rawResult; @@ -206,12 +203,7 @@ export function useGlobalStyle( throw 'Unsupported source'; } - return [ result, setStyle, _links ]; -} - -export function useGlobalStyleLinks() { - const { merged: mergedConfig } = useContext( GlobalStylesContext ); - return mergedConfig?._links; + return [ result, setStyle ]; } /** From 7fecf34c7d3027aab571cd8fdd75b2a7a0e62362 Mon Sep 17 00:00:00 2001 From: ramon Date: Sat, 18 May 2024 17:29:41 -0700 Subject: [PATCH 24/25] Bad revert --- packages/block-editor/src/components/global-styles/hooks.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index e0de34cf2280e2..5c1e87001ca84e 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -206,6 +206,11 @@ export function useGlobalStyle( return [ result, setStyle ]; } +export function useGlobalStyleLinks() { + const { merged: mergedConfig } = useContext( GlobalStylesContext ); + return mergedConfig?._links; +} + /** * React hook that overrides a global settings object with block and element specific settings. * From 5d3ba0b0a15e0f5677bba6f23ff5c494df406a54 Mon Sep 17 00:00:00 2001 From: ramon Date: Sun, 19 May 2024 21:08:24 -0700 Subject: [PATCH 25/25] Rename wp:theme-file-uris to wp:theme-file Rename utils file --- lib/class-wp-rest-global-styles-controller-gutenberg.php | 6 +++--- ...utenberg-rest-global-styles-revisions-controller-6-6.php | 2 +- .../src/components/global-styles/background-panel.js | 2 +- .../{set-theme-file-uris.js => theme-file-uri-utils.js} | 5 +++-- .../{set-theme-file-uris.js => theme-file-uri-utils.js} | 2 +- .../components/global-styles/use-global-styles-output.js | 4 ++-- .../src/components/global-styles/background-panel.js | 2 +- ...lass-wp-rest-global-styles-controller-gutenberg-test.php | 4 ++-- 8 files changed, 14 insertions(+), 13 deletions(-) rename packages/block-editor/src/components/global-styles/test/{set-theme-file-uris.js => theme-file-uri-utils.js} (96%) rename packages/block-editor/src/components/global-styles/{set-theme-file-uris.js => theme-file-uri-utils.js} (97%) diff --git a/lib/class-wp-rest-global-styles-controller-gutenberg.php b/lib/class-wp-rest-global-styles-controller-gutenberg.php index 1b527162688666..3c960564a8fe0a 100644 --- a/lib/class-wp-rest-global-styles-controller-gutenberg.php +++ b/lib/class-wp-rest-global-styles-controller-gutenberg.php @@ -416,7 +416,7 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V if ( $theme_json ) { $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file'] = $resolved_theme_uris; } } $response->add_links( $links ); @@ -643,7 +643,7 @@ public function get_theme_item( $request ) { ); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme ); if ( ! empty( $resolved_theme_uris ) ) { - $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file'] = $resolved_theme_uris; } $response->add_links( $links ); @@ -712,7 +712,7 @@ public function get_theme_items( $request ) { if ( ! empty( $resolved_theme_uris ) ) { $data->add_links( array( - 'https://api.w.org/theme-file-uris' => $resolved_theme_uris, + 'https://api.w.org/theme-file' => $resolved_theme_uris, ) ); } diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php index 5888287395ca7f..f725366c33cfb8 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php @@ -88,7 +88,7 @@ public function prepare_item_for_response( $post, $request ) { $links = array(); $resolved_theme_uris = WP_Theme_JSON_Resolver_Gutenberg::get_resolved_theme_uris( $theme_json ); if ( ! empty( $resolved_theme_uris ) ) { - $links['https://api.w.org/theme-file-uris'] = $resolved_theme_uris; + $links['https://api.w.org/theme-file'] = $resolved_theme_uris; } $response->add_links( $links ); diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index 1960ee19bc2401..b8c3b6358ab5f3 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -38,7 +38,7 @@ import { TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; -import { getResolvedThemeFilePath } from './set-theme-file-uris'; +import { getResolvedThemeFilePath } from './theme-file-uri-utils'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { diff --git a/packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js similarity index 96% rename from packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js rename to packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js index db01af89da8304..06c482b67826bd 100644 --- a/packages/block-editor/src/components/global-styles/test/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/test/theme-file-uri-utils.js @@ -1,9 +1,10 @@ /** * Internal dependencies */ -import setThemeFileUris, { +import { + setThemeFileUris, getResolvedThemeFilePath, -} from '../set-theme-file-uris'; +} from '../theme-file-uri-utils'; const themeFileURIs = [ { diff --git a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js similarity index 97% rename from packages/block-editor/src/components/global-styles/set-theme-file-uris.js rename to packages/block-editor/src/components/global-styles/theme-file-uri-utils.js index a6ae86da4fb3e9..1ab05a45f0d54b 100644 --- a/packages/block-editor/src/components/global-styles/set-theme-file-uris.js +++ b/packages/block-editor/src/components/global-styles/theme-file-uri-utils.js @@ -54,7 +54,7 @@ function setMutably( object, path, value ) { * @param {Array} themeFileURIs A collection of absolute theme file URIs and their corresponding file paths. * @return {Object} Returns mutated object. */ -export default function setThemeFileUris( themeJson, themeFileURIs ) { +export function setThemeFileUris( themeJson, themeFileURIs ) { if ( ! themeJson?.styles || ! themeFileURIs ) { return themeJson; } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 56244b24b61ca3..0513c9ce3fe172 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -35,7 +35,7 @@ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; import BlockContext from '../block-context'; import { unlock } from '../../lock-unlock'; -import setThemeFileUris from './set-theme-file-uris'; +import { setThemeFileUris } from './theme-file-uri-utils'; // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. @@ -1220,7 +1220,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); mergedConfig = setThemeFileUris( mergedConfig, - mergedConfig?._links?.[ 'wp:theme-file-uris' ] + mergedConfig?._links?.[ 'wp:theme-file' ] ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js index a16b5452140d3f..44a9fccaa15ed0 100644 --- a/packages/edit-site/src/components/global-styles/background-panel.js +++ b/packages/edit-site/src/components/global-styles/background-panel.js @@ -62,7 +62,7 @@ export default function BackgroundPanel() { headerLabel={ __( 'Image' ) } defaultValues={ BACKGROUND_DEFAULT_VALUES } defaultControls={ defaultControls } - themeFileURIs={ _links?.[ 'wp:theme-file-uris' ] } + themeFileURIs={ _links?.[ 'wp:theme-file' ] } /> ); } diff --git a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php index 09108a54a68fb0..f5b216a084a4c9 100644 --- a/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php +++ b/phpunit/class-wp-rest-global-styles-controller-gutenberg-test.php @@ -123,14 +123,14 @@ public function test_get_theme_items() { $expected = array( array( '_links' => array( - 'curies' => array( + 'curies' => array( array( 'name' => 'wp', 'href' => 'https://api.w.org/{rel}', 'templated' => true, ), ), - 'wp:theme-file-uris' => array( + 'wp:theme-file' => array( array( 'href' => 'http://localhost:8889/wp-content/themes/emptytheme/img/1024x768_emptytheme_test_image.jpg', 'name' => 'file:./img/1024x768_emptytheme_test_image.jpg',