Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/7.0/11230.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/11230

* https://github.com/WordPress/gutenberg/pull/76347
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
/**
* REST API: Gutenberg_REST_Revisions_Controller class
*
* @package gutenberg
*/

/**
* Controller which provides REST endpoint for revisions.
*
* This overrides the core WP_REST_Revisions_Controller to use
* rest_is_field_included() instead of in_array() for content, title, excerpt,
* and guid fields. This allows clients to request individual sub-fields
* (e.g. content.raw without content.rendered) via the _fields parameter,
* avoiding expensive rendering when only raw data is needed.
*
* @see WP_REST_Revisions_Controller
*/
class Gutenberg_REST_Revisions_Controller extends WP_REST_Revisions_Controller {

/**
* Prepares the revision for the REST response.
*
* @param WP_Post $item Post revision object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $item, $request ) {
// Restores the more descriptive, specific name for use within this method.
$post = $item;

$GLOBALS['post'] = $post;

setup_postdata( $post );

// Don't prepare the response body for HEAD requests.
if ( $request->is_method( 'HEAD' ) ) {
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php */
return apply_filters( 'rest_prepare_revision', new WP_REST_Response( array() ), $post, $request );
}

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

if ( in_array( 'author', $fields, true ) ) {
$data['author'] = (int) $post->post_author;
}

if ( in_array( 'date', $fields, true ) ) {
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
}

if ( in_array( 'date_gmt', $fields, true ) ) {
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
}

if ( in_array( 'id', $fields, true ) ) {
$data['id'] = $post->ID;
}

if ( in_array( 'modified', $fields, true ) ) {
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
}

if ( in_array( 'modified_gmt', $fields, true ) ) {
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
}

if ( in_array( 'parent', $fields, true ) ) {
$data['parent'] = (int) $post->post_parent;
}

if ( in_array( 'slug', $fields, true ) ) {
$data['slug'] = $post->post_name;
}

if ( rest_is_field_included( 'guid', $fields ) ) {
$data['guid'] = array();
}
if ( rest_is_field_included( 'guid.rendered', $fields ) ) {
/** This filter is documented in wp-includes/post-template.php */
$data['guid']['rendered'] = apply_filters( 'get_the_guid', $post->guid, $post->ID );
}
if ( rest_is_field_included( 'guid.raw', $fields ) ) {
$data['guid']['raw'] = $post->guid;
}

if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
if ( rest_is_field_included( 'title.raw', $fields ) ) {
$data['title']['raw'] = $post->post_title;
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
$data['title']['rendered'] = get_the_title( $post->ID );
}

if ( rest_is_field_included( 'content', $fields ) ) {
$data['content'] = array();
}
if ( rest_is_field_included( 'content.raw', $fields ) ) {
$data['content']['raw'] = $post->post_content;
}
if ( rest_is_field_included( 'content.rendered', $fields ) ) {
/** This filter is documented in wp-includes/post-template.php */
$data['content']['rendered'] = apply_filters( 'the_content', $post->post_content );
}

if ( rest_is_field_included( 'excerpt', $fields ) ) {
$data['excerpt'] = array();
}
if ( rest_is_field_included( 'excerpt.raw', $fields ) ) {
$data['excerpt']['raw'] = $post->post_excerpt;
}
if ( rest_is_field_included( 'excerpt.rendered', $fields ) ) {
$data['excerpt']['rendered'] = $this->prepare_excerpt_response( $post->post_excerpt, $post );
}

if ( rest_is_field_included( 'meta', $fields ) ) {
$data['meta'] = $this->meta->get_value( $post->ID, $request );
}

$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 ( ! empty( $data['parent'] ) ) {
$response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) );
}

/**
* Filters a revision returned from the REST API.
*
* Allows modification of the revision right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post The original revision object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_revision', $response, $post, $request );
}
}
21 changes: 21 additions & 0 deletions lib/compat/wordpress-7.0/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,24 @@ function gutenberg_override_autosaves_rest_controller( $args ) {
}

add_filter( 'register_post_type_args', 'gutenberg_override_autosaves_rest_controller', 10, 1 );

/**
* Overrides the default REST controller for revisions to support nested
* _fields parameters (e.g. content.raw without content.rendered).
*
* The core WP_REST_Revisions_Controller uses in_array() checks for content,
* title, excerpt, and guid fields, which prevents sub-field filtering via
* the _fields parameter. The Gutenberg override uses rest_is_field_included()
* so that clients can avoid expensive server-side rendering when only raw
* data is needed.
*
* Only overrides when revisions_rest_controller_class is not explicitly set.
*/
function gutenberg_override_revisions_rest_controller( $args ) {
if ( empty( $args['revisions_rest_controller_class'] ) ) {
$args['revisions_rest_controller_class'] = 'Gutenberg_REST_Revisions_Controller';
}
return $args;
}

add_filter( 'register_post_type_args', 'gutenberg_override_revisions_rest_controller', 10, 1 );
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function gutenberg_is_experiment_enabled( $name ) {

// WordPress 7.0 compat.
require __DIR__ . '/compat/wordpress-7.0/class-gutenberg-rest-autosaves-controller.php';
require __DIR__ . '/compat/wordpress-7.0/class-gutenberg-rest-revisions-controller.php';
require __DIR__ . '/compat/wordpress-7.0/class-gutenberg-rest-block-patterns-controller-7-0.php';
require __DIR__ . '/compat/wordpress-7.0/class-gutenberg-rest-templates-controller-7-0.php';
require __DIR__ . '/compat/wordpress-7.0/class-gutenberg-rest-static-templates-controller.php';
Expand Down
2 changes: 2 additions & 0 deletions packages/core-data/src/hooks/use-entity-prop.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export default function useEntityProp( kind, name, prop, _id ) {
{
per_page: -1,
context: 'edit',
_fields:
'id,date,author,meta,title.raw,excerpt.raw,content.raw',
}
);
const entityConfig = select( STORE_NAME ).getEntityConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ function RevisionsSlider() {
}

const entityConfig = getEntityConfig( 'postType', postType );
const query = { per_page: -1, context: 'edit' };
const query = {
per_page: -1,
context: 'edit',
_fields:
'id,date,author,meta,title.raw,excerpt.raw,content.raw',
};
return {
revisions: getRevisions( 'postType', postType, postId, query ),
isLoading: isResolving( 'getRevisions', [
Expand Down
10 changes: 8 additions & 2 deletions packages/editor/src/store/private-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -614,10 +614,16 @@ export const restoreRevision =
const postType = select.getCurrentPostType();
const postId = select.getCurrentPostId();

const revision = registry
.select( coreStore )
// Use resolveSelect to ensure the revision is fetched if not yet
// in the store. The _fields parameter matches the query used by
// getRevisions so the result is served from cache without an
// extra API call.
const revision = await registry
.resolveSelect( coreStore )
.getRevision( 'postType', postType, postId, revisionId, {
context: 'edit',
_fields:
'id,date,author,meta,title.raw,excerpt.raw,content.raw',
} );

if ( ! revision ) {
Expand Down
14 changes: 12 additions & 2 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,12 @@ export const getCurrentRevision = createRegistrySelector(
'postType',
postType,
postId,
{ per_page: -1, context: 'edit' }
{
per_page: -1,
context: 'edit',
_fields:
'id,date,author,meta,title.raw,excerpt.raw,content.raw',
}
);
if ( ! revisions ) {
return null;
Expand Down Expand Up @@ -396,7 +401,12 @@ export const getPreviousRevision = createRegistrySelector(
'postType',
postType,
postId,
{ per_page: -1, context: 'edit' }
{
per_page: -1,
context: 'edit',
_fields:
'id,date,author,meta,title.raw,excerpt.raw,content.raw',
}
);
if ( ! revisions ) {
return null;
Expand Down
Loading