Make WordPress Core

Changeset 52062


Ignore:
Timestamp:
11/08/2021 11:09:53 PM (5 years ago)
Author:
noisysocks
Message:

Editor: Add block theme infrastructure

Adds the required infrastructure to render block-based themes. This is sourced
from the Gutenberg plugin.

Fixes #54335.
Props bernhard-reiter, youknowriad, ntsekouras, hellofromtonya.

Location:
trunk
Files:
5 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-template-utils.php

    r51300 r52062  
    11<?php
    22/**
    3  * Utilities used to fetch and create templates.
     3 * Utilities used to fetch and create templates and template parts.
    44 *
    55 * @package WordPress
     
    77 */
    88
     9// Define constants for supported wp_template_part_area taxonomy.
     10if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) {
     11        define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' );
     12}
     13if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) {
     14        define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' );
     15}
     16if ( ! defined( 'WP_TEMPLATE_PART_AREA_SIDEBAR' ) ) {
     17        define( 'WP_TEMPLATE_PART_AREA_SIDEBAR', 'sidebar' );
     18}
     19if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) {
     20        define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' );
     21}
     22
     23/**
     24 * Returns a filtered list of allowed area values for template parts.
     25 *
     26 * @since 5.9.0
     27 *
     28 * @return array The supported template part area values.
     29 */
     30function get_allowed_block_template_part_areas() {
     31        $default_area_definitions = array(
     32                array(
     33                        'area'        => WP_TEMPLATE_PART_AREA_UNCATEGORIZED,
     34                        'label'       => __( 'General' ),
     35                        'description' => __(
     36                                'General templates often perform a specific role like displaying post content, and are not tied to any particular area.'
     37                        ),
     38                        'icon'        => 'layout',
     39                        'area_tag'    => 'div',
     40                ),
     41                array(
     42                        'area'        => WP_TEMPLATE_PART_AREA_HEADER,
     43                        'label'       => __( 'Header' ),
     44                        'description' => __(
     45                                'The Header template defines a page area that typically contains a title, logo, and main navigation.'
     46                        ),
     47                        'icon'        => 'header',
     48                        'area_tag'    => 'header',
     49                ),
     50                array(
     51                        'area'        => WP_TEMPLATE_PART_AREA_FOOTER,
     52                        'label'       => __( 'Footer' ),
     53                        'description' => __(
     54                                'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.'
     55                        ),
     56                        'icon'        => 'footer',
     57                        'area_tag'    => 'footer',
     58                ),
     59        );
     60
     61        /**
     62         * Filters the list of allowed template part area values.
     63         *
     64         * @since 5.9.0
     65         *
     66         * @param array $default_areas An array of supported area objects.
     67         */
     68        return apply_filters( 'default_wp_template_part_areas', $default_area_definitions );
     69}
     70
     71
     72/**
     73 * Returns a filtered list of default template types, containing their
     74 * localized titles and descriptions.
     75 *
     76 * @since 5.9.0
     77 *
     78 * @return array The default template types.
     79 */
     80function get_default_block_template_types() {
     81        $default_template_types = array(
     82                'index'          => array(
     83                        'title'       => _x( 'Index', 'Template name' ),
     84                        'description' => __( 'The default template used when no other template is available. This is a required template in WordPress.' ),
     85                ),
     86                'home'           => array(
     87                        'title'       => _x( 'Home', 'Template name' ),
     88                        'description' => __( 'Template used for the main page that displays blog posts. This is the front page by default in WordPress. If a static front page is set, this is the template used for the page that contains the latest blog posts.' ),
     89                ),
     90                'front-page'     => array(
     91                        'title'       => _x( 'Front Page', 'Template name' ),
     92                        'description' => __( 'Template used to render the front page of the site, whether it displays blog posts or a static page. The front page template takes precedence over the "Home" template.' ),
     93                ),
     94                'singular'       => array(
     95                        'title'       => _x( 'Singular', 'Template name' ),
     96                        'description' => __( 'Template used for displaying single views of the content. This template is a fallback for the Single, Post, and Page templates, which take precedence when they exist.' ),
     97                ),
     98                'single'         => array(
     99                        'title'       => _x( 'Single Post', 'Template name' ),
     100                        'description' => __( 'Template used to display a single blog post.' ),
     101                ),
     102                'page'           => array(
     103                        'title'       => _x( 'Page', 'Template name' ),
     104                        'description' => __( 'Template used to display individual pages.' ),
     105                ),
     106                'archive'        => array(
     107                        'title'       => _x( 'Archive', 'Template name' ),
     108                        'description' => __( 'The archive template displays multiple entries at once. It is used as a fallback for the Category, Author, and Date templates, which take precedence when they are available.' ),
     109                ),
     110                'author'         => array(
     111                        'title'       => _x( 'Author', 'Template name' ),
     112                        'description' => __( 'Archive template used to display a list of posts from a single author.' ),
     113                ),
     114                'category'       => array(
     115                        'title'       => _x( 'Category', 'Template name' ),
     116                        'description' => __( 'Archive template used to display a list of posts from the same category.' ),
     117                ),
     118                'taxonomy'       => array(
     119                        'title'       => _x( 'Taxonomy', 'Template name' ),
     120                        'description' => __( 'Archive template used to display a list of posts from the same taxonomy.' ),
     121                ),
     122                'date'           => array(
     123                        'title'       => _x( 'Date', 'Template name' ),
     124                        'description' => __( 'Archive template used to display a list of posts from a specific date.' ),
     125                ),
     126                'tag'            => array(
     127                        'title'       => _x( 'Tag', 'Template name' ),
     128                        'description' => __( 'Archive template used to display a list of posts with a given tag.' ),
     129                ),
     130                'attachment'     => array(
     131                        'title'       => __( 'Media' ),
     132                        'description' => __( 'Template used to display individual media items or attachments.' ),
     133                ),
     134                'search'         => array(
     135                        'title'       => _x( 'Search', 'Template name' ),
     136                        'description' => __( 'Template used to display search results.' ),
     137                ),
     138                'privacy-policy' => array(
     139                        'title'       => __( 'Privacy Policy' ),
     140                        'description' => '',
     141                ),
     142                '404'            => array(
     143                        'title'       => _x( '404', 'Template name' ),
     144                        'description' => __( 'Template shown when no content is found.' ),
     145                ),
     146        );
     147
     148        /**
     149         * Filters the list of template types.
     150         *
     151         * @since 5.9.0
     152         *
     153         * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ].
     154         */
     155        return apply_filters( 'default_template_types', $default_template_types );
     156}
     157
     158/**
     159 * Checks whether the input 'area' is a supported value.
     160 * Returns the input if supported, otherwise returns the 'uncategorized' value.
     161 *
     162 * @access private
     163 * @since 5.9.0
     164 *
     165 * @param string $type Template part area name.
     166 *
     167 * @return string Input if supported, else the uncategorized value.
     168 */
     169function _filter_block_template_part_area( $type ) {
     170        $allowed_areas = array_map(
     171                static function ( $item ) {
     172                        return $item['area'];
     173                },
     174                get_allowed_block_template_part_areas()
     175        );
     176        if ( in_array( $type, $allowed_areas, true ) ) {
     177                return $type;
     178        }
     179
     180        $warning_message = sprintf(
     181                /* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */
     182                __( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ),
     183                $type,
     184                WP_TEMPLATE_PART_AREA_UNCATEGORIZED
     185        );
     186        trigger_error( $warning_message, E_USER_NOTICE );
     187        return WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
     188}
     189
     190/**
     191 * Finds all nested template part file paths in a theme's directory.
     192 *
     193 * @access private
     194 * @since 5.9.0
     195 *
     196 * @param string $base_directory The theme's file path.
     197 * @return array $path_list A list of paths to all template part files.
     198 */
     199function _get_block_templates_paths( $base_directory ) {
     200        $path_list = array();
     201        if ( file_exists( $base_directory ) ) {
     202                $nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
     203                $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH );
     204                foreach ( $nested_html_files as $path => $file ) {
     205                        $path_list[] = $path;
     206                }
     207        }
     208        return $path_list;
     209}
     210
     211/**
     212 * Retrieves the template file from the theme for a given slug.
     213 *
     214 * @access private
     215 * @since 5.9.0
     216 *
     217 * @param string $template_type wp_template or wp_template_part.
     218 * @param string $slug template slug.
     219 *
     220 * @return array|null Template.
     221 */
     222function _get_block_template_file( $template_type, $slug ) {
     223        if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
     224                return null;
     225        }
     226
     227        $template_base_paths = array(
     228                'wp_template'      => 'block-templates',
     229                'wp_template_part' => 'block-template-parts',
     230        );
     231        $themes              = array(
     232                get_stylesheet() => get_stylesheet_directory(),
     233                get_template()   => get_template_directory(),
     234        );
     235        foreach ( $themes as $theme_slug => $theme_dir ) {
     236                $file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html';
     237                if ( file_exists( $file_path ) ) {
     238                        $new_template_item = array(
     239                                'slug'  => $slug,
     240                                'path'  => $file_path,
     241                                'theme' => $theme_slug,
     242                                'type'  => $template_type,
     243                        );
     244
     245                        if ( 'wp_template_part' === $template_type ) {
     246                                return _add_block_template_part_area_info( $new_template_item );
     247                        }
     248
     249                        if ( 'wp_template' === $template_type ) {
     250                                return _add_block_template_info( $new_template_item );
     251                        }
     252
     253                        return $new_template_item;
     254                }
     255        }
     256
     257        return null;
     258}
     259
     260/**
     261 * Retrieves the template files from  the theme.
     262 *
     263 * @access private
     264 * @since 5.9.0
     265 *
     266 * @param string $template_type wp_template or wp_template_part.
     267 *
     268 * @return array Template.
     269 */
     270function _get_block_templates_files( $template_type ) {
     271        if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
     272                return null;
     273        }
     274
     275        $template_base_paths = array(
     276                'wp_template'      => 'block-templates',
     277                'wp_template_part' => 'block-template-parts',
     278        );
     279        $themes              = array(
     280                get_stylesheet() => get_stylesheet_directory(),
     281                get_template()   => get_template_directory(),
     282        );
     283
     284        $template_files = array();
     285        foreach ( $themes as $theme_slug => $theme_dir ) {
     286                $theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
     287                foreach ( $theme_template_files as $template_file ) {
     288                        $template_base_path = $template_base_paths[ $template_type ];
     289                        $template_slug      = substr(
     290                                $template_file,
     291                                // Starting position of slug.
     292                                strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
     293                                // Subtract ending '.html'.
     294                                -5
     295                        );
     296                        $new_template_item = array(
     297                                'slug'  => $template_slug,
     298                                'path'  => $template_file,
     299                                'theme' => $theme_slug,
     300                                'type'  => $template_type,
     301                        );
     302
     303                        if ( 'wp_template_part' === $template_type ) {
     304                                $template_files[] = _add_block_template_part_area_info( $new_template_item );
     305                        }
     306
     307                        if ( 'wp_template' === $template_type ) {
     308                                $template_files[] = _add_block_template_info( $new_template_item );
     309                        }
     310                }
     311        }
     312
     313        return $template_files;
     314}
     315
     316/**
     317 * Attempts to add custom template information to the template item.
     318 *
     319 * @access private
     320 * @since 5.9.0
     321 *
     322 * @param array $template_item Template to add information to (requires 'slug' field).
     323 * @return array Template
     324 */
     325function _add_block_template_info( $template_item ) {
     326        if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
     327                return $template_item;
     328        }
     329
     330        $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates();
     331        if ( isset( $theme_data[ $template_item['slug'] ] ) ) {
     332                $template_item['title']     = $theme_data[ $template_item['slug'] ]['title'];
     333                $template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes'];
     334        }
     335
     336        return $template_item;
     337}
     338
     339/**
     340 * Attempts to add the template part's area information to the input template.
     341 *
     342 * @access private
     343 * @since 5.9.0
     344 *
     345 * @param array $template_info Template to add information to (requires 'type' and 'slug' fields).
     346 *
     347 * @return array Template.
     348 */
     349function _add_block_template_part_area_info( $template_info ) {
     350        if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
     351                $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts();
     352        }
     353
     354        if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) {
     355                $template_info['title'] = $theme_data[ $template_info['slug'] ]['title'];
     356                $template_info['area']  = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] );
     357        } else {
     358                $template_info['area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
     359        }
     360
     361        return $template_info;
     362}
     363
     364/**
     365 * Returns an array containing the references of
     366 * the passed blocks and their inner blocks.
     367 *
     368 * @access private
     369 * @since 5.9.0
     370 *
     371 * @param array $blocks array of blocks.
     372 *
     373 * @return array block references to the passed blocks and their inner blocks.
     374 */
     375function _flatten_blocks( &$blocks ) {
     376        $all_blocks = array();
     377        $queue      = array();
     378        foreach ( $blocks as &$block ) {
     379                $queue[] = &$block;
     380        }
     381
     382        while ( count( $queue ) > 0 ) {
     383                $block = &$queue[0];
     384                array_shift( $queue );
     385                $all_blocks[] = &$block;
     386
     387                if ( ! empty( $block['innerBlocks'] ) ) {
     388                        foreach ( $block['innerBlocks'] as &$inner_block ) {
     389                                $queue[] = &$inner_block;
     390                        }
     391                }
     392        }
     393
     394        return $all_blocks;
     395}
     396
     397/**
     398 * Parses wp_template content and injects the current theme's
     399 * stylesheet as a theme attribute into each wp_template_part
     400 *
     401 * @access private
     402 * @since 5.9.0
     403 *
     404 * @param string $template_content serialized wp_template content.
     405 *
     406 * @return string Updated wp_template content.
     407 */
     408function _inject_theme_attribute_in_block_template_content( $template_content ) {
     409        $has_updated_content = false;
     410        $new_content         = '';
     411        $template_blocks     = parse_blocks( $template_content );
     412
     413        $blocks = _flatten_blocks( $template_blocks );
     414        foreach ( $blocks as &$block ) {
     415                if (
     416                        'core/template-part' === $block['blockName'] &&
     417                        ! isset( $block['attrs']['theme'] )
     418                ) {
     419                        $block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
     420                        $has_updated_content     = true;
     421                }
     422        }
     423
     424        if ( $has_updated_content ) {
     425                foreach ( $template_blocks as &$block ) {
     426                        $new_content .= serialize_block( $block );
     427                }
     428
     429                return $new_content;
     430        }
     431
     432        return $template_content;
     433}
     434
     435/**
     436 * Build a unified template object based on a theme file.
     437 *
     438 * @access private
     439 * @since 5.9.0
     440 *
     441 * @param array $template_file Theme file.
     442 * @param array $template_type wp_template or wp_template_part.
     443 *
     444 * @return WP_Block_Template Template.
     445 */
     446function _build_block_template_result_from_file( $template_file, $template_type ) {
     447        $default_template_types = get_default_block_template_types();
     448        $template_content       = file_get_contents( $template_file['path'] );
     449        $theme                  = wp_get_theme()->get_stylesheet();
     450
     451        $template                 = new WP_Block_Template();
     452        $template->id             = $theme . '//' . $template_file['slug'];
     453        $template->theme          = $theme;
     454        $template->content        = _inject_theme_attribute_in_block_template_content( $template_content );
     455        $template->slug           = $template_file['slug'];
     456        $template->source         = 'theme';
     457        $template->type           = $template_type;
     458        $template->title          = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug'];
     459        $template->status         = 'publish';
     460        $template->has_theme_file = true;
     461        $template->is_custom      = true;
     462
     463        if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
     464                $template->description = $default_template_types[ $template_file['slug'] ]['description'];
     465                $template->title       = $default_template_types[ $template_file['slug'] ]['title'];
     466                $template->is_custom   = false;
     467        }
     468
     469        if ( 'wp_template' === $template_type && isset( $template_file['postTypes'] ) ) {
     470                $template->post_types = $template_file['postTypes'];
     471        }
     472
     473        if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) {
     474                $template->area = $template_file['area'];
     475        }
     476
     477        return $template;
     478}
     479
    9480/**
    10481 * Build a unified template object based a post Object.
    11482 *
    12483 * @access private
    13  * @since 5.8.0
     484 * @since 5.9.0
    14485 *
    15486 * @param WP_Post $post Template post.
     
    17488 * @return WP_Block_Template|WP_Error Template.
    18489 */
    19 function _build_template_result_from_post( $post ) {
    20         $terms = get_the_terms( $post, 'wp_theme' );
     490function _build_block_template_result_from_post( $post ) {
     491        $default_template_types = get_default_block_template_types();
     492        $terms                  = get_the_terms( $post, 'wp_theme' );
    21493
    22494        if ( is_wp_error( $terms ) ) {
     
    28500        }
    29501
    30         $theme = $terms[0]->name;
     502        $theme          = $terms[0]->name;
     503        $has_theme_file = wp_get_theme()->get_stylesheet() === $theme &&
     504                null !== _get_block_template_file( $post->post_type, $post->post_name );
    31505
    32506        $template                 = new WP_Block_Template();
     
    41515        $template->title          = $post->post_title;
    42516        $template->status         = $post->post_status;
    43         $template->has_theme_file = false;
     517        $template->has_theme_file = $has_theme_file;
     518        $template->is_custom      = true;
     519
     520        if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
     521                $template->is_custom = false;
     522        }
     523
     524        if ( 'wp_template_part' === $post->post_type ) {
     525                $type_terms = get_the_terms( $post, 'wp_template_part_area' );
     526                if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
     527                        $template->area = $type_terms[0]->name;
     528                }
     529        }
    44530
    45531        return $template;
     
    54540 *     Optional. Arguments to retrieve templates.
    55541 *
    56  *     @type array  $slug__in List of slugs to include.
    57  *     @type int    $wp_id Post ID of customized template.
     542 *     @type array  $slug__in  List of slugs to include.
     543 *     @type int    $wp_id     Post ID of customized template.
     544 *     @type string $area      A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only).
     545 *     @type string $post_type Post type to get the templates for.
    58546 * }
    59  * @param string $template_type Optional. The template type (post type). Default 'wp_template'.
    60  * @return WP_Block_Template[] Block template objects.
     547 * @param array $template_type wp_template or wp_template_part.
     548 *
     549 * @return array Templates.
    61550 */
    62551function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
     552        /**
     553         * Filters the block templates array before the query takes place.
     554         *
     555         * Return a non-null value to bypass the WordPress queries.
     556         *
     557         * @since 5.9
     558         *
     559         * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query,
     560         *                                                  or null to allow WP to run it's normal queries.
     561         * @param array $query {
     562         *     Optional. Arguments to retrieve templates.
     563         *
     564         *     @type array  $slug__in List of slugs to include.
     565         *     @type int    $wp_id Post ID of customized template.
     566         *     @type string $post_type Post type to get the templates for.
     567         * }
     568         * @param array $template_type wp_template or wp_template_part.
     569         */
     570        $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
     571        if ( ! is_null( $templates ) ) {
     572                return $templates;
     573        }
     574
     575        $post_type     = isset( $query['post_type'] ) ? $query['post_type'] : '';
    63576        $wp_query_args = array(
    64577                'post_status'    => array( 'auto-draft', 'draft', 'publish' ),
     
    75588        );
    76589
     590        if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
     591                $wp_query_args['tax_query'][]           = array(
     592                        'taxonomy' => 'wp_template_part_area',
     593                        'field'    => 'name',
     594                        'terms'    => $query['area'],
     595                );
     596                $wp_query_args['tax_query']['relation'] = 'AND';
     597        }
     598
    77599        if ( isset( $query['slug__in'] ) ) {
    78600                $wp_query_args['post_name__in'] = $query['slug__in'];
    79601        }
    80602
    81         // This is only needed for the regular templates CPT listing and editor.
     603        // This is only needed for the regular templates/template parts CPT listing and editor.
    82604        if ( isset( $query['wp_id'] ) ) {
    83605                $wp_query_args['p'] = $query['wp_id'];
     
    89611        $query_result   = array();
    90612        foreach ( $template_query->posts as $post ) {
    91                 $template = _build_template_result_from_post( $post );
    92 
    93                 if ( ! is_wp_error( $template ) ) {
    94                         $query_result[] = $template;
    95                 }
    96         }
    97 
    98         return $query_result;
     613                $template = _build_block_template_result_from_post( $post );
     614
     615                if ( is_wp_error( $template ) ) {
     616                        continue;
     617                }
     618
     619                if ( $post_type && ! $template->is_custom ) {
     620                        continue;
     621                }
     622
     623                $query_result[] = $template;
     624        }
     625
     626        if ( ! isset( $query['wp_id'] ) ) {
     627                $template_files = _get_block_templates_files( $template_type );
     628                foreach ( $template_files as $template_file ) {
     629                        $template = _build_block_template_result_from_file( $template_file, $template_type );
     630
     631                        if ( $post_type && ! $template->is_custom ) {
     632                                continue;
     633                        }
     634
     635                        if ( $post_type &&
     636                                isset( $template->post_types ) &&
     637                                ! in_array( $post_type, $template->post_types, true )
     638                        ) {
     639                                continue;
     640                        }
     641
     642                        $is_not_custom   = false === array_search(
     643                                wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'],
     644                                array_column( $query_result, 'id' ),
     645                                true
     646                        );
     647                        $fits_slug_query =
     648                                ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true );
     649                        $fits_area_query =
     650                                ! isset( $query['area'] ) || $template_file['area'] === $query['area'];
     651                        $should_include  = $is_not_custom && $fits_slug_query && $fits_area_query;
     652                        if ( $should_include ) {
     653                                $query_result[] = $template;
     654                        }
     655                }
     656        }
     657
     658        /**
     659         * Filters the array of queried block templates array after they've been fetched.
     660         *
     661         * @since 5.9
     662         *
     663         * @param WP_Block_Template[] $query_result Array of found block templates.
     664         * @param array $query {
     665         *     Optional. Arguments to retrieve templates.
     666         *
     667         *     @type array  $slug__in List of slugs to include.
     668         *     @type int    $wp_id Post ID of customized template.
     669         * }
     670         * @param array $template_type wp_template or wp_template_part.
     671         */
     672        return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
    99673}
    100674
     
    105679 *
    106680 * @param string $id            Template unique identifier (example: theme_slug//template_slug).
    107  * @param string $template_type Optional. The template type (post type). Default 'wp_template'.
     681 * @param array  $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`.
     682 *                              Default `'wp_template'`.
     683 *
    108684 * @return WP_Block_Template|null Template.
    109685 */
    110686function get_block_template( $id, $template_type = 'wp_template' ) {
     687        /**
     688         * Filters the block templates array before the query takes place.
     689         *
     690         * Return a non-null value to bypass the WordPress queries.
     691         *
     692         * @since 5.9.0
     693         *
     694         * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
     695         *                                               or null to allow WP to run its normal queries.
     696         * @param string $id                             Template unique identifier (example: theme_slug//template_slug).
     697         * @param array  $template_type                  Template type: `'wp_template'` or '`wp_template_part'`.
     698         */
     699        $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type );
     700        if ( ! is_null( $block_template ) ) {
     701                return $block_template;
     702        }
     703
    111704        $parts = explode( '//', $id, 2 );
    112705        if ( count( $parts ) < 2 ) {
     
    132725
    133726        if ( count( $posts ) > 0 ) {
    134                 $template = _build_template_result_from_post( $posts[0] );
     727                $template = _build_block_template_result_from_post( $posts[0] );
    135728
    136729                if ( ! is_wp_error( $template ) ) {
     
    139732        }
    140733
    141         return null;
    142 }
     734        $block_template = get_block_file_template( $id, $template_type );
     735
     736        /**
     737         * Filters the array of queried block templates array after they've been fetched.
     738         *
     739         * @since 5.9
     740         *
     741         * @param WP_Block_Template $block_template The found block template.
     742         * @param string            $id             Template unique identifier (example: theme_slug//template_slug).
     743         * @param array             $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
     744         */
     745        return apply_filters( 'get_block_template', $block_template, $id, $template_type );
     746}
     747
     748/**
     749 * Retrieves a single unified template object using its id.
     750 *
     751 * @since 5.9.0
     752 *
     753 * @param string $id            Template unique identifier (example: theme_slug//template_slug).
     754 * @param array  $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`.
     755 *                              Default `'wp_template'`.
     756 */
     757function get_block_file_template( $id, $template_type = 'wp_template' ) {
     758        /**
     759         * Filters the block templates array before the query takes place.
     760         *
     761         * Return a non-null value to bypass the WordPress queries.
     762         *
     763         *
     764         * @since 5.9.0
     765         *
     766         * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
     767         *                                               or null to allow WP to run its normal queries.
     768         * @param string $id                             Template unique identifier (example: theme_slug//template_slug).
     769         * @param array  $template_type                  Template type: `'wp_template'` or '`wp_template_part'`.
     770         */
     771        $block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type );
     772        if ( ! is_null( $block_template ) ) {
     773                return $block_template;
     774        }
     775
     776        $parts = explode( '//', $id, 2 );
     777        if ( count( $parts ) < 2 ) {
     778                /** This filter is documented at the end of this function */
     779                return apply_filters( 'get_block_file_template', null, $id, $template_type );
     780        }
     781        list( $theme, $slug ) = $parts;
     782
     783        if ( wp_get_theme()->get_stylesheet() !== $theme ) {
     784                /** This filter is documented at the end of this function */
     785                return apply_filters( 'get_block_file_template', null, $id, $template_type );
     786        }
     787
     788        $template_file = _get_block_template_file( $template_type, $slug );
     789        if ( null === $template_file ) {
     790                /** This filter is documented at the end of this function */
     791                return apply_filters( 'get_block_file_template', null, $id, $template_type );
     792        }
     793
     794        $block_template = _build_block_template_result_from_file( $template_file, $template_type );
     795
     796        /**
     797         * Filters the array of queried block templates array after they've been fetched.
     798         *
     799         * @since 5.9.0
     800         *
     801         * @param WP_Block_Template $block_template The found block template.
     802         * @param string            $id             Template unique identifier (example: theme_slug//template_slug).
     803         * @param array             $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
     804         */
     805        return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
     806}
     807
     808/**
     809 * Print a template-part.
     810 *
     811 * @since 5.9.0
     812 *
     813 * @param string $part The template-part to print. Use "header" or "footer".
     814 */
     815function block_template_part( $part ) {
     816        $template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
     817        if ( ! $template_part || empty( $template_part->content ) ) {
     818                return;
     819        }
     820        echo do_blocks( $template_part->content );
     821}
     822
     823/**
     824 * Print the header template-part.
     825 *
     826 * @since 5.9.0
     827 */
     828function block_header_area() {
     829        block_template_part( 'header' );
     830}
     831
     832/**
     833 * Print the footer template-part.
     834 *
     835 * @since 5.9.0
     836 */
     837function block_footer_area() {
     838        block_template_part( 'footer' );
     839}
  • trunk/src/wp-includes/blocks.php

    r52057 r52062  
    11551155 * It's used in QueryPaginationNext and QueryPaginationPrevious blocks.
    11561156 *
     1157 * @since 5.9.0
     1158 *
    11571159 * @param WP_Block $block   Block instance.
    11581160 * @param boolean  $is_next Flag for hanlding `next/previous` blocks.
  • trunk/src/wp-includes/blocks/index.php

    r51176 r52062  
    3838require ABSPATH . WPINC . '/blocks/social-link.php';
    3939require ABSPATH . WPINC . '/blocks/tag-cloud.php';
     40require ABSPATH . WPINC . '/blocks/template-part.php';
    4041
    4142/**
  • trunk/src/wp-includes/class-wp-block-template.php

    r51003 r52062  
    101101         */
    102102        public $has_theme_file;
     103
     104        /**
     105         * Whether a template is a custom template.
     106         *
     107         * @since 5.9.0
     108         *
     109         * @var boolean
     110         */
     111        public $is_custom = true;
    103112}
  • trunk/src/wp-includes/class-wp-theme-json.php

    r52049 r52062  
    602602        public function get_custom_templates() {
    603603                $custom_templates = array();
    604                 if ( ! isset( $this->theme_json['customTemplates'] ) ) {
     604                if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) {
    605605                        return $custom_templates;
    606606                }
     
    626626        public function get_template_parts() {
    627627                $template_parts = array();
    628                 if ( ! isset( $this->theme_json['templateParts'] ) ) {
     628                if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) {
    629629                        return $template_parts;
    630630                }
  • trunk/src/wp-includes/default-filters.php

    r52049 r52062  
    665665add_filter( 'render_block_context', '_block_template_render_without_post_block_context' );
    666666add_filter( 'pre_wp_unique_post_slug', 'wp_filter_wp_template_unique_post_slug', 10, 5 );
     667add_action( 'save_post_wp_template_part', 'wp_set_unique_slug_on_create_template_part' );
    667668add_action( 'wp_footer', 'the_block_template_skip_link' );
    668669add_action( 'setup_theme', 'wp_enable_block_templates' );
  • trunk/src/wp-includes/post.php

    r52045 r52062  
    356356                        ),
    357357                        'map_meta_cap'          => true,
     358                        'supports'              => array(
     359                                'title',
     360                                'slug',
     361                                'excerpt',
     362                                'editor',
     363                                'revisions',
     364                        ),
     365                )
     366        );
     367
     368        register_post_type(
     369                'wp_template_part',
     370                array(
     371                        'labels'                => array(
     372                                'name'                  => __( 'Template Parts' ),
     373                                'singular_name'         => __( 'Template Part' ),
     374                                'add_new'               => _x( 'Add New', 'Template Part' ),
     375                                'add_new_item'          => __( 'Add New Template Part' ),
     376                                'new_item'              => __( 'New Template Part' ),
     377                                'edit_item'             => __( 'Edit Template Part' ),
     378                                'view_item'             => __( 'View Template Part' ),
     379                                'all_items'             => __( 'All Template Parts' ),
     380                                'search_items'          => __( 'Search Template Parts' ),
     381                                'parent_item_colon'     => __( 'Parent Template Part:' ),
     382                                'not_found'             => __( 'No template parts found.' ),
     383                                'not_found_in_trash'    => __( 'No template parts found in Trash.' ),
     384                                'archives'              => __( 'Template part archives' ),
     385                                'insert_into_item'      => __( 'Insert into template part' ),
     386                                'uploaded_to_this_item' => __( 'Uploaded to this template part' ),
     387                                'filter_items_list'     => __( 'Filter template parts list' ),
     388                                'items_list_navigation' => __( 'Template parts list navigation' ),
     389                                'items_list'            => __( 'Template parts list' ),
     390                        ),
     391                        'description'           => __( 'Template parts to include in your templates.' ),
     392                        'public'                => false,
     393                        '_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
     394                        'has_archive'           => false,
     395                        'show_ui'               => false,
     396                        'show_in_menu'          => false,
     397                        'show_in_rest'          => true,
     398                        'rewrite'               => false,
     399                        'rest_base'             => 'template-parts',
     400                        'rest_controller_class' => 'WP_REST_Templates_Controller',
     401                        'map_meta_cap'          => true,
     402                        'capabilities'          => array(
     403                                'create_posts'           => 'edit_theme_options',
     404                                'delete_posts'           => 'edit_theme_options',
     405                                'delete_others_posts'    => 'edit_theme_options',
     406                                'delete_private_posts'   => 'edit_theme_options',
     407                                'delete_published_posts' => 'edit_theme_options',
     408                                'edit_posts'             => 'edit_theme_options',
     409                                'edit_others_posts'      => 'edit_theme_options',
     410                                'edit_private_posts'     => 'edit_theme_options',
     411                                'edit_published_posts'   => 'edit_theme_options',
     412                                'publish_posts'          => 'edit_theme_options',
     413                                'read'                   => 'edit_theme_options',
     414                                'read_private_posts'     => 'edit_theme_options',
     415                        ),
    358416                        'supports'              => array(
    359417                                'title',
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php

    r51962 r52062  
    157157                        $query['area'] = $request['area'];
    158158                }
     159                if ( isset( $request['post_type'] ) ) {
     160                        $query['post_type'] = $request['post_type'];
     161                }
    159162
    160163                $templates = array();
     
    188191         */
    189192        public function get_item( $request ) {
    190                 $template = get_block_template( $request['id'], $this->post_type );
     193                if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
     194                        $template = get_block_file_template( $request['id'], $this->post_type );
     195                } else {
     196                        $template = get_block_template( $request['id'], $this->post_type );
     197                }
    191198
    192199                if ( ! $template ) {
     
    221228                if ( ! $template ) {
    222229                        return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
     230                }
     231
     232                if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
     233                        wp_delete_post( $template->wp_id, true );
     234                        return $this->prepare_item_for_response( get_block_file_template( $request['id'], $this->post_type ), $request );
    223235                }
    224236
     
    396408                }
    397409
     410                if ( 'wp_template_part' === $this->post_type ) {
     411                        if ( isset( $request['area'] ) ) {
     412                                $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
     413                        } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
     414                                $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
     415                        } elseif ( ! $template->area ) {
     416                                $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
     417                        }
     418                }
     419
    398420                return $changes;
    399421        }
     
    503525         *
    504526         * @since 5.8.0
     527         * @since 5.9.0 Added `'area'` and `'post_type'`.
    505528         *
    506529         * @return array Collection parameters.
     
    508531        public function get_collection_params() {
    509532                return array(
    510                         'context' => $this->get_context_param(),
    511                         'wp_id'   => array(
     533                        'context'   => $this->get_context_param(),
     534                        'wp_id'     => array(
    512535                                'description' => __( 'Limit to the specified post id.' ),
    513536                                'type'        => 'integer',
    514537                        ),
     538                        'area'      => array(
     539                                'description' => __( 'Limit to the specified template part area.' ),
     540                                'type'        => 'string',
     541                        ),
     542                        'post_type' => array(
     543                                'description' => __( 'Post type to get the templates for.' ),
     544                                'type'        => 'string',
     545                        ),
    515546                );
    516547        }
     
    520551         *
    521552         * @since 5.8.0
     553         * @since 5.9.0 Added `'area'`.
    522554         *
    523555         * @return array Item schema data.
     
    597629                );
    598630
     631                if ( 'wp_template_part' === $this->post_type ) {
     632                        $schema['properties']['area'] = array(
     633                                'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ),
     634                                'type'        => 'string',
     635                                'context'     => array( 'embed', 'view', 'edit' ),
     636                        );
     637                }
     638
    599639                $this->schema = $schema;
    600640
  • trunk/src/wp-includes/taxonomy.php

    r52041 r52062  
    1919 *
    2020 * @since 2.8.0
     21 * @since 5.9.0 Added `'wp_template_part_area'` taxonomy.
    2122 *
    2223 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     
    176177        register_taxonomy(
    177178                'wp_theme',
    178                 array( 'wp_template', 'wp_global_styles' ),
     179                array( 'wp_template', 'wp_template_part', 'wp_global_styles' ),
    179180                array(
    180181                        'public'            => false,
     
    183184                                'name'          => __( 'Themes' ),
    184185                                'singular_name' => __( 'Theme' ),
     186                        ),
     187                        'query_var'         => false,
     188                        'rewrite'           => false,
     189                        'show_ui'           => false,
     190                        '_builtin'          => true,
     191                        'show_in_nav_menus' => false,
     192                        'show_in_rest'      => false,
     193                )
     194        );
     195
     196        register_taxonomy(
     197                'wp_template_part_area',
     198                array( 'wp_template_part' ),
     199                array(
     200                        'public'            => false,
     201                        'hierarchical'      => false,
     202                        'labels'            => array(
     203                                'name'          => __( 'Template Part Areas' ),
     204                                'singular_name' => __( 'Template Part Area' ),
    185205                        ),
    186206                        'query_var'         => false,
  • trunk/src/wp-includes/theme-templates.php

    r51199 r52062  
    11<?php
     2
     3/**
     4 * Sets a custom slug when creating auto-draft template parts.
     5 *
     6 * This is only needed for auto-drafts created by the regular WP editor.
     7 * If this page is to be removed, this won't be necessary.
     8 *
     9 * @since 5.9.0
     10 *
     11 */
     12function wp_set_unique_slug_on_create_template_part( $post_id ) {
     13        $post = get_post( $post_id );
     14        if ( 'auto-draft' !== $post->post_status ) {
     15                return;
     16        }
     17
     18        if ( ! $post->post_name ) {
     19                wp_update_post(
     20                        array(
     21                                'ID'        => $post_id,
     22                                'post_name' => 'custom_slug_' . uniqid(),
     23                        )
     24                );
     25        }
     26
     27        $terms = get_the_terms( $post_id, 'wp_theme' );
     28        if ( ! is_array( $terms ) || ! count( $terms ) ) {
     29                wp_set_post_terms( $post_id, wp_get_theme()->get_stylesheet(), 'wp_theme' );
     30        }
     31}
    232
    333/**
     
    1545 */
    1646function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
    17         if ( 'wp_template' !== $post_type ) {
     47        if ( 'wp_template' !== $post_type && 'wp_template_part' !== $post_type ) {
    1848                return $override_slug;
    1949        }
  • trunk/tests/phpunit/includes/functions.php

    r51105 r52062  
    338338        remove_action( 'init', 'register_core_block_types_from_metadata' );
    339339        remove_action( 'init', 'register_block_core_legacy_widget' );
     340        remove_action( 'init', 'register_block_core_template_part' );
    340341}
    341342tests_add_filter( 'init', '_unhook_block_registration', 1000 );
  • trunk/tests/phpunit/tests/block-template-utils.php

    r52010 r52062  
    77
    88/**
    9  * Tests for the Block Template Loader abstraction layer.
     9 * Tests for the Block Templates abstraction layer.
    1010 */
    1111class Block_Template_Utils_Test extends WP_UnitTestCase {
    1212        private static $post;
     13        private static $template_part_post;
    1314
    1415        public static function wpSetUpBeforeClass() {
     16                // We may need a block theme.
     17                // switch_theme( 'tt1-blocks' );
     18
     19                // Set up a template post corresponding to a different theme.
     20                // We do this to ensure resolution and slug creation works as expected,
     21                // even with another post of that same name present for another theme.
     22                $args       = array(
     23                        'post_type'    => 'wp_template',
     24                        'post_name'    => 'my_template',
     25                        'post_title'   => 'My Template',
     26                        'post_content' => 'Content',
     27                        'post_excerpt' => 'Description of my template',
     28                        'tax_input'    => array(
     29                                'wp_theme' => array(
     30                                        'this-theme-should-not-resolve',
     31                                ),
     32                        ),
     33                );
     34                self::$post = self::factory()->post->create_and_get( $args );
     35                wp_set_post_terms( self::$post->ID, 'this-theme-should-not-resolve', 'wp_theme' );
     36
    1537                // Set up template post.
    1638                $args       = array(
     
    2850                self::$post = self::factory()->post->create_and_get( $args );
    2951                wp_set_post_terms( self::$post->ID, get_stylesheet(), 'wp_theme' );
     52
     53                // Set up template part post.
     54                $template_part_args       = array(
     55                        'post_type'    => 'wp_template_part',
     56                        'post_name'    => 'my_template_part',
     57                        'post_title'   => 'My Template Part',
     58                        'post_content' => 'Content',
     59                        'post_excerpt' => 'Description of my template part',
     60                        'tax_input'    => array(
     61                                'wp_theme'              => array(
     62                                        get_stylesheet(),
     63                                ),
     64                                'wp_template_part_area' => array(
     65                                        WP_TEMPLATE_PART_AREA_HEADER,
     66                                ),
     67                        ),
     68                );
     69                self::$template_part_post = self::factory()->post->create_and_get( $template_part_args );
     70                wp_set_post_terms( self::$template_part_post->ID, WP_TEMPLATE_PART_AREA_HEADER, 'wp_template_part_area' );
     71                wp_set_post_terms( self::$template_part_post->ID, get_stylesheet(), 'wp_theme' );
    3072        }
    3173
     
    3577
    3678        public function test_build_template_result_from_post() {
    37                 $template = _build_template_result_from_post(
     79                $template = _build_block_template_result_from_post(
    3880                        self::$post,
    3981                        'wp_template'
     
    4183
    4284                $this->assertNotWPError( $template );
    43                 $this->assertSame( get_stylesheet() . '//my_template', $template->id );
    44                 $this->assertSame( get_stylesheet(), $template->theme );
    45                 $this->assertSame( 'my_template', $template->slug );
    46                 $this->assertSame( 'publish', $template->status );
    47                 $this->assertSame( 'custom', $template->source );
    48                 $this->assertSame( 'My Template', $template->title );
    49                 $this->assertSame( 'Description of my template', $template->description );
    50                 $this->assertSame( 'wp_template', $template->type );
     85                $this->assertEquals( get_stylesheet() . '//my_template', $template->id );
     86                $this->assertEquals( get_stylesheet(), $template->theme );
     87                $this->assertEquals( 'my_template', $template->slug );
     88                $this->assertEquals( 'publish', $template->status );
     89                $this->assertEquals( 'custom', $template->source );
     90                $this->assertEquals( 'My Template', $template->title );
     91                $this->assertEquals( 'Description of my template', $template->description );
     92                $this->assertEquals( 'wp_template', $template->type );
     93
     94                // Test template parts.
     95                $template_part = _build_block_template_result_from_post(
     96                        self::$template_part_post,
     97                        'wp_template_part'
     98                );
     99                $this->assertNotWPError( $template_part );
     100                $this->assertEquals( get_stylesheet() . '//my_template_part', $template_part->id );
     101                $this->assertEquals( get_stylesheet(), $template_part->theme );
     102                $this->assertEquals( 'my_template_part', $template_part->slug );
     103                $this->assertEquals( 'publish', $template_part->status );
     104                $this->assertEquals( 'custom', $template_part->source );
     105                $this->assertEquals( 'My Template Part', $template_part->title );
     106                $this->assertEquals( 'Description of my template part', $template_part->description );
     107                $this->assertEquals( 'wp_template_part', $template_part->type );
     108                $this->assertEquals( WP_TEMPLATE_PART_AREA_HEADER, $template_part->area );
     109        }
     110
     111        function test_build_block_template_result_from_file() {
     112                $template = _build_block_template_result_from_file(
     113                        array(
     114                                'slug' => 'single',
     115                                'path' => __DIR__ . '/../data/templates/template.html',
     116                        ),
     117                        'wp_template'
     118                );
     119
     120                $this->assertEquals( get_stylesheet() . '//single', $template->id );
     121                $this->assertEquals( get_stylesheet(), $template->theme );
     122                $this->assertEquals( 'single', $template->slug );
     123                $this->assertEquals( 'publish', $template->status );
     124                $this->assertEquals( 'theme', $template->source );
     125                $this->assertEquals( 'Single Post', $template->title );
     126                $this->assertEquals( 'Template used to display a single blog post.', $template->description );
     127                $this->assertEquals( 'wp_template', $template->type );
     128
     129                // Test template parts.
     130                $template_part = _build_block_template_result_from_file(
     131                        array(
     132                                'slug' => 'header',
     133                                'path' => __DIR__ . '/../data/templates/template.html',
     134                                'area' => WP_TEMPLATE_PART_AREA_HEADER,
     135                        ),
     136                        'wp_template_part'
     137                );
     138                $this->assertEquals( get_stylesheet() . '//header', $template_part->id );
     139                $this->assertEquals( get_stylesheet(), $template_part->theme );
     140                $this->assertEquals( 'header', $template_part->slug );
     141                $this->assertEquals( 'publish', $template_part->status );
     142                $this->assertEquals( 'theme', $template_part->source );
     143                $this->assertEquals( 'header', $template_part->title );
     144                $this->assertEquals( '', $template_part->description );
     145                $this->assertEquals( 'wp_template_part', $template_part->type );
     146                $this->assertEquals( WP_TEMPLATE_PART_AREA_HEADER, $template_part->area );
     147        }
     148
     149        function test_inject_theme_attribute_in_block_template_content() {
     150                $theme                           = get_stylesheet();
     151                $content_without_theme_attribute = '<!-- wp:template-part {"slug":"header","align":"full", "tagName":"header","className":"site-header"} /-->';
     152                $template_content                = _inject_theme_attribute_in_block_template_content(
     153                        $content_without_theme_attribute,
     154                        $theme
     155                );
     156                $expected                        = sprintf(
     157                        '<!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /-->',
     158                        get_stylesheet()
     159                );
     160                $this->assertEquals( $expected, $template_content );
     161
     162                $content_without_theme_attribute_nested = '<!-- wp:group --><!-- wp:template-part {"slug":"header","align":"full", "tagName":"header","className":"site-header"} /--><!-- /wp:group -->';
     163                $template_content                       = _inject_theme_attribute_in_block_template_content(
     164                        $content_without_theme_attribute_nested,
     165                        $theme
     166                );
     167                $expected                               = sprintf(
     168                        '<!-- wp:group --><!-- wp:template-part {"slug":"header","align":"full","tagName":"header","className":"site-header","theme":"%s"} /--><!-- /wp:group -->',
     169                        get_stylesheet()
     170                );
     171                $this->assertEquals( $expected, $template_content );
     172
     173                // Does not inject theme when there is an existing theme attribute.
     174                $content_with_existing_theme_attribute = '<!-- wp:template-part {"slug":"header","theme":"fake-theme","align":"full", "tagName":"header","className":"site-header"} /-->';
     175                $template_content                      = _inject_theme_attribute_in_block_template_content(
     176                        $content_with_existing_theme_attribute,
     177                        $theme
     178                );
     179                $this->assertEquals( $content_with_existing_theme_attribute, $template_content );
     180
     181                // Does not inject theme when there is no template part.
     182                $content_with_no_template_part = '<!-- wp:post-content /-->';
     183                $template_content              = _inject_theme_attribute_in_block_template_content(
     184                        $content_with_no_template_part,
     185                        $theme
     186                );
     187                $this->assertEquals( $content_with_no_template_part, $template_content );
     188        }
     189
     190        /**
     191         * Should retrieve the template from the theme files.
     192         */
     193        function test_get_block_template_from_file() {
     194                $this->markTestIncomplete();
     195                // Requires switching to a block theme.
     196                /* $id       = get_stylesheet() . '//' . 'index';
     197                $template = get_block_template( $id, 'wp_template' );
     198                $this->assertEquals( $id, $template->id );
     199                $this->assertEquals( get_stylesheet(), $template->theme );
     200                $this->assertEquals( 'index', $template->slug );
     201                $this->assertEquals( 'publish', $template->status );
     202                $this->assertEquals( 'theme', $template->source );
     203                $this->assertEquals( 'wp_template', $template->type );
     204
     205                // Test template parts.
     206                $id       = get_stylesheet() . '//' . 'header';
     207                $template = get_block_template( $id, 'wp_template_part' );
     208                $this->assertEquals( $id, $template->id );
     209                $this->assertEquals( get_stylesheet(), $template->theme );
     210                $this->assertEquals( 'header', $template->slug );
     211                $this->assertEquals( 'publish', $template->status );
     212                $this->assertEquals( 'theme', $template->source );
     213                $this->assertEquals( 'wp_template_part', $template->type );
     214                $this->assertEquals( WP_TEMPLATE_PART_AREA_HEADER, $template->area );
     215                */
    51216        }
    52217
     
    57222                $id       = get_stylesheet() . '//' . 'my_template';
    58223                $template = get_block_template( $id, 'wp_template' );
    59                 $this->assertSame( $id, $template->id );
    60                 $this->assertSame( get_stylesheet(), $template->theme );
    61                 $this->assertSame( 'my_template', $template->slug );
    62                 $this->assertSame( 'publish', $template->status );
    63                 $this->assertSame( 'custom', $template->source );
    64                 $this->assertSame( 'wp_template', $template->type );
     224                $this->assertEquals( $id, $template->id );
     225                $this->assertEquals( get_stylesheet(), $template->theme );
     226                $this->assertEquals( 'my_template', $template->slug );
     227                $this->assertEquals( 'publish', $template->status );
     228                $this->assertEquals( 'custom', $template->source );
     229                $this->assertEquals( 'wp_template', $template->type );
     230
     231                // Test template parts.
     232                $id       = get_stylesheet() . '//' . 'my_template_part';
     233                $template = get_block_template( $id, 'wp_template_part' );
     234                $this->assertEquals( $id, $template->id );
     235                $this->assertEquals( get_stylesheet(), $template->theme );
     236                $this->assertEquals( 'my_template_part', $template->slug );
     237                $this->assertEquals( 'publish', $template->status );
     238                $this->assertEquals( 'custom', $template->source );
     239                $this->assertEquals( 'wp_template_part', $template->type );
     240                $this->assertEquals( WP_TEMPLATE_PART_AREA_HEADER, $template->area );
    65241        }
    66242
    67243        /**
    68          * Should retrieve block templates.
     244         * Should retrieve block templates (file and CPT)
    69245         */
    70246        public function test_get_block_templates() {
     
    85261                $this->assertContains( get_stylesheet() . '//' . 'my_template', $template_ids );
    86262
     263                // The result might change in a block theme.
     264                // $this->assertContains( get_stylesheet() . '//' . 'index', $template_ids );
     265
    87266                // Filter by slug.
    88267                $templates    = get_block_templates( array( 'slug__in' => array( 'my_template' ) ), 'wp_template' );
    89268                $template_ids = get_template_ids( $templates );
    90                 $this->assertSame( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
     269                $this->assertEquals( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
    91270
    92271                // Filter by CPT ID.
    93272                $templates    = get_block_templates( array( 'wp_id' => self::$post->ID ), 'wp_template' );
    94273                $template_ids = get_template_ids( $templates );
    95                 $this->assertSame( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
     274                $this->assertEquals( array( get_stylesheet() . '//' . 'my_template' ), $template_ids );
     275
     276                // Filter template part by area.
     277                // Requires a block theme.
     278                /*$templates    = get_block_templates( array( 'area' => WP_TEMPLATE_PART_AREA_HEADER ), 'wp_template_part' );
     279                $template_ids = get_template_ids( $templates );
     280                $this->assertEquals(
     281                        array(
     282                                get_stylesheet() . '//' . 'my_template_part',
     283                                get_stylesheet() . '//' . 'header',
     284                        ),
     285                        $template_ids
     286                );
     287                */
     288        }
     289
     290        /**
     291         * Should flatten nested blocks
     292         */
     293        function test_flatten_blocks() {
     294                $content_template_part_inside_group = '<!-- wp:group --><!-- wp:template-part {"slug":"header"} /--><!-- /wp:group -->';
     295                $blocks                             = parse_blocks( $content_template_part_inside_group );
     296                $actual                             = _flatten_blocks( $blocks );
     297                $expected                           = array( $blocks[0], $blocks[0]['innerBlocks'][0] );
     298                $this->assertEquals( $expected, $actual );
     299
     300                $content_template_part_inside_group_inside_group = '<!-- wp:group --><!-- wp:group --><!-- wp:template-part {"slug":"header"} /--><!-- /wp:group --><!-- /wp:group -->';
     301                $blocks   = parse_blocks( $content_template_part_inside_group_inside_group );
     302                $actual   = _flatten_blocks( $blocks );
     303                $expected = array( $blocks[0], $blocks[0]['innerBlocks'][0], $blocks[0]['innerBlocks'][0]['innerBlocks'][0] );
     304                $this->assertEquals( $expected, $actual );
     305
     306                $content_without_inner_blocks = '<!-- wp:group /-->';
     307                $blocks                       = parse_blocks( $content_without_inner_blocks );
     308                $actual                       = _flatten_blocks( $blocks );
     309                $expected                     = array( $blocks[0] );
     310                $this->assertEquals( $expected, $actual );
    96311        }
    97312}
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r52051 r52062  
    133133                        '/wp/v2/block-types/(?P<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)',
    134134                        '/wp/v2/settings',
     135                        '/wp/v2/template-parts',
     136                        '/wp/v2/template-parts/(?P<id>[\/\w-]+)',
     137                        '/wp/v2/template-parts/(?P<id>[\d]+)/autosaves',
     138                        '/wp/v2/template-parts/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)',
     139                        '/wp/v2/template-parts/(?P<parent>[\d]+)/revisions',
     140                        '/wp/v2/template-parts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)',
    135141                        '/wp/v2/templates',
    136142                        '/wp/v2/templates/(?P<id>[\/\w-]+)',
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r52051 r52062  
    41654165                            "type": "integer",
    41664166                            "required": false
     4167                        },
     4168                        "area": {
     4169                            "description": "Limit to the specified template part area.",
     4170                            "type": "string",
     4171                            "required": false
     4172                        },
     4173                        "post_type": {
     4174                            "description": "Post type to get the templates for.",
     4175                            "type": "string",
     4176                            "required": false
    41674177                        }
    41684178                    }
     
    45514561        },
    45524562        "/wp/v2/templates/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     4563            "namespace": "wp/v2",
     4564            "methods": [
     4565                "GET"
     4566            ],
     4567            "endpoints": [
     4568                {
     4569                    "methods": [
     4570                        "GET"
     4571                    ],
     4572                    "args": {
     4573                        "parent": {
     4574                            "description": "The ID for the parent of the autosave.",
     4575                            "type": "integer",
     4576                            "required": false
     4577                        },
     4578                        "id": {
     4579                            "description": "The ID for the autosave.",
     4580                            "type": "integer",
     4581                            "required": false
     4582                        },
     4583                        "context": {
     4584                            "description": "Scope under which the request is made; determines fields present in response.",
     4585                            "type": "string",
     4586                            "enum": [
     4587                                "view",
     4588                                "embed",
     4589                                "edit"
     4590                            ],
     4591                            "default": "view",
     4592                            "required": false
     4593                        }
     4594                    }
     4595                }
     4596            ]
     4597        },
     4598        "/wp/v2/template-parts": {
     4599            "namespace": "wp/v2",
     4600            "methods": [
     4601                "GET",
     4602                "POST"
     4603            ],
     4604            "endpoints": [
     4605                {
     4606                    "methods": [
     4607                        "GET"
     4608                    ],
     4609                    "args": {
     4610                        "context": {
     4611                            "description": "Scope under which the request is made; determines fields present in response.",
     4612                            "type": "string",
     4613                            "enum": [
     4614                                "view",
     4615                                "embed",
     4616                                "edit"
     4617                            ],
     4618                            "required": false
     4619                        },
     4620                        "wp_id": {
     4621                            "description": "Limit to the specified post id.",
     4622                            "type": "integer",
     4623                            "required": false
     4624                        },
     4625                        "area": {
     4626                            "description": "Limit to the specified template part area.",
     4627                            "type": "string",
     4628                            "required": false
     4629                        },
     4630                        "post_type": {
     4631                            "description": "Post type to get the templates for.",
     4632                            "type": "string",
     4633                            "required": false
     4634                        }
     4635                    }
     4636                },
     4637                {
     4638                    "methods": [
     4639                        "POST"
     4640                    ],
     4641                    "args": {
     4642                        "slug": {
     4643                            "description": "Unique slug identifying the template.",
     4644                            "type": "string",
     4645                            "minLength": 1,
     4646                            "pattern": "[a-zA-Z_\\-]+",
     4647                            "required": true
     4648                        },
     4649                        "theme": {
     4650                            "description": "Theme identifier for the template.",
     4651                            "type": "string",
     4652                            "required": false
     4653                        },
     4654                        "content": {
     4655                            "default": "",
     4656                            "description": "Content of template.",
     4657                            "type": [
     4658                                "object",
     4659                                "string"
     4660                            ],
     4661                            "required": false
     4662                        },
     4663                        "title": {
     4664                            "default": "",
     4665                            "description": "Title of template.",
     4666                            "type": [
     4667                                "object",
     4668                                "string"
     4669                            ],
     4670                            "required": false
     4671                        },
     4672                        "description": {
     4673                            "default": "",
     4674                            "description": "Description of template.",
     4675                            "type": "string",
     4676                            "required": false
     4677                        },
     4678                        "status": {
     4679                            "default": "publish",
     4680                            "description": "Status of template.",
     4681                            "type": "string",
     4682                            "required": false
     4683                        },
     4684                        "area": {
     4685                            "description": "Where the template part is intended for use (header, footer, etc.)",
     4686                            "type": "string",
     4687                            "required": false
     4688                        }
     4689                    }
     4690                }
     4691            ],
     4692            "_links": {
     4693                "self": [
     4694                    {
     4695                        "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts"
     4696                    }
     4697                ]
     4698            }
     4699        },
     4700        "/wp/v2/template-parts/(?P<id>[\\/\\w-]+)": {
     4701            "namespace": "wp/v2",
     4702            "methods": [
     4703                "GET",
     4704                "POST",
     4705                "PUT",
     4706                "PATCH",
     4707                "DELETE"
     4708            ],
     4709            "endpoints": [
     4710                {
     4711                    "methods": [
     4712                        "GET"
     4713                    ],
     4714                    "args": {
     4715                        "id": {
     4716                            "description": "The id of a template",
     4717                            "type": "string",
     4718                            "required": false
     4719                        }
     4720                    }
     4721                },
     4722                {
     4723                    "methods": [
     4724                        "POST",
     4725                        "PUT",
     4726                        "PATCH"
     4727                    ],
     4728                    "args": {
     4729                        "slug": {
     4730                            "description": "Unique slug identifying the template.",
     4731                            "type": "string",
     4732                            "minLength": 1,
     4733                            "pattern": "[a-zA-Z_\\-]+",
     4734                            "required": false
     4735                        },
     4736                        "theme": {
     4737                            "description": "Theme identifier for the template.",
     4738                            "type": "string",
     4739                            "required": false
     4740                        },
     4741                        "content": {
     4742                            "description": "Content of template.",
     4743                            "type": [
     4744                                "object",
     4745                                "string"
     4746                            ],
     4747                            "required": false
     4748                        },
     4749                        "title": {
     4750                            "description": "Title of template.",
     4751                            "type": [
     4752                                "object",
     4753                                "string"
     4754                            ],
     4755                            "required": false
     4756                        },
     4757                        "description": {
     4758                            "description": "Description of template.",
     4759                            "type": "string",
     4760                            "required": false
     4761                        },
     4762                        "status": {
     4763                            "description": "Status of template.",
     4764                            "type": "string",
     4765                            "required": false
     4766                        },
     4767                        "area": {
     4768                            "description": "Where the template part is intended for use (header, footer, etc.)",
     4769                            "type": "string",
     4770                            "required": false
     4771                        }
     4772                    }
     4773                },
     4774                {
     4775                    "methods": [
     4776                        "DELETE"
     4777                    ],
     4778                    "args": {
     4779                        "force": {
     4780                            "type": "boolean",
     4781                            "default": false,
     4782                            "description": "Whether to bypass Trash and force deletion.",
     4783                            "required": false
     4784                        }
     4785                    }
     4786                }
     4787            ]
     4788        },
     4789        "/wp/v2/template-parts/(?P<parent>[\\d]+)/revisions": {
     4790            "namespace": "wp/v2",
     4791            "methods": [
     4792                "GET"
     4793            ],
     4794            "endpoints": [
     4795                {
     4796                    "methods": [
     4797                        "GET"
     4798                    ],
     4799                    "args": {
     4800                        "parent": {
     4801                            "description": "The ID for the parent of the revision.",
     4802                            "type": "integer",
     4803                            "required": false
     4804                        },
     4805                        "context": {
     4806                            "description": "Scope under which the request is made; determines fields present in response.",
     4807                            "type": "string",
     4808                            "enum": [
     4809                                "view",
     4810                                "embed",
     4811                                "edit"
     4812                            ],
     4813                            "default": "view",
     4814                            "required": false
     4815                        },
     4816                        "page": {
     4817                            "description": "Current page of the collection.",
     4818                            "type": "integer",
     4819                            "default": 1,
     4820                            "minimum": 1,
     4821                            "required": false
     4822                        },
     4823                        "per_page": {
     4824                            "description": "Maximum number of items to be returned in result set.",
     4825                            "type": "integer",
     4826                            "minimum": 1,
     4827                            "maximum": 100,
     4828                            "required": false
     4829                        },
     4830                        "search": {
     4831                            "description": "Limit results to those matching a string.",
     4832                            "type": "string",
     4833                            "required": false
     4834                        },
     4835                        "exclude": {
     4836                            "description": "Ensure result set excludes specific IDs.",
     4837                            "type": "array",
     4838                            "items": {
     4839                                "type": "integer"
     4840                            },
     4841                            "default": [],
     4842                            "required": false
     4843                        },
     4844                        "include": {
     4845                            "description": "Limit result set to specific IDs.",
     4846                            "type": "array",
     4847                            "items": {
     4848                                "type": "integer"
     4849                            },
     4850                            "default": [],
     4851                            "required": false
     4852                        },
     4853                        "offset": {
     4854                            "description": "Offset the result set by a specific number of items.",
     4855                            "type": "integer",
     4856                            "required": false
     4857                        },
     4858                        "order": {
     4859                            "description": "Order sort attribute ascending or descending.",
     4860                            "type": "string",
     4861                            "default": "desc",
     4862                            "enum": [
     4863                                "asc",
     4864                                "desc"
     4865                            ],
     4866                            "required": false
     4867                        },
     4868                        "orderby": {
     4869                            "description": "Sort collection by object attribute.",
     4870                            "type": "string",
     4871                            "default": "date",
     4872                            "enum": [
     4873                                "date",
     4874                                "id",
     4875                                "include",
     4876                                "relevance",
     4877                                "slug",
     4878                                "include_slugs",
     4879                                "title"
     4880                            ],
     4881                            "required": false
     4882                        }
     4883                    }
     4884                }
     4885            ]
     4886        },
     4887        "/wp/v2/template-parts/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)": {
     4888            "namespace": "wp/v2",
     4889            "methods": [
     4890                "GET",
     4891                "DELETE"
     4892            ],
     4893            "endpoints": [
     4894                {
     4895                    "methods": [
     4896                        "GET"
     4897                    ],
     4898                    "args": {
     4899                        "parent": {
     4900                            "description": "The ID for the parent of the revision.",
     4901                            "type": "integer",
     4902                            "required": false
     4903                        },
     4904                        "id": {
     4905                            "description": "Unique identifier for the revision.",
     4906                            "type": "integer",
     4907                            "required": false
     4908                        },
     4909                        "context": {
     4910                            "description": "Scope under which the request is made; determines fields present in response.",
     4911                            "type": "string",
     4912                            "enum": [
     4913                                "view",
     4914                                "embed",
     4915                                "edit"
     4916                            ],
     4917                            "default": "view",
     4918                            "required": false
     4919                        }
     4920                    }
     4921                },
     4922                {
     4923                    "methods": [
     4924                        "DELETE"
     4925                    ],
     4926                    "args": {
     4927                        "parent": {
     4928                            "description": "The ID for the parent of the revision.",
     4929                            "type": "integer",
     4930                            "required": false
     4931                        },
     4932                        "id": {
     4933                            "description": "Unique identifier for the revision.",
     4934                            "type": "integer",
     4935                            "required": false
     4936                        },
     4937                        "force": {
     4938                            "type": "boolean",
     4939                            "default": false,
     4940                            "description": "Required to be true, as revisions do not support trashing.",
     4941                            "required": false
     4942                        }
     4943                    }
     4944                }
     4945            ]
     4946        },
     4947        "/wp/v2/template-parts/(?P<id>[\\d]+)/autosaves": {
     4948            "namespace": "wp/v2",
     4949            "methods": [
     4950                "GET",
     4951                "POST"
     4952            ],
     4953            "endpoints": [
     4954                {
     4955                    "methods": [
     4956                        "GET"
     4957                    ],
     4958                    "args": {
     4959                        "parent": {
     4960                            "description": "The ID for the parent of the autosave.",
     4961                            "type": "integer",
     4962                            "required": false
     4963                        },
     4964                        "context": {
     4965                            "description": "Scope under which the request is made; determines fields present in response.",
     4966                            "type": "string",
     4967                            "enum": [
     4968                                "view",
     4969                                "embed",
     4970                                "edit"
     4971                            ],
     4972                            "default": "view",
     4973                            "required": false
     4974                        }
     4975                    }
     4976                },
     4977                {
     4978                    "methods": [
     4979                        "POST"
     4980                    ],
     4981                    "args": {
     4982                        "parent": {
     4983                            "description": "The ID for the parent of the autosave.",
     4984                            "type": "integer",
     4985                            "required": false
     4986                        },
     4987                        "slug": {
     4988                            "description": "Unique slug identifying the template.",
     4989                            "type": "string",
     4990                            "minLength": 1,
     4991                            "pattern": "[a-zA-Z_\\-]+",
     4992                            "required": false
     4993                        },
     4994                        "theme": {
     4995                            "description": "Theme identifier for the template.",
     4996                            "type": "string",
     4997                            "required": false
     4998                        },
     4999                        "content": {
     5000                            "description": "Content of template.",
     5001                            "type": [
     5002                                "object",
     5003                                "string"
     5004                            ],
     5005                            "required": false
     5006                        },
     5007                        "title": {
     5008                            "description": "Title of template.",
     5009                            "type": [
     5010                                "object",
     5011                                "string"
     5012                            ],
     5013                            "required": false
     5014                        },
     5015                        "description": {
     5016                            "description": "Description of template.",
     5017                            "type": "string",
     5018                            "required": false
     5019                        },
     5020                        "status": {
     5021                            "description": "Status of template.",
     5022                            "type": "string",
     5023                            "required": false
     5024                        },
     5025                        "area": {
     5026                            "description": "Where the template part is intended for use (header, footer, etc.)",
     5027                            "type": "string",
     5028                            "required": false
     5029                        }
     5030                    }
     5031                }
     5032            ]
     5033        },
     5034        "/wp/v2/template-parts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
    45535035            "namespace": "wp/v2",
    45545036            "methods": [
     
    86119093            ]
    86129094        }
     9095    },
     9096    "wp_template_part": {
     9097        "description": "Template parts to include in your templates.",
     9098        "hierarchical": false,
     9099        "name": "Template Parts",
     9100        "slug": "wp_template_part",
     9101        "taxonomies": [],
     9102        "rest_base": "template-parts",
     9103        "rest_namespace": "wp/v2",
     9104        "_links": {
     9105            "collection": [
     9106                {
     9107                    "href": "http://example.org/index.php?rest_route=/wp/v2/types"
     9108                }
     9109            ],
     9110            "wp:items": [
     9111                {
     9112                    "href": "http://example.org/index.php?rest_route=/wp/v2/template-parts"
     9113                }
     9114            ],
     9115            "curies": [
     9116                {
     9117                    "name": "wp",
     9118                    "href": "https://api.w.org/{rel}",
     9119                    "templated": true
     9120                }
     9121            ]
     9122        }
    86139123    }
    86149124};
  • trunk/tools/webpack/blocks.js

    r52011 r52062  
    5454                'social-link',
    5555                'tag-cloud',
     56                'template-part',
    5657        ];
    5758        const blockFolders = [
Note: See TracChangeset for help on using the changeset viewer.