From e9c3308b987e34cd8e33813fac8e89d34e0c4234 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:20:53 +0100 Subject: [PATCH 01/11] Update site-editor.php --- src/wp-admin/site-editor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php index b0bb4e2bb1c10..b5dd7fadb67ec 100644 --- a/src/wp-admin/site-editor.php +++ b/src/wp-admin/site-editor.php @@ -182,6 +182,7 @@ static function ( $classes ) { array( rest_get_route_for_post_type_items( 'attachment' ), 'OPTIONS' ), array( rest_get_route_for_post_type_items( 'page' ), 'OPTIONS' ), '/wp/v2/types?context=view', + '/wp/v2/_wp_static_template?context=edit', '/wp/v2/types/wp_template?context=edit', '/wp/v2/types/wp_template_part?context=edit', '/wp/v2/templates?context=edit&per_page=-1', From 064b000ffbaba0ac7c41004f8f718e2db55196b8 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 10:36:02 +0200 Subject: [PATCH 02/11] initial changes --- src/wp-includes/block-template-utils.php | 103 ++++++++++++++++++++++- src/wp-includes/block-template.php | 72 +++++++++++++++- src/wp-includes/default-filters.php | 5 ++ src/wp-includes/option.php | 19 +++++ src/wp-includes/post.php | 19 ++++- src/wp-includes/rest-api.php | 28 ++++++ src/wp-includes/theme-templates.php | 5 ++ 7 files changed, 243 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index be15b8c398b13..051239d5c44f0 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1074,6 +1074,46 @@ function _build_block_template_result_from_post( $post ) { return $template; } +function get_registered_block_templates( $query ) { + $template_files = _get_block_templates_files( 'wp_template', $query ); + $query_result = array(); + + // _get_block_templates_files seems broken, it does not obey the query. + if ( isset( $query['slug__in'] ) && is_array( $query['slug__in'] ) ) { + $template_files = array_filter( + $template_files, + function ( $template_file ) use ( $query ) { + return in_array( $template_file['slug'], $query['slug__in'], true ); + } + ); + } + + foreach ( $template_files as $template_file ) { + $query_result[] = _build_block_template_result_from_file( $template_file, 'wp_template' ); + } + + // Add templates registered through the template registry. Filtering out the + // ones which have a theme file. + $registered_templates = WP_Block_Templates_Registry::get_instance()->get_by_query( $query ); + $matching_registered_templates = array_filter( + $registered_templates, + function ( $registered_template ) use ( $template_files ) { + foreach ( $template_files as $template_file ) { + if ( $template_file['slug'] === $registered_template->slug ) { + return false; + } + } + return true; + } + ); + + $query_result = array_merge( $query_result, $matching_registered_templates ); + + // Templates added by PHP filter also count as registered templates. + /** This filter is documented in wp-includes/block-template-utils.php */ + return apply_filters( 'get_block_templates', $query_result, $query, 'wp_template' ); +} + /** * Retrieves a list of unified template objects based on a query. * @@ -1152,6 +1192,8 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' ) $wp_query_args['post_status'] = 'publish'; } + $active_templates = get_option( 'active_templates', array() ); + $template_query = new WP_Query( $wp_query_args ); $query_result = array(); foreach ( $template_query->posts as $post ) { @@ -1173,7 +1215,14 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' ) continue; } - $query_result[] = $template; + if ( $template->is_custom || isset( $query['wp_id'] ) ) { + // Custom templates don't need to be activated, leave them be. + // Also don't filter out templates when querying by wp_id. + $query_result[] = $template; + } elseif ( isset( $active_templates[ $template->slug ] ) && $active_templates[ $template->slug ] === $post->ID ) { + // Only include active templates. + $query_result[] = $template; + } } if ( ! isset( $query['wp_id'] ) ) { @@ -1296,6 +1345,24 @@ function get_block_template( $id, $template_type = 'wp_template' ) { return null; } list( $theme, $slug ) = $parts; + + $active_templates = get_option( 'active_templates', array() ); + + if ( ! empty( $active_templates[ $slug ] ) ) { + if ( is_int( $active_templates[ $slug ] ) ) { + $post = get_post( $active_templates[ $slug ] ); + if ( $post && 'publish' === $post->post_status ) { + $template = _build_block_template_result_from_post( $post ); + + if ( ! is_wp_error( $template ) && $theme === $template->theme ) { + return $template; + } + } + } elseif ( false === $active_templates[ $slug ] ) { + return null; + } + } + $wp_query_args = array( 'post_name__in' => array( $slug ), 'post_type' => $template_type, @@ -1316,6 +1383,12 @@ function get_block_template( $id, $template_type = 'wp_template' ) { if ( count( $posts ) > 0 ) { $template = _build_block_template_result_from_post( $posts[0] ); + // Custom templates don't need to be activated, so if it's a custom + // template, return it. + if ( ! is_wp_error( $template ) && $template->is_custom ) { + return $template; + } + if ( ! is_wp_error( $template ) ) { return $template; } @@ -1779,3 +1852,31 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated return $changes; } + +function wp_assign_new_template_to_theme( $changes, $request ) { + // Do not run this for templates created through the old enpoint. + $template = $request['id'] ? get_block_template( $request['id'], 'wp_template' ) : null; + if ( $template ) { + return $changes; + } + $changes->tax_input = array( + 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), + ); + // All new templates saved will receive meta so we can distinguish between + // templates created the old way as edits and templates created the new way. + $changes->meta_input = array( + 'is_inactive_by_default' => true, + ); + return $changes; +} + +function wp_maybe_activate_template( $post_id ) { + $post = get_post( $post_id ); + $is_inactive_by_default = get_post_meta( $post_id, 'is_inactive_by_default', true ); + if ( $is_inactive_by_default ) { + return; + } + $active_templates = get_option( 'active_templates', array() ); + $active_templates[ $post->post_name ] = $post->ID; + update_option( 'active_templates', $active_templates ); +} diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index eecbe2d61dd61..2c56fcd706a17 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -164,11 +164,77 @@ function resolve_block_template( $template_type, $template_hierarchy, $fallback_ $template_hierarchy ); - // Find all potential templates 'wp_template' post matching the hierarchy. + $object = get_queried_object(); + $specific_template = $object ? get_page_template_slug( $object ) : null; + $active_templates = (array) get_option( 'active_templates', array() ); + + // Remove templates slugs that are deactivated, except if it's the specific + // template or index. + $slugs = array_filter( + $slugs, + function ( $slug ) use ( $specific_template, $active_templates ) { + $should_ignore = $slug === $specific_template || 'index' === $slug; + return $should_ignore || ( ! isset( $active_templates[ $slug ] ) || false !== $active_templates[ $slug ] ); + } + ); + + // We expect one template for each slug. Use the active template if it is + // set and exists. Otherwise use the static template. + $templates = array(); + $remaining_slugs = array(); + + foreach ( $slugs as $slug ) { + if ( $slug === $specific_template || empty( $active_templates[ $slug ] ) ) { + $remaining_slugs[] = $slug; + continue; + } + + // TODO: it need to be possible to set a static template as active. + $post = get_post( $active_templates[ $slug ] ); + if ( ! $post || 'publish' !== $post->post_status ) { + $remaining_slugs[] = $slug; + continue; + } + + $template = _build_block_template_result_from_post( $post ); + + // Ensure the active templates are associated with the active theme. + // See _build_block_template_object_from_post_object. + if ( get_stylesheet() !== $template->theme ) { + $remaining_slugs[] = $slug; + continue; + } + + $templates[] = $template; + } + + // Apply the filter to the active templates for backward compatibility. + /** This filter is documented in wp-includes/block-template-utils.php */ + if ( ! empty( $templates ) ) { + $templates = apply_filters( + 'get_block_templates', + $templates, + array( + 'slug__in' => array_map( + function ( $template ) { + return $template->slug; + }, + $templates + ), + ), + 'wp_template' + ); + } + + // For any remaining slugs, use the static template. $query = array( - 'slug__in' => $slugs, + 'slug__in' => $remaining_slugs, ); - $templates = get_block_templates( $query ); + $templates = array_merge( $templates, gutenberg_get_registered_block_templates( $query ) ); + + if ( $specific_template ) { + $templates = array_merge( $templates, get_block_templates( array( 'slug__in' => array( $specific_template ) ) ) ); + } // Order these templates per slug priority. // Build map of template slugs to their priority in the current hierarchy. diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 52830054ffa20..ba2776506395c 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -740,6 +740,7 @@ // Block templates post type and rendering. add_filter( 'render_block_context', '_block_template_render_without_post_block_context' ); add_filter( 'pre_wp_unique_post_slug', 'wp_filter_wp_template_unique_post_slug', 10, 5 ); +add_action( 'save_post_wp_template', 'wp_maybe_activate_template' ); add_action( 'save_post_wp_template_part', 'wp_set_unique_slug_on_create_template_part' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_template_skip_link' ); add_action( 'wp_footer', 'the_block_template_skip_link' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_block_template_skip_link(). @@ -780,6 +781,10 @@ add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' ); add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes' ); +// Assign the wp_theme term to any newly created wp_template with the new endpoint. +// Must run before `inject_ignored_hooked_blocks_metadata_attributes`. +add_action( 'rest_pre_insert_wp_template', 'wp_assign_new_template_to_theme', 9, 2 ); + // Update ignoredHookedBlocks postmeta for some post types. add_filter( 'rest_pre_insert_page', 'update_ignored_hooked_blocks_postmeta' ); add_filter( 'rest_pre_insert_post', 'update_ignored_hooked_blocks_postmeta' ); diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 7cb4736c2840b..58217ce3177a4 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -2959,6 +2959,25 @@ function register_initial_settings() { 'description' => __( 'Allow people to submit comments on new posts.' ), ) ); + + register_setting( + 'reading', + 'active_templates', + array( + 'type' => 'object', + // Do not set the default value to an empty array! For some reason + // that will prevent the option from being set to an empty array. + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + // Properties can be integers, strings, or false + // (deactivated). + 'additionalProperties' => true, + ), + ), + 'label' => 'Active Templates', + ) + ); } /** diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 19cd927c5c8dc..d73088f0ede00 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -398,10 +398,8 @@ function create_initial_post_types() { 'show_in_menu' => false, 'show_in_rest' => true, 'rewrite' => false, - 'rest_base' => 'templates', - 'rest_controller_class' => 'WP_REST_Templates_Controller', - 'autosave_rest_controller_class' => 'WP_REST_Template_Autosaves_Controller', - 'revisions_rest_controller_class' => 'WP_REST_Template_Revisions_Controller', + 'rest_base' => 'wp_template', + 'rest_controller_class' => 'WP_REST_Posts_Controller', 'late_route_registration' => true, 'capability_type' => array( 'template', 'templates' ), 'capabilities' => array( @@ -426,6 +424,7 @@ function create_initial_post_types() { 'editor', 'revisions', 'author', + 'custom-fields', ), ) ); @@ -8629,4 +8628,16 @@ function wp_create_initial_post_meta() { ), ) ); + + // Allow setting the is_wp_suggestion meta field, which partly determines if + // a template is a custom template. + register_post_meta( + 'wp_template', + 'is_wp_suggestion', + array( + 'type' => 'boolean', + 'show_in_rest' => true, + 'single' => true, + ) + ); } diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 836e0e5ec8a23..aebdf982bb5fa 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -289,6 +289,34 @@ function create_initial_rest_routes() { } } + // Register the old templates endpoint. + global $wp_post_types; + $wp_post_types['wp_template']->rest_base = 'templates'; + $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $wp_post_types['wp_template']->rest_base = 'wp_template'; + $controller->register_routes(); + + register_rest_field( + 'wp_template', + 'theme', + array( + 'get_callback' => function ( $post_arr ) { + // add_additional_fields_to_object is also called for the old + // templates controller, so we need to check if the id is an + // integer to make sure it's the proper post type endpoint. + if ( ! is_int( $post_arr['id'] ) ) { + $template = get_block_template( $post_arr['id'], 'wp_template' ); + return $template ? $template->theme : null; + } + $terms = get_the_terms( $post_arr['id'], 'wp_theme' ); + if ( is_wp_error( $terms ) || empty( $terms ) ) { + return null; + } + return $terms[0]->slug; + }, + ) + ); + // Post types. $controller = new WP_REST_Post_Types_Controller(); $controller->register_routes(); diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index eed0fb9b2b029..7ebc3cdc1a793 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -49,6 +49,11 @@ function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_id return $override_slug; } + // For wp_template, slugs no longer have to be unique within the same theme. + if ( 'wp_template' !== $post_type ) { + return $override_slug; + } + if ( ! $override_slug ) { $override_slug = $slug; } From 7635f183f69720e2bc15c97851bc06cd3ba6f30b Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 15:45:05 +0200 Subject: [PATCH 03/11] Add registered templates endpoint --- src/wp-includes/block-template.php | 2 +- src/wp-includes/rest-api.php | 10 +- ...p-rest-registered-templates-controller.php | 104 ++++++++++++++++++ src/wp-settings.php | 1 + 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-registered-templates-controller.php diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 2c56fcd706a17..2dd7b2e3c099d 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -230,7 +230,7 @@ function ( $template ) { $query = array( 'slug__in' => $remaining_slugs, ); - $templates = array_merge( $templates, gutenberg_get_registered_block_templates( $query ) ); + $templates = array_merge( $templates, get_registered_block_templates( $query ) ); if ( $specific_template ) { $templates = array_merge( $templates, get_block_templates( array( 'slug__in' => array( $specific_template ) ) ) ); diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index aebdf982bb5fa..ab3db782bf467 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -263,6 +263,15 @@ function rest_api_default_filters() { * @since 4.7.0 */ function create_initial_rest_routes() { + global $wp_post_types; + + // Register the registered templates endpoint. For that we need to copy the + // wp_template post type so that it's available as an entity in core-data. + $wp_post_types['wp_registered_template'] = clone $wp_post_types['wp_template']; + $wp_post_types['wp_registered_template']->name = 'wp_registered_template'; + $wp_post_types['wp_registered_template']->rest_base = 'wp_registered_template'; + $wp_post_types['wp_registered_template']->rest_controller_class = 'WP_REST_Registered_Templates_Controller'; + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { $controller = $post_type->get_rest_controller(); @@ -290,7 +299,6 @@ function create_initial_rest_routes() { } // Register the old templates endpoint. - global $wp_post_types; $wp_post_types['wp_template']->rest_base = 'templates'; $controller = new WP_REST_Templates_Controller( 'wp_template' ); $wp_post_types['wp_template']->rest_base = 'wp_template'; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-registered-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-registered-templates-controller.php new file mode 100644 index 0000000000000..97b65af75742d --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-registered-templates-controller.php @@ -0,0 +1,104 @@ +namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + // Lists/updates a single template based on the given id. + register_rest_route( + $this->namespace, + // The route. + sprintf( + '/%s/(?P%s%s)', + $this->rest_base, + /* + * Matches theme's directory: `/themes///` or `/themes//`. + * Excludes invalid directory name characters: `/:<>*?"|`. + */ + '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', + // Matches the template name. + '[\/\w%-]+' + ), + array( + 'args' => array( + 'id' => array( + 'description' => __( 'The id of a template' ), + 'type' => 'string', + 'sanitize_callback' => array( $this, '_sanitize_template_id' ), + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + public function get_item_schema() { + $schema = parent::get_item_schema(); + $schema['properties']['is_custom'] = array( + 'description' => __( 'Whether a template is a custom template.' ), + 'type' => 'bool', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + $schema['properties']['plugin'] = array( + 'type' => 'string', + 'description' => __( 'Plugin that registered the template.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ); + return $schema; + } + + public function get_items( $request ) { + $query = array(); + if ( isset( $request['area'] ) ) { + $query['area'] = $request['area']; + } + if ( isset( $request['post_type'] ) ) { + $query['post_type'] = $request['post_type']; + } + $query_result = get_registered_block_templates( $query ); + $templates = array(); + foreach ( $query_result as $template ) { + $item = $this->prepare_item_for_response( $template, $request ); + $item->data['type'] = 'wp_registered_template'; + $templates[] = $this->prepare_response_for_collection( $item ); + } + + return rest_ensure_response( $templates ); + } + + public function get_item( $request ) { + $template = get_block_file_template( $request['id'], 'wp_template' ); + + if ( ! $template ) { + return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); + } + + $item = $this->prepare_item_for_response( $template, $request ); + // adjust the template type here instead + $item->data['type'] = 'wp_registered_template'; + return rest_ensure_response( $item ); + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index b67b385e791a2..579765028c14b 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -325,6 +325,7 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-registered-templates-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-url-details-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-families-controller.php'; From bee1e4a4516ac098e5a99edc6fdafe1460675cc6 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 16:41:07 +0200 Subject: [PATCH 04/11] Restore autosave and revisisions controller --- src/wp-includes/rest-api.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index ab3db782bf467..7d4798eecdd91 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -298,10 +298,18 @@ function create_initial_rest_routes() { } } - // Register the old templates endpoint. + // Register the old templates endpoints. $wp_post_types['wp_template']->rest_base = 'templates'; $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $wp_post_types['wp_template']->rest_controller = $controller; + $revisions_controller = new WP_REST_Template_Revisions_Controller( 'wp_template' ); + $wp_post_types['wp_template']->revisions_rest_controller = $revisions_controller; + $autosaves_controller = new WP_REST_Template_Autosaves_Controller( 'wp_template' ); + $wp_post_types['wp_template']->rest_controller = null; + $wp_post_types['wp_template']->revisions_rest_controller = null; $wp_post_types['wp_template']->rest_base = 'wp_template'; + $autosaves_controller->register_routes(); + $revisions_controller->register_routes(); $controller->register_routes(); register_rest_field( From 3b09dae0ceaee1e8743d29d16b807fd1a686f1b2 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 16:57:33 +0200 Subject: [PATCH 05/11] Update expected endpoints and settings --- tests/phpunit/tests/rest-api/rest-schema-setup.php | 8 ++++++++ tests/phpunit/tests/rest-api/rest-settings-controller.php | 1 + 2 files changed, 9 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 7f8de5f0dd83d..b5e98e5333002 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -157,6 +157,14 @@ public function test_expected_routes_in_schema() { '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/autosaves/(?P[\d]+)', '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions', '/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)/revisions/(?P[\d]+)', + '/wp/v2/wp_registered_template', + '/wp/v2/wp_registered_template/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+)', + '/wp/v2/wp_template', + '/wp/v2/wp_template/(?P[\d]+)', + '/wp/v2/wp_template/(?P[\d]+)/autosaves', + '/wp/v2/wp_template/(?P[\d]+)/autosaves/(?P[\d]+)', + '/wp/v2/wp_template/(?P[\d]+)/revisions', + '/wp/v2/wp_template/(?P[\d]+)/revisions/(?P[\d]+)', '/wp/v2/templates/lookup', '/wp/v2/themes', '/wp/v2/themes/(?P[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php index c6b42c365fd1c..000293e824928 100644 --- a/tests/phpunit/tests/rest-api/rest-settings-controller.php +++ b/tests/phpunit/tests/rest-api/rest-settings-controller.php @@ -119,6 +119,7 @@ public function test_get_items() { 'default_ping_status', 'default_comment_status', 'site_icon', // Registered in wp-includes/blocks/site-logo.php + 'active_templates', ); if ( ! is_multisite() ) { From ec3dfd72e7e991648d8f34ab5d9e6ad2f48e6630 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 17:28:28 +0200 Subject: [PATCH 06/11] Make sure to not override meta and tax input --- src/wp-includes/block-template-utils.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 051239d5c44f0..88e3cfe8103c9 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1859,14 +1859,16 @@ function wp_assign_new_template_to_theme( $changes, $request ) { if ( $template ) { return $changes; } - $changes->tax_input = array( - 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), - ); + if ( ! isset( $changes->tax_input ) ) { + $changes->tax_input = array(); + } + $changes->tax_input['wp_theme'] = isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(); // All new templates saved will receive meta so we can distinguish between // templates created the old way as edits and templates created the new way. - $changes->meta_input = array( - 'is_inactive_by_default' => true, - ); + if ( ! isset( $changes->meta_input ) ) { + $changes->meta_input = array(); + } + $changes->meta_input['is_inactive_by_default'] = true; return $changes; } From 949cf122e208b9025ef5ede47a415263c50e2059 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 22:31:13 +0200 Subject: [PATCH 07/11] Fix unit tests --- src/wp-includes/rest-api.php | 35 +++++++++++++++---- .../wpRestTemplateAutosavesController.php | 13 +++++++ .../wpRestTemplateRevisionsController.php | 9 +++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 7d4798eecdd91..79caa617902ae 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -298,16 +298,39 @@ function create_initial_rest_routes() { } } - // Register the old templates endpoints. + // Register the old templates endpoints. The WP_REST_Templates_Controller + // and sub-controllers used linked to the wp_template post type, but are no + // longer. They still require a post type object when contructing the class. + // To maintain backward and changes to these controller classes, we make use + // that the wp_template post type has the right information it needs. $wp_post_types['wp_template']->rest_base = 'templates'; - $controller = new WP_REST_Templates_Controller( 'wp_template' ); - $wp_post_types['wp_template']->rest_controller = $controller; - $revisions_controller = new WP_REST_Template_Revisions_Controller( 'wp_template' ); + // Store the classes so they can be restored. + $original_rest_controller_class = $wp_post_types['wp_template']->rest_controller_class; + $original_autosave_rest_controller_class = $wp_post_types['wp_template']->autosave_rest_controller_class; + $original_revisions_rest_controller_class = $wp_post_types['wp_template']->revisions_rest_controller_class; + // Temporarily set the old classes. + $wp_post_types['wp_template']->rest_controller_class = 'WP_REST_Templates_Controller'; + $wp_post_types['wp_template']->autosave_rest_controller_class = 'WP_REST_Template_Autosaves_Controller'; + $wp_post_types['wp_template']->revisions_rest_controller_class = 'WP_REST_Template_Revisions_Controller'; + // Initialize the controllers. The order is important: the autosave + // controller needs both the templates and revisions controllers. + $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $wp_post_types['wp_template']->rest_controller = $controller; + $revisions_controller = new WP_REST_Template_Revisions_Controller( 'wp_template' ); $wp_post_types['wp_template']->revisions_rest_controller = $revisions_controller; - $autosaves_controller = new WP_REST_Template_Autosaves_Controller( 'wp_template' ); - $wp_post_types['wp_template']->rest_controller = null; + $autosaves_controller = new WP_REST_Template_Autosaves_Controller( 'wp_template' ); + // Unset the controller cache, it will be re-initialized when + // get_rest_controller is called. + $wp_post_types['wp_template']->rest_controller = null; $wp_post_types['wp_template']->revisions_rest_controller = null; + // Restore the original classes. + $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; + $wp_post_types['wp_template']->autosave_rest_controller_class = $original_autosave_rest_controller_class; + $wp_post_types['wp_template']->revisions_rest_controller_class = $original_revisions_rest_controller_class; + // Restore the original base. $wp_post_types['wp_template']->rest_base = 'wp_template'; + + // Register the old routes. $autosaves_controller->register_routes(); $revisions_controller->register_routes(); $controller->register_routes(); diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php index d3cbf91260488..df8d5aa351dcf 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php @@ -589,7 +589,20 @@ public function test_prepare_item_with_data_provider( $parent_post_property_name ); $autosave_db_post = get_post( $autosave_post_id ); $request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/' . $template_id . '/autosaves/' . $autosave_db_post->ID ); + // See create_initial_rest_routes. The controller need the post type + // with adjusted settings to initialize. + global $wp_post_types; + $wp_post_types['wp_template']->rest_base = 'templates'; + $original_rest_controller_class = $wp_post_types['wp_template']->rest_controller_class; + $original_revisions_rest_controller_class = $wp_post_types['wp_template']->revisions_rest_controller_class; + $wp_post_types['wp_template']->rest_controller_class = 'WP_REST_Templates_Controller'; + $wp_post_types['wp_template']->revisions_rest_controller_class = 'WP_REST_Template_Revisions_Controller'; + $wp_post_types['wp_template']->rest_controller = null; + $wp_post_types['wp_template']->revisions_rest_controller = null; $controller = new WP_REST_Template_Autosaves_Controller( $parent_post->post_type ); + $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; + $wp_post_types['wp_template']->revisions_rest_controller_class = $original_revisions_rest_controller_class; + $wp_post_types['wp_template']->rest_base = 'wp_template'; $response = $controller->prepare_item_for_response( $autosave_db_post, $request ); $this->assertInstanceOf( WP_REST_Response::class, diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php index e8a18b275e7cd..acb12bdc13668 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -830,7 +830,16 @@ public function test_prepare_item_with_data_provider( $parent_post_property_name $revision_id = array_shift( $revisions ); $post = get_post( $revision_id ); $request = new WP_REST_Request( 'GET', '/wp/v2/' . $rest_base . '/' . $template_id . '/revisions/' . $revision_id ); + // See create_initial_rest_routes. The controller need the post type + // with adjusted settings to initialize. + global $wp_post_types; + $wp_post_types['wp_template']->rest_base = 'templates'; + $original_rest_controller_class = $wp_post_types['wp_template']->rest_controller_class; + $wp_post_types['wp_template']->rest_controller_class = 'WP_REST_Templates_Controller'; + $wp_post_types['wp_template']->rest_controller = null; $controller = new WP_REST_Template_Revisions_Controller( $parent_post->post_type ); + $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; + $wp_post_types['wp_template']->rest_base = 'wp_template'; $response = $controller->prepare_item_for_response( $post, $request ); $this->assertInstanceOf( WP_REST_Response::class, From b1d0ccef8df565e7463637d942944e607ab90bb1 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 22:31:32 +0200 Subject: [PATCH 08/11] Adjust preload paths --- src/wp-admin/site-editor.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php index b5dd7fadb67ec..03d62ddf0eecd 100644 --- a/src/wp-admin/site-editor.php +++ b/src/wp-admin/site-editor.php @@ -182,7 +182,7 @@ static function ( $classes ) { array( rest_get_route_for_post_type_items( 'attachment' ), 'OPTIONS' ), array( rest_get_route_for_post_type_items( 'page' ), 'OPTIONS' ), '/wp/v2/types?context=view', - '/wp/v2/_wp_static_template?context=edit', + '/wp/v2/wp_registered_template?context=edit', '/wp/v2/types/wp_template?context=edit', '/wp/v2/types/wp_template_part?context=edit', '/wp/v2/templates?context=edit&per_page=-1', @@ -245,7 +245,9 @@ static function ( $classes ) { ); } } -} else { +} else if ( isset( $_GET['p'] ) && '/' !== $_GET['p'] ) { + // Only prefetch for the root. If we preload it for all pages and it's not + // used it won't be possible to invalidate. $preload_paths[] = '/wp/v2/templates/lookup?slug=front-page'; $preload_paths[] = '/wp/v2/templates/lookup?slug=home'; } From 2ee34ad87b284fa490b4cf9e1ddd4712493382a8 Mon Sep 17 00:00:00 2001 From: ella Date: Mon, 20 Oct 2025 23:19:19 +0200 Subject: [PATCH 09/11] php lint, update fixtures --- .../wpRestTemplateAutosavesController.php | 4 +- .../wpRestTemplateRevisionsController.php | 4 +- tests/qunit/fixtures/wp-api-generated.js | 2227 +++++++++++++---- 3 files changed, 1676 insertions(+), 559 deletions(-) diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php index df8d5aa351dcf..0c105e8759bb3 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php @@ -599,11 +599,11 @@ public function test_prepare_item_with_data_provider( $parent_post_property_name $wp_post_types['wp_template']->revisions_rest_controller_class = 'WP_REST_Template_Revisions_Controller'; $wp_post_types['wp_template']->rest_controller = null; $wp_post_types['wp_template']->revisions_rest_controller = null; - $controller = new WP_REST_Template_Autosaves_Controller( $parent_post->post_type ); + $controller = new WP_REST_Template_Autosaves_Controller( $parent_post->post_type ); $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; $wp_post_types['wp_template']->revisions_rest_controller_class = $original_revisions_rest_controller_class; $wp_post_types['wp_template']->rest_base = 'wp_template'; - $response = $controller->prepare_item_for_response( $autosave_db_post, $request ); + $response = $controller->prepare_item_for_response( $autosave_db_post, $request ); $this->assertInstanceOf( WP_REST_Response::class, $response, diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php index acb12bdc13668..655436c7ae4b0 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -837,10 +837,10 @@ public function test_prepare_item_with_data_provider( $parent_post_property_name $original_rest_controller_class = $wp_post_types['wp_template']->rest_controller_class; $wp_post_types['wp_template']->rest_controller_class = 'WP_REST_Templates_Controller'; $wp_post_types['wp_template']->rest_controller = null; - $controller = new WP_REST_Template_Revisions_Controller( $parent_post->post_type ); + $controller = new WP_REST_Template_Revisions_Controller( $parent_post->post_type ); $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; $wp_post_types['wp_template']->rest_base = 'wp_template'; - $response = $controller->prepare_item_for_response( $post, $request ); + $response = $controller->prepare_item_for_response( $post, $request ); $this->assertInstanceOf( WP_REST_Response::class, $response, diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index bc9ea0a2dc424..8f6b69222797e 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -21,13 +21,7 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": { - "application-passwords": { - "endpoints": { - "authorization": "http://example.org/wp-admin/authorize-application.php" - } - } - }, + "authentication": [], "routes": { "/": { "namespace": "", @@ -5507,7 +5501,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { + "/wp/v2/wp_template/(?P[\\d]+)/revisions": { "namespace": "wp/v2", "methods": [ "GET" @@ -5519,8 +5513,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The id of a template", - "type": "string", + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, "context": { @@ -5605,7 +5599,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { + "/wp/v2/wp_template/(?P[\\d]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -5618,8 +5612,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The id of a template", - "type": "string", + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, "id": { @@ -5646,8 +5640,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The id of a template", - "type": "string", + "description": "The ID for the parent of the revision.", + "type": "integer", "required": false }, "id": { @@ -5665,7 +5659,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { + "/wp/v2/wp_template/(?P[\\d]+)/autosaves": { "namespace": "wp/v2", "methods": [ "GET", @@ -5677,9 +5671,9 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "id": { - "description": "The id of a template", - "type": "string", + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", "required": false }, "context": { @@ -5700,72 +5694,134 @@ mockedApiResponse.Schema = { "POST" ], "args": { - "id": { - "description": "The id of a template", - "type": "string", + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", "required": false }, "slug": { - "description": "Unique slug identifying the template.", + "description": "An alphanumeric identifier for the post unique to its type.", "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", "required": false }, - "theme": { - "description": "Theme identifier for the template.", + "status": { + "description": "A named status for the post.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "type": { - "description": "Type of template.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, - "content": { - "description": "Content of template.", - "type": [ - "object", - "string" - ], + "title": { + "description": "The title for the post.", + "type": "object", "properties": { "raw": { - "description": "Content for the template, as it exists in the database.", + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", "type": "string", "context": [ "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ "edit" ] }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + }, "block_version": { - "description": "Version of the content block format used by the template.", + "description": "Version of the content block format used by the post.", "type": "integer", "context": [ "edit" ], "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true } }, "required": false }, - "title": { - "description": "Title of template.", - "type": [ - "object", - "string" - ], + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, + "excerpt": { + "description": "The excerpt for the post.", + "type": "object", "properties": { "raw": { - "description": "Title for the template, as it exists in the database.", + "description": "Excerpt for the post, as it exists in the database.", "type": "string", "context": [ - "view", - "edit", - "embed" + "edit" ] }, "rendered": { - "description": "HTML title for the template, transformed for display.", + "description": "HTML excerpt for the post, transformed for display.", "type": "string", "context": [ "view", @@ -5773,37 +5829,36 @@ mockedApiResponse.Schema = { "embed" ], "readonly": true + }, + "protected": { + "description": "Whether the excerpt is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true } }, "required": false }, - "description": { - "description": "Description of template.", - "type": "string", + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "status": { - "description": "Status of template.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "author": { - "description": "The ID for the author of the template.", - "type": "integer", "required": false } } } ] }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { + "/wp/v2/wp_template/(?P[\\d]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET" @@ -5815,8 +5870,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The id of a template", - "type": "string", + "description": "The ID for the parent of the autosave.", + "type": "integer", "required": false }, "id": { @@ -5839,7 +5894,7 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/templates": { + "/wp/v2/wp_template": { "namespace": "wp/v2", "methods": [ "GET", @@ -5850,6 +5905,9 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -5862,92 +5920,278 @@ mockedApiResponse.Schema = { "default": "view", "required": false }, - "wp_id": { - "description": "Limit to the specified post id.", + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, "required": false }, - "area": { - "description": "Limit to the specified template part area.", + "search": { + "description": "Limit results to those matching a string.", "type": "string", "required": false }, - "post_type": { - "description": "Post type to get the templates for.", + "after": { + "description": "Limit response to posts published after a given ISO8601 compliant date.", "type": "string", + "format": "date-time", "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "slug": { - "description": "Unique slug identifying the template.", + }, + "modified_after": { + "description": "Limit response to posts modified after a given ISO8601 compliant date.", "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", - "required": true + "format": "date-time", + "required": false }, - "theme": { - "description": "Theme identifier for the template.", + "author": { + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "author_exclude": { + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "before": { + "description": "Limit response to posts published before a given ISO8601 compliant date.", "type": "string", + "format": "date-time", "required": false }, - "type": { - "description": "Type of template.", + "modified_before": { + "description": "Limit response to posts modified before a given ISO8601 compliant date.", "type": "string", + "format": "date-time", "required": false }, - "content": { - "default": "", - "description": "Content of template.", + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "date", + "enum": [ + "author", + "date", + "id", + "include", + "modified", + "parent", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + }, + "search_columns": { + "default": [], + "description": "Array of column names to be searched.", + "type": "array", + "items": { + "enum": [ + "post_title", + "post_content", + "post_excerpt" + ], + "type": "string" + }, + "required": false + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "request-pending", + "request-confirmed", + "request-failed", + "request-completed", + "any" + ], + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "allow_batch": { + "v1": true + }, + "args": { + "date": { + "description": "The date the post was published, in the site's timezone.", "type": [ - "object", - "string" + "string", + "null" ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", "properties": { "raw": { - "description": "Content for the template, as it exists in the database.", + "description": "Title for the post, as it exists in the database.", "type": "string", "context": [ - "view", "edit" ] }, - "block_version": { - "description": "Version of the content block format used by the template.", - "type": "integer", + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", "context": [ - "edit" + "view", + "edit", + "embed" ], "readonly": true } }, "required": false }, - "title": { - "default": "", - "description": "Title of template.", - "type": [ - "object", - "string" - ], + "content": { + "description": "The content for the post.", + "type": "object", "properties": { "raw": { - "description": "Title for the template, as it exists in the database.", + "description": "Content for the post, as it exists in the database.", "type": "string", "context": [ - "view", - "edit", - "embed" + "edit" ] }, "rendered": { - "description": "HTML title for the template, transformed for display.", + "description": "HTML content for the post, transformed for display.", "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", "context": [ "view", "edit", @@ -5958,64 +6202,53 @@ mockedApiResponse.Schema = { }, "required": false }, - "description": { - "default": "", - "description": "Description of template.", - "type": "string", - "required": false - }, - "status": { - "default": "publish", - "description": "Status of template.", - "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, "author": { - "description": "The ID for the author of the template.", + "description": "The ID for the author of the post.", "type": "integer", "required": false - } - } - } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/templates" - } - ] - } - }, - "/wp/v2/templates/lookup": { - "namespace": "wp/v2", - "methods": [ - "GET" - ], - "endpoints": [ - { - "methods": [ - "GET" - ], - "args": { - "slug": { - "description": "The slug of the template to get the fallback for", - "type": "string", - "required": true }, - "is_custom": { - "description": "Indicates if a template is custom or part of the template hierarchy", - "type": "boolean", + "excerpt": { + "description": "The excerpt for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Excerpt for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML excerpt for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + }, + "protected": { + "description": "Whether the excerpt is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, "required": false }, - "template_prefix": { - "description": "The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`", + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", "type": "string", "required": false } @@ -6025,12 +6258,12 @@ mockedApiResponse.Schema = { "_links": { "self": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/templates/lookup" + "href": "http://example.org/index.php?rest_route=/wp/v2/wp_template" } ] } }, - "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { + "/wp/v2/wp_template/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -6044,10 +6277,13 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "description": "Unique identifier for the post.", + "type": "integer", "required": false }, "context": { @@ -6060,6 +6296,16 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + }, + "excerpt_length": { + "description": "Override the default excerpt length.", + "type": "integer", + "required": false + }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", + "required": false } } }, @@ -6069,73 +6315,138 @@ mockedApiResponse.Schema = { "PUT", "PATCH" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", "required": false }, "slug": { - "description": "Unique slug identifying the template.", + "description": "An alphanumeric identifier for the post unique to its type.", "type": "string", - "minLength": 1, - "pattern": "[a-zA-Z0-9_\\%-]+", "required": false }, - "theme": { - "description": "Theme identifier for the template.", + "status": { + "description": "A named status for the post.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], "required": false }, - "type": { - "description": "Type of template.", + "password": { + "description": "A password to protect access to the content and excerpt.", "type": "string", "required": false }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, "content": { - "description": "Content of template.", - "type": [ - "object", - "string" - ], + "description": "The content for the post.", + "type": "object", "properties": { "raw": { - "description": "Content for the template, as it exists in the database.", + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", "type": "string", "context": [ "view", "edit" - ] + ], + "readonly": true }, "block_version": { - "description": "Version of the content block format used by the template.", + "description": "Version of the content block format used by the post.", "type": "integer", "context": [ "edit" ], "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true } }, "required": false }, - "title": { - "description": "Title of template.", - "type": [ - "object", - "string" - ], + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, + "excerpt": { + "description": "The excerpt for the post.", + "type": "object", "properties": { "raw": { - "description": "Title for the template, as it exists in the database.", + "description": "Excerpt for the post, as it exists in the database.", "type": "string", "context": [ - "view", - "edit", - "embed" + "edit" ] }, "rendered": { - "description": "HTML title for the template, transformed for display.", + "description": "HTML excerpt for the post, transformed for display.", "type": "string", "context": [ "view", @@ -6143,30 +6454,29 @@ mockedApiResponse.Schema = { "embed" ], "readonly": true + }, + "protected": { + "description": "Whether the excerpt is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true } }, "required": false }, - "description": { - "description": "Description of template.", - "type": "string", + "meta": { + "description": "Meta fields.", + "type": "object", + "properties": [], "required": false }, - "status": { - "description": "Status of template.", + "template": { + "description": "The theme file to use to display the post.", "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], - "required": false - }, - "author": { - "description": "The ID for the author of the template.", - "type": "integer", "required": false } } @@ -6175,10 +6485,13 @@ mockedApiResponse.Schema = { "methods": [ "DELETE" ], + "allow_batch": { + "v1": true + }, "args": { "id": { - "description": "The id of a template", - "type": "string", + "description": "Unique identifier for the post.", + "type": "integer", "required": false }, "force": { @@ -7246,48 +7559,229 @@ mockedApiResponse.Schema = { }, "required": false }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" - }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + }, + "status": { + "default": "publish", + "description": "Limit result set to posts assigned one or more statuses.", + "type": "array", + "items": { + "enum": [ + "publish", + "future", + "draft", + "pending", + "private", + "trash", + "auto-draft", + "inherit", + "request-pending", + "request-confirmed", + "request-failed", + "request-completed", + "any" + ], + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "allow_batch": { + "v1": true + }, + "args": { + "date": { + "description": "The date the post was published, in the site's timezone.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "date_gmt": { + "description": "The date the post was published, as GMT.", + "type": [ + "string", + "null" + ], + "format": "date-time", + "required": false + }, + "slug": { + "description": "An alphanumeric identifier for the post unique to its type.", + "type": "string", + "required": false + }, + "status": { + "description": "A named status for the post.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "password": { + "description": "A password to protect access to the content and excerpt.", + "type": "string", + "required": false + }, + "title": { + "description": "The title for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Title for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML title for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "content": { + "description": "The content for the post.", + "type": "object", + "properties": { + "raw": { + "description": "Content for the post, as it exists in the database.", + "type": "string", + "context": [ + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML content for the post, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + }, + "block_version": { + "description": "Version of the content block format used by the post.", + "type": "integer", + "context": [ + "edit", + "embed" + ], + "readonly": true + }, + "protected": { + "description": "Whether the content is protected with a password.", + "type": "boolean", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "template": { + "description": "The theme file to use to display the post.", + "type": "string", + "required": false + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + } + ] + } + }, + "/wp/v2/navigation/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false }, - "status": { - "default": "publish", - "description": "Limit result set to posts assigned one or more statuses.", - "type": "array", - "items": { - "enum": [ - "publish", - "future", - "draft", - "pending", - "private", - "trash", - "auto-draft", - "inherit", - "request-pending", - "request-confirmed", - "request-failed", - "request-completed", - "any" - ], - "type": "string" - }, + "password": { + "description": "The password for the post if it is password protected.", + "type": "string", "required": false } } }, { "methods": [ - "POST" + "POST", + "PUT", + "PATCH" ], "allow_batch": { "v1": true }, "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, "date": { "description": "The date the post was published, in the site's timezone.", "type": [ @@ -7403,23 +7897,132 @@ mockedApiResponse.Schema = { "required": false } } + }, + { + "methods": [ + "DELETE" + ], + "allow_batch": { + "v1": true + }, + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Whether to bypass Trash and force deletion.", + "required": false + } + } } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "namespace": "wp/v2", + "methods": [ + "GET" ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/navigation" + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "minimum": 1, + "maximum": 100, + "required": false + }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by object attribute.", + "type": "string", + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], + "required": false + } } - ] - } + } + ] }, - "/wp/v2/navigation/(?P[\\d]+)": { + "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", "DELETE" ], "endpoints": [ @@ -7427,12 +8030,14 @@ mockedApiResponse.Schema = { "methods": [ "GET" ], - "allow_batch": { - "v1": true - }, "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, @@ -7446,26 +8051,71 @@ mockedApiResponse.Schema = { ], "default": "view", "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "parent": { + "description": "The ID for the parent of the revision.", + "type": "integer", + "required": false }, - "password": { - "description": "The password for the post if it is password protected.", + "id": { + "description": "Unique identifier for the revision.", + "type": "integer", + "required": false + }, + "force": { + "type": "boolean", + "default": false, + "description": "Required to be true, as revisions do not support trashing.", + "required": false + } + } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", "required": false } } }, { "methods": [ - "POST", - "PUT", - "PATCH" + "POST" ], - "allow_batch": { - "v1": true - }, "args": { - "id": { - "description": "Unique identifier for the post.", + "parent": { + "description": "The ID for the parent of the autosave.", "type": "integer", "required": false }, @@ -7584,14 +8234,237 @@ mockedApiResponse.Schema = { "required": false } } + } + ] + }, + "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "description": "The ID for the parent of the autosave.", + "type": "integer", + "required": false + }, + "id": { + "description": "The ID for the autosave.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + } + ] + }, + "/wp/v2/font-families": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "page": { + "description": "Current page of the collection.", + "type": "integer", + "default": 1, + "minimum": 1, + "required": false + }, + "per_page": { + "description": "Maximum number of items to be returned in result set.", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, + "required": false + }, + "exclude": { + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "include": { + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, + "offset": { + "description": "Offset the result set by a specific number of items.", + "type": "integer", + "required": false + }, + "order": { + "description": "Order sort attribute ascending or descending.", + "type": "string", + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "required": false + }, + "orderby": { + "description": "Sort collection by post attribute.", + "type": "string", + "default": "id", + "enum": [ + "id", + "include" + ], + "required": false + }, + "slug": { + "description": "Limit result set to posts with one or more specific slugs.", + "type": "array", + "items": { + "type": "string" + }, + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 3, + "minimum": 2, + "maximum": 3, + "required": false + }, + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" + } + ] + } + }, + "/wp/v2/font-families/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + } + } + }, + { + "methods": [ + "POST", + "PUT", + "PATCH" + ], + "args": { + "id": { + "description": "Unique identifier for the post.", + "type": "integer", + "required": false + }, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 3, + "minimum": 2, + "maximum": 3, + "required": false + }, + "font_family_settings": { + "description": "font-family declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } }, { "methods": [ "DELETE" ], - "allow_batch": { - "v1": true - }, "args": { "id": { "description": "Unique identifier for the post.", @@ -7608,10 +8481,11 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/navigation/(?P[\\d]+)/revisions": { + "/wp/v2/font-families/(?P[\\d]+)/font-faces": { "namespace": "wp/v2", "methods": [ - "GET" + "GET", + "POST" ], "endpoints": [ { @@ -7619,10 +8493,10 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -7644,14 +8518,10 @@ mockedApiResponse.Schema = { }, "per_page": { "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "minimum": 1, - "maximum": 100, - "required": false - }, - "search": { - "description": "Limit results to those matching a string.", - "type": "string", + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100, "required": false }, "exclude": { @@ -7672,6 +8542,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -7688,25 +8566,45 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by object attribute.", + "description": "Sort collection by post attribute.", "type": "string", - "default": "date", + "default": "id", "enum": [ - "date", "id", - "include", - "relevance", - "slug", - "include_slugs", - "title" + "include" ], "required": false } } + }, + { + "methods": [ + "POST" + ], + "args": { + "font_family_id": { + "description": "The ID for the parent font family of the font face.", + "type": "integer", + "required": true + }, + "theme_json_version": { + "description": "Version of the theme.json schema used for the typography settings.", + "type": "integer", + "default": 3, + "minimum": 2, + "maximum": 3, + "required": false + }, + "font_face_settings": { + "description": "font-face declaration in theme.json format, encoded as a string.", + "type": "string", + "required": true + } + } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/revisions/(?P[\\d]+)": { + "/wp/v2/font-families/(?P[\\d]+)/font-faces/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -7718,15 +8616,15 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "id": { - "description": "Unique identifier for the revision.", + "description": "Unique identifier for the font face.", "type": "integer", - "required": false + "required": true }, "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -7746,31 +8644,30 @@ mockedApiResponse.Schema = { "DELETE" ], "args": { - "parent": { - "description": "The ID for the parent of the revision.", + "font_family_id": { + "description": "The ID for the parent font family of the font face.", "type": "integer", - "required": false + "required": true }, "id": { - "description": "Unique identifier for the revision.", + "description": "Unique identifier for the font face.", "type": "integer", - "required": false + "required": true }, "force": { "type": "boolean", "default": false, - "description": "Required to be true, as revisions do not support trashing.", + "description": "Whether to bypass Trash and force deletion.", "required": false } } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves": { + "/wp/v2/wp_registered_template": { "namespace": "wp/v2", "methods": [ - "GET", - "POST" + "GET" ], "endpoints": [ { @@ -7778,11 +8675,59 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" + ], + "default": "view", + "required": false + }, + "wp_id": { + "description": "Limit to the specified post id.", "type": "integer", "required": false }, + "area": { + "description": "Limit to the specified template part area.", + "type": "string", + "required": false + }, + "post_type": { + "description": "Post type to get the templates for.", + "type": "string", + "required": false + } + } + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/wp_registered_template" + } + ] + } + }, + "/wp/v2/wp_registered_template/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "id": { + "description": "The id of a template", + "type": "string", + "required": false + }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", @@ -7795,116 +8740,111 @@ mockedApiResponse.Schema = { "required": false } } - }, + } + ] + }, + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ { "methods": [ - "POST" + "GET" ], "args": { - "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "id": { + "description": "The id of a template", + "type": "string", "required": false }, - "date": { - "description": "The date the post was published, in the site's timezone.", - "type": [ - "string", - "null" + "context": { + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string", + "enum": [ + "view", + "embed", + "edit" ], - "format": "date-time", + "default": "view", "required": false - }, - "date_gmt": { - "description": "The date the post was published, as GMT.", - "type": [ - "string", - "null" - ], - "format": "date-time", + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "id": { + "description": "The id of a template", + "type": "string", "required": false }, "slug": { - "description": "An alphanumeric identifier for the post unique to its type.", + "description": "Unique slug identifying the template.", "type": "string", + "minLength": 1, + "pattern": "[a-zA-Z0-9_\\%-]+", "required": false }, - "status": { - "description": "A named status for the post.", + "theme": { + "description": "Theme identifier for the template.", "type": "string", - "enum": [ - "publish", - "future", - "draft", - "pending", - "private" - ], "required": false }, - "password": { - "description": "A password to protect access to the content and excerpt.", + "type": { + "description": "Type of template.", "type": "string", "required": false }, - "title": { - "description": "The title for the post.", - "type": "object", + "content": { + "description": "Content of template.", + "type": [ + "object", + "string" + ], "properties": { "raw": { - "description": "Title for the post, as it exists in the database.", + "description": "Content for the template, as it exists in the database.", "type": "string", "context": [ - "edit", - "embed" + "view", + "edit" ] }, - "rendered": { - "description": "HTML title for the post, transformed for display.", - "type": "string", + "block_version": { + "description": "Version of the content block format used by the template.", + "type": "integer", "context": [ - "view", - "edit", - "embed" + "edit" ], "readonly": true } }, "required": false }, - "content": { - "description": "The content for the post.", - "type": "object", + "title": { + "description": "Title of template.", + "type": [ + "object", + "string" + ], "properties": { "raw": { - "description": "Content for the post, as it exists in the database.", + "description": "Title for the template, as it exists in the database.", "type": "string", "context": [ + "view", "edit", "embed" ] }, "rendered": { - "description": "HTML content for the post, transformed for display.", - "type": "string", - "context": [ - "view", - "edit", - "embed" - ], - "readonly": true - }, - "block_version": { - "description": "Version of the content block format used by the post.", - "type": "integer", - "context": [ - "edit", - "embed" - ], - "readonly": true - }, - "protected": { - "description": "Whether the content is protected with a password.", - "type": "boolean", + "description": "HTML title for the template, transformed for display.", + "type": "string", "context": [ "view", "edit", @@ -7915,16 +8855,33 @@ mockedApiResponse.Schema = { }, "required": false }, - "template": { - "description": "The theme file to use to display the post.", + "description": { + "description": "Description of template.", + "type": "string", + "required": false + }, + "status": { + "description": "Status of template.", "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "author": { + "description": "The ID for the author of the template.", + "type": "integer", "required": false } } } ] }, - "/wp/v2/navigation/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/autosaves/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET" @@ -7936,8 +8893,8 @@ mockedApiResponse.Schema = { ], "args": { "parent": { - "description": "The ID for the parent of the autosave.", - "type": "integer", + "description": "The id of a template", + "type": "string", "required": false }, "id": { @@ -7960,11 +8917,10 @@ mockedApiResponse.Schema = { } ] }, - "/wp/v2/font-families": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions": { "namespace": "wp/v2", "methods": [ - "GET", - "POST" + "GET" ], "endpoints": [ { @@ -7972,6 +8928,11 @@ mockedApiResponse.Schema = { "GET" ], "args": { + "parent": { + "description": "The id of a template", + "type": "string", + "required": false + }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", @@ -7993,11 +8954,15 @@ mockedApiResponse.Schema = { "per_page": { "description": "Maximum number of items to be returned in result set.", "type": "integer", - "default": 10, "minimum": 1, "maximum": 100, "required": false }, + "search": { + "description": "Limit results to those matching a string.", + "type": "string", + "required": false + }, "exclude": { "description": "Ensure result set excludes specific IDs.", "type": "array", @@ -8016,14 +8981,6 @@ mockedApiResponse.Schema = { "default": [], "required": false }, - "search_semantics": { - "description": "How to interpret the search input.", - "type": "string", - "enum": [ - "exact" - ], - "required": false - }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -8040,61 +8997,28 @@ mockedApiResponse.Schema = { "required": false }, "orderby": { - "description": "Sort collection by post attribute.", + "description": "Sort collection by object attribute.", "type": "string", - "default": "id", + "default": "date", "enum": [ + "date", "id", - "include" + "include", + "relevance", + "slug", + "include_slugs", + "title" ], "required": false - }, - "slug": { - "description": "Limit result set to posts with one or more specific slugs.", - "type": "array", - "items": { - "type": "string" - }, - "required": false - } - } - }, - { - "methods": [ - "POST" - ], - "args": { - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", - "type": "integer", - "default": 3, - "minimum": 2, - "maximum": 3, - "required": false - }, - "font_family_settings": { - "description": "font-family declaration in theme.json format, encoded as a string.", - "type": "string", - "required": true } } } - ], - "_links": { - "self": [ - { - "href": "http://example.org/index.php?rest_route=/wp/v2/font-families" - } - ] - } + ] }, - "/wp/v2/font-families/(?P[\\d]+)": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)/revisions/(?P[\\d]+)": { "namespace": "wp/v2", "methods": [ "GET", - "POST", - "PUT", - "PATCH", "DELETE" ], "endpoints": [ @@ -8103,8 +9027,13 @@ mockedApiResponse.Schema = { "GET" ], "args": { + "parent": { + "description": "The id of a template", + "type": "string", + "required": false + }, "id": { - "description": "Unique identifier for the post.", + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, @@ -8123,52 +9052,30 @@ mockedApiResponse.Schema = { }, { "methods": [ - "POST", - "PUT", - "PATCH" + "DELETE" ], "args": { - "id": { - "description": "Unique identifier for the post.", - "type": "integer", - "required": false - }, - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", - "type": "integer", - "default": 3, - "minimum": 2, - "maximum": 3, + "parent": { + "description": "The id of a template", + "type": "string", "required": false }, - "font_family_settings": { - "description": "font-family declaration in theme.json format, encoded as a string.", - "type": "string", - "required": true - } - } - }, - { - "methods": [ - "DELETE" - ], - "args": { "id": { - "description": "Unique identifier for the post.", + "description": "Unique identifier for the revision.", "type": "integer", "required": false }, "force": { "type": "boolean", "default": false, - "description": "Whether to bypass Trash and force deletion.", + "description": "Required to be true, as revisions do not support trashing.", "required": false } } } ] }, - "/wp/v2/font-families/(?P[\\d]+)/font-faces": { + "/wp/v2/templates": { "namespace": "wp/v2", "methods": [ "GET", @@ -8180,11 +9087,6 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true - }, "context": { "description": "Scope under which the request is made; determines fields present in response.", "type": "string", @@ -8196,105 +9098,181 @@ mockedApiResponse.Schema = { "default": "view", "required": false }, - "page": { - "description": "Current page of the collection.", + "wp_id": { + "description": "Limit to the specified post id.", "type": "integer", - "default": 1, - "minimum": 1, "required": false }, - "per_page": { - "description": "Maximum number of items to be returned in result set.", - "type": "integer", - "default": 10, - "minimum": 1, - "maximum": 100, + "area": { + "description": "Limit to the specified template part area.", + "type": "string", "required": false }, - "exclude": { - "description": "Ensure result set excludes specific IDs.", - "type": "array", - "items": { - "type": "integer" + "post_type": { + "description": "Post type to get the templates for.", + "type": "string", + "required": false + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "slug": { + "description": "Unique slug identifying the template.", + "type": "string", + "minLength": 1, + "pattern": "[a-zA-Z0-9_\\%-]+", + "required": true + }, + "theme": { + "description": "Theme identifier for the template.", + "type": "string", + "required": false + }, + "type": { + "description": "Type of template.", + "type": "string", + "required": false + }, + "content": { + "default": "", + "description": "Content of template.", + "type": [ + "object", + "string" + ], + "properties": { + "raw": { + "description": "Content for the template, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit" + ] + }, + "block_version": { + "description": "Version of the content block format used by the template.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + } }, - "default": [], "required": false }, - "include": { - "description": "Limit result set to specific IDs.", - "type": "array", - "items": { - "type": "integer" + "title": { + "default": "", + "description": "Title of template.", + "type": [ + "object", + "string" + ], + "properties": { + "raw": { + "description": "Title for the template, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML title for the template, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } }, - "default": [], "required": false }, - "search_semantics": { - "description": "How to interpret the search input.", + "description": { + "default": "", + "description": "Description of template.", "type": "string", - "enum": [ - "exact" - ], - "required": false - }, - "offset": { - "description": "Offset the result set by a specific number of items.", - "type": "integer", "required": false }, - "order": { - "description": "Order sort attribute ascending or descending.", + "status": { + "default": "publish", + "description": "Status of template.", "type": "string", - "default": "desc", "enum": [ - "asc", - "desc" + "publish", + "future", + "draft", + "pending", + "private" ], "required": false }, - "orderby": { - "description": "Sort collection by post attribute.", - "type": "string", - "default": "id", - "enum": [ - "id", - "include" - ], + "author": { + "description": "The ID for the author of the template.", + "type": "integer", "required": false } } - }, + } + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/templates" + } + ] + } + }, + "/wp/v2/templates/lookup": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ { "methods": [ - "POST" + "GET" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", + "slug": { + "description": "The slug of the template to get the fallback for", + "type": "string", "required": true }, - "theme_json_version": { - "description": "Version of the theme.json schema used for the typography settings.", - "type": "integer", - "default": 3, - "minimum": 2, - "maximum": 3, + "is_custom": { + "description": "Indicates if a template is custom or part of the template hierarchy", + "type": "boolean", "required": false }, - "font_face_settings": { - "description": "font-face declaration in theme.json format, encoded as a string.", + "template_prefix": { + "description": "The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`", "type": "string", - "required": true + "required": false } } } - ] + ], + "_links": { + "self": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/templates/lookup" + } + ] + } }, - "/wp/v2/font-families/(?P[\\d]+)/font-faces/(?P[\\d]+)": { + "/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w%-]+)": { "namespace": "wp/v2", "methods": [ "GET", + "POST", + "PUT", + "PATCH", "DELETE" ], "endpoints": [ @@ -8303,15 +9281,10 @@ mockedApiResponse.Schema = { "GET" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true - }, "id": { - "description": "Unique identifier for the font face.", - "type": "integer", - "required": true + "description": "The id of a template", + "type": "string", + "required": false }, "context": { "description": "Scope under which the request is made; determines fields present in response.", @@ -8328,18 +9301,121 @@ mockedApiResponse.Schema = { }, { "methods": [ - "DELETE" + "POST", + "PUT", + "PATCH" ], "args": { - "font_family_id": { - "description": "The ID for the parent font family of the font face.", - "type": "integer", - "required": true - }, "id": { - "description": "Unique identifier for the font face.", + "description": "The id of a template", + "type": "string", + "required": false + }, + "slug": { + "description": "Unique slug identifying the template.", + "type": "string", + "minLength": 1, + "pattern": "[a-zA-Z0-9_\\%-]+", + "required": false + }, + "theme": { + "description": "Theme identifier for the template.", + "type": "string", + "required": false + }, + "type": { + "description": "Type of template.", + "type": "string", + "required": false + }, + "content": { + "description": "Content of template.", + "type": [ + "object", + "string" + ], + "properties": { + "raw": { + "description": "Content for the template, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit" + ] + }, + "block_version": { + "description": "Version of the content block format used by the template.", + "type": "integer", + "context": [ + "edit" + ], + "readonly": true + } + }, + "required": false + }, + "title": { + "description": "Title of template.", + "type": [ + "object", + "string" + ], + "properties": { + "raw": { + "description": "Title for the template, as it exists in the database.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ] + }, + "rendered": { + "description": "HTML title for the template, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + }, + "required": false + }, + "description": { + "description": "Description of template.", + "type": "string", + "required": false + }, + "status": { + "description": "Status of template.", + "type": "string", + "enum": [ + "publish", + "future", + "draft", + "pending", + "private" + ], + "required": false + }, + "author": { + "description": "The ID for the author of the template.", "type": "integer", - "required": true + "required": false + } + } + }, + { + "methods": [ + "DELETE" + ], + "args": { + "id": { + "description": "The id of a template", + "type": "string", + "required": false }, "force": { "type": "boolean", @@ -9726,7 +10802,8 @@ mockedApiResponse.Schema = { "wp_global_styles": "wp_global_styles", "wp_navigation": "wp_navigation", "wp_font_family": "wp_font_family", - "wp_font_face": "wp_font_face" + "wp_font_face": "wp_font_face", + "wp_registered_template": "wp_registered_template" } }, "required": false @@ -11128,6 +12205,13 @@ mockedApiResponse.Schema = { ], "required": false }, + "active_templates": { + "title": "Active Templates", + "description": "", + "type": "object", + "additionalProperties": true, + "required": false + }, "site_logo": { "title": "Logo", "description": "Site logo.", @@ -13461,7 +14545,7 @@ mockedApiResponse.TypesCollection = { "slug": "wp_template", "icon": null, "taxonomies": [], - "rest_base": "templates", + "rest_base": "wp_template", "rest_namespace": "wp/v2", "template": [], "template_lock": false, @@ -13473,7 +14557,7 @@ mockedApiResponse.TypesCollection = { ], "wp:items": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/templates" + "href": "http://example.org/index.php?rest_route=/wp/v2/wp_template" } ], "curies": [ @@ -13644,6 +14728,38 @@ mockedApiResponse.TypesCollection = { } ] } + }, + "wp_registered_template": { + "description": "Templates to include in your theme.", + "hierarchical": false, + "has_archive": false, + "name": "Templates", + "slug": "wp_registered_template", + "icon": null, + "taxonomies": [], + "rest_base": "wp_registered_template", + "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, + "_links": { + "collection": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/types" + } + ], + "wp:items": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/wp_registered_template" + } + ], + "curies": [ + { + "name": "wp", + "href": "https://api.w.org/{rel}", + "templated": true + } + ] + } } }; @@ -14251,6 +15367,7 @@ mockedApiResponse.settings = { "page_for_posts": 0, "default_ping_status": "open", "default_comment_status": "open", + "active_templates": null, "site_logo": null, "site_icon": 0 }; From ce98ab9588518ec2a19406e6d1c5679e854f49dd Mon Sep 17 00:00:00 2001 From: ella Date: Tue, 21 Oct 2025 09:58:04 +0200 Subject: [PATCH 10/11] Adjust fixture --- tests/qunit/fixtures/wp-api-generated.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 8f6b69222797e..74047f2c5c865 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -21,7 +21,13 @@ mockedApiResponse.Schema = { "wp-site-health/v1", "wp-block-editor/v1" ], - "authentication": [], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "http://example.org/wp-admin/authorize-application.php" + } + } + }, "routes": { "/": { "namespace": "", From 0f857498623ef1fef3366c90ca3ace760a22882f Mon Sep 17 00:00:00 2001 From: ella Date: Tue, 21 Oct 2025 10:10:54 +0200 Subject: [PATCH 11/11] lint --- src/wp-admin/site-editor.php | 2 +- src/wp-includes/block-template-utils.php | 6 +++--- src/wp-includes/rest-api.php | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php index 03d62ddf0eecd..d27f461c9f595 100644 --- a/src/wp-admin/site-editor.php +++ b/src/wp-admin/site-editor.php @@ -245,7 +245,7 @@ static function ( $classes ) { ); } } -} else if ( isset( $_GET['p'] ) && '/' !== $_GET['p'] ) { +} elseif ( isset( $_GET['p'] ) && '/' !== $_GET['p'] ) { // Only prefetch for the root. If we preload it for all pages and it's not // used it won't be possible to invalidate. $preload_paths[] = '/wp/v2/templates/lookup?slug=front-page'; diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 88e3cfe8103c9..b0ca8c6caff43 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -1363,7 +1363,7 @@ function get_block_template( $id, $template_type = 'wp_template' ) { } } - $wp_query_args = array( + $wp_query_args = array( 'post_name__in' => array( $slug ), 'post_type' => $template_type, 'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ), @@ -1377,8 +1377,8 @@ function get_block_template( $id, $template_type = 'wp_template' ) { ), ), ); - $template_query = new WP_Query( $wp_query_args ); - $posts = $template_query->posts; + $template_query = new WP_Query( $wp_query_args ); + $posts = $template_query->posts; if ( count( $posts ) > 0 ) { $template = _build_block_template_result_from_post( $posts[0] ); diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 79caa617902ae..8402f2d8e3c38 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -314,9 +314,9 @@ function create_initial_rest_routes() { $wp_post_types['wp_template']->revisions_rest_controller_class = 'WP_REST_Template_Revisions_Controller'; // Initialize the controllers. The order is important: the autosave // controller needs both the templates and revisions controllers. - $controller = new WP_REST_Templates_Controller( 'wp_template' ); - $wp_post_types['wp_template']->rest_controller = $controller; - $revisions_controller = new WP_REST_Template_Revisions_Controller( 'wp_template' ); + $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $wp_post_types['wp_template']->rest_controller = $controller; + $revisions_controller = new WP_REST_Template_Revisions_Controller( 'wp_template' ); $wp_post_types['wp_template']->revisions_rest_controller = $revisions_controller; $autosaves_controller = new WP_REST_Template_Autosaves_Controller( 'wp_template' ); // Unset the controller cache, it will be re-initialized when @@ -324,8 +324,8 @@ function create_initial_rest_routes() { $wp_post_types['wp_template']->rest_controller = null; $wp_post_types['wp_template']->revisions_rest_controller = null; // Restore the original classes. - $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; - $wp_post_types['wp_template']->autosave_rest_controller_class = $original_autosave_rest_controller_class; + $wp_post_types['wp_template']->rest_controller_class = $original_rest_controller_class; + $wp_post_types['wp_template']->autosave_rest_controller_class = $original_autosave_rest_controller_class; $wp_post_types['wp_template']->revisions_rest_controller_class = $original_revisions_rest_controller_class; // Restore the original base. $wp_post_types['wp_template']->rest_base = 'wp_template';