Skip to content

Commit

Permalink
REST API: Fix issue with Template and Template Part Revision/Autosave…
Browse files Browse the repository at this point in the history
… REST API controllers.

The Template and Template Part REST API controllers have unique characteristics compared to other post type REST API controllers. They do not rely on integer IDs to reference objects; instead, they use a combination of the theme name and slug of the template, like 'twentytwentyfour//home.' Consequently, when the post types template and template part were introduced in [52062], it led to the registration of REST API endpoints for autosaves and revisions with invalid URL structures.

In this commit, we introduce new functionality to enable custom autosave and revisions endpoints to be registered at the post type level. Similar to the 'rest_controller_class' parameter, developers can now define 'revisions_rest_controller' and 'autosave_rest_controller.' This empowers developers to create custom controllers for these functionalities. Additionally, we introduce a 'late_route_registration' parameter, which proves helpful when dealing with custom URL patterns and regex pattern matching issues.
This commit registers new classes for template and template part autosave and revisions controllers, differentiating them from standard controllers in the following ways:
* The response shape now matches that of the template controller.
* Permission checks align with the template controller.
* A custom URL pattern is introduced to support slug-based identification of templates.

Furthermore, we've updated the utility function '_build_block_template_result_from_post' to support passing revision post objects. This enhancement ensures compatibility with the custom revisions controller.

Props spacedmonkey, revgeorge, andraganescu, hellofromTonya, antonvlasenko, kadamwhite, ironprogrammer, costdev, mukesh27, timothyblynjacobs, adamsilverstein. 
Fixes 56922.

git-svn-id: https://develop.svn.wordpress.org/trunk@56819 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
spacedmonkey committed Oct 10, 2023
1 parent a9bb470 commit 1f51e1f
Show file tree
Hide file tree
Showing 14 changed files with 3,758 additions and 1,869 deletions.
26 changes: 17 additions & 9 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -724,14 +724,22 @@ function _wp_build_title_and_description_for_taxonomy_block_template( $taxonomy,
*
* @since 5.9.0
* @since 6.3.0 Added `modified` property to template objects.
* @since 6.4.0 Added support for a revision post to be passed to this function.
* @access private
*
* @param WP_Post $post Template post.
* @return WP_Block_Template|WP_Error Template or error object.
*/
function _build_block_template_result_from_post( $post ) {
$default_template_types = get_default_block_template_types();
$terms = get_the_terms( $post, 'wp_theme' );

$post_id = wp_is_post_revision( $post );
if ( ! $post_id ) {
$post_id = $post;
}
$parent_post = get_post( $post_id );

$terms = get_the_terms( $parent_post, 'wp_theme' );

if ( is_wp_error( $terms ) ) {
return $terms;
Expand All @@ -745,12 +753,12 @@ function _build_block_template_result_from_post( $post ) {
$template_file = _get_block_template_file( $post->post_type, $post->post_name );
$has_theme_file = get_stylesheet() === $theme && null !== $template_file;

$origin = get_post_meta( $post->ID, 'origin', true );
$is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true );
$origin = get_post_meta( $parent_post->ID, 'origin', true );
$is_wp_suggestion = get_post_meta( $parent_post->ID, 'is_wp_suggestion', true );

$template = new WP_Block_Template();
$template->wp_id = $post->ID;
$template->id = $theme . '//' . $post->post_name;
$template->id = $theme . '//' . $parent_post->post_name;
$template->theme = $theme;
$template->content = $post->post_content;
$template->slug = $post->post_name;
Expand All @@ -765,23 +773,23 @@ function _build_block_template_result_from_post( $post ) {
$template->author = $post->post_author;
$template->modified = $post->post_modified;

if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
if ( 'wp_template' === $parent_post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
$template->post_types = $template_file['postTypes'];
}

if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
if ( 'wp_template' === $parent_post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
$template->is_custom = false;
}

if ( 'wp_template_part' === $post->post_type ) {
$type_terms = get_the_terms( $post, 'wp_template_part_area' );
if ( 'wp_template_part' === $parent_post->post_type ) {
$type_terms = get_the_terms( $parent_post, 'wp_template_part_area' );
if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
$template->area = $type_terms[0]->name;
}
}

// Check for a block template without a description and title or with a title equal to the slug.
if ( 'wp_template' === $post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) {
if ( 'wp_template' === $parent_post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) {
$matches = array();

// Check for a block template for a single author, page, post, tag, category, custom post type, or custom taxonomy.
Expand Down
193 changes: 162 additions & 31 deletions src/wp-includes/class-wp-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,54 @@ final class WP_Post_Type {
*/
public $rest_controller;

/**
* The controller for this post type's revisions REST API endpoints.
*
* Custom controllers must extend WP_REST_Controller.
*
* @since 6.4.0
* @var string|bool $revisions_rest_controller_class
*/
public $revisions_rest_controller_class;

/**
* The controller instance for this post type's revisions REST API endpoints.
*
* Lazily computed. Should be accessed using {@see WP_Post_Type::get_revisions_rest_controller()}.
*
* @since 6.4.0
* @var WP_REST_Controller $revisions_rest_controller
*/
public $revisions_rest_controller;

/**
* The controller for this post type's autosave REST API endpoints.
*
* Custom controllers must extend WP_REST_Controller.
*
* @since 6.4.0
* @var string|bool $autosave_rest_controller_class
*/
public $autosave_rest_controller_class;

/**
* The controller instance for this post type's autosave REST API endpoints.
*
* Lazily computed. Should be accessed using {@see WP_Post_Type::get_autosave_rest_controller()}.
*
* @since 6.4.0
* @var WP_REST_Controller $autosave_rest_controller
*/
public $autosave_rest_controller;

/**
* A flag to register the post type REST API controller after its associated autosave / revisions controllers, instead of before. Registration order affects route matching priority.
*
* @since 6.4.0
* @var bool $late_route_registration
*/
public $late_route_registration;

/**
* Constructor.
*
Expand Down Expand Up @@ -455,6 +503,7 @@ public function set_props( $args ) {
* - `register_page_post_type_args`
*
* @since 6.0.0
* @since 6.4.0 Added `late_route_registration`, `autosave_rest_controller_class` and `revisions_rest_controller_class` arguments.
*
* @param array $args Array of arguments for registering a post type.
* See the register_post_type() function for accepted arguments.
Expand All @@ -466,37 +515,40 @@ public function set_props( $args ) {

// Args prefixed with an underscore are reserved for internal use.
$defaults = array(
'labels' => array(),
'description' => '',
'public' => false,
'hierarchical' => false,
'exclude_from_search' => null,
'publicly_queryable' => null,
'show_ui' => null,
'show_in_menu' => null,
'show_in_nav_menus' => null,
'show_in_admin_bar' => null,
'menu_position' => null,
'menu_icon' => null,
'capability_type' => 'post',
'capabilities' => array(),
'map_meta_cap' => null,
'supports' => array(),
'register_meta_box_cb' => null,
'taxonomies' => array(),
'has_archive' => false,
'rewrite' => true,
'query_var' => true,
'can_export' => true,
'delete_with_user' => null,
'show_in_rest' => false,
'rest_base' => false,
'rest_namespace' => false,
'rest_controller_class' => false,
'template' => array(),
'template_lock' => false,
'_builtin' => false,
'_edit_link' => 'post.php?post=%d',
'labels' => array(),
'description' => '',
'public' => false,
'hierarchical' => false,
'exclude_from_search' => null,
'publicly_queryable' => null,
'show_ui' => null,
'show_in_menu' => null,
'show_in_nav_menus' => null,
'show_in_admin_bar' => null,
'menu_position' => null,
'menu_icon' => null,
'capability_type' => 'post',
'capabilities' => array(),
'map_meta_cap' => null,
'supports' => array(),
'register_meta_box_cb' => null,
'taxonomies' => array(),
'has_archive' => false,
'rewrite' => true,
'query_var' => true,
'can_export' => true,
'delete_with_user' => null,
'show_in_rest' => false,
'rest_base' => false,
'rest_namespace' => false,
'rest_controller_class' => false,
'autosave_rest_controller_class' => false,
'revisions_rest_controller_class' => false,
'late_route_registration' => false,
'template' => array(),
'template_lock' => false,
'_builtin' => false,
'_edit_link' => 'post.php?post=%d',
);

$args = array_merge( $defaults, $args );
Expand Down Expand Up @@ -816,6 +868,85 @@ public function get_rest_controller() {
return $this->rest_controller;
}

/**
* Gets the REST API revisions controller for this post type.
*
* Will only instantiate the controller class once per request.
*
* @since 6.4.0
*
* @return WP_REST_Controller|null The controller instance, or null if the post type
* is set not to show in rest.
*/
public function get_revisions_rest_controller() {
if ( ! $this->show_in_rest ) {
return null;
}

if ( ! post_type_supports( $this->name, 'revisions' ) ) {
return null;
}

$class = $this->revisions_rest_controller_class ? $this->revisions_rest_controller_class : WP_REST_Revisions_Controller::class;
if ( ! class_exists( $class ) ) {
return null;
}

if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
return null;
}

if ( ! $this->revisions_rest_controller ) {
$this->revisions_rest_controller = new $class( $this->name );
}

if ( ! ( $this->revisions_rest_controller instanceof $class ) ) {
return null;
}

return $this->revisions_rest_controller;
}

/**
* Gets the REST API autosave controller for this post type.
*
* Will only instantiate the controller class once per request.
*
* @since 6.4.0
*
* @return WP_REST_Controller|null The controller instance, or null if the post type
* is set not to show in rest.
*/
public function get_autosave_rest_controller() {
if ( ! $this->show_in_rest ) {
return null;
}

if ( 'attachment' === $this->name ) {
return null;
}

$class = $this->autosave_rest_controller_class ? $this->autosave_rest_controller_class : WP_REST_Autosaves_Controller::class;

if ( ! class_exists( $class ) ) {
return null;
}

if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
return null;
}

if ( ! $this->autosave_rest_controller ) {
$this->autosave_rest_controller = new $class( $this->name );
}

if ( ! ( $this->autosave_rest_controller instanceof $class ) ) {
return null;
}

return $this->autosave_rest_controller;
}

/**
* Returns the default labels for post types.
*
Expand Down
Loading

0 comments on commit 1f51e1f

Please sign in to comment.