diff --git a/lib/load.php b/lib/load.php
index a0996f3b94f4ff..35b49b2ead5b95 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -41,6 +41,7 @@
require dirname( __FILE__ ) . '/blocks.php';
require dirname( __FILE__ ) . '/templates.php';
+require dirname( __FILE__ ) . '/template-loader.php';
require dirname( __FILE__ ) . '/client-assets.php';
require dirname( __FILE__ ) . '/demo.php';
require dirname( __FILE__ ) . '/widgets.php';
diff --git a/lib/template-canvas.php b/lib/template-canvas.php
new file mode 100644
index 00000000000000..a0e0da7ea01752
--- /dev/null
+++ b/lib/template-canvas.php
@@ -0,0 +1,23 @@
+
+
+>
+
+
+
+
+
+>
+
+
+
+
+
+
+
diff --git a/lib/template-loader.php b/lib/template-loader.php
new file mode 100644
index 00000000000000..1c6923091ce323
--- /dev/null
+++ b/lib/template-loader.php
@@ -0,0 +1,177 @@
+ 'wp_template',
+ 'post_status' => 'publish',
+ 'post_name__in' => $slugs,
+ 'orderby' => 'post_name__in',
+ 'posts_per_page' => 1,
+ )
+ );
+
+ if ( $template_query->have_posts() ) {
+ $template_posts = $template_query->get_posts();
+ $_wp_current_template_post = array_shift( $template_posts );
+ }
+
+ // Add extra hooks for template canvas.
+ add_action( 'wp_head', 'gutenberg_viewport_meta_tag', 0 );
+ remove_action( 'wp_head', '_wp_render_title_tag', 1 );
+ add_action( 'wp_head', 'gutenberg_render_title_tag', 1 );
+
+ // This file will be included instead of the theme's template file.
+ return gutenberg_dir_path() . 'lib/template-canvas.php';
+}
+
+/**
+ * Displays title tag with content, regardless of whether theme has title-tag support.
+ *
+ * @see _wp_render_title_tag()
+ */
+function gutenberg_render_title_tag() {
+ echo '' . wp_get_document_title() . '' . "\n";
+}
+
+/**
+ * Renders the markup for the current template.
+ */
+function gutenberg_render_the_template() {
+ global $_wp_current_template_post;
+ global $wp_embed;
+
+ if ( ! $_wp_current_template_post || 'wp_template' !== $_wp_current_template_post->post_type ) {
+ echo '' . esc_html__( 'No matching template found', 'gutenberg' ) . '
';
+ return;
+ }
+
+ $content = $_wp_current_template_post->post_content;
+
+ $content = $wp_embed->run_shortcode( $content );
+ $content = $wp_embed->autoembed( $content );
+ $content = do_blocks( $content );
+ $content = wptexturize( $content );
+ $content = wp_make_content_images_responsive( $content );
+ $content = str_replace( ']]>', ']]>', $content );
+
+ // Wrap block template in .wp-site-blocks to allow for specific descendant styles
+ // (e.g. `.wp-site-blocks > *`).
+ echo '';
+ echo $content; // phpcs:ignore WordPress.Security.EscapeOutput
+ echo '
';
+}
+
+/**
+ * Renders a 'viewport' meta tag.
+ *
+ * This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas.
+ */
+function gutenberg_viewport_meta_tag() {
+ echo '' . "\n";
+}
+
+/**
+ * Strips .php suffix from template file names.
+ *
+ * @access private
+ *
+ * @param string $template_file Template file name.
+ * @return string Template file name without extension.
+ */
+function gutenberg_strip_php_suffix( $template_file ) {
+ return preg_replace( '/\.php$/', '', $template_file );
+}
diff --git a/lib/templates.php b/lib/templates.php
index dce2905b0ae52d..35b81aaf41100e 100644
--- a/lib/templates.php
+++ b/lib/templates.php
@@ -10,7 +10,7 @@
*/
function gutenberg_register_template_post_type() {
if (
- get_option( 'gutenberg-experiments' ) &&
+ ! get_option( 'gutenberg-experiments' ) ||
! array_key_exists( 'gutenberg-full-site-editing', get_option( 'gutenberg-experiments' ) )
) {
return;
@@ -65,3 +65,32 @@ function gutenberg_grant_template_caps( array $allcaps ) {
return $allcaps;
}
add_filter( 'user_has_cap', 'gutenberg_grant_template_caps' );
+
+/**
+ * Filters capabilities to prevent deletion of the 'wp_template' post with slug 'index'.
+ *
+ * Similar to today's themes, this template should always exist.
+ *
+ * @param array $caps Array of the user's capabilities.
+ * @param string $cap Capability name.
+ * @param int $user_id The user ID.
+ * @param array $args Adds the context to the cap. Typically the object ID.
+ * @return array Filtered $caps.
+ */
+function gutenberg_prevent_index_template_deletion( $caps, $cap, $user_id, $args ) {
+ if ( 'delete_post' !== $cap || ! isset( $args[0] ) ) {
+ return $caps;
+ }
+
+ $post = get_post( $args[0] );
+ if ( ! $post || 'wp_template' !== $post->post_type ) {
+ return $caps;
+ }
+
+ if ( 'index' === $post->post_name ) {
+ $caps[] = 'do_not_allow';
+ }
+
+ return $caps;
+}
+add_filter( 'map_meta_cap', 'gutenberg_prevent_index_template_deletion', 10, 4 );