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.

Built from https://develop.svn.wordpress.org/trunk@58262


git-svn-id: http://core.svn.wordpress.org/trunk@57725 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
noisysocks 2024-05-31 01:19:14 +00:00
parent de4d4199cb
commit 1f57be1a1e
5 changed files with 138 additions and 13 deletions

View File

@ -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;
}
}

View File

@ -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 ) ) {

View File

@ -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
@ -554,6 +567,10 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
'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 );
}
/**

View File

@ -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.
@ -283,9 +284,11 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
$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();
}
@ -325,8 +328,18 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr
$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;
}
/**

View File

@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '6.6-alpha-58261';
$wp_version = '6.6-alpha-58262';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.