Skip to content

Commit

Permalink
Added a new endpoint to the global styles rest API (/revisions)
Browse files Browse the repository at this point in the history
- Limit is 100 revisions in response
Added experimental revisions selector/action/resolver to the core-data store
Abstracted compare revisions into isGlobalStyleConfigEqual and added tests
Showing selector control in the UI if revision count is > 10
  • Loading branch information
ramonjd committed Dec 22, 2022
1 parent e7c2d5f commit 777738b
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ class Gutenberg_REST_Global_Styles_Controller_6_2 extends WP_REST_Global_Styles_
* @return void
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\/\w-]+)/revisions',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item_revisions' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
parent::register_routes();
}

Expand Down Expand Up @@ -84,6 +103,59 @@ public function get_item_schema() {

return $this->add_additional_fields_schema( $this->schema );
}
/**
* Returns revisions of the given global styles config custom post type.
*
* @since 6.2
*
* @param WP_REST_Request $request The request instance.
*
* @return WP_REST_Response|WP_Error
*/
public function get_item_revisions( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
$revisions = array();
$raw_config = json_decode( $post->post_content, true );
$is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];

if ( $is_global_styles_user_theme_json ) {
$user_theme_revisions = wp_get_post_revisions(
$post->ID,
array(
'author' => $post->post_author,
'posts_per_page' => 10,
)
);

if ( ! empty( $user_theme_revisions ) ) {
// Mostly taken from wp_prepare_revisions_for_js().
foreach ( $user_theme_revisions as $revision ) {
$raw_revision_config = json_decode( $revision->post_content, true );
$config = ( new WP_Theme_JSON_Gutenberg( $raw_revision_config, 'custom' ) )->get_raw_data();
$now_gmt = time();
$modified = strtotime( $revision->post_modified );
$modified_gmt = strtotime( $revision->post_modified_gmt . ' +0000' );
/* translators: %s: Human-readable time difference. */
$time_ago = sprintf( __( '%s ago', 'gutenberg' ), human_time_diff( $modified_gmt, $now_gmt ) );
$date_short = date_i18n( _x( 'j M @ H:i', 'revision date short format', 'gutenberg' ), $modified );
$revisions[] = array(
'styles' => ! empty( $config['styles'] ) ? $config['styles'] : new stdClass(),
'settings' => ! empty( $config['settings'] ) ? $config['settings'] : new stdClass(),
'title' => array(
'raw' => $revision->post_modified,
/* translators: 1: Human-readable time difference, 2: short date combined to show rendered revision date. */
'rendered' => sprintf( __( '%1$s (%2$s)', 'gutenberg' ), $time_ago, $date_short ),
),
'id' => $revision->ID,
);
}
}
}
return rest_ensure_response( $revisions );
}

/**
* Prepare a global styles config output for response.
Expand Down Expand Up @@ -133,38 +205,6 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V
$data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
}

if ( $is_global_styles_user_theme_json && rest_is_field_included( 'revisions', $fields ) ) {
$user_theme_revisions = wp_get_post_revisions(
$post->ID,
array(
'author' => $post->post_author,
'posts_per_page' => 10,
)
);
if ( empty( $user_theme_revisions ) ) {
$data['revisions'] = array();
} else {
$user_revisions = array();
// Mostly taken from wp_prepare_revisions_for_js().
foreach ( $user_theme_revisions as $revision ) {
$raw_revision_config = json_decode( $revision->post_content, true );
$config = ( new WP_Theme_JSON_Gutenberg( $raw_revision_config, 'custom' ) )->get_raw_data();
$now_gmt = time();
$modified = strtotime( $revision->post_modified );
$modified_gmt = strtotime( $revision->post_modified_gmt . ' +0000' );
$user_revisions[] = array(
'styles' => ! empty( $config['styles'] ) ? $config['styles'] : new stdClass(),
'settings' => ! empty( $config['settings'] ) ? $config['settings'] : new stdClass(),
'dateShort' => date_i18n( _x( 'j M @ H:i', 'revision date short format' ), $modified ),
/* translators: %s: Human-readable time difference. */
'timeAgo' => sprintf( __( '%s ago' ), human_time_diff( $modified_gmt, $now_gmt ) ),
'id' => $revision->ID,
);
}
$data['revisions'] = $user_revisions;
}
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
Expand All @@ -174,6 +214,15 @@ 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 );
if ( $is_global_styles_user_theme_json ) {
$revisions = wp_get_latest_revision_id_and_total_count( $post->ID );
$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
$revisions_base = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $post->ID );
$links['version-history'] = array(
'href' => rest_url( $revisions_base ),
'count' => $revisions_count,
);
}
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions();
Expand Down
34 changes: 28 additions & 6 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,21 @@ export function receiveCurrentTheme( currentTheme ) {
}

/**
* Returns an action object used in signalling that the current global styles id has been received.
* Returns an action object used in signalling that the current global styles has been received.
* Ignored from documentation as it's internal to the data store.
*
* @ignore
*
* @param {string} currentGlobalStylesId The current global styles id.
* @param {Object} currentGlobalStyles The current global styles CPT.
*
* @return {Object} Action object.
*/
export function __experimentalReceiveCurrentGlobalStylesId(
currentGlobalStylesId
export function __experimentalReceiveCurrentGlobalStyles(
currentGlobalStyles
) {
return {
type: 'RECEIVE_CURRENT_GLOBAL_STYLES_ID',
id: currentGlobalStylesId,
type: 'RECEIVE_CURRENT_GLOBAL_STYLES',
globalStyles: currentGlobalStyles,
};
}

Expand Down Expand Up @@ -193,6 +193,28 @@ export function __experimentalReceiveThemeGlobalStyleVariations(
};
}

/**
* Returns an action object used in signalling that the theme global styles CPT post revisions have been received.
* Ignored from documentation as it's internal to the data store.
*
* @ignore
*
* @param {number} currentId The post id.
* @param {Array} revisions The global styles revisions.
*
* @return {Object} Action object.
*/
export function __experimentalReceiveThemeGlobalStyleRevisions(
currentId,
revisions
) {
return {
type: 'RECEIVE_THEME_GLOBAL_STYLE_REVISIONS',
currentId,
revisions,
};
}

/**
* Returns an action object used in signalling that the index has been received.
*
Expand Down
29 changes: 25 additions & 4 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ export function currentTheme( state = undefined, action ) {
*
* @return {string|undefined} Updated state.
*/
export function currentGlobalStylesId( state = undefined, action ) {
export function currentGlobalStyles( state = undefined, action ) {
switch ( action.type ) {
case 'RECEIVE_CURRENT_GLOBAL_STYLES_ID':
return action.id;
case 'RECEIVE_CURRENT_GLOBAL_STYLES':
return action.globalStyles;
}

return state;
Expand Down Expand Up @@ -187,6 +187,26 @@ export function themeGlobalStyleVariations( state = {}, action ) {
return state;
}

/**
* Reducer managing the theme global styles revisions.
*
* @param {Record<string, object>} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Record<string, object>} Updated state.
*/
export function themeGlobalStyleRevisions( state = {}, action ) {
switch ( action.type ) {
case 'RECEIVE_THEME_GLOBAL_STYLE_REVISIONS':
return {
...state,
[ action.currentId ]: action.revisions,
};
}

return state;
}

/**
* Higher Order Reducer for a given entity config. It supports:
*
Expand Down Expand Up @@ -646,9 +666,10 @@ export default combineReducers( {
terms,
users,
currentTheme,
currentGlobalStylesId,
currentGlobalStyles,
currentUser,
themeGlobalStyleVariations,
themeGlobalStyleRevisions,
themeBaseGlobalStyles,
taxonomies,
entities,
Expand Down
35 changes: 32 additions & 3 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => {
);
};

export const __experimentalGetCurrentGlobalStylesId =
export const __experimentalGetCurrentGlobalStyles =
() =>
async ( { dispatch, resolveSelect } ) => {
const activeThemes = await resolveSelect.getEntityRecords(
Expand All @@ -468,8 +468,8 @@ export const __experimentalGetCurrentGlobalStylesId =
const globalStylesObject = await apiFetch( {
url: globalStylesURL,
} );
dispatch.__experimentalReceiveCurrentGlobalStylesId(
globalStylesObject.id
dispatch.__experimentalReceiveCurrentGlobalStyles(
globalStylesObject
);
}
};
Expand Down Expand Up @@ -500,6 +500,35 @@ export const __experimentalGetCurrentThemeGlobalStylesVariations =
);
};

export const __experimentalGetCurrentThemeGlobalStylesRevisions =
() =>
async ( { resolveSelect, dispatch } ) => {
const currentGlobalStyles =
await resolveSelect.__experimentalGetCurrentGlobalStyles();
const revisionsURL =
currentGlobalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.href;
if ( revisionsURL ) {
const revisions = await apiFetch( {
url: revisionsURL,
} );
dispatch.__experimentalReceiveThemeGlobalStyleRevisions(
currentGlobalStyles?.id,
revisions
);
}
};

__experimentalGetCurrentThemeGlobalStylesRevisions.shouldInvalidate = (
action
) => {
return (
action.type === 'SAVE_ENTITY_RECORD_FINISH' &&
action.kind === 'root' &&
! action.error &&
action.name === 'globalStyles'
);
};

export const getBlockPatterns =
() =>
async ( { dispatch } ) => {
Expand Down
37 changes: 32 additions & 5 deletions packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ export interface State {
autosaves: Record< string | number, Array< unknown > >;
blockPatterns: Array< unknown >;
blockPatternCategories: Array< unknown >;
currentGlobalStylesId: string;
currentGlobalStyles: GlobalStyles;
currentTheme: string;
currentUser: ET.User< 'edit' >;
embedPreviews: Record< string, { html: string } >;
entities: EntitiesState;
themeBaseGlobalStyles: Record< string, Object >;
themeGlobalStyleVariations: Record< string, string >;
themeGlobalStyleRevisions: Record< number, Object >;
undo: UndoState;
users: UserState;
}
Expand Down Expand Up @@ -66,6 +67,13 @@ interface UserState {
byId: Record< EntityRecordKey, ET.User< 'edit' > >;
}

type GlobalStyles = {
title: { raw: string; rendered: string };
id: string;
settings: Record< string, Object >;
styles: Record< string, Object >;
};

type Optional< T > = T | undefined;

/**
Expand Down Expand Up @@ -983,10 +991,12 @@ export function getCurrentTheme( state: State ): any {
*
* @param state Data state.
*
* @return The current global styles ID.
* @return The current global styles.
*/
export function __experimentalGetCurrentGlobalStylesId( state: State ): string {
return state.currentGlobalStylesId;
export function __experimentalGetCurrentGlobalStyles(
state: State
): GlobalStyles {
return state.currentGlobalStyles;
}

/**
Expand Down Expand Up @@ -1238,7 +1248,7 @@ export function __experimentalGetCurrentThemeBaseGlobalStyles(
}

/**
* Return the ID of the current global styles object.
* Returns the variations of the current global styles theme.
*
* @param state Data state.
*
Expand All @@ -1254,6 +1264,23 @@ export function __experimentalGetCurrentThemeGlobalStylesVariations(
return state.themeGlobalStyleVariations[ currentTheme.stylesheet ];
}

/**
* Returns the revisions of the current global styles theme.
*
* @param state Data state.
*
* @return The current global styles.
*/
export function __experimentalGetCurrentThemeGlobalStylesRevisions(
state: State
): GlobalStyles | null {
const currentGlobalStyles = __experimentalGetCurrentGlobalStyles( state );
if ( ! currentGlobalStyles?.id ) {
return null;
}
return state.themeGlobalStyleRevisions[ currentGlobalStyles.id ];
}

/**
* Retrieve the list of registered block patterns.
*
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/src/components/global-styles/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const DEFAULT_GLOBAL_STYLES_CONTEXT = {
base: {},
merged: {},
setUserConfig: () => {},
userConfigRevisionsCount: 0,
};

export const GlobalStylesContext = createContext(
Expand Down
Loading

1 comment on commit 777738b

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/3754885732
📝 Reported issues:

Please sign in to comment.