Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FSE: Fall back to next best template in hierarchy when querying through REST API (Take Two) #41848

Closed
103 changes: 79 additions & 24 deletions lib/compat/wordpress-6.1/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,73 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t
return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
}

function gutenberg_get_template_slugs( $template ) {
$limit = 2;
if ( strpos( $template, 'single-' ) === 0 || strpos( $template, 'taxonomy-' ) === 0 ) {
// E.g. single-post-mypost or taxonomy-recipes-vegetarian.
$limit = 3;
}
$parts = explode( '-', $template, $limit );
$type = array_shift( $parts );
$slugs = array( $type );

foreach ( $parts as $part ) {
array_unshift( $slugs, $slugs[0] . '-' . $part );
}
return $slugs;
}

function gutenberg_get_block_template_type_for_slug( $slug ) {
$slug_parts = explode( '-', $slug, 3 );

if ( count( $slug_parts ) > 1 ) {
if ( 'single' === $slug_parts [0] ) {
// Get CPT labels
$post_type = get_post_type_object( $slug_parts[1] );
$labels = $post_type->labels;

if ( count( $slug_parts ) > 2 ) {
// Now we look for the CPT with slug as defined in $slug_parts[2]
$post = get_page_by_path( $slug_parts[2], OBJECT, $slug_parts[1] );
$title = sprintf(
// translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the singular name of a post type and %2$s is the name of the post, e.g. "Post: Hello, WordPress"
__( '%1$s: %2$s' ),
$labels->singular_name,
$post->post_title
);
$description = sprintf(
// translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Post: Hello, WordPress"
__( 'Template for %1$s' ),
$title
);
} else {
$title = sprintf(
// translators: %s: Name of the post type e.g: "Post".
__( 'Single item: %s' ),
$labels->singular_name
);
$description = sprintf(
// translators: %s: Name of the post type e.g: "Post".
__( 'Displays a single item: %s.' ),
$labels->singular_name
);
}
}
} else {
$default_template_types = get_default_block_template_types();

if ( array_key_exists( $slug, $default_template_types ) ) {
$title = $default_template_types[ $slug ]['title'];
$description = $default_template_types[ $slug ]['description'];
}
}

return array(
'title' => $title,
'description' => $description,
);
}

/**
* Retrieves a single unified template object using its id.
*
Expand Down Expand Up @@ -195,33 +262,21 @@ function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) {
if ( count( $parts ) < 2 ) {
return null;
}
list( $theme, $slug ) = $parts;
$wp_query_args = array(
'post_name__in' => array( $slug ),
'post_type' => $template_type,
'post_status' => array( 'auto-draft', 'draft', 'publish', 'trash' ),
'posts_per_page' => 1,
'no_found_rows' => true,
'tax_query' => array(
array(
'taxonomy' => 'wp_theme',
'field' => 'name',
'terms' => $theme,
),
),
);
$template_query = new WP_Query( $wp_query_args );
$posts = $template_query->posts;

if ( count( $posts ) > 0 ) {
$template = gutenberg_build_block_template_result_from_post( $posts[0] );
list( , $slug ) = $parts;

if ( ! is_wp_error( $template ) ) {
return $template;
}
$templates = gutenberg_get_template_slugs( $slug );
$block_template = resolve_block_template( $slug, $templates, '' );
if ( ! $block_template ) {
$block_template = resolve_block_template( 'index', array(), '' );
}
// This might give us a fallback template with a different ID,
// so we have to override it to make sure it's correct.
$block_template->id = $id;
$block_template->slug = $slug;

$block_template = get_block_file_template( $id, $template_type );
$template_type_info = gutenberg_get_block_template_type_for_slug( $slug );
$block_template->title = $template_type_info['title'];
$block_template->description = $template_type_info['description'];

/**
* Filters the queried block template object after it's been fetched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ public function create_item( $request ) {
);
}

/**
* Deletes a single template.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$template = get_block_template( $request['id'], $this->post_type );
if ( ! $template || $template->id !== $request['id'] ) { // Make sure there is a template for this ID (and not just a fallback one).
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
}

return parent::delete_item( $request );
}

/**
* Updates a single template.
*
Expand Down
50 changes: 11 additions & 39 deletions packages/edit-site/src/components/add-new-template/new-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
tag,
} from '@wordpress/icons';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
Expand Down Expand Up @@ -79,7 +78,7 @@ export default function NewTemplate( { postType } ) {
const [ showCustomTemplateModal, setShowCustomTemplateModal ] =
useState( false );
const [ entityForSuggestions, setEntityForSuggestions ] = useState( {} );
const { existingTemplates, defaultTemplateTypes } = useSelect(
const { existingTemplates, defaultTemplateTypes, theme } = useSelect(
( select ) => ( {
existingTemplates: select( coreStore ).getEntityRecords(
'postType',
Expand All @@ -88,52 +87,25 @@ export default function NewTemplate( { postType } ) {
),
defaultTemplateTypes:
select( editorStore ).__experimentalGetDefaultTemplateTypes(),
theme: select( coreStore ).getCurrentTheme(),
} ),
[]
);
const postTypesEntitiesInfo = usePostTypesEntitiesInfo( existingTemplates );
const { saveEntityRecord } = useDispatch( coreStore );
const { createErrorNotice } = useDispatch( noticesStore );

const { setTemplate } = useDispatch( editSiteStore );

async function createTemplate( template ) {
try {
const { title, description, slug } = template;
const newTemplate = await saveEntityRecord(
'postType',
'wp_template',
{
description,
// Slugs need to be strings, so this is for template `404`
slug: slug.toString(),
status: 'publish',
title,
// This adds a post meta field in template that is part of `is_custom` value calculation.
is_wp_suggestion: true,
},
{ throwOnError: true }
);

// Set template before navigating away to avoid initial stale value.
setTemplate( newTemplate.id, newTemplate.slug );

// Navigate to the created template editor.
history.push( {
postId: newTemplate.id,
postType: newTemplate.type,
} );
const { slug } = template;
const templateId = theme.stylesheet + '//' + slug.toString();

// TODO: Add a success notice?
} catch ( error ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while creating the template.' );
// Set template before navigating away to avoid initial stale value.
setTemplate( templateId, slug );

createErrorNotice( errorMessage, {
type: 'snackbar',
} );
}
history.push( {
postId: templateId,
postType: 'wp_template',
} );
}
const existingTemplateSlugs = ( existingTemplates || [] ).map(
( { slug } ) => slug
Expand Down
67 changes: 67 additions & 0 deletions phpunit/compat/wordpress-6.1/block-template-utils-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* Tests_Block_Template_Utils class
*
* @package gutenberg
*/

/**
* Tests for the Block Templates abstraction layer.
*
* @group block-templates
*/
class Tests_Block_Template_Utils_6_1 extends WP_UnitTestCase {
private static $post;

public static function wpSetUpBeforeClass() {
$args = array(
'post_type' => 'post',
'post_name' => 'hello-world',
'post_title' => 'Hello World!',
'post_content' => 'Welcome to WordPress. This is your first post. Edit or delete it, then start writing!',
'post_excerpt' => 'Welcome to WordPress.',
);
self::$post = self::factory()->post->create_and_get( $args );
}

public static function wpTearDownAfterClass() {
wp_delete_post( self::$post->ID );
}

public function test_get_template_slugs() {
$templates = gutenberg_get_template_slugs( 'single-post-hello-world' );
$this->assertSame(
array(
'single-post-hello-world',
// Should *not* fall back to `single-post-hello`!
'single-post',
'single',
),
$templates
);
}

public function test_gutenberg_get_block_template_type_for_slug_generic() {
$template_slug = 'single-' . self::$post->post_type;
$template_info = gutenberg_get_block_template_type_for_slug( $template_slug );
$this->assertSame(
array(
'title' => 'Single item: Post',
'description' => 'Displays a single item: Post.',
),
$template_info
);
}

public function test_gutenberg_get_block_template_type_for_slug_individual() {
$template_slug = 'single-' . self::$post->post_type . '-' . self::$post->post_name;
$template_info = gutenberg_get_block_template_type_for_slug( $template_slug );
$this->assertSame(
array(
'title' => 'Post: Hello World!',
'description' => 'Template for Post: Hello World!',
),
$template_info
);
}
}