diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index db1a76d17e721..32c6acc06d8b1 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -237,6 +237,7 @@
+
diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php
index 54413d809bc78..a16fe2914c821 100644
--- a/src/wp-includes/block-template-utils.php
+++ b/src/wp-includes/block-template-utils.php
@@ -147,7 +147,7 @@ function get_default_block_template_types() {
),
'category' => array(
'title' => _x( 'Category', 'Template name' ),
- 'description' => __( 'Displays latest posts in single post category.' ),
+ 'description' => __( 'Displays latest posts from a single post category.' ),
),
'taxonomy' => array(
'title' => _x( 'Taxonomy', 'Template name' ),
@@ -555,7 +555,8 @@ function _build_block_template_result_from_post( $post ) {
$template_file = _get_block_template_file( $post->post_type, $post->post_name );
$has_theme_file = wp_get_theme()->get_stylesheet() === $theme && null !== $template_file;
- $origin = get_post_meta( $post->ID, 'origin', true );
+ $origin = get_post_meta( $post->ID, 'origin', true );
+ $is_wp_suggestion = get_post_meta( $post->ID, 'is_wp_suggestion', true );
$template = new WP_Block_Template();
$template->wp_id = $post->ID;
@@ -570,7 +571,7 @@ function _build_block_template_result_from_post( $post ) {
$template->title = $post->post_title;
$template->status = $post->post_status;
$template->has_theme_file = $has_theme_file;
- $template->is_custom = true;
+ $template->is_custom = empty( $is_wp_suggestion );
$template->author = $post->post_author;
if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
@@ -679,7 +680,8 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' )
continue;
}
- if ( $post_type &&
+ if (
+ $post_type &&
isset( $template->post_types ) &&
! in_array( $post_type, $template->post_types, true )
) {
@@ -912,9 +914,10 @@ function block_footer_area() {
* @return Bool Whether this file is in an ignored directory.
*/
function wp_is_theme_directory_ignored( $path ) {
- $directories_to_ignore = array( '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' );
+ $directories_to_ignore = array( '.DS_Store', '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' );
+
foreach ( $directories_to_ignore as $directory ) {
- if ( strpos( $path, $directory ) === 0 ) {
+ if ( str_starts_with( $path, $directory ) ) {
return true;
}
}
@@ -1023,3 +1026,74 @@ function wp_generate_block_templates_export_file() {
return $filename;
}
+
+/**
+ * Gets the template hierarchy for the given template slug to be created.
+ *
+ *
+ * Note: Always add `index` as the last fallback template.
+ *
+ * @since 6.1.0
+ *
+ * @param string $slug The template slug to be created.
+ * @param boolean $is_custom Optional. Indicates if a template is custom or
+ * part of the template hierarchy. Default false.
+ * @param string $template_prefix Optional. The template prefix for the created template.
+ * Used to extract the main template type, e.g.
+ * in `taxonomy-books` the `taxonomy` is extracted.
+ * Default empty string.
+ * @return string[] The template hierarchy.
+ */
+function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) {
+ if ( 'index' === $slug ) {
+ return array( 'index' );
+ }
+ if ( $is_custom ) {
+ return array( 'page', 'singular', 'index' );
+ }
+ if ( 'front-page' === $slug ) {
+ return array( 'front-page', 'home', 'index' );
+ }
+
+ $template_hierarchy = array( $slug );
+
+ // Most default templates don't have `$template_prefix` assigned.
+ if ( $template_prefix ) {
+ list( $type ) = explode( '-', $template_prefix );
+ // These checks are needed because the `$slug` above is always added.
+ if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) {
+ $template_hierarchy[] = $template_prefix;
+ }
+ if ( $slug !== $type ) {
+ $template_hierarchy[] = $type;
+ }
+ }
+
+ // Handle `archive` template.
+ if (
+ str_starts_with( $slug, 'author' ) ||
+ str_starts_with( $slug, 'taxonomy' ) ||
+ str_starts_with( $slug, 'category' ) ||
+ str_starts_with( $slug, 'tag' ) ||
+ 'date' === $slug
+ ) {
+ $template_hierarchy[] = 'archive';
+ }
+ // Handle `single` template.
+ if ( 'attachment' === $slug ) {
+ $template_hierarchy[] = 'single';
+ }
+
+ // Handle `singular` template.
+ if (
+ str_starts_with( $slug, 'single' ) ||
+ str_starts_with( $slug, 'page' ) ||
+ 'attachment' === $slug
+ ) {
+ $template_hierarchy[] = 'singular';
+ }
+
+ $template_hierarchy[] = 'index';
+
+ return $template_hierarchy;
+};
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
index cb2daa4b292c6..8223f1dd6f38e 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
@@ -186,54 +186,58 @@ public function prepare_item_for_response( $item, $request ) {
$fields = $this->get_fields_for_response( $request );
$data = array();
- if ( in_array( 'capabilities', $fields, true ) ) {
+ if ( rest_is_field_included( 'capabilities', $fields ) ) {
$data['capabilities'] = $post_type->cap;
}
- if ( in_array( 'description', $fields, true ) ) {
+ if ( rest_is_field_included( 'description', $fields ) ) {
$data['description'] = $post_type->description;
}
- if ( in_array( 'hierarchical', $fields, true ) ) {
+ if ( rest_is_field_included( 'hierarchical', $fields ) ) {
$data['hierarchical'] = $post_type->hierarchical;
}
- if ( in_array( 'visibility', $fields, true ) ) {
+ if ( rest_is_field_included( 'visibility', $fields ) ) {
$data['visibility'] = array(
'show_in_nav_menus' => (bool) $post_type->show_in_nav_menus,
'show_ui' => (bool) $post_type->show_ui,
);
}
- if ( in_array( 'viewable', $fields, true ) ) {
+ if ( rest_is_field_included( 'viewable', $fields ) ) {
$data['viewable'] = is_post_type_viewable( $post_type );
}
- if ( in_array( 'labels', $fields, true ) ) {
+ if ( rest_is_field_included( 'labels', $fields ) ) {
$data['labels'] = $post_type->labels;
}
- if ( in_array( 'name', $fields, true ) ) {
+ if ( rest_is_field_included( 'name', $fields ) ) {
$data['name'] = $post_type->label;
}
- if ( in_array( 'slug', $fields, true ) ) {
+ if ( rest_is_field_included( 'slug', $fields ) ) {
$data['slug'] = $post_type->name;
}
- if ( in_array( 'supports', $fields, true ) ) {
+ if ( rest_is_field_included( 'icon', $fields ) ) {
+ $data['icon'] = $post_type->menu_icon;
+ }
+
+ if ( rest_is_field_included( 'supports', $fields ) ) {
$data['supports'] = $supports;
}
- if ( in_array( 'taxonomies', $fields, true ) ) {
+ if ( rest_is_field_included( 'taxonomies', $fields ) ) {
$data['taxonomies'] = array_values( $taxonomies );
}
- if ( in_array( 'rest_base', $fields, true ) ) {
+ if ( rest_is_field_included( 'rest_base', $fields ) ) {
$data['rest_base'] = $base;
}
- if ( in_array( 'rest_namespace', $fields, true ) ) {
+ if ( rest_is_field_included( 'rest_namespace', $fields ) ) {
$data['rest_namespace'] = $namespace;
}
@@ -287,6 +291,7 @@ protected function prepare_links( $post_type ) {
* @since 4.7.0
* @since 4.8.0 The `supports` property was added.
* @since 5.9.0 The `visibility` and `rest_namespace` properties were added.
+ * @since 6.1.0 The `icon` property was added.
*
* @return array Item schema data.
*/
@@ -385,6 +390,12 @@ public function get_item_schema() {
),
),
),
+ 'icon' => array(
+ 'description' => __( 'The icon for the post type.' ),
+ 'type' => array( 'string', 'null' ),
+ 'context' => array( 'view', 'edit', 'embed' ),
+ 'readonly' => true,
+ ),
),
);
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
index 7e60674890646..e26b4d17e5ebe 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
@@ -42,6 +42,7 @@ public function __construct( $post_type ) {
* Registers the controllers routes.
*
* @since 5.8.0
+ * @since 6.1.0 Endpoint for fallback template content.
*/
public function register_routes() {
// Lists all templates.
@@ -65,6 +66,34 @@ public function register_routes() {
)
);
+ // Get fallback template content.
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/lookup',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_template_fallback' ),
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
+ 'args' => array(
+ 'slug' => array(
+ 'description' => __( 'The slug of the template to get the fallback for' ),
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ 'is_custom' => array(
+ 'description' => __( ' Indicates if a template is custom or part of the template hierarchy' ),
+ 'type' => 'boolean',
+ ),
+ 'template_prefix' => array(
+ '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',
+ ),
+ ),
+ ),
+ )
+ );
+
// Lists/updates a single template based on the given id.
register_rest_route(
$this->namespace,
@@ -117,6 +146,21 @@ public function register_routes() {
);
}
+ /**
+ * Returns the fallback template for the given slug.
+ *
+ * @since 6.1.0
+ *
+ * @param WP_REST_Request $request The request instance.
+ * @return WP_REST_Response|WP_Error
+ */
+ public function get_template_fallback( $request ) {
+ $hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] );
+ $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
+ $response = $this->prepare_item_for_response( $fallback_template, $request );
+ return rest_ensure_response( $response );
+ }
+
/**
* Checks if the user has permissions to make the request.
*
@@ -525,6 +569,15 @@ protected function prepare_item_for_database( $request ) {
$changes->post_excerpt = $template->description;
}
+ if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) {
+ $changes->meta_input = wp_parse_args(
+ array(
+ 'is_wp_suggestion' => $request['is_wp_suggestion'],
+ ),
+ $changes->meta_input = array()
+ );
+ }
+
if ( 'wp_template_part' === $this->post_type ) {
if ( isset( $request['area'] ) ) {
$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
diff --git a/tests/phpunit/tests/block-templates/base.php b/tests/phpunit/tests/block-templates/base.php
new file mode 100644
index 0000000000000..f6a5a9f4dfcec
--- /dev/null
+++ b/tests/phpunit/tests/block-templates/base.php
@@ -0,0 +1,86 @@
+post->create_and_get(
+ array(
+ 'post_type' => 'wp_template',
+ 'post_name' => 'my_template',
+ 'post_title' => 'My Template',
+ 'post_content' => 'Content',
+ 'post_excerpt' => 'Description of my template',
+ 'tax_input' => array(
+ 'wp_theme' => array(
+ 'this-theme-should-not-resolve',
+ ),
+ ),
+ )
+ );
+
+ wp_set_post_terms( self::$template_post->ID, 'this-theme-should-not-resolve', 'wp_theme' );
+
+ // Set up template post.
+ self::$template_post = $factory->post->create_and_get(
+ array(
+ 'post_type' => 'wp_template',
+ 'post_name' => 'my_template',
+ 'post_title' => 'My Template',
+ 'post_content' => 'Content',
+ 'post_excerpt' => 'Description of my template',
+ 'tax_input' => array(
+ 'wp_theme' => array(
+ self::TEST_THEME,
+ ),
+ ),
+ )
+ );
+
+ wp_set_post_terms( self::$template_post->ID, self::TEST_THEME, 'wp_theme' );
+
+ // Set up template part post.
+ self::$template_part_post = $factory->post->create_and_get(
+ array(
+ 'post_type' => 'wp_template_part',
+ 'post_name' => 'my_template_part',
+ 'post_title' => 'My Template Part',
+ 'post_content' => 'Content',
+ 'post_excerpt' => 'Description of my template part',
+ 'tax_input' => array(
+ 'wp_theme' => array(
+ self::TEST_THEME,
+ ),
+ 'wp_template_part_area' => array(
+ WP_TEMPLATE_PART_AREA_HEADER,
+ ),
+ ),
+ )
+ );
+
+ wp_set_post_terms( self::$template_part_post->ID, WP_TEMPLATE_PART_AREA_HEADER, 'wp_template_part_area' );
+ wp_set_post_terms( self::$template_part_post->ID, self::TEST_THEME, 'wp_theme' );
+ }
+
+ public static function wpTearDownAfterClass() {
+ wp_delete_post( self::$template_post->ID );
+ wp_delete_post( self::$template_part_post->ID );
+ }
+
+ public function set_up() {
+ parent::set_up();
+ switch_theme( self::TEST_THEME );
+ }
+}
diff --git a/tests/phpunit/tests/block-templates/getTemplateHierarchy.php b/tests/phpunit/tests/block-templates/getTemplateHierarchy.php
new file mode 100644
index 0000000000000..e550b22778417
--- /dev/null
+++ b/tests/phpunit/tests/block-templates/getTemplateHierarchy.php
@@ -0,0 +1,124 @@
+assertSame( $expected, get_template_hierarchy( ...$args ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_get_template_hierarchy() {
+ return array(
+ 'front-page' => array(
+ 'args' => array( 'front-page' ),
+ 'expected' => array( 'front-page', 'home', 'index' ),
+ ),
+ 'custom template' => array(
+ 'args' => array( 'whatever-slug', true ),
+ 'expected' => array( 'page', 'singular', 'index' ),
+ ),
+ 'page' => array(
+ 'args' => array( 'page' ),
+ 'expected' => array( 'page', 'singular', 'index' ),
+ ),
+ 'tag' => array(
+ 'args' => array( 'tag' ),
+ 'expected' => array( 'tag', 'archive', 'index' ),
+ ),
+ 'author' => array(
+ 'args' => array( 'author' ),
+ 'expected' => array( 'author', 'archive', 'index' ),
+ ),
+ 'date' => array(
+ 'args' => array( 'date' ),
+ 'expected' => array( 'date', 'archive', 'index' ),
+ ),
+ 'taxonomy' => array(
+ 'args' => array( 'taxonomy' ),
+ 'expected' => array( 'taxonomy', 'archive', 'index' ),
+ ),
+ 'attachment' => array(
+ 'args' => array( 'attachment' ),
+ 'expected' => array( 'attachment', 'single', 'singular', 'index' ),
+ ),
+ 'singular' => array(
+ 'args' => array( 'singular' ),
+ 'expected' => array( 'singular', 'index' ),
+ ),
+ 'single' => array(
+ 'args' => array( 'single' ),
+ 'expected' => array( 'single', 'singular', 'index' ),
+ ),
+ 'archive' => array(
+ 'args' => array( 'archive' ),
+ 'expected' => array( 'archive', 'index' ),
+ ),
+ 'index' => array(
+ 'args' => array( 'index' ),
+ 'expected' => array( 'index' ),
+ ),
+ 'specific taxonomies' => array(
+ 'args' => array( 'taxonomy-books', false, 'taxonomy-books' ),
+ 'expected' => array( 'taxonomy-books', 'taxonomy', 'archive', 'index' ),
+ ),
+ 'single word categories' => array(
+ 'args' => array( 'category-fruits', false, 'category' ),
+ 'expected' => array( 'category-fruits', 'category', 'archive', 'index' ),
+ ),
+ 'multi word categories' => array(
+ 'args' => array( 'category-fruits-yellow', false, 'category' ),
+ 'expected' => array( 'category-fruits-yellow', 'category', 'archive', 'index' ),
+ ),
+ 'single word taxonomy and term' => array(
+ 'args' => array( 'taxonomy-books-action', false, 'taxonomy-books' ),
+ 'expected' => array( 'taxonomy-books-action', 'taxonomy-books', 'taxonomy', 'archive', 'index' ),
+ ),
+ 'single word taxonomy and multi word term' => array(
+ 'args' => array( 'taxonomy-books-action-adventure', false, 'taxonomy-books' ),
+ 'expected' => array( 'taxonomy-books-action-adventure', 'taxonomy-books', 'taxonomy', 'archive', 'index' ),
+ ),
+ 'multi word taxonomy and term' => array(
+ 'args' => array( 'taxonomy-greek-books-action-adventure', false, 'taxonomy-greek-books' ),
+ 'expected' => array( 'taxonomy-greek-books-action-adventure', 'taxonomy-greek-books', 'taxonomy', 'archive', 'index' ),
+ ),
+ 'single word post type' => array(
+ 'args' => array( 'single-book', false, 'single-book' ),
+ 'expected' => array( 'single-book', 'single', 'singular', 'index' ),
+ ),
+ 'multi word post type' => array(
+ 'args' => array( 'single-art-project', false, 'single-art-project' ),
+ 'expected' => array( 'single-art-project', 'single', 'singular', 'index' ),
+ ),
+ 'single post with multi word post type' => array(
+ 'args' => array( 'single-art-project-imagine', false, 'single-art-project' ),
+ 'expected' => array( 'single-art-project-imagine', 'single-art-project', 'single', 'singular', 'index' ),
+ ),
+ 'single page' => array(
+ 'args' => array( 'page-hi', false, 'page' ),
+ 'expected' => array( 'page-hi', 'page', 'singular', 'index' ),
+ ),
+ 'authors' => array(
+ 'args' => array( 'author-rigas', false, 'author' ),
+ 'expected' => array( 'author-rigas', 'author', 'archive', 'index' ),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/rest-api/rest-post-types-controller.php b/tests/phpunit/tests/rest-api/rest-post-types-controller.php
index 0ab2a7b937005..982fb4e04a23d 100644
--- a/tests/phpunit/tests/rest-api/rest-post-types-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-post-types-controller.php
@@ -156,24 +156,31 @@ public function test_prepare_item_limit_fields() {
);
}
+ /**
+ * @ticket 56467
+ *
+ * @covers WP_REST_Post_Types_Controller::get_item_schema
+ */
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
- $this->assertCount( 12, $properties );
- $this->assertArrayHasKey( 'capabilities', $properties );
- $this->assertArrayHasKey( 'description', $properties );
- $this->assertArrayHasKey( 'hierarchical', $properties );
- $this->assertArrayHasKey( 'viewable', $properties );
- $this->assertArrayHasKey( 'labels', $properties );
- $this->assertArrayHasKey( 'name', $properties );
- $this->assertArrayHasKey( 'slug', $properties );
- $this->assertArrayHasKey( 'supports', $properties );
- $this->assertArrayHasKey( 'taxonomies', $properties );
- $this->assertArrayHasKey( 'rest_base', $properties );
- $this->assertArrayHasKey( 'rest_namespace', $properties );
- $this->assertArrayHasKey( 'visibility', $properties );
+
+ $this->assertCount( 13, $properties, 'Schema should have 13 properties' );
+ $this->assertArrayHasKey( 'capabilities', $properties, '`capabilities` should be included in the schema' );
+ $this->assertArrayHasKey( 'description', $properties, '`description` should be included in the schema' );
+ $this->assertArrayHasKey( 'hierarchical', $properties, '`hierarchical` should be included in the schema' );
+ $this->assertArrayHasKey( 'viewable', $properties, '`viewable` should be included in the schema' );
+ $this->assertArrayHasKey( 'labels', $properties, '`labels` should be included in the schema' );
+ $this->assertArrayHasKey( 'name', $properties, '`name` should be included in the schema' );
+ $this->assertArrayHasKey( 'slug', $properties, '`slug` should be included in the schema' );
+ $this->assertArrayHasKey( 'supports', $properties, '`supports` should be included in the schema' );
+ $this->assertArrayHasKey( 'taxonomies', $properties, '`taxonomies` should be included in the schema' );
+ $this->assertArrayHasKey( 'rest_base', $properties, '`rest_base` should be included in the schema' );
+ $this->assertArrayHasKey( 'rest_namespace', $properties, '`rest_namespace` should be included in the schema' );
+ $this->assertArrayHasKey( 'visibility', $properties, '`visibility` should be included in the schema' );
+ $this->assertArrayHasKey( 'icon', $properties, '`icon` should be included in the schema' );
}
public function test_get_additional_field_registration() {
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
index a1e4330c0cc4a..afc1ebc125830 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-setup.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php
@@ -150,12 +150,14 @@ public function test_expected_routes_in_schema() {
'/wp/v2/template-parts/(?P[\d]+)/autosaves/(?P[\d]+)',
'/wp/v2/template-parts/(?P[\d]+)/revisions',
'/wp/v2/template-parts/(?P[\d]+)/revisions/(?P[\d]+)',
+ '/wp/v2/template-parts/lookup',
'/wp/v2/templates',
'/wp/v2/templates/(?P[\d]+)/autosaves',
'/wp/v2/templates/(?P([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w-]+)',
'/wp/v2/templates/(?P[\d]+)/autosaves/(?P[\d]+)',
'/wp/v2/templates/(?P[\d]+)/revisions',
'/wp/v2/templates/(?P[\d]+)/revisions/(?P[\d]+)',
+ '/wp/v2/templates/lookup',
'/wp/v2/themes',
'/wp/v2/themes/(?P[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
'/wp/v2/plugins',
diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
index c53701f9b6a8c..0ddd023c012f2 100644
--- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
+++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php
@@ -56,6 +56,7 @@ public static function wpTearDownAfterClass() {
/**
* @covers WP_REST_Templates_Controller::register_routes
* @ticket 54596
+ * @ticket 56467
*/
public function test_register_routes() {
$routes = rest_get_server()->get_routes();
@@ -69,6 +70,11 @@ public function test_register_routes() {
$routes,
'Single template based on the given ID route does not exist'
);
+ $this->assertArrayHasKey(
+ '/wp/v2/templates/lookup',
+ $routes,
+ 'Get template fallback content route does not exist'
+ );
}
/**
@@ -678,4 +684,109 @@ protected function find_and_normalize_template_by_id( $templates, $id ) {
return null;
}
+ /**
+ * @dataProvider data_create_item_with_is_wp_suggestion
+ * @ticket 56467
+ * @covers WP_REST_Templates_Controller::create_item
+ *
+ * @param array $body_params Data set to test.
+ * @param array $expected Expected results.
+ */
+ public function test_create_item_with_is_wp_suggestion( array $body_params, array $expected ) {
+ // Set up the user.
+ $body_params['author'] = self::$admin_id;
+ $expected['author'] = self::$admin_id;
+ wp_set_current_user( self::$admin_id );
+
+ $request = new WP_REST_Request( 'POST', '/wp/v2/templates' );
+ $request->set_body_params( $body_params );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ unset( $data['_links'] );
+ unset( $data['wp_id'] );
+
+ $this->assertSame( $expected, $data );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_create_item_with_is_wp_suggestion() {
+ $expected = array(
+ 'id' => 'default//page-rigas',
+ 'theme' => 'default',
+ 'content' => array(
+ 'raw' => 'Content',
+ ),
+ 'slug' => 'page-rigas',
+ 'source' => 'custom',
+ 'origin' => null,
+ 'type' => 'wp_template',
+ 'description' => 'Just a description',
+ 'title' => array(
+ 'raw' => 'My Template',
+ 'rendered' => 'My Template',
+ ),
+ 'status' => 'publish',
+ 'has_theme_file' => false,
+ 'is_custom' => false,
+ 'author' => null,
+ );
+
+ return array(
+ 'is_wp_suggestion: true' => array(
+ 'body_params' => array(
+ 'slug' => 'page-rigas',
+ 'description' => 'Just a description',
+ 'title' => 'My Template',
+ 'content' => 'Content',
+ 'is_wp_suggestion' => true,
+ 'author' => null,
+ ),
+ 'expected' => $expected,
+ ),
+ 'is_wp_suggestion: false' => array(
+ 'body_params' => array(
+ 'slug' => 'page-hi',
+ 'description' => 'Just a description',
+ 'title' => 'My Template',
+ 'content' => 'Content',
+ 'is_wp_suggestion' => false,
+ 'author' => null,
+ ),
+ 'expected' => array_merge(
+ $expected,
+ array(
+ 'id' => 'default//page-hi',
+ 'slug' => 'page-hi',
+ 'is_custom' => true,
+ )
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @ticket 56467
+ * @covers WP_REST_Templates_Controller::get_template_fallback
+ */
+ public function test_get_template_fallback() {
+ wp_set_current_user( self::$admin_id );
+ switch_theme( 'block-theme' );
+ $request = new WP_REST_Request( 'GET', '/wp/v2/templates/lookup' );
+ // Should fallback to `index.html`.
+ $request->set_param( 'slug', 'tag-status' );
+ $request->set_param( 'is_custom', false );
+ $request->set_param( 'template_prefix', 'tag' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 'index', $response->get_data()['slug'], 'Should fallback to `index.html`.' );
+ // Should fallback to `page.html`.
+ $request->set_param( 'slug', 'page-hello' );
+ $request->set_param( 'is_custom', false );
+ $request->set_param( 'template_prefix', 'page' );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 'page', $response->get_data()['slug'], 'Should fallback to `page.html`.' );
+ }
}
diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js
index 4020b25da844d..051f176fc4a7f 100644
--- a/tests/qunit/fixtures/wp-api-generated.js
+++ b/tests/qunit/fixtures/wp-api-generated.js
@@ -5140,6 +5140,43 @@ mockedApiResponse.Schema = {
]
}
},
+ "/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",
+ "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`",
+ "type": "string",
+ "required": false
+ }
+ }
+ }
+ ],
+ "_links": {
+ "self": [
+ {
+ "href": "http://example.org/index.php?rest_route=/wp/v2/templates/lookup"
+ }
+ ]
+ }
+ },
"/wp/v2/templates/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
"namespace": "wp/v2",
"methods": [
@@ -5792,6 +5829,43 @@ mockedApiResponse.Schema = {
]
}
},
+ "/wp/v2/template-parts/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",
+ "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`",
+ "type": "string",
+ "required": false
+ }
+ }
+ }
+ ],
+ "_links": {
+ "self": [
+ {
+ "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts/lookup"
+ }
+ ]
+ }
+ },
"/wp/v2/template-parts/(?P([^\\/:<>\\*\\?\"\\|]+(?:\\/[^\\/:<>\\*\\?\"\\|]+)?)[\\/\\w-]+)": {
"namespace": "wp/v2",
"methods": [
@@ -11591,6 +11665,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Posts",
"slug": "post",
+ "icon": "dashicons-admin-post",
"taxonomies": [
"category",
"post_tag"
@@ -11622,6 +11697,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": true,
"name": "Pages",
"slug": "page",
+ "icon": "dashicons-admin-page",
"taxonomies": [],
"rest_base": "pages",
"rest_namespace": "wp/v2",
@@ -11650,6 +11726,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Media",
"slug": "attachment",
+ "icon": "dashicons-admin-media",
"taxonomies": [],
"rest_base": "media",
"rest_namespace": "wp/v2",
@@ -11678,6 +11755,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Navigation Menu Items",
"slug": "nav_menu_item",
+ "icon": null,
"taxonomies": [
"nav_menu"
],
@@ -11708,6 +11786,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Reusable blocks",
"slug": "wp_block",
+ "icon": null,
"taxonomies": [],
"rest_base": "blocks",
"rest_namespace": "wp/v2",
@@ -11736,6 +11815,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Templates",
"slug": "wp_template",
+ "icon": null,
"taxonomies": [],
"rest_base": "templates",
"rest_namespace": "wp/v2",
@@ -11764,6 +11844,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Template Parts",
"slug": "wp_template_part",
+ "icon": null,
"taxonomies": [],
"rest_base": "template-parts",
"rest_namespace": "wp/v2",
@@ -11792,6 +11873,7 @@ mockedApiResponse.TypesCollection = {
"hierarchical": false,
"name": "Navigation Menus",
"slug": "wp_navigation",
+ "icon": null,
"taxonomies": [],
"rest_base": "navigation",
"rest_namespace": "wp/v2",
@@ -11822,6 +11904,7 @@ mockedApiResponse.TypeModel = {
"hierarchical": false,
"name": "Posts",
"slug": "post",
+ "icon": "dashicons-admin-post",
"taxonomies": [
"category",
"post_tag"