mirror of
git://develop.git.wordpress.org/
synced 2025-04-07 13:43:25 +02:00
Block Themes: Add support for relative URLs in top-level theme.json styles
Allow using relative `file:` URLs in top-level theme.json properties such as `styles.background`, and modify the REST API to provide clients with the absolute URLs via a 'https://api.w.org/theme-file' attribute in the `_links` array. Props ramonopoly. Fixes #61273. git-svn-id: https://develop.svn.wordpress.org/trunk@58262 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
b6a3b9c7d1
commit
1540cdbf9e
src/wp-includes
tests/phpunit
data/themedir1/block-theme/styles
tests
@ -744,4 +744,82 @@ class WP_Theme_JSON_Resolver {
|
||||
}
|
||||
return $variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves relative paths in theme.json styles to theme absolute paths
|
||||
* and returns them in an array that can be embedded
|
||||
* as the value of `_link` object in REST API responses.
|
||||
*
|
||||
* @since 6.6.0
|
||||
*
|
||||
* @param WP_Theme_JSON $theme_json A theme json instance.
|
||||
* @return array An array of resolved paths.
|
||||
*/
|
||||
public static function get_resolved_theme_uris( $theme_json ) {
|
||||
$resolved_theme_uris = array();
|
||||
|
||||
if ( ! $theme_json instanceof WP_Theme_JSON ) {
|
||||
return $resolved_theme_uris;
|
||||
}
|
||||
|
||||
$theme_json_data = $theme_json->get_raw_data();
|
||||
|
||||
// Top level styles.
|
||||
$background_image_url = isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ? $theme_json_data['styles']['background']['backgroundImage']['url'] : null;
|
||||
|
||||
/*
|
||||
* 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 ) &&
|
||||
// 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' => sanitize_url( get_theme_file_uri( $src_url ) ),
|
||||
'target' => 'styles.background.backgroundImage.url',
|
||||
);
|
||||
if ( isset( $file_type['type'] ) ) {
|
||||
$resolved_theme_uri['type'] = $file_type['type'];
|
||||
}
|
||||
$resolved_theme_uris[] = $resolved_theme_uri;
|
||||
}
|
||||
|
||||
return $resolved_theme_uris;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $theme_json A theme json instance.
|
||||
* @return WP_Theme_JSON 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 );
|
||||
if ( empty( $resolved_urls ) ) {
|
||||
return $theme_json;
|
||||
}
|
||||
|
||||
$resolved_theme_json_data = array(
|
||||
'version' => WP_Theme_JSON::LATEST_SCHEMA,
|
||||
);
|
||||
|
||||
foreach ( $resolved_urls as $resolved_url ) {
|
||||
$path = explode( '.', $resolved_url['target'] );
|
||||
_wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] );
|
||||
}
|
||||
|
||||
$theme_json->merge( new WP_Theme_JSON( $resolved_theme_json_data ) );
|
||||
|
||||
return $theme_json;
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,7 @@ function wp_get_global_styles( $path = array(), $context = array() ) {
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @since 6.1.0 Added 'base-layout-styles' support.
|
||||
* @since 6.6.0 Resolves relative paths in theme.json styles to theme absolute paths.
|
||||
*
|
||||
* @param array $types Optional. Types of styles to load.
|
||||
* It accepts as values 'variables', 'presets', 'styles', 'base-layout-styles'.
|
||||
@ -179,9 +180,9 @@ function wp_get_global_stylesheet( $types = array() ) {
|
||||
}
|
||||
}
|
||||
|
||||
$tree = WP_Theme_JSON_Resolver::get_merged_data();
|
||||
|
||||
$tree = WP_Theme_JSON_Resolver::resolve_theme_file_uris( WP_Theme_JSON_Resolver::get_merged_data() );
|
||||
$supports_theme_json = wp_theme_has_theme_json();
|
||||
|
||||
if ( empty( $types ) && ! $supports_theme_json ) {
|
||||
$types = array( 'variables', 'presets', 'base-layout-styles' );
|
||||
} elseif ( empty( $types ) ) {
|
||||
|
@ -289,6 +289,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
* Prepare a global styles config output for response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @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.
|
||||
@ -298,8 +299,10 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
$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 = null;
|
||||
if ( $is_global_styles_user_theme_json ) {
|
||||
$config = ( new WP_Theme_JSON( $raw_config, 'custom' ) )->get_raw_data();
|
||||
$theme_json = new WP_Theme_JSON( $raw_config, 'custom' );
|
||||
$config = $theme_json->get_raw_data();
|
||||
}
|
||||
|
||||
// Base fields for every post.
|
||||
@ -341,6 +344,15 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
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 ( $theme_json ) {
|
||||
$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
|
||||
if ( ! empty( $resolved_theme_uris ) ) {
|
||||
$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
|
||||
}
|
||||
}
|
||||
|
||||
$response->add_links( $links );
|
||||
if ( ! empty( $links['self']['href'] ) ) {
|
||||
$actions = $this->get_available_actions( $post, $request );
|
||||
@ -515,6 +527,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
* Returns the given theme global styles config.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @since 6.6.0 Added custom relative theme file URIs to `_links`.
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
@ -549,11 +562,15 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
$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::get_resolved_theme_uris( $theme );
|
||||
if ( ! empty( $resolved_theme_uris ) ) {
|
||||
$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
|
||||
}
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
@ -591,6 +608,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @since 6.2.0 Returns parent theme variations, if they exist.
|
||||
* @since 6.6.0 Added custom relative theme file URIs to `_links` for each item.
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
*
|
||||
@ -606,9 +624,24 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
|
||||
);
|
||||
}
|
||||
|
||||
$response = array();
|
||||
$variations = WP_Theme_JSON_Resolver::get_style_variations();
|
||||
|
||||
return rest_ensure_response( $variations );
|
||||
foreach ( $variations as $variation ) {
|
||||
$variation_theme_json = new WP_Theme_JSON( $variation );
|
||||
$resolved_theme_uris = WP_Theme_JSON_Resolver::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' => $resolved_theme_uris,
|
||||
)
|
||||
);
|
||||
}
|
||||
$response[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -268,6 +268,7 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 6.3.0
|
||||
* @since 6.6.0 Added resolved URI links to the response.
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
@ -281,11 +282,13 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
|
||||
return $global_styles_config;
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
$theme_json = null;
|
||||
|
||||
if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) {
|
||||
$global_styles_config = ( new WP_Theme_JSON( $global_styles_config, 'custom' ) )->get_raw_data();
|
||||
$theme_json = new WP_Theme_JSON( $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();
|
||||
}
|
||||
@ -322,11 +325,21 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
|
||||
$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 );
|
||||
$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 );
|
||||
$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
if ( ! empty( $resolved_theme_uris ) ) {
|
||||
$response->add_links(
|
||||
array(
|
||||
'https://api.w.org/theme-file' => $resolved_theme_uris,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,5 +14,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"background": {
|
||||
"backgroundImage": {
|
||||
"url": "file:./assets/sugarloaf-mountain.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,12 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
switch_theme( 'tt1-blocks' );
|
||||
add_filter( 'theme_file_uri', array( $this, 'filter_theme_file_uri' ) );
|
||||
}
|
||||
|
||||
public function tear_down() {
|
||||
remove_filter( 'theme_file_uri', array( $this, 'filter_theme_file_uri' ) );
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,6 +85,17 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
self::delete_user( self::$subscriber_id );
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.,
|
||||
* /var/www/tests/phpunit/includes/../data/themedir1/block-theme/assets/sugarloaf-mountain.jpg
|
||||
*/
|
||||
public function filter_theme_file_uri( $file ) {
|
||||
$file_name = substr( strrchr( $file, '/' ), 1 );
|
||||
return 'https://example.org/wp-content/themes/example-theme/assets/' . $file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers WP_REST_Global_Styles_Controller::register_routes
|
||||
* @ticket 54596
|
||||
@ -119,6 +136,12 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
// Controller does not use get_context_param().
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a GET request to the global styles variations endpoint.
|
||||
*
|
||||
* @covers WP_REST_Global_Styles_Controller::get_theme_items
|
||||
* @ticket 61273
|
||||
*/
|
||||
public function test_get_theme_items() {
|
||||
wp_set_current_user( self::$admin_id );
|
||||
switch_theme( 'block-theme' );
|
||||
@ -128,7 +151,6 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
$expected = array(
|
||||
array(
|
||||
'version' => 2,
|
||||
'title' => 'variation-a',
|
||||
'settings' => array(
|
||||
'blocks' => array(
|
||||
'core/paragraph' => array(
|
||||
@ -146,10 +168,10 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
),
|
||||
),
|
||||
),
|
||||
'title' => 'variation-a',
|
||||
),
|
||||
array(
|
||||
'version' => 2,
|
||||
'title' => 'variation-b',
|
||||
'settings' => array(
|
||||
'blocks' => array(
|
||||
'core/post-title' => array(
|
||||
@ -167,6 +189,31 @@ class WP_REST_Global_Styles_Controller_Test extends WP_Test_REST_Controller_Test
|
||||
),
|
||||
),
|
||||
),
|
||||
'styles' => array(
|
||||
'background' => array(
|
||||
'backgroundImage' => array(
|
||||
'url' => 'file:./assets/sugarloaf-mountain.jpg',
|
||||
),
|
||||
),
|
||||
),
|
||||
'title' => 'variation-b',
|
||||
'_links' => array(
|
||||
'curies' => array(
|
||||
array(
|
||||
'name' => 'wp',
|
||||
'href' => 'https://api.w.org/{rel}',
|
||||
'templated' => true,
|
||||
),
|
||||
),
|
||||
'wp:theme-file' => array(
|
||||
array(
|
||||
'href' => 'https://example.org/wp-content/themes/example-theme/assets/sugarloaf-mountain.jpg',
|
||||
'name' => 'file:./assets/sugarloaf-mountain.jpg',
|
||||
'target' => 'styles.background.backgroundImage.url',
|
||||
'type' => 'image/jpeg',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'version' => 2,
|
||||
|
@ -103,6 +103,7 @@ class Tests_Theme_wpThemeJsonResolver extends WP_UnitTestCase {
|
||||
add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
|
||||
add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
|
||||
add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );
|
||||
add_filter( 'theme_file_uri', array( $this, 'filter_theme_file_uri' ) );
|
||||
$this->queries = array();
|
||||
// Clear caches.
|
||||
wp_clean_themes_cache();
|
||||
@ -113,12 +114,24 @@ class Tests_Theme_wpThemeJsonResolver extends WP_UnitTestCase {
|
||||
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
|
||||
wp_clean_themes_cache();
|
||||
unset( $GLOBALS['wp_themes'] );
|
||||
remove_filter( 'theme_file_uri', array( $this, 'filter_theme_file_uri' ) );
|
||||
|
||||
// Reset data between tests.
|
||||
wp_clean_theme_json_cache();
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.,
|
||||
* /var/www/tests/phpunit/includes/../data/themedir1/block-theme/assets/sugarloaf-mountain.jpg
|
||||
*/
|
||||
public function filter_theme_file_uri( $file ) {
|
||||
$file_name = substr( strrchr( $file, '/' ), 1 );
|
||||
return 'https://example.org/wp-content/themes/example-theme/assets/' . $file_name;
|
||||
}
|
||||
|
||||
public function filter_set_theme_root() {
|
||||
return $this->theme_root;
|
||||
}
|
||||
@ -1176,4 +1189,74 @@ class Tests_Theme_wpThemeJsonResolver extends WP_UnitTestCase {
|
||||
$default_presets_for_block = $theme_json->get_settings()['shadow']['defaultPresets'];
|
||||
$this->assertTrue( $default_presets_for_block );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that relative paths are resolved and merged into the theme.json data.
|
||||
*
|
||||
* @covers WP_Theme_JSON_Resolver::resolve_theme_file_uris
|
||||
* @ticket 61273
|
||||
*/
|
||||
public function test_resolve_theme_file_uris() {
|
||||
$theme_json = new WP_Theme_JSON(
|
||||
array(
|
||||
'version' => WP_Theme_JSON::LATEST_SCHEMA,
|
||||
'styles' => array(
|
||||
'background' => array(
|
||||
'backgroundImage' => array(
|
||||
'url' => 'file:./assets/image.png',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$expected_data = array(
|
||||
'version' => WP_Theme_JSON::LATEST_SCHEMA,
|
||||
'styles' => array(
|
||||
'background' => array(
|
||||
'backgroundImage' => array(
|
||||
'url' => 'https://example.org/wp-content/themes/example-theme/assets/image.png',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$actual = WP_Theme_JSON_Resolver::resolve_theme_file_uris( $theme_json );
|
||||
|
||||
$this->assertSame( $expected_data, $actual->get_raw_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that them uris are resolved and bundled with other metadata in an array.
|
||||
*
|
||||
* @covers WP_Theme_JSON_Resolver::get_resolved_theme_uris
|
||||
* @ticket 61273
|
||||
*/
|
||||
public function test_get_resolved_theme_uris() {
|
||||
$theme_json = new WP_Theme_JSON(
|
||||
array(
|
||||
'version' => WP_Theme_JSON::LATEST_SCHEMA,
|
||||
'styles' => array(
|
||||
'background' => array(
|
||||
'backgroundImage' => array(
|
||||
'url' => 'file:./assets/image.png',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$expected_data = array(
|
||||
array(
|
||||
'name' => 'file:./assets/image.png',
|
||||
'href' => 'https://example.org/wp-content/themes/example-theme/assets/image.png',
|
||||
'target' => 'styles.background.backgroundImage.url',
|
||||
'type' => 'image/png',
|
||||
),
|
||||
);
|
||||
|
||||
$actual = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
|
||||
|
||||
$this->assertSame( $expected_data, $actual );
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user