<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[51003] trunk: Block Editor: Introduce block templates for classic themes.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/51003">51003</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/51003","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>youknowriad</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-05-25 14:19:14 +0000 (Tue, 25 May 2021)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Block Editor: Introduce block templates for classic themes.

With this patch, users will be able to create custom block based templates
and assign them to specific pages/posts.

Themes can also opt-out of this feature

Props bernhard-reiter, carlomanf.
Fixes <a href="https://core.trac.wordpress.org/ticket/53176">#53176</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadmineditformblocksphp">trunk/src/wp-admin/edit-form-blocks.php</a></li>
<li><a href="#trunksrcwpincludesclasswpthemephp">trunk/src/wp-includes/class-wp-theme.php</a></li>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpincludespostphp">trunk/src/wp-includes/post.php</a></li>
<li><a href="#trunksrcwpincludesscriptloaderphp">trunk/src/wp-includes/script-loader.php</a></li>
<li><a href="#trunksrcwpincludestaxonomyphp">trunk/src/wp-includes/taxonomy.php</a></li>
<li><a href="#trunksrcwpincludestemplatephp">trunk/src/wp-includes/template.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemasetupphp">trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php</a></li>
<li><a href="#trunktestsphpunitteststhemewpThemeJsonphp">trunk/tests/phpunit/tests/theme/wpThemeJson.php</a></li>
<li><a href="#trunktestsphpunitteststhemewpThemeJsonResolverphp">trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocktemplateutilsphp">trunk/src/wp-includes/block-template-utils.php</a></li>
<li><a href="#trunksrcwpincludesblocktemplatephp">trunk/src/wp-includes/block-template.php</a></li>
<li><a href="#trunksrcwpincludesclasswpblocktemplatephp">trunk/src/wp-includes/class-wp-block-template.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswpresttemplatescontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php</a></li>
<li><a href="#trunksrcwpincludestemplatecanvasphp">trunk/src/wp-includes/template-canvas.php</a></li>
<li><a href="#trunksrcwpincludesthemetemplatesphp">trunk/src/wp-includes/theme-templates.php</a></li>
<li><a href="#trunktestsphpunittestsblocktemplateutilsphp">trunk/tests/phpunit/tests/block-template-utils.php</a></li>
<li><a href="#trunktestsphpunittestsblocktemplatephp">trunk/tests/phpunit/tests/block-template.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiresttemplatescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-templates-controller.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadmineditformblocksphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/edit-form-blocks.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/edit-form-blocks.php   2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-admin/edit-form-blocks.php     2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -220,6 +220,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        'supportsLayout'                       => WP_Theme_JSON_Resolver::theme_has_support(),
</span><span class="cx" style="display: block; padding: 0 10px">        '__experimentalBlockPatterns'          => WP_Block_Patterns_Registry::get_instance()->get_all_registered(),
</span><span class="cx" style="display: block; padding: 0 10px">        '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        'supportsTemplateMode'                 => current_theme_supports( 'block-templates' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Whether or not to load the 'postcustom' meta box is stored as a user meta
</span><span class="cx" style="display: block; padding: 0 10px">        // field so that we're not always loading its assets.
</span></span></pre></div>
<a id="trunksrcwpincludesblocktemplateutilsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/block-template-utils.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-template-utils.php                            (rev 0)
+++ trunk/src/wp-includes/block-template-utils.php      2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,144 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Utilities used to fetch and create templates.
+ *
+ * @package WordPress
+ * @since 5.8.0
+ */
+
+/**
+ * Build a unified template object based a post Object.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @param WP_Post $post Template post.
+ *
+ * @return WP_Block_Template|WP_Error Template.
+ */
+function _build_template_result_from_post( $post ) {
+       $terms = get_the_terms( $post, 'wp_theme' );
+
+       if ( is_wp_error( $terms ) ) {
+               return $terms;
+       }
+
+       if ( ! $terms ) {
+               return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
+       }
+
+       $theme = $terms[0]->name;
+
+       $template                 = new WP_Block_Template();
+       $template->wp_id          = $post->ID;
+       $template->id             = $theme . '//' . $post->post_name;
+       $template->theme          = $theme;
+       $template->content        = $post->post_content;
+       $template->slug           = $post->post_name;
+       $template->source         = 'custom';
+       $template->type           = $post->post_type;
+       $template->description    = $post->post_excerpt;
+       $template->title          = $post->post_title;
+       $template->status         = $post->post_status;
+       $template->has_theme_file = false;
+
+       return $template;
+}
+
+/**
+ * Retrieves a list of unified template objects based on a query.
+ *
+ * @since 5.8.0
+ *
+ * @param array $query {
+ *     Optional. Arguments to retrieve templates.
+ *
+ *     @type array  $slug__in List of slugs to include.
+ *     @type int    $wp_id Post ID of customized template.
+ * }
+ * @param string $template_type wp_template.
+ *
+ * @return array Templates.
+ */
+function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
+       $wp_query_args = array(
+               'post_status'    => array( 'auto-draft', 'draft', 'publish' ),
+               'post_type'      => $template_type,
+               'posts_per_page' => -1,
+               'no_found_rows'  => true,
+               'tax_query'      => array(
+                       array(
+                               'taxonomy' => 'wp_theme',
+                               'field'    => 'name',
+                               'terms'    => wp_get_theme()->get_stylesheet(),
+                       ),
+               ),
+       );
+
+       if ( isset( $query['slug__in'] ) ) {
+               $wp_query_args['post_name__in'] = $query['slug__in'];
+       }
+
+       // This is only needed for the regular templates CPT listing and editor.
+       if ( isset( $query['wp_id'] ) ) {
+               $wp_query_args['p'] = $query['wp_id'];
+       } else {
+               $wp_query_args['post_status'] = 'publish';
+       }
+
+       $template_query = new WP_Query( $wp_query_args );
+       $query_result   = array();
+       foreach ( $template_query->get_posts() as $post ) {
+               $template = _build_template_result_from_post( $post );
+
+               if ( ! is_wp_error( $template ) ) {
+                       $query_result[] = $template;
+               }
+       }
+
+       return $query_result;
+}
+
+/**
+ * Retrieves a single unified template object using its id.
+ *
+ * @since 5.8.0
+ *
+ * @param string $id Template unique identifier (example: theme_slug//template_slug).
+ * @param string $template_type wp_template.
+ *
+ * @return WP_Block_Template|null Template.
+ */
+function get_block_template( $id, $template_type = 'wp_template' ) {
+       $parts = explode( '//', $id, 2 );
+       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->get_posts();
+
+       if ( count( $posts ) > 0 ) {
+               $template = _build_template_result_from_post( $posts[0] );
+
+               if ( ! is_wp_error( $template ) ) {
+                       return $template;
+               }
+       }
+
+       return null;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/block-template-utils.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesblocktemplatephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/block-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-template.php                          (rev 0)
+++ trunk/src/wp-includes/block-template.php    2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,228 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Block template loader functions.
+ *
+ * @package WordPress
+ */
+
+/**
+ * Find a block template with equal or higher specificity than a given PHP template file.
+ *
+ * Internally, this communicates the block content that needs to be used by the template canvas through a global variable.
+ *
+ * @since 5.8.0
+ *
+ * @param string $template  Path to the template. See locate_template().
+ * @param string $type      Sanitized filename without extension.
+ * @param array  $templates A list of template candidates, in descending order of priority.
+ * @return string The path to the Full Site Editing template canvas file, or the fallback PHP template.
+ */
+function locate_block_template( $template, $type, array $templates ) {
+       global $_wp_current_template_content;
+
+       if ( $template ) {
+               // locate_template() has found a PHP template at the path specified by $template.
+               // That means that we have a fallback candidate if we cannot find a block template
+               // with higher specificity.
+               // Thus, before looking for matching block themes, we shorten our list of candidate
+               // templates accordingly.
+
+               // Locate the index of $template (without the theme directory path) in $templates.
+               $relative_template_path = str_replace(
+                       array( get_stylesheet_directory() . '/', get_template_directory() . '/' ),
+                       '',
+                       $template
+               );
+               $index                  = array_search( $relative_template_path, $templates, true );
+
+               // If the template hiearchy algorithm has successfully located a PHP template file,
+               // we will only consider block templates with higher or equal specificity.
+               $templates = array_slice( $templates, 0, $index + 1 );
+       }
+
+       $block_template = resolve_block_template( $type, $templates );
+
+       if ( $block_template ) {
+               if ( empty( $block_template->content ) && is_user_logged_in() ) {
+                       $_wp_current_template_content =
+                       sprintf(
+                               /* translators: %s: Template title */
+                               __( 'Empty template: %s' ),
+                               $block_template->title
+                       );
+               } elseif ( ! empty( $block_template->content ) ) {
+                       $_wp_current_template_content = $block_template->content;
+               }
+               if ( isset( $_GET['_wp-find-template'] ) ) {
+                       wp_send_json_success( $block_template );
+               }
+       } else {
+               if ( $template ) {
+                       return $template;
+               }
+
+               if ( 'index' === $type ) {
+                       if ( isset( $_GET['_wp-find-template'] ) ) {
+                               wp_send_json_error( array( 'message' => __( 'No matching template found.' ) ) );
+                       }
+               } else {
+                       return ''; // So that the template loader keeps looking for templates.
+               }
+       }
+
+       // Add hooks for template canvas.
+       // Add viewport meta tag.
+       add_action( 'wp_head', '_block_template_viewport_meta_tag', 0 );
+
+       // Render title tag with content, regardless of whether theme has title-tag support.
+       remove_action( 'wp_head', '_wp_render_title_tag', 1 );    // Remove conditional title tag rendering...
+       add_action( 'wp_head', '_block_template_render_title_tag', 1 ); // ...and make it unconditional.
+
+       // This file will be included instead of the theme's template file.
+       return ABSPATH . WPINC . '/template-canvas.php';
+}
+
+/**
+ * Return the correct 'wp_template' to render for the request template type.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * Accepts an optional $template_hierarchy argument as a hint.
+ *
+ * @param string   $template_type      The current template type.
+ * @param string[] $template_hierarchy (optional) The current template hierarchy, ordered by priority.
+ * @return WP_Block_Template|null template A template object, or null if none could be found.
+ */
+function resolve_block_template( $template_type, $template_hierarchy ) {
+       if ( ! $template_type ) {
+               return null;
+       }
+
+       if ( empty( $template_hierarchy ) ) {
+               $template_hierarchy = array( $template_type );
+       }
+
+       $slugs = array_map(
+               '_strip_template_file_suffix',
+               $template_hierarchy
+       );
+
+       // Find all potential templates 'wp_template' post matching the hierarchy.
+       $query     = array(
+               'theme'    => wp_get_theme()->get_stylesheet(),
+               'slug__in' => $slugs,
+       );
+       $templates = get_block_templates( $query );
+
+       // Order these templates per slug priority.
+       // Build map of template slugs to their priority in the current hierarchy.
+       $slug_priorities = array_flip( $slugs );
+
+       usort(
+               $templates,
+               function ( $template_a, $template_b ) use ( $slug_priorities ) {
+                       return $slug_priorities[ $template_a->slug ] - $slug_priorities[ $template_b->slug ];
+               }
+       );
+
+       return count( $templates ) ? $templates[0] : null;
+}
+
+/**
+ * Displays title tag with content, regardless of whether theme has title-tag support.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @see _wp_render_title_tag()
+ */
+function _block_template_render_title_tag() {
+       echo '<title>' . wp_get_document_title() . '</title>' . "\n";
+}
+
+/**
+ * Returns the markup for the current template.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @return string block tempate markup.
+ */
+function get_the_block_template_html() {
+       global $_wp_current_template_content;
+       global $wp_embed;
+
+       if ( ! $_wp_current_template_content ) {
+               if ( is_user_logged_in() ) {
+                       return '<h1>' . esc_html__( 'No matching template found' ) . '</h1>';
+               }
+               return;
+       }
+
+       $content = $wp_embed->run_shortcode( $_wp_current_template_content );
+       $content = $wp_embed->autoembed( $content );
+       $content = do_blocks( $content );
+       $content = wptexturize( $content );
+       if ( function_exists( 'wp_filter_content_tags' ) ) {
+               $content = wp_filter_content_tags( $content );
+       } else {
+               $content = wp_make_content_images_responsive( $content );
+       }
+       $content = str_replace( ']]>', ']]&gt;', $content );
+
+       // Wrap block template in .wp-site-blocks to allow for specific descendant styles
+       // (e.g. `.wp-site-blocks > *`).
+       return '<div class="wp-site-blocks">' . $content . '</div>';
+}
+
+/**
+ * Renders a 'viewport' meta tag.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas.
+ */
+function _block_template_viewport_meta_tag() {
+       echo '<meta name="viewport" content="width=device-width, initial-scale=1" />' . "\n";
+}
+
+/**
+ * Strips .php or .html suffix from template file names.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @param string $template_file Template file name.
+ * @return string Template file name without extension.
+ */
+function _strip_template_file_suffix( $template_file ) {
+       return preg_replace( '/\.(php|html)$/', '', $template_file );
+}
+
+/**
+ * Removes post details from block context when rendering a block template.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @param array $context Default context.
+ *
+ * @return array Filtered context.
+ */
+function _block_template_render_without_post_block_context( $context ) {
+       /*
+        * When loading a template directly and not through a page
+        * that resolves it, the top-level post ID and type context get set to that
+        * of the template. Templates are just the structure of a site, and
+        * they should not be available as post context because blocks like Post
+        * Content would recurse infinitely.
+        */
+       if ( isset( $context['postType'] ) && 'wp_template' === $context['postType'] ) {
+               unset( $context['postId'] );
+               unset( $context['postType'] );
+       }
+
+       return $context;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/block-template.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesclasswpblocktemplatephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/class-wp-block-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-block-template.php                         (rev 0)
+++ trunk/src/wp-includes/class-wp-block-template.php   2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,103 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Blocks API: WP_Block_Template class
+ *
+ * @package WordPress
+ * @since 5.8.0
+ */
+
+/**
+ * Class representing a block template.
+ *
+ * @since 5.8.0
+ */
+class WP_Block_Template {
+
+       /**
+        * Type: wp_template.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $type;
+
+       /**
+        * Theme.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $theme;
+
+       /**
+        * Template slug.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $slug;
+
+       /**
+        * Id.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $id;
+
+       /**
+        * Title.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $title = '';
+
+       /**
+        * Content.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $content = '';
+
+       /**
+        * Description.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $description = '';
+
+       /**
+        * Source of the content. `theme` and `custom` is used for now.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $source = 'theme';
+
+       /**
+        * Post Id.
+        *
+        * @since 5.8.0
+        * @var integer|null
+        */
+       public $wp_id;
+
+       /**
+        * Template Status.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       public $status;
+
+       /**
+        * Whether a template is, or is based upon, an existing template file.
+        *
+        * @since 5.8.0
+        * @var boolean
+        */
+       public $has_theme_file;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/class-wp-block-template.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesclasswpthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme.php  2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/class-wp-theme.php    2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1182,6 +1182,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Returns the theme's post templates.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.7.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 5.8.0 Include block templates.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return string[] Array of page templates, keyed by filename and post type,
</span><span class="cx" style="display: block; padding: 0 10px">         *                  with the value of the translated header name.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1219,6 +1220,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        if ( current_theme_supports( 'block-templates' ) ) {
+                               $block_templates = get_block_templates( array(), 'wp_template' );
+                               foreach ( get_post_types( array( 'public' => true ) ) as $type ) {
+                                       foreach ( $block_templates as $block_template ) {
+                                               $post_templates[ $type ][ $block_template->slug ] = $block_template->title;
+                                       }
+                               }
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->cache_add( 'post_templates', $post_templates );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/default-filters.php   2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -555,6 +555,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in <head>.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer.
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'admin_footer-post.php', 'wp_add_iframed_editor_assets_html' );
+add_action( 'admin_footer-post-new.php', 'wp_add_iframed_editor_assets_html' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Taxonomy.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'change_locale', 'create_initial_taxonomies' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -633,4 +636,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'user_has_cap', 'wp_maybe_grant_resume_extensions_caps', 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Block Templates CPT 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( 'wp_footer', 'the_block_template_skip_link' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> unset( $filter, $action );
</span></span></pre></div>
<a id="trunksrcwpincludespostphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/post.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/post.php    2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/post.php      2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -310,6 +310,66 @@
</span><span class="cx" style="display: block; padding: 0 10px">                )
</span><span class="cx" style="display: block; padding: 0 10px">        );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        register_post_type(
+               'wp_template',
+               array(
+                       'labels'                => array(
+                               'name'                  => __( 'Templates' ),
+                               'singular_name'         => __( 'Template' ),
+                               'menu_name'             => _x( 'Templates', 'Admin Menu text' ),
+                               'add_new'               => _x( 'Add New', 'Template' ),
+                               'add_new_item'          => __( 'Add New Template' ),
+                               'new_item'              => __( 'New Template' ),
+                               'edit_item'             => __( 'Edit Template' ),
+                               'view_item'             => __( 'View Template' ),
+                               'all_items'             => __( 'All Templates' ),
+                               'search_items'          => __( 'Search Templates' ),
+                               'parent_item_colon'     => __( 'Parent Template:' ),
+                               'not_found'             => __( 'No templates found.' ),
+                               'not_found_in_trash'    => __( 'No templates found in Trash.' ),
+                               'archives'              => __( 'Template archives' ),
+                               'insert_into_item'      => __( 'Insert into template' ),
+                               'uploaded_to_this_item' => __( 'Uploaded to this template' ),
+                               'filter_items_list'     => __( 'Filter templates list' ),
+                               'items_list_navigation' => __( 'Templates list navigation' ),
+                               'items_list'            => __( 'Templates list' ),
+                       ),
+                       'description'           => __( 'Templates to include in your theme.' ),
+                       'public'                => false,
+                       'has_archive'           => false,
+                       'show_ui'               => false,
+                       'show_in_menu'          => false,
+                       'show_in_admin_bar'     => false,
+                       'show_in_rest'          => true,
+                       'rewrite'               => false,
+                       'rest_base'             => 'templates',
+                       'rest_controller_class' => 'WP_REST_Templates_Controller',
+                       'capability_type'       => array( 'template', 'templates' ),
+                       'capabilities'          => array(
+                               'create_posts'           => 'edit_theme_options',
+                               'delete_posts'           => 'edit_theme_options',
+                               'delete_others_posts'    => 'edit_theme_options',
+                               'delete_private_posts'   => 'edit_theme_options',
+                               'delete_published_posts' => 'edit_theme_options',
+                               'edit_posts'             => 'edit_theme_options',
+                               'edit_others_posts'      => 'edit_theme_options',
+                               'edit_private_posts'     => 'edit_theme_options',
+                               'edit_published_posts'   => 'edit_theme_options',
+                               'publish_posts'          => 'edit_theme_options',
+                               'read'                   => 'edit_theme_options',
+                               'read_private_posts'     => 'edit_theme_options',
+                       ),
+                       'map_meta_cap'          => true,
+                       'supports'              => array(
+                               'title',
+                               'slug',
+                               'excerpt',
+                               'editor',
+                               'revisions',
+                       ),
+               )
+       );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         register_post_status(
</span><span class="cx" style="display: block; padding: 0 10px">                'publish',
</span><span class="cx" style="display: block; padding: 0 10px">                array(
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswpresttemplatescontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php                           (rev 0)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php     2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,603 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * REST API: WP_REST_Templates_Controller class
+ *
+ * @package    WordPress
+ * @subpackage REST_API
+ * @since 5.8.0
+ */
+
+/**
+ * Base Templates REST API Controller.
+ *
+ * @since 5.8.0
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Templates_Controller extends WP_REST_Controller {
+
+       /**
+        * Post type.
+        *
+        * @since 5.8.0
+        * @var string
+        */
+       protected $post_type;
+
+       /**
+        * Constructor.
+        *
+        * @since 5.8.0
+        *
+        * @param string $post_type Post type.
+        */
+       public function __construct( $post_type ) {
+               $this->post_type = $post_type;
+               $this->namespace = 'wp/v2';
+               $obj             = get_post_type_object( $post_type );
+               $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
+       }
+
+       /**
+        * Registers the controllers routes.
+        *
+        * @since 5.8.0
+        *
+        * @return void
+        */
+       public function register_routes() {
+               // Lists all templates.
+               register_rest_route(
+                       $this->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(),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::CREATABLE,
+                                       'callback'            => array( $this, 'create_item' ),
+                                       'permission_callback' => array( $this, 'create_item_permissions_check' ),
+                                       'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
+                               ),
+                               'schema' => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+
+               // Lists/updates a single template based on the given id.
+               register_rest_route(
+                       $this->namespace,
+                       '/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
+                       array(
+                               array(
+                                       'methods'             => WP_REST_Server::READABLE,
+                                       'callback'            => array( $this, 'get_item' ),
+                                       'permission_callback' => array( $this, 'get_item_permissions_check' ),
+                                       'args'                => array(
+                                               'id' => array(
+                                                       'description' => __( 'The id of a template' ),
+                                                       'type'        => 'string',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::EDITABLE,
+                                       'callback'            => array( $this, 'update_item' ),
+                                       'permission_callback' => array( $this, 'update_item_permissions_check' ),
+                                       'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
+                               ),
+                               array(
+                                       'methods'             => WP_REST_Server::DELETABLE,
+                                       'callback'            => array( $this, 'delete_item' ),
+                                       'permission_callback' => array( $this, 'delete_item_permissions_check' ),
+                                       'args'                => array(
+                                               'force' => array(
+                                                       'type'        => 'boolean',
+                                                       'default'     => false,
+                                                       'description' => __( 'Whether to bypass Trash and force deletion.' ),
+                                               ),
+                                       ),
+                               ),
+                               'schema' => array( $this, 'get_public_item_schema' ),
+                       )
+               );
+       }
+
+       /**
+        * Checks if the user has permissions to make the request.
+        *
+        * @since 5.8.0
+        *
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       protected function permissions_check() {
+               // Verify if the current user has edit_theme_options capability.
+               // This capability is required to edit/view/delete templates.
+               if ( ! current_user_can( 'edit_theme_options' ) ) {
+                       return new WP_Error(
+                               'rest_cannot_manage_templates',
+                               __( 'Sorry, you are not allowed to access the templates on this site.' ),
+                               array(
+                                       'status' => rest_authorization_required_code(),
+                               )
+                       );
+               }
+
+               return true;
+       }
+
+       /**
+        * Checks if a given request has access to read templates.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
+        */
+       public function get_items_permissions_check( $request ) {
+               return $this->permissions_check( $request );
+       }
+
+       /**
+        * Returns a list of templates.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request The request instance.
+        *
+        * @return WP_REST_Response
+        */
+       public function get_items( $request ) {
+               $query = array();
+               if ( isset( $request['wp_id'] ) ) {
+                       $query['wp_id'] = $request['wp_id'];
+               }
+               if ( isset( $request['area'] ) ) {
+                       $query['area'] = $request['area'];
+               }
+               $templates = array();
+               foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
+                       $data        = $this->prepare_item_for_response( $template, $request );
+                       $templates[] = $this->prepare_response_for_collection( $data );
+               }
+
+               return rest_ensure_response( $templates );
+       }
+
+       /**
+        * Checks if a given request has access to read a single template.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
+        */
+       public function get_item_permissions_check( $request ) {
+               return $this->permissions_check( $request );
+       }
+
+       /**
+        * Returns the given template
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request The request instance.
+        *
+        * @return WP_REST_Response|WP_Error
+        */
+       public function get_item( $request ) {
+               $template = get_block_template( $request['id'], $this->post_type );
+
+               if ( ! $template ) {
+                       return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
+               }
+
+               return $this->prepare_item_for_response( $template, $request );
+       }
+
+       /**
+        * Checks if a given request has access to write a single template.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
+        */
+       public function update_item_permissions_check( $request ) {
+               return $this->permissions_check( $request );
+       }
+
+       /**
+        * Updates 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 update_item( $request ) {
+               $template = get_block_template( $request['id'], $this->post_type );
+               if ( ! $template ) {
+                       return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
+               }
+
+               $changes = $this->prepare_item_for_database( $request );
+
+               if ( 'custom' === $template->source ) {
+                       $result = wp_update_post( wp_slash( (array) $changes ), true );
+               } else {
+                       $result = wp_insert_post( wp_slash( (array) $changes ), true );
+               }
+               if ( is_wp_error( $result ) ) {
+                       return $result;
+               }
+
+               $template      = get_block_template( $request['id'], $this->post_type );
+               $fields_update = $this->update_additional_fields_for_object( $template, $request );
+               if ( is_wp_error( $fields_update ) ) {
+                       return $fields_update;
+               }
+
+               return $this->prepare_item_for_response(
+                       get_block_template( $request['id'], $this->post_type ),
+                       $request
+               );
+       }
+
+       /**
+        * Checks if a given request has access to create a template.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
+        */
+       public function create_item_permissions_check( $request ) {
+               return $this->permissions_check( $request );
+       }
+
+       /**
+        * Creates 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 create_item( $request ) {
+               $changes            = $this->prepare_item_for_database( $request );
+               $changes->post_name = $request['slug'];
+               $result             = wp_insert_post( wp_slash( (array) $changes ), true );
+               if ( is_wp_error( $result ) ) {
+                       return $result;
+               }
+               $posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
+               if ( ! count( $posts ) ) {
+                       return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
+               }
+               $id            = $posts[0]->id;
+               $template      = get_block_template( $id, $this->post_type );
+               $fields_update = $this->update_additional_fields_for_object( $template, $request );
+               if ( is_wp_error( $fields_update ) ) {
+                       return $fields_update;
+               }
+
+               return $this->prepare_item_for_response(
+                       get_block_template( $id, $this->post_type ),
+                       $request
+               );
+       }
+
+       /**
+        * Checks if a given request has access to delete a single template.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Full details about the request.
+        * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
+        */
+       public function delete_item_permissions_check( $request ) {
+               return $this->permissions_check( $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 ) {
+                       return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
+               }
+               if ( 'custom' !== $template->source ) {
+                       return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
+               }
+
+               $id    = $template->wp_id;
+               $force = (bool) $request['force'];
+
+               // If we're forcing, then delete permanently.
+               if ( $force ) {
+                       $previous = $this->prepare_item_for_response( $template, $request );
+                       wp_delete_post( $id, true );
+                       $response = new WP_REST_Response();
+                       $response->set_data(
+                               array(
+                                       'deleted'  => true,
+                                       'previous' => $previous->get_data(),
+                               )
+                       );
+
+                       return $response;
+               }
+
+               // Otherwise, only trash if we haven't already.
+               if ( 'trash' === $template->status ) {
+                       return new WP_Error(
+                               'rest_template_already_trashed',
+                               __( 'The template has already been deleted.' ),
+                               array( 'status' => 410 )
+                       );
+               }
+
+               wp_trash_post( $id );
+               $template->status = 'trash';
+               return $this->prepare_item_for_response( $template, $request );
+       }
+
+       /**
+        * Prepares a single template for create or update.
+        *
+        * @since 5.8.0
+        *
+        * @param WP_REST_Request $request Request object.
+        * @return stdClass Changes to pass to wp_update_post.
+        */
+       protected function prepare_item_for_database( $request ) {
+               $template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
+               $changes  = new stdClass();
+               if ( null === $template ) {
+                       $changes->post_type   = $this->post_type;
+                       $changes->post_status = 'publish';
+                       $changes->tax_input   = array(
+                               'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
+                       );
+               } elseif ( 'custom' !== $template->source ) {
+                       $changes->post_name   = $template->slug;
+                       $changes->post_type   = $this->post_type;
+                       $changes->post_status = 'publish';
+                       $changes->tax_input   = array(
+                               'wp_theme' => $template->theme,
+                       );
+               } else {
+                       $changes->post_name   = $template->slug;
+                       $changes->ID          = $template->wp_id;
+                       $changes->post_status = 'publish';
+               }
+               if ( isset( $request['content'] ) ) {
+                       $changes->post_content = $request['content'];
+               } elseif ( null !== $template && 'custom' !== $template->source ) {
+                       $changes->post_content = $template->content;
+               }
+               if ( isset( $request['title'] ) ) {
+                       $changes->post_title = $request['title'];
+               } elseif ( null !== $template && 'custom' !== $template->source ) {
+                       $changes->post_title = $template->title;
+               }
+               if ( isset( $request['description'] ) ) {
+                       $changes->post_excerpt = $request['description'];
+               } elseif ( null !== $template && 'custom' !== $template->source ) {
+                       $changes->post_excerpt = $template->description;
+               }
+
+               return $changes;
+       }
+
+       /**
+        * Prepare a single template output for response
+        *
+        * @since 5.8.0
+        *
+        * @param WP_Block_Template $template Template instance.
+        * @param WP_REST_Request   $request Request object.
+        *
+        * @return WP_REST_Response $data
+        */
+       public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+               $result = array(
+                       'id'             => $template->id,
+                       'theme'          => $template->theme,
+                       'content'        => array( 'raw' => $template->content ),
+                       'slug'           => $template->slug,
+                       'source'         => $template->source,
+                       'type'           => $template->type,
+                       'description'    => $template->description,
+                       'title'          => array(
+                               'raw'      => $template->title,
+                               'rendered' => $template->title,
+                       ),
+                       'status'         => $template->status,
+                       'wp_id'          => $template->wp_id,
+                       'has_theme_file' => $template->has_theme_file,
+               );
+
+               if ( 'wp_template_part' === $template->type ) {
+                       $result['area'] = $template->area;
+               }
+
+               $result = $this->add_additional_fields_to_object( $result, $request );
+
+               $response = rest_ensure_response( $result );
+               $links    = $this->prepare_links( $template->id );
+               $response->add_links( $links );
+               if ( ! empty( $links['self']['href'] ) ) {
+                       $actions = $this->get_available_actions();
+                       $self    = $links['self']['href'];
+                       foreach ( $actions as $rel ) {
+                               $response->add_link( $rel, $self );
+                       }
+               }
+
+               return $response;
+       }
+
+
+       /**
+        * Prepares links for the request.
+        *
+        * @since 5.8.0
+        *
+        * @param integer $id ID.
+        * @return array Links for the given post.
+        */
+       protected function prepare_links( $id ) {
+               $base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
+
+               $links = array(
+                       'self'       => array(
+                               'href' => rest_url( trailingslashit( $base ) . $id ),
+                       ),
+                       'collection' => array(
+                               'href' => rest_url( $base ),
+                       ),
+                       'about'      => array(
+                               'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
+                       ),
+               );
+
+               return $links;
+       }
+
+       /**
+        * Get the link relations available for the post and current user.
+        *
+        * @since 5.8.0
+        *
+        * @return array List of link relations.
+        */
+       protected function get_available_actions() {
+               $rels = array();
+
+               $post_type = get_post_type_object( $this->post_type );
+
+               if ( current_user_can( $post_type->cap->publish_posts ) ) {
+                       $rels[] = 'https://api.w.org/action-publish';
+               }
+
+               if ( current_user_can( 'unfiltered_html' ) ) {
+                       $rels[] = 'https://api.w.org/action-unfiltered-html';
+               }
+
+               return $rels;
+       }
+
+       /**
+        * Retrieves the query params for the posts collection.
+        *
+        * @since 5.8.0
+        *
+        * @return array Collection parameters.
+        */
+       public function get_collection_params() {
+               return array(
+                       'context' => $this->get_context_param(),
+                       'wp_id'   => array(
+                               'description' => __( 'Limit to the specified post id.' ),
+                               'type'        => 'integer',
+                       ),
+               );
+       }
+
+       /**
+        * Retrieves the block type' schema, conforming to JSON Schema.
+        *
+        * @since 5.8.0
+        *
+        * @return array Item schema data.
+        */
+       public function get_item_schema() {
+               if ( $this->schema ) {
+                       return $this->add_additional_fields_schema( $this->schema );
+               }
+
+               $schema = array(
+                       '$schema'    => 'http://json-schema.org/draft-04/schema#',
+                       'title'      => $this->post_type,
+                       'type'       => 'object',
+                       'properties' => array(
+                               'id'             => array(
+                                       'description' => __( 'ID of template.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'slug'           => array(
+                                       'description' => __( 'Unique slug identifying the template.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'required'    => true,
+                                       'minLength'   => 1,
+                                       'pattern'     => '[a-zA-Z_\-]+',
+                               ),
+                               'theme'          => array(
+                                       'description' => __( 'Theme identifier for the template.' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                               'source'         => array(
+                                       'description' => __( 'Source of template' ),
+                                       'type'        => 'string',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'content'        => array(
+                                       'description' => __( 'Content of template.' ),
+                                       'type'        => array( 'object', 'string' ),
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                               'title'          => array(
+                                       'description' => __( 'Title of template.' ),
+                                       'type'        => array( 'object', 'string' ),
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                               'description'    => array(
+                                       'description' => __( 'Description of template.' ),
+                                       'type'        => 'string',
+                                       'default'     => '',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                               'status'         => array(
+                                       'description' => __( 'Status of template.' ),
+                                       'type'        => 'string',
+                                       'default'     => 'publish',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                               ),
+                               'wp_id'          => array(
+                                       'description' => __( 'Post ID.' ),
+                                       'type'        => 'integer',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                               'has_theme_file' => array(
+                                       'description' => __( 'Theme file exists.' ),
+                                       'type'        => 'bool',
+                                       'context'     => array( 'embed', 'view', 'edit' ),
+                                       'readonly'    => true,
+                               ),
+                       ),
+               );
+
+               $this->schema = $schema;
+
+               return $this->add_additional_fields_schema( $this->schema );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludesscriptloaderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/script-loader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/script-loader.php   2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/script-loader.php     2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2662,3 +2662,65 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Inject the block editor assets that need to be loaded into the editor's iframe as an inline script.
+ *
+ * @since 5.8.0
+ */
+function wp_add_iframed_editor_assets_html() {
+       $script_handles = array();
+       $style_handles  = array(
+               'wp-block-editor',
+               'wp-block-library',
+               'wp-block-library-theme',
+               'wp-edit-blocks',
+       );
+
+       $block_registry = WP_Block_Type_Registry::get_instance();
+
+       foreach ( $block_registry->get_all_registered() as $block_type ) {
+               if ( ! empty( $block_type->style ) ) {
+                       $style_handles[] = $block_type->style;
+               }
+
+               if ( ! empty( $block_type->editor_style ) ) {
+                       $style_handles[] = $block_type->editor_style;
+               }
+
+               if ( ! empty( $block_type->script ) ) {
+                       $script_handles[] = $block_type->script;
+               }
+       }
+
+       $style_handles = array_unique( $style_handles );
+       $done          = wp_styles()->done;
+
+       ob_start();
+
+       wp_styles()->done = array();
+       wp_styles()->do_items( $style_handles );
+       wp_styles()->done = $done;
+
+       $styles = ob_get_clean();
+
+       $script_handles = array_unique( $script_handles );
+       $done           = wp_scripts()->done;
+
+       ob_start();
+
+       wp_scripts()->done = array();
+       wp_scripts()->do_items( $script_handles );
+       wp_scripts()->done = $done;
+
+       $scripts = ob_get_clean();
+
+       $editor_assets = wp_json_encode(
+               array(
+                       'styles'  => $styles,
+                       'scripts' => $scripts,
+               )
+       );
+
+       echo "<script>window.__editorAssets = $editor_assets</script>";
+}
</ins></span></pre></div>
<a id="trunksrcwpincludestaxonomyphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/taxonomy.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/taxonomy.php        2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/taxonomy.php  2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -172,6 +172,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
</span><span class="cx" style="display: block; padding: 0 10px">                )
</span><span class="cx" style="display: block; padding: 0 10px">        );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       register_taxonomy(
+               'wp_theme',
+               array( 'wp_template' ),
+               array(
+                       'public'            => false,
+                       'hierarchical'      => false,
+                       'labels'            => array(
+                               'name'          => __( 'Themes' ),
+                               'singular_name' => __( 'Theme' ),
+                       ),
+                       'query_var'         => false,
+                       'rewrite'           => false,
+                       'show_ui'           => false,
+                       '_builtin'          => true,
+                       'show_in_nav_menus' => false,
+                       'show_in_rest'      => false,
+               )
+       );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcwpincludestemplatecanvasphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/template-canvas.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/template-canvas.php                         (rev 0)
+++ trunk/src/wp-includes/template-canvas.php   2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,28 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Template canvas file to render the current 'wp_template'.
+ *
+ * @package WordPress
+ */
+
+/**
+ * Get the template HTML.
+ * This needs to run before <head> so that blocks can add scripts and styles in wp_head().
+ */
+$template_html = get_the_block_template_html();
+?>
+<!DOCTYPE html>
+<html <?php language_attributes(); ?>>
+<head>
+       <meta charset="<?php bloginfo( 'charset' ); ?>" />
+       <?php wp_head(); ?>
+</head>
+
+<body <?php body_class(); ?>>
+<?php wp_body_open(); ?>
+
+<?php echo $template_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
+
+<?php wp_footer(); ?>
+</body>
+</html>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/template-canvas.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpincludestemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/template.php        2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-includes/template.php  2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -63,6 +63,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $template = locate_template( $templates );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $template = locate_block_template( $template, $type, $templates );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Filters the path of the queried template by type.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span></span></pre></div>
<a id="trunksrcwpincludesthemetemplatesphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/theme-templates.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/theme-templates.php                         (rev 0)
+++ trunk/src/wp-includes/theme-templates.php   2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,163 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Generates a unique slug for templates.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @param string $override_slug The filtered value of the slug (starts as `null` from apply_filter).
+ * @param string $slug          The original/un-filtered slug (post_name).
+ * @param int    $post_ID       Post ID.
+ * @param string $post_status   No uniqueness checks are made if the post is still draft or pending.
+ * @param string $post_type     Post type.
+ * @return string The original, desired slug.
+ */
+function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
+       if ( 'wp_template' !== $post_type ) {
+               return $override_slug;
+       }
+
+       if ( ! $override_slug ) {
+               $override_slug = $slug;
+       }
+
+       // Template slugs must be unique within the same theme.
+       // TODO - Figure out how to update this to work for a multi-theme
+       // environment.  Unfortunately using `get_the_terms` for the 'wp-theme'
+       // term does not work in the case of new entities since is too early in
+       // the process to have been saved to the entity.  So for now we use the
+       // currently activated theme for creation.
+       $theme = wp_get_theme()->get_stylesheet();
+       $terms = get_the_terms( $post_ID, 'wp_theme' );
+       if ( $terms && ! is_wp_error( $terms ) ) {
+               $theme = $terms[0]->name;
+       }
+
+       $check_query_args = array(
+               'post_name__in'  => array( $override_slug ),
+               'post_type'      => $post_type,
+               'posts_per_page' => 1,
+               'no_found_rows'  => true,
+               'post__not_in'   => array( $post_ID ),
+               'tax_query'      => array(
+                       array(
+                               'taxonomy' => 'wp_theme',
+                               'field'    => 'name',
+                               'terms'    => $theme,
+                       ),
+               ),
+       );
+       $check_query      = new WP_Query( $check_query_args );
+       $posts            = $check_query->get_posts();
+
+       if ( count( $posts ) > 0 ) {
+               $suffix = 2;
+               do {
+                       $query_args                  = $check_query_args;
+                       $alt_post_name               = _truncate_post_slug( $override_slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
+                       $query_args['post_name__in'] = array( $alt_post_name );
+                       $query                       = new WP_Query( $query_args );
+                       $suffix++;
+               } while ( count( $query->get_posts() ) > 0 );
+               $override_slug = $alt_post_name;
+       }
+
+       return $override_slug;
+}
+
+/**
+ * Print the skip-link script & styles.
+ *
+ * @access private
+ * @since 5.8.0
+ *
+ * @return void
+ */
+function the_block_template_skip_link() {
+
+       // Early exit if not an FSE theme.
+       if ( ! current_theme_supports( 'block-templates' ) ) {
+               return;
+       }
+       ?>
+
+       <?php
+       /**
+        * Print the skip-link styles.
+        */
+       ?>
+       <style id="skip-link-styles">
+               .skip-link.screen-reader-text {
+                       border: 0;
+                       clip: rect(1px,1px,1px,1px);
+                       clip-path: inset(50%);
+                       height: 1px;
+                       margin: -1px;
+                       overflow: hidden;
+                       padding: 0;
+                       position: absolute !important;
+                       width: 1px;
+                       word-wrap: normal !important;
+               }
+
+               .skip-link.screen-reader-text:focus {
+                       background-color: #eee;
+                       clip: auto !important;
+                       clip-path: none;
+                       color: #444;
+                       display: block;
+                       font-size: 1em;
+                       height: auto;
+                       left: 5px;
+                       line-height: normal;
+                       padding: 15px 23px 14px;
+                       text-decoration: none;
+                       top: 5px;
+                       width: auto;
+                       z-index: 100000;
+               }
+       </style>
+       <?php
+       /**
+        * Print the skip-link script.
+        */
+       ?>
+       <script>
+       ( function() {
+               var skipLinkTarget = document.querySelector( 'main' ),
+                       parentEl,
+                       skipLinkTargetID,
+                       skipLink;
+
+               // Early exit if a skip-link target can't be located.
+               if ( ! skipLinkTarget ) {
+                       return;
+               }
+
+               // Get the site wrapper.
+               // The skip-link will be injected in the beginning of it.
+               parentEl = document.querySelector( '.wp-site-blocks' ) || document.body,
+
+               // Get the skip-link target's ID, and generate one if it doesn't exist.
+               skipLinkTargetID = skipLinkTarget.id;
+               if ( ! skipLinkTargetID ) {
+                       skipLinkTargetID = 'wp--skip-link--target';
+                       skipLinkTarget.id = skipLinkTargetID;
+               }
+
+               // Create the skip link.
+               skipLink = document.createElement( 'a' );
+               skipLink.classList.add( 'skip-link', 'screen-reader-text' );
+               skipLink.href = '#' + skipLinkTargetID;
+               skipLink.innerHTML = '<?php esc_html_e( 'Skip to content' ); ?>';
+
+               // Inject the skip link.
+               parentEl.insertAdjacentElement( 'afterbegin', skipLink );
+       }() );
+       </script>
+       <?php
+}
+
+// By default, themes support block templates.
+add_theme_support( 'block-templates' );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/theme-templates.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/src/wp-settings.php   2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -170,6 +170,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-date-query.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/theme.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-theme.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/class-wp-block-template.php';
+require ABSPATH . WPINC . '/block-template-utils.php';
+require ABSPATH . WPINC . '/block-template.php';
+require ABSPATH . WPINC . '/theme-templates.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/template.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/https-detection.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/https-migration.php';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -268,6 +272,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-sidebars-controller.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';
</span></span></pre></div>
<a id="trunktestsphpunittestsblocktemplateutilsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/block-template-utils.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/block-template-utils.php                                (rev 0)
+++ trunk/tests/phpunit/tests/block-template-utils.php  2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,97 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Block_Template_Utils_Test class
+ *
+ * @package    WordPress
+ */
+
+/**
+ * Tests for the Block Template Loader abstraction layer.
+ */
+class Block_Template_Utils_Test extends WP_UnitTestCase {
+       private static $post;
+
+       public static function wpSetUpBeforeClass() {
+               // Set up template post.
+               $args       = 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(
+                                       get_stylesheet(),
+                               ),
+                       ),
+               );
+               self::$post = self::factory()->post->create_and_get( $args );
+               wp_set_post_terms( self::$post->ID, get_stylesheet(), 'wp_theme' );
+       }
+
+       public static function wpTearDownAfterClass() {
+               wp_delete_post( self::$post->ID );
+       }
+
+       function test_build_template_result_from_post() {
+               $template = _build_template_result_from_post(
+                       self::$post,
+                       'wp_template'
+               );
+
+               $this->assertNotWPError( $template );
+               $this->assertEquals( get_stylesheet() . '//my_template', $template->id );
+               $this->assertEquals( get_stylesheet(), $template->theme );
+               $this->assertEquals( 'my_template', $template->slug );
+               $this->assertEquals( 'publish', $template->status );
+               $this->assertEquals( 'custom', $template->source );
+               $this->assertEquals( 'My Template', $template->title );
+               $this->assertEquals( 'Description of my template', $template->description );
+               $this->assertEquals( 'wp_template', $template->type );
+       }
+
+       /**
+        * Should retrieve the template from the CPT.
+        */
+       function test_get_block_template_from_post() {
+               $id       = get_stylesheet() . '//' . 'my_template';
+               $template = get_block_template( $id, 'wp_template' );
+               $this->assertEquals( $id, $template->id );
+               $this->assertEquals( get_stylesheet(), $template->theme );
+               $this->assertEquals( 'my_template', $template->slug );
+               $this->assertEquals( 'publish', $template->status );
+               $this->assertEquals( 'custom', $template->source );
+               $this->assertEquals( 'wp_template', $template->type );
+       }
+
+       /**
+        * Should retrieve block templates.
+        */
+       function test_get_block_templates() {
+               function get_template_ids( $templates ) {
+                       return array_map(
+                               function( $template ) {
+                                       return $template->id;
+                               },
+                               $templates
+                       );
+               }
+
+               // All results.
+               $templates    = get_block_templates( array(), 'wp_template' );
+               $template_ids = get_template_ids( $templates );
+
+               // Avoid testing the entire array because the theme might add/remove templates.
+               $this->assertContains( get_stylesheet() . '//' . 'my_template', $template_ids );
+
+               // Filter by slug.
+               $templates    = get_block_templates( array( 'slug__in' => array( 'my_template' ) ), 'wp_template' );
+               $template_ids = get_template_ids( $templates );
+               $this->assertEquals( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
+
+               // Filter by CPT ID.
+               $templates    = get_block_templates( array( 'wp_id' => self::$post->ID ), 'wp_template' );
+               $template_ids = get_template_ids( $templates );
+               $this->assertEquals( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/block-template-utils.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsblocktemplatephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/block-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/block-template.php                              (rev 0)
+++ trunk/tests/phpunit/tests/block-template.php        2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,87 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Block_Template_Test class
+ *
+ * @package WordPress
+ */
+
+/**
+ * Tests for the block template loading algorithm.
+ */
+class Block_Template_Test extends WP_UnitTestCase {
+       private static $post;
+
+       private static $template_canvas_path = ABSPATH . WPINC . '/template-canvas.php';
+
+       public static function wpSetUpBeforeClass() {
+               // Set up custom template post.
+               $args       = array(
+                       'post_type'    => 'wp_template',
+                       'post_name'    => 'wp-custom-template-my-block-template',
+                       'post_title'   => 'My Custom Block Template',
+                       'post_content' => 'Content',
+                       'post_excerpt' => 'Description of my block template',
+                       'tax_input'    => array(
+                               'wp_theme' => array(
+                                       get_stylesheet(),
+                               ),
+                       ),
+               );
+               self::$post = self::factory()->post->create_and_get( $args );
+               wp_set_post_terms( self::$post->ID, get_stylesheet(), 'wp_theme' );
+       }
+
+       public static function wpTearDownAfterClass() {
+               wp_delete_post( self::$post->ID );
+       }
+
+       public function tearDown() {
+               global $_wp_current_template_content;
+               unset( $_wp_current_template_content );
+       }
+
+       /**
+        * Regression: https://github.com/WordPress/gutenberg/issues/31399.
+        */
+       function test_custom_page_php_template_takes_precedence_over_all_other_templates() {
+               $custom_page_template      = 'templates/full-width.php';
+               $custom_page_template_path = get_stylesheet_directory() . '/' . $custom_page_template;
+               $type                      = 'page';
+               $templates                 = array(
+                       $custom_page_template,
+                       'page-slug.php',
+                       'page-1.php',
+                       'page.php',
+               );
+               $resolved_template_path    = locate_block_template( $custom_page_template_path, $type, $templates );
+               $this->assertEquals( $custom_page_template_path, $resolved_template_path );
+       }
+
+       /**
+        * Covers: https://github.com/WordPress/gutenberg/pull/30438.
+        */
+       function test_custom_page_block_template_takes_precedence_over_all_other_templates() {
+               global $_wp_current_template_content;
+
+               $custom_page_block_template = 'wp-custom-template-my-block-template';
+               $page_template_path         = get_stylesheet_directory() . '/' . 'page.php';
+               $type                       = 'page';
+               $templates                  = array(
+                       $custom_page_block_template,
+                       'page-slug.php',
+                       'page-1.php',
+                       'page.php',
+               );
+               $resolved_template_path     = locate_block_template( $page_template_path, $type, $templates );
+               $this->assertEquals( self::$template_canvas_path, $resolved_template_path );
+               $this->assertEquals( self::$post->post_content, $_wp_current_template_content );
+       }
+
+       /**
+        * Regression: https://github.com/WordPress/gutenberg/issues/31652.
+        */
+       function test_template_remains_unchanged_if_templates_array_is_empty() {
+               $resolved_template_path = locate_block_template( '', 'search', array() );
+               $this->assertEquals( '', $resolved_template_path );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/block-template.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestschemasetupphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php  2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php    2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -129,6 +129,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/block-types/(?P<namespace>[a-zA-Z0-9_-]+)',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/block-types/(?P<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/settings',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        '/wp/v2/templates',
+                       '/wp/v2/templates/(?P<id>[\/\w-]+)',
+                       '/wp/v2/templates/(?P<id>[\d]+)/autosaves',
+                       '/wp/v2/templates/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)',
+                       '/wp/v2/templates/(?P<parent>[\d]+)/revisions',
+                       '/wp/v2/templates/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)',
</ins><span class="cx" style="display: block; padding: 0 10px">                         '/wp/v2/themes',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/themes/(?P<stylesheet>[\w-]+)',
</span><span class="cx" style="display: block; padding: 0 10px">                        '/wp/v2/plugins',
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiresttemplatescontrollerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/rest-templates-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-templates-controller.php                          (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/rest-templates-controller.php    2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,197 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Unit tests covering the templates endpoint..
+ *
+ * @package WordPress
+ * @subpackage REST API
+ */
+
+class WP_REST_Template_Controller_Test extends WP_Test_REST_Controller_Testcase {
+       /**
+        * @var int
+        */
+       protected static $admin_id;
+       private static $post;
+
+       /**
+        * Create fake data before our tests run.
+        *
+        * @param WP_UnitTest_Factory $factory Helper that lets us create fake data.
+        */
+       public static function wpSetupBeforeClass( $factory ) {
+               self::$admin_id = $factory->user->create(
+                       array(
+                               'role' => 'administrator',
+                       )
+               );
+
+               // Set up template post.
+               $args       = 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(
+                                       get_stylesheet(),
+                               ),
+                       ),
+               );
+               self::$post = self::factory()->post->create_and_get( $args );
+               wp_set_post_terms( self::$post->ID, get_stylesheet(), 'wp_theme' );
+       }
+
+       public static function wpTearDownAfterClass() {
+               wp_delete_post( self::$post->ID );
+       }
+
+
+       public function test_register_routes() {
+               $routes = rest_get_server()->get_routes();
+               $this->assertArrayHasKey( '/wp/v2/templates', $routes );
+               $this->assertArrayHasKey( '/wp/v2/templates/(?P<id>[\/\w-]+)', $routes );
+       }
+
+       public function test_context_param() {
+               // TODO: Implement test_context_param() method.
+       }
+
+       public function test_get_items() {
+               function find_and_normalize_template_by_id( $templates, $id ) {
+                       foreach ( $templates as $template ) {
+                               if ( $template['id'] === $id ) {
+                                       unset( $template['content'] );
+                                       unset( $template['_links'] );
+                                       return $template;
+                               }
+                       }
+
+                       return null;
+               }
+
+               wp_set_current_user( 0 );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/templates' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_cannot_manage_templates', $response, 401 );
+
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/templates' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+
+               $this->assertEquals(
+                       array(
+                               'id'             => 'default//my_template',
+                               'theme'          => 'default',
+                               'slug'           => 'my_template',
+                               'title'          => array(
+                                       'raw'      => 'My Template',
+                                       'rendered' => 'My Template',
+                               ),
+                               'description'    => 'Description of my template.',
+                               'status'         => 'publish',
+                               'source'         => 'custom',
+                               'type'           => 'wp_template',
+                               'wp_id'          => self::$post->ID,
+                               'has_theme_file' => false,
+                       ),
+                       find_and_normalize_template_by_id( $data, 'default//my_template' )
+               );
+       }
+
+       public function test_get_item() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'GET', '/wp/v2/templates/default//my_template' );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               unset( $data['content'] );
+               unset( $data['_links'] );
+
+               $this->assertEquals(
+                       array(
+                               'id'             => 'default//my_template',
+                               'theme'          => 'default',
+                               'slug'           => 'my_template',
+                               'title'          => array(
+                                       'raw'      => 'My Template',
+                                       'rendered' => 'My Template',
+                               ),
+                               'description'    => 'Description of my template.',
+                               'status'         => 'publish',
+                               'source'         => 'custom',
+                               'type'           => 'wp_template',
+                               'wp_id'          => self::$post->ID,
+                               'has_theme_file' => false,
+                       ),
+                       $data
+               );
+       }
+
+       public function test_create_item() {
+               wp_set_current_user( self::$admin_id );
+               $request = new WP_REST_Request( 'POST', '/wp/v2/templates' );
+               $request->set_body_params(
+                       array(
+                               'slug'        => 'my_custom_template',
+                               'title'       => 'My Template',
+                               'description' => 'Just a description',
+                               'content'     => 'Content',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               unset( $data['_links'] );
+               unset( $data['wp_id'] );
+
+               $this->assertEquals(
+                       array(
+                               'id'             => 'default//my_custom_template',
+                               'theme'          => 'default',
+                               'slug'           => 'my_custom_template',
+                               'title'          => array(
+                                       'raw'      => 'My Template',
+                                       'rendered' => 'My Template',
+                               ),
+                               'description'    => 'Just a description',
+                               'status'         => 'publish',
+                               'source'         => 'custom',
+                               'type'           => 'wp_template',
+                               'content'        => array(
+                                       'raw' => 'Content',
+                               ),
+                               'has_theme_file' => false,
+                       ),
+                       $data
+               );
+       }
+
+       public function test_update_item() {
+               wp_set_current_user( self::$admin_id );
+               $request = new WP_REST_Request( 'PUT', '/wp/v2/templates/default//my_template' );
+               $request->set_body_params(
+                       array(
+                               'title' => 'My new Index Title',
+                       )
+               );
+               $response = rest_get_server()->dispatch( $request );
+               $data     = $response->get_data();
+               $this->assertEquals( 'My new Index Title', $data['title']['raw'] );
+               $this->assertEquals( 'custom', $data['source'] );
+       }
+
+       public function test_delete_item() {
+               wp_set_current_user( self::$admin_id );
+               $request  = new WP_REST_Request( 'DELETE', '/wp/v2/templates/justrandom//template' );
+               $response = rest_get_server()->dispatch( $request );
+               $this->assertErrorResponse( 'rest_template_not_found', $response, 404 );
+       }
+
+       public function test_prepare_item() {
+               // TODO: Implement test_prepare_item() method.
+       }
+
+       public function test_get_item_schema() {
+               // TODO: Implement test_get_item_schema() method.
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/rest-templates-controller.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitteststhemewpThemeJsonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/theme/wpThemeJson.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/theme/wpThemeJson.php   2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/tests/phpunit/tests/theme/wpThemeJson.php     2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,6 +5,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordPress
</span><span class="cx" style="display: block; padding: 0 10px">  * @subpackage Theme
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *
</ins><span class="cx" style="display: block; padding: 0 10px">  * @since 5.8.0
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @group themes
</span></span></pre></div>
<a id="trunktestsphpunitteststhemewpThemeJsonResolverphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php   2021-05-25 13:54:12 UTC (rev 51002)
+++ trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php     2021-05-25 14:19:14 UTC (rev 51003)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5,6 +5,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @package WordPress
</span><span class="cx" style="display: block; padding: 0 10px">  * @subpackage Theme
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *
</ins><span class="cx" style="display: block; padding: 0 10px">  * @since 5.8.0
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @group themes
</span></span></pre>
</div>
</div>

</body>
</html>