Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background image: add support for relative theme path URLs in top-level theme.json styles #6608

Closed
77 changes: 77 additions & 0 deletions src/wp-includes/class-wp-theme-json-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -744,4 +744,81 @@ public static function get_style_variations() {
}
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_Gutenberg $theme_json A theme json instance.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
* @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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove space.

*/
$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 ) ) {
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
$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_Gutenberg $theme_json A theme json instance.
* @return WP_Theme_JSON_Gutenberg Theme merged with resolved paths, if any found.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
*/
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;
}
}
5 changes: 3 additions & 2 deletions src/wp-includes/global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down Expand Up @@ -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 ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ protected function prepare_item_for_database( $request ) {
* Prepare a global styles config output for response.
*
* @since 5.9.0
* @since 6.2.0 Handling of style.css was added to WP_Theme_JSON.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
* @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.
Expand All @@ -298,8 +300,10 @@ public function prepare_item_for_response( $post, $request ) {
$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.
Expand Down Expand Up @@ -341,6 +345,15 @@ public function prepare_item_for_response( $post, $request ) {

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 );
Expand Down Expand Up @@ -515,6 +528,7 @@ public function get_theme_item_permissions_check( $request ) {
* 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
Expand Down Expand Up @@ -549,11 +563,15 @@ public function get_theme_item( $request ) {
$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 );
}

Expand Down Expand Up @@ -591,6 +609,7 @@ public function get_theme_items_permissions_check( $request ) {
*
* @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.
*
Expand All @@ -606,9 +625,24 @@ public function get_theme_items( $request ) {
);
}

$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 );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public function get_items( $request ) {
* 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.
Expand All @@ -281,11 +282,13 @@ public function prepare_item_for_response( $post, $request ) {
return $global_styles_config;
}

$fields = $this->get_fields_for_response( $request );
$data = array();
$fields = $this->get_fields_for_response( $request );
$data = array();
$theme_json = array();

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();
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
if ( rest_is_field_included( 'settings', $fields ) ) {
$data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass();
}
Expand Down Expand Up @@ -322,11 +325,19 @@ public function prepare_item_for_response( $post, $request ) {
$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 );
$links = array();
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
ramonjd marked this conversation as resolved.
Show resolved Hide resolved

ramonjd marked this conversation as resolved.
Show resolved Hide resolved
return rest_ensure_response( $data );
if ( ! empty( $resolved_theme_uris ) ) {
$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
$response->add_links( $links );
}

return $response;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@
}
}
}
},
"styles": {
"background": {
"backgroundImage": {
"url": "file:./assets/sugarloaf-mountain.jpg"
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
50 changes: 48 additions & 2 deletions tests/phpunit/tests/rest-api/rest-global-styles-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -79,6 +85,17 @@ public static function wpTearDownAfterClass() {
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
Expand Down Expand Up @@ -119,6 +136,11 @@ public function test_context_param() {
// Controller does not use get_context_param().
}

/**
* @covers WP_REST_Global_Styles_Controller::get_theme_items
* @ticket 61273
*
*/
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
public function test_get_theme_items() {
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
wp_set_current_user( self::$admin_id );
switch_theme( 'block-theme' );
Expand All @@ -128,7 +150,6 @@ public function test_get_theme_items() {
$expected = array(
array(
'version' => 2,
'title' => 'variation-a',
'settings' => array(
'blocks' => array(
'core/paragraph' => array(
Expand All @@ -146,10 +167,10 @@ public function test_get_theme_items() {
),
),
),
'title' => 'variation-a',
),
array(
'version' => 2,
'title' => 'variation-b',
'settings' => array(
'blocks' => array(
'core/post-title' => array(
Expand All @@ -167,6 +188,31 @@ public function test_get_theme_items() {
),
),
),
'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,
Expand Down
Loading
Loading