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 #61271

Merged
merged 25 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
514e7b9
This initial commit:
ramonjd May 1, 2024
fa9e75f
For testing purposes, resolve in get_merged_data - should it be optio…
ramonjd May 1, 2024
2ba6cfd
Rollback test of imperative method in resolver
ramonjd May 1, 2024
17048c3
Moves resolution of file paths back to theme_json resolver
ramonjd May 7, 2024
88aac46
Backend resolution of theme file URIs for global styles.
ramonjd May 8, 2024
4137b37
Working on revisions
ramonjd May 9, 2024
57cd28c
So my linter is working again
ramonjd May 10, 2024
06c2bb0
Changed the relative link to `wp:theme-file-uris`
ramonjd May 13, 2024
3e283e0
Added some explanatory TODOs
ramonjd May 13, 2024
1752858
Adding valid link attributes to the _link object.
ramonjd May 14, 2024
98de1c6
Added some unit tests for utils
ramonjd May 14, 2024
70c26ba
Switching to using file: prefix, which is an established theme.json c…
ramonjd May 16, 2024
b94a512
Fix linting
andrewserong May 18, 2024
efae7f2
Remove TODO
ramonjd May 18, 2024
eb4ab0f
Update tests
ramonjd May 18, 2024
b0fb937
dump var_dump
ramonjd May 18, 2024
7c083bc
Check for $theme_json before resolving
ramonjd May 18, 2024
17d51bd
be explicit about the background value file:./
ramonjd May 18, 2024
60c8bb1
Remove unnecessary empty check
ramonjd May 18, 2024
dd07557
Abstracting getting any resolved URI to separate hook
ramonjd May 18, 2024
26866fc
Update lib/class-wp-theme-json-resolver-gutenberg.php
noisysocks May 18, 2024
f01dcdf
Update lib/class-wp-theme-json-resolver-gutenberg.php
noisysocks May 18, 2024
25f8754
Revert useGlobalStyle changes - no longer required given the new hook
ramonjd May 19, 2024
7fecf34
Bad revert
ramonjd May 19, 2024
5d3ba0b
Rename wp:theme-file-uris to wp:theme-file
ramonjd May 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 38 additions & 7 deletions lib/class-wp-rest-global-styles-controller-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -366,8 +367,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 = null;
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.
Expand Down Expand Up @@ -409,6 +412,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 ( $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'] = $resolved_theme_uris;
}
}
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions();
Expand Down Expand Up @@ -620,18 +630,22 @@ 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 ) ) {
$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['https://api.w.org/theme-file'] = $resolved_theme_uris;
}

$response->add_links( $links );
}

Expand Down Expand Up @@ -671,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.
*
Expand All @@ -686,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' => $resolved_theme_uris,
)
);
}
$response[] = $this->prepare_response_for_collection( $data );
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
}

return rest_ensure_response( $response );
}

/**
Expand Down
74 changes: 74 additions & 0 deletions lib/class-wp-theme-json-resolver-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -760,4 +760,78 @@ 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.
* @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_Gutenberg ) {
return $resolved_theme_uris;
}

$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 ) &&
// 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_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 ) {
$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_Gutenberg::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_Gutenberg( $resolved_theme_json_data ) );

return $theme_json;
}
}
10 changes: 0 additions & 10 deletions lib/compat/wordpress-6.5/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* REST API: Gutenberg_REST_Global_Styles_Revisions_Controller class, inspired by WP_REST_Revisions_Controller.
*
* @package WordPress
* @subpackage REST_API
* @since 6.3.0
*/

/**
* 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
*/
class Gutenberg_REST_Global_Styles_Revisions_Controller_6_6 extends Gutenberg_REST_Global_Styles_Revisions_Controller_6_5 {
/**
* 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.
* @return WP_REST_Response|WP_Error Response object.
*/
public function prepare_item_for_response( $post, $request ) {
$parent = $this->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 );
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
if ( ! empty( $resolved_theme_uris ) ) {
$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
}
$response->add_links( $links );

return $response;
}
}
11 changes: 11 additions & 0 deletions lib/compat/wordpress-6.6/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
1 change: 1 addition & 0 deletions lib/global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { getResolvedThemeFilePath } from './theme-file-uri-utils';

const IMAGE_BACKGROUND_TYPE = 'image';
const DEFAULT_CONTROLS = {
Expand Down Expand Up @@ -191,6 +192,7 @@ function BackgroundImageToolsPanelItem( {
onChange,
style,
inheritedValue,
themeFileURIs,
} ) {
const mediaUpload = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
Expand Down Expand Up @@ -301,7 +303,10 @@ function BackgroundImageToolsPanelItem( {
<InspectorImagePreview
label={ title }
filename={ title || __( 'Untitled' ) }
url={ url }
url={ getResolvedThemeFilePath(
url,
themeFileURIs
) }
/>
}
variant="secondary"
Expand Down Expand Up @@ -340,6 +345,7 @@ function BackgroundSizeToolsPanelItem( {
style,
inheritedValue,
defaultValues,
themeFileURIs,
} ) {
const sizeValue =
style?.background?.backgroundSize ||
Expand Down Expand Up @@ -468,7 +474,7 @@ function BackgroundSizeToolsPanelItem( {
<FocalPointPicker
__next40pxDefaultSize
label={ __( 'Position' ) }
url={ imageValue }
url={ getResolvedThemeFilePath( imageValue, themeFileURIs ) }
value={ backgroundPositionToCoords( positionValue ) }
onChange={ updateBackgroundPosition }
/>
Expand Down Expand Up @@ -553,6 +559,7 @@ export default function BackgroundPanel( {
defaultControls = DEFAULT_CONTROLS,
defaultValues = {},
headerLabel = __( 'Background image' ),
themeFileURIs,
} ) {
const resetAllFilter = useCallback( ( previousValue ) => {
return {
Expand All @@ -577,6 +584,7 @@ export default function BackgroundPanel( {
isShownByDefault={ defaultControls.backgroundImage }
style={ value }
inheritedValue={ inheritedValue }
themeFileURIs={ themeFileURIs }
/>
{ shouldShowBackgroundSizeControls && (
<BackgroundSizeToolsPanelItem
Expand All @@ -586,6 +594,7 @@ export default function BackgroundPanel( {
style={ value }
inheritedValue={ inheritedValue }
defaultValues={ defaultValues }
themeFileURIs={ themeFileURIs }
/>
) }
</Wrapper>
Expand Down