-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Global styles revisions: add route for single styles revisions #55827
Conversation
- Adding route for single global styles revisions
This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress. If so, it is recommended to create a new Trac ticket and submit a pull request to the WordPress Core Github repository soon after this pull request is merged. If you're unsure, you can always ask for help in the #core-editor channel in WordPress Slack. Thank you! ❤️ View changed files❔ lib/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php ❔ lib/compat/wordpress-6.5/rest-api.php ❔ lib/compat/wordpress-6.4/rest-api.php ❔ lib/load.php ❔ phpunit/class-gutenberg-rest-global-styles-revisions-controller-test.php |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works nicely for me via making calls within the site editor via wp.apiFetch( { path: '/wp/v2/global-styles/2054/revisions/4125' } );
(where 2054
is my global styles id
and 4125
is a valid revision).
✅ Requesting a valid global styles revision works as expected
✅ Attempting to request an arbitrary post id correctly fails
✅ Fails when requesting a post's revision id instead of a global styles revision
Just left a couple of comments: I wasn't able to attack it manually, but it looks like we're not (yet) checking that the revision id matches the parent? Rather it's currently checking just that the parent can be accessed?
return $parent; | ||
} | ||
|
||
$revision = $this->get_revision( $request['id'] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the get_items
version of retrieving revisions, they're guarded behind a check against the $parent
of if ( wp_revisions_enabled( $paren ) ) {
... should we add a similar check here, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've modelled this class as closely as possible to WP_REST_Revisions_Controller.
The reason I wouldn't want to make these changes here is because they should be probably be raised in trac and discussed in the wider context of revisions since they all work this way.
Even so I think it should be okay for global styles.
This is what the docs say about the supports
flag for register_post_type, specifically:
'revisions' feature dictates whether the post type will store revisions
So if a post type cannot store revisions, there'll be none to fetch.
It's possible wp_revisions_enabled
is used in get_items
to save an expensive new WP_Query()
call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've modelled this class as closely as possible to WP_REST_Revisions_Controller.
@ramonjd I'm curious why you couldn't just use the core revisions controller, what feature was missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamsilverstein Thanks for the question. It is a good one.
Global styles post revisions store stringified JSON in the post content field. When preparing the response, the JSON needs to be parsed and structured according to the theme.json model, that is, something like this:
{
"settings": {},
"styles": {},
... // whatever else comes along
}
That's the main difference - the responses are different.
Also deleting revisions wasn't a requirement.
I did consider extending WP_REST_Revisions_Controller
way back when I introduced the global styles revisions endpoint.
Global styles revisions were a bit new and had the following characteristics:
- Didn't, and still doesn't, return excerpt, content, guid fields (has 2 custom fields: settings, styles and later ??)
- Did not (yet) require delete routes
- Had to do some JSON parsing when preparing the response
Back then, it was more code to override the parent class's functions, and I thought having it as a stand alone class left more elbow room for subsequent tweaks and changes. Especially since global styles revisions were new and evolving.
Also, since I tried to sync what I needed from WP_REST_Revisions_Controller
, I thought that it wouldn't affect backwards compatibility if we one day decided to refactor this code and extend WP_REST_Revisions_Controller
.
Today I think the functionality is more balanced. Overriding fields and response-related methods would still be needed, but are a lot of shared methods between the two classes and, arguably, a delete route would bring it in line with current revision expectations.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @ramonjd thanks for clarifying your thinking here. I agree it makes sense to build out a separate class for your exact needs, especially while building something new where you want to change things and avoid breaking the existing controller.
That's the main difference - the responses are different.
Given that you mainly want to alter the response behavior, I wonder if you could use the existing revisions controller and leverage the rest_prepare_revision
filter provided to adjust the shape of the return as you desire - this should let you control the exact response you want for each revision.
My main concern with adding another controller is the added maintenance burden. For example the issue you discovered with mismatched parent ids would need to get fixed in two places.
If you feel your controller is doing everything it needs to, I would encourage at least experimenting with using the filter approach and relying on the existing controller. This should reduce the amount of code you need.
cc: @TimothyBJacobs for a second opinion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I appreciate the insight. 🙇🏻
If you feel your controller is doing everything it needs to, I would encourage at least experimenting with using the filter approach and relying on the existing controller.
Good call. I'd like to try this. The maintenance burden is on my mind 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 let me know if you find any shortcomings in the core endpoint or the filter/hooks it provides - if so maybe we can address those in core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a Core PR in progress here:
I ended up replacing the base class with WP_REST_Revisions_Controller
so that the WP_REST_Global_Styles_Revisions_Controller
was still available (I wasn't sure about how radical I should be about maintaining backwards compatibility), but more importantly, so I could preserve the following features by overwriting class methods:
- no deletion of revisions (I think this can come later - probably needs a discussion with Gutenberg Core members)
- massage the schema
Thanks for the nudge! It removes a lot of code.
$revision = get_post( (int) $id ); | ||
if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { | ||
return $error; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check that the $revision->post_parent
matches the provided parent id? Currently it looks like the endpoint might only be checking that the parent is valid and can be retrieved, not that the revision id matches that parent 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also taken from WP_REST_Revisions_Controller
.
The parent post type check ('wp_global_styles'
) should be enough for our purposes to start with.
You bring up a great point though.
Currently I can, for any post type, return the revision of different post just by knowing the post ID.
So for the following:
await wp.apiFetch( { path: '/wp/v2/posts/19/revisions/40' } )
19
is the id of a different parent. So long as 19
is valid and 40
is a revision post type, I'll get the revision object of 40
, whose parent is 21
.
As with the above comment, it could be a good question to raise in Core Slack or in a trac ticket.
There might be a reason why all post type revisions work this way. 🤔
I'll make a note to ask. Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question asked here: https://wordpress.slack.com/archives/C02RQC26G/p1699504213232439
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanations @ramonjd! I agree, after seeing how you've copied the approach used in https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php exactly, it sounds like the questions I raised are best resolved separately if need be, for now it makes sense for this endpoint to match the existing revisions behaviour 👍
The checks in the current form were all adequate for the global styles use case and correctly guarded against folks retrieving things they don't have access to read.
LGTM! ✨
* Basic setup: - Adding route for single global styles revisions * Expected 1 blank line at end of file; 2 found * Add test for get_item * Add ticket number and 404 test
Now that #55827 has landed we can start supporting individual global styles URLs as well.
…tures (#56416) * Remove default value of query {} on getRevision. It messes up selector memoization since the function signatures are different. * Resolve individual revisions as per `getEntityRecords`. This ensures that any subsequent calls to `getRevision` for revisions that we've already fetched via `getRevisions()` are marked as resolved and therefore do not trigger the resolver (and therefore an API call). * Copy update. Now that #55827 has landed we can start supporting individual global styles URLs as well. * While I'm here... :)
Tracking:
Core sync PR:
What?
Adding route for single global styles revisions:
/wp/v2/global-styles/${ parentId }/revisions/${ revisionsId }
Why?
To make global styles revisions compatible with the proposed Core Data revisions API, that is, so that we can fetch single revisions via
getRevision()
.Testing Instructions
Run the tests!