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 #53176. Built from https://develop.svn.wordpress.org/trunk@51003 git-svn-id: http://core.svn.wordpress.org/trunk@50612 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
30d411f482
commit
4ae0e4220f
|
@ -220,6 +220,7 @@ $editor_settings = array(
|
||||||
'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(),
|
'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(),
|
||||||
'__experimentalBlockPatterns' => WP_Block_Patterns_Registry::get_instance()->get_all_registered(),
|
'__experimentalBlockPatterns' => WP_Block_Patterns_Registry::get_instance()->get_all_registered(),
|
||||||
'__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(),
|
'__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(),
|
||||||
|
'supportsTemplateMode' => current_theme_supports( 'block-templates' ),
|
||||||
|
|
||||||
// Whether or not to load the 'postcustom' meta box is stored as a user meta
|
// Whether or not to load the 'postcustom' meta box is stored as a user meta
|
||||||
// field so that we're not always loading its assets.
|
// field so that we're not always loading its assets.
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?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;
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
<?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( ']]>', ']]>', $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;
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?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;
|
||||||
|
}
|
|
@ -1182,6 +1182,7 @@ final class WP_Theme implements ArrayAccess {
|
||||||
* Returns the theme's post templates.
|
* Returns the theme's post templates.
|
||||||
*
|
*
|
||||||
* @since 4.7.0
|
* @since 4.7.0
|
||||||
|
* @since 5.8.0 Include block templates.
|
||||||
*
|
*
|
||||||
* @return string[] Array of page templates, keyed by filename and post type,
|
* @return string[] Array of page templates, keyed by filename and post type,
|
||||||
* with the value of the translated header name.
|
* with the value of the translated header name.
|
||||||
|
@ -1219,6 +1220,15 @@ final class WP_Theme implements ArrayAccess {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->cache_add( 'post_templates', $post_templates );
|
$this->cache_add( 'post_templates', $post_templates );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -555,6 +555,9 @@ add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 );
|
||||||
add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in <head>.
|
add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in <head>.
|
||||||
add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer.
|
add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer.
|
||||||
|
|
||||||
|
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' );
|
||||||
|
|
||||||
// Taxonomy.
|
// Taxonomy.
|
||||||
add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority.
|
add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority.
|
||||||
add_action( 'change_locale', 'create_initial_taxonomies' );
|
add_action( 'change_locale', 'create_initial_taxonomies' );
|
||||||
|
@ -633,4 +636,9 @@ add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 );
|
||||||
add_filter( 'user_has_cap', 'wp_maybe_grant_resume_extensions_caps', 1 );
|
add_filter( 'user_has_cap', 'wp_maybe_grant_resume_extensions_caps', 1 );
|
||||||
add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 );
|
add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 );
|
||||||
|
|
||||||
|
// 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' );
|
||||||
|
|
||||||
unset( $filter, $action );
|
unset( $filter, $action );
|
||||||
|
|
|
@ -310,6 +310,66 @@ function create_initial_post_types() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
register_post_status(
|
register_post_status(
|
||||||
'publish',
|
'publish',
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -0,0 +1,603 @@
|
||||||
|
<?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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -2662,3 +2662,65 @@ function wp_maybe_inline_styles() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>";
|
||||||
|
}
|
||||||
|
|
|
@ -172,6 +172,25 @@ function create_initial_taxonomies() {
|
||||||
'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
|
'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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>
|
|
@ -63,6 +63,8 @@ function get_query_template( $type, $templates = array() ) {
|
||||||
|
|
||||||
$template = locate_template( $templates );
|
$template = locate_template( $templates );
|
||||||
|
|
||||||
|
$template = locate_block_template( $template, $type, $templates );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the path of the queried template by type.
|
* Filters the path of the queried template by type.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
<?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' );
|
|
@ -13,7 +13,7 @@
|
||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '5.8-alpha-51002';
|
$wp_version = '5.8-alpha-51003';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|
|
@ -170,6 +170,10 @@ require ABSPATH . WPINC . '/query.php';
|
||||||
require ABSPATH . WPINC . '/class-wp-date-query.php';
|
require ABSPATH . WPINC . '/class-wp-date-query.php';
|
||||||
require ABSPATH . WPINC . '/theme.php';
|
require ABSPATH . WPINC . '/theme.php';
|
||||||
require ABSPATH . WPINC . '/class-wp-theme.php';
|
require ABSPATH . WPINC . '/class-wp-theme.php';
|
||||||
|
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';
|
||||||
require ABSPATH . WPINC . '/template.php';
|
require ABSPATH . WPINC . '/template.php';
|
||||||
require ABSPATH . WPINC . '/https-detection.php';
|
require ABSPATH . WPINC . '/https-detection.php';
|
||||||
require ABSPATH . WPINC . '/https-migration.php';
|
require ABSPATH . WPINC . '/https-migration.php';
|
||||||
|
@ -268,6 +272,7 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-site-health-control
|
||||||
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-sidebars-controller.php';
|
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-sidebars-controller.php';
|
||||||
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php';
|
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php';
|
||||||
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php';
|
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php';
|
||||||
|
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php';
|
||||||
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
|
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php';
|
||||||
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
|
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php';
|
||||||
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';
|
require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';
|
||||||
|
|
Loading…
Reference in New Issue