diff --git a/wp-includes/class-wp-theme-json-resolver.php b/wp-includes/class-wp-theme-json-resolver.php index 59c5f54b96..db3a48c5c8 100644 --- a/wp-includes/class-wp-theme-json-resolver.php +++ b/wp-includes/class-wp-theme-json-resolver.php @@ -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; + } } diff --git a/wp-includes/global-styles-and-settings.php b/wp-includes/global-styles-and-settings.php index fd1890c53a..fbf4fe2c52 100644 --- a/wp-includes/global-styles-and-settings.php +++ b/wp-includes/global-styles-and-settings.php @@ -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 ) ) { diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index e9d5006d53..3b2caf88fe 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -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 ); } /** diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php index 4a37f28d37..54285fa560 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php @@ -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; } /** diff --git a/wp-includes/version.php b/wp-includes/version.php index b20be4fbd2..7e457609ff 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -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.