From f05f235042e74207a709622ebf2887f874c90612 Mon Sep 17 00:00:00 2001 From: noisysocks Date: Mon, 8 Nov 2021 23:10:59 +0000 Subject: [PATCH] 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. Built from https://develop.svn.wordpress.org/trunk@52062 git-svn-id: http://core.svn.wordpress.org/trunk@51654 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/block-template-utils.php | 733 +++++++++++++++++- wp-includes/blocks.php | 2 + wp-includes/blocks/index.php | 1 + wp-includes/blocks/template-part.php | 155 ++++ wp-includes/blocks/template-part/block.json | 35 + .../blocks/template-part/editor-rtl.css | 138 ++++ .../blocks/template-part/editor-rtl.min.css | 1 + wp-includes/blocks/template-part/editor.css | 138 ++++ .../blocks/template-part/editor.min.css | 1 + .../blocks/template-part/theme-rtl.css | 81 ++ .../blocks/template-part/theme-rtl.min.css | 1 + wp-includes/blocks/template-part/theme.css | 81 ++ .../blocks/template-part/theme.min.css | 1 + wp-includes/class-wp-block-template.php | 9 + wp-includes/class-wp-theme-json.php | 4 +- wp-includes/default-filters.php | 1 + wp-includes/post.php | 58 ++ .../class-wp-rest-templates-controller.php | 46 +- wp-includes/taxonomy.php | 22 +- wp-includes/theme-templates.php | 32 +- wp-includes/version.php | 2 +- 21 files changed, 1516 insertions(+), 26 deletions(-) create mode 100644 wp-includes/blocks/template-part.php create mode 100644 wp-includes/blocks/template-part/block.json create mode 100644 wp-includes/blocks/template-part/editor-rtl.css create mode 100644 wp-includes/blocks/template-part/editor-rtl.min.css create mode 100644 wp-includes/blocks/template-part/editor.css create mode 100644 wp-includes/blocks/template-part/editor.min.css create mode 100644 wp-includes/blocks/template-part/theme-rtl.css create mode 100644 wp-includes/blocks/template-part/theme-rtl.min.css create mode 100644 wp-includes/blocks/template-part/theme.css create mode 100644 wp-includes/blocks/template-part/theme.min.css diff --git a/wp-includes/block-template-utils.php b/wp-includes/block-template-utils.php index 2c5b68707b..e0e64cb6f1 100644 --- a/wp-includes/block-template-utils.php +++ b/wp-includes/block-template-utils.php @@ -1,23 +1,495 @@ WP_TEMPLATE_PART_AREA_UNCATEGORIZED, + 'label' => __( 'General' ), + 'description' => __( + 'General templates often perform a specific role like displaying post content, and are not tied to any particular area.' + ), + 'icon' => 'layout', + 'area_tag' => 'div', + ), + array( + 'area' => WP_TEMPLATE_PART_AREA_HEADER, + 'label' => __( 'Header' ), + 'description' => __( + 'The Header template defines a page area that typically contains a title, logo, and main navigation.' + ), + 'icon' => 'header', + 'area_tag' => 'header', + ), + array( + 'area' => WP_TEMPLATE_PART_AREA_FOOTER, + 'label' => __( 'Footer' ), + 'description' => __( + 'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.' + ), + 'icon' => 'footer', + 'area_tag' => 'footer', + ), + ); + + /** + * Filters the list of allowed template part area values. + * + * @since 5.9.0 + * + * @param array $default_areas An array of supported area objects. + */ + return apply_filters( 'default_wp_template_part_areas', $default_area_definitions ); +} + + +/** + * Returns a filtered list of default template types, containing their + * localized titles and descriptions. + * + * @since 5.9.0 + * + * @return array The default template types. + */ +function get_default_block_template_types() { + $default_template_types = array( + 'index' => array( + 'title' => _x( 'Index', 'Template name' ), + 'description' => __( 'The default template used when no other template is available. This is a required template in WordPress.' ), + ), + 'home' => array( + 'title' => _x( 'Home', 'Template name' ), + '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.' ), + ), + 'front-page' => array( + 'title' => _x( 'Front Page', 'Template name' ), + '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.' ), + ), + 'singular' => array( + 'title' => _x( 'Singular', 'Template name' ), + '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.' ), + ), + 'single' => array( + 'title' => _x( 'Single Post', 'Template name' ), + 'description' => __( 'Template used to display a single blog post.' ), + ), + 'page' => array( + 'title' => _x( 'Page', 'Template name' ), + 'description' => __( 'Template used to display individual pages.' ), + ), + 'archive' => array( + 'title' => _x( 'Archive', 'Template name' ), + '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.' ), + ), + 'author' => array( + 'title' => _x( 'Author', 'Template name' ), + 'description' => __( 'Archive template used to display a list of posts from a single author.' ), + ), + 'category' => array( + 'title' => _x( 'Category', 'Template name' ), + 'description' => __( 'Archive template used to display a list of posts from the same category.' ), + ), + 'taxonomy' => array( + 'title' => _x( 'Taxonomy', 'Template name' ), + 'description' => __( 'Archive template used to display a list of posts from the same taxonomy.' ), + ), + 'date' => array( + 'title' => _x( 'Date', 'Template name' ), + 'description' => __( 'Archive template used to display a list of posts from a specific date.' ), + ), + 'tag' => array( + 'title' => _x( 'Tag', 'Template name' ), + 'description' => __( 'Archive template used to display a list of posts with a given tag.' ), + ), + 'attachment' => array( + 'title' => __( 'Media' ), + 'description' => __( 'Template used to display individual media items or attachments.' ), + ), + 'search' => array( + 'title' => _x( 'Search', 'Template name' ), + 'description' => __( 'Template used to display search results.' ), + ), + 'privacy-policy' => array( + 'title' => __( 'Privacy Policy' ), + 'description' => '', + ), + '404' => array( + 'title' => _x( '404', 'Template name' ), + 'description' => __( 'Template shown when no content is found.' ), + ), + ); + + /** + * Filters the list of template types. + * + * @since 5.9.0 + * + * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ]. + */ + return apply_filters( 'default_template_types', $default_template_types ); +} + +/** + * Checks whether the input 'area' is a supported value. + * Returns the input if supported, otherwise returns the 'uncategorized' value. + * + * @access private + * @since 5.9.0 + * + * @param string $type Template part area name. + * + * @return string Input if supported, else the uncategorized value. + */ +function _filter_block_template_part_area( $type ) { + $allowed_areas = array_map( + static function ( $item ) { + return $item['area']; + }, + get_allowed_block_template_part_areas() + ); + if ( in_array( $type, $allowed_areas, true ) ) { + return $type; + } + + $warning_message = sprintf( + /* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */ + __( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ), + $type, + WP_TEMPLATE_PART_AREA_UNCATEGORIZED + ); + trigger_error( $warning_message, E_USER_NOTICE ); + return WP_TEMPLATE_PART_AREA_UNCATEGORIZED; +} + +/** + * Finds all nested template part file paths in a theme's directory. + * + * @access private + * @since 5.9.0 + * + * @param string $base_directory The theme's file path. + * @return array $path_list A list of paths to all template part files. + */ +function _get_block_templates_paths( $base_directory ) { + $path_list = array(); + if ( file_exists( $base_directory ) ) { + $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); + $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH ); + foreach ( $nested_html_files as $path => $file ) { + $path_list[] = $path; + } + } + return $path_list; +} + +/** + * Retrieves the template file from the theme for a given slug. + * + * @access private + * @since 5.9.0 + * + * @param string $template_type wp_template or wp_template_part. + * @param string $slug template slug. + * + * @return array|null Template. + */ +function _get_block_template_file( $template_type, $slug ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + return null; + } + + $template_base_paths = array( + 'wp_template' => 'block-templates', + 'wp_template_part' => 'block-template-parts', + ); + $themes = array( + get_stylesheet() => get_stylesheet_directory(), + get_template() => get_template_directory(), + ); + foreach ( $themes as $theme_slug => $theme_dir ) { + $file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html'; + if ( file_exists( $file_path ) ) { + $new_template_item = array( + 'slug' => $slug, + 'path' => $file_path, + 'theme' => $theme_slug, + 'type' => $template_type, + ); + + if ( 'wp_template_part' === $template_type ) { + return _add_block_template_part_area_info( $new_template_item ); + } + + if ( 'wp_template' === $template_type ) { + return _add_block_template_info( $new_template_item ); + } + + return $new_template_item; + } + } + + return null; +} + +/** + * Retrieves the template files from the theme. + * + * @access private + * @since 5.9.0 + * + * @param string $template_type wp_template or wp_template_part. + * + * @return array Template. + */ +function _get_block_templates_files( $template_type ) { + if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { + return null; + } + + $template_base_paths = array( + 'wp_template' => 'block-templates', + 'wp_template_part' => 'block-template-parts', + ); + $themes = array( + get_stylesheet() => get_stylesheet_directory(), + get_template() => get_template_directory(), + ); + + $template_files = array(); + foreach ( $themes as $theme_slug => $theme_dir ) { + $theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] ); + foreach ( $theme_template_files as $template_file ) { + $template_base_path = $template_base_paths[ $template_type ]; + $template_slug = substr( + $template_file, + // Starting position of slug. + strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ), + // Subtract ending '.html'. + -5 + ); + $new_template_item = array( + 'slug' => $template_slug, + 'path' => $template_file, + 'theme' => $theme_slug, + 'type' => $template_type, + ); + + if ( 'wp_template_part' === $template_type ) { + $template_files[] = _add_block_template_part_area_info( $new_template_item ); + } + + if ( 'wp_template' === $template_type ) { + $template_files[] = _add_block_template_info( $new_template_item ); + } + } + } + + return $template_files; +} + +/** + * Attempts to add custom template information to the template item. + * + * @access private + * @since 5.9.0 + * + * @param array $template_item Template to add information to (requires 'slug' field). + * @return array Template + */ +function _add_block_template_info( $template_item ) { + if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { + return $template_item; + } + + $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates(); + if ( isset( $theme_data[ $template_item['slug'] ] ) ) { + $template_item['title'] = $theme_data[ $template_item['slug'] ]['title']; + $template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes']; + } + + return $template_item; +} + +/** + * Attempts to add the template part's area information to the input template. + * + * @access private + * @since 5.9.0 + * + * @param array $template_info Template to add information to (requires 'type' and 'slug' fields). + * + * @return array Template. + */ +function _add_block_template_part_area_info( $template_info ) { + if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + $theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts(); + } + + if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) { + $template_info['title'] = $theme_data[ $template_info['slug'] ]['title']; + $template_info['area'] = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] ); + } else { + $template_info['area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + } + + return $template_info; +} + +/** + * Returns an array containing the references of + * the passed blocks and their inner blocks. + * + * @access private + * @since 5.9.0 + * + * @param array $blocks array of blocks. + * + * @return array block references to the passed blocks and their inner blocks. + */ +function _flatten_blocks( &$blocks ) { + $all_blocks = array(); + $queue = array(); + foreach ( $blocks as &$block ) { + $queue[] = &$block; + } + + while ( count( $queue ) > 0 ) { + $block = &$queue[0]; + array_shift( $queue ); + $all_blocks[] = &$block; + + if ( ! empty( $block['innerBlocks'] ) ) { + foreach ( $block['innerBlocks'] as &$inner_block ) { + $queue[] = &$inner_block; + } + } + } + + return $all_blocks; +} + +/** + * Parses wp_template content and injects the current theme's + * stylesheet as a theme attribute into each wp_template_part + * + * @access private + * @since 5.9.0 + * + * @param string $template_content serialized wp_template content. + * + * @return string Updated wp_template content. + */ +function _inject_theme_attribute_in_block_template_content( $template_content ) { + $has_updated_content = false; + $new_content = ''; + $template_blocks = parse_blocks( $template_content ); + + $blocks = _flatten_blocks( $template_blocks ); + foreach ( $blocks as &$block ) { + if ( + 'core/template-part' === $block['blockName'] && + ! isset( $block['attrs']['theme'] ) + ) { + $block['attrs']['theme'] = wp_get_theme()->get_stylesheet(); + $has_updated_content = true; + } + } + + if ( $has_updated_content ) { + foreach ( $template_blocks as &$block ) { + $new_content .= serialize_block( $block ); + } + + return $new_content; + } + + return $template_content; +} + +/** + * Build a unified template object based on a theme file. + * + * @access private + * @since 5.9.0 + * + * @param array $template_file Theme file. + * @param array $template_type wp_template or wp_template_part. + * + * @return WP_Block_Template Template. + */ +function _build_block_template_result_from_file( $template_file, $template_type ) { + $default_template_types = get_default_block_template_types(); + $template_content = file_get_contents( $template_file['path'] ); + $theme = wp_get_theme()->get_stylesheet(); + + $template = new WP_Block_Template(); + $template->id = $theme . '//' . $template_file['slug']; + $template->theme = $theme; + $template->content = _inject_theme_attribute_in_block_template_content( $template_content ); + $template->slug = $template_file['slug']; + $template->source = 'theme'; + $template->type = $template_type; + $template->title = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug']; + $template->status = 'publish'; + $template->has_theme_file = true; + $template->is_custom = true; + + if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) { + $template->description = $default_template_types[ $template_file['slug'] ]['description']; + $template->title = $default_template_types[ $template_file['slug'] ]['title']; + $template->is_custom = false; + } + + if ( 'wp_template' === $template_type && isset( $template_file['postTypes'] ) ) { + $template->post_types = $template_file['postTypes']; + } + + if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) { + $template->area = $template_file['area']; + } + + return $template; +} + /** * Build a unified template object based a post Object. * * @access private - * @since 5.8.0 + * @since 5.9.0 * * @param WP_Post $post Template post. * * @return WP_Block_Template|WP_Error Template. */ -function _build_template_result_from_post( $post ) { - $terms = get_the_terms( $post, 'wp_theme' ); +function _build_block_template_result_from_post( $post ) { + $default_template_types = get_default_block_template_types(); + $terms = get_the_terms( $post, 'wp_theme' ); if ( is_wp_error( $terms ) ) { return $terms; @@ -27,7 +499,9 @@ function _build_template_result_from_post( $post ) { return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) ); } - $theme = $terms[0]->name; + $theme = $terms[0]->name; + $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && + null !== _get_block_template_file( $post->post_type, $post->post_name ); $template = new WP_Block_Template(); $template->wp_id = $post->ID; @@ -40,7 +514,19 @@ function _build_template_result_from_post( $post ) { $template->description = $post->post_excerpt; $template->title = $post->post_title; $template->status = $post->post_status; - $template->has_theme_file = false; + $template->has_theme_file = $has_theme_file; + $template->is_custom = true; + + if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) { + $template->is_custom = false; + } + + if ( 'wp_template_part' === $post->post_type ) { + $type_terms = get_the_terms( $post, 'wp_template_part_area' ); + if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) { + $template->area = $type_terms[0]->name; + } + } return $template; } @@ -53,13 +539,40 @@ function _build_template_result_from_post( $post ) { * @param array $query { * Optional. Arguments to retrieve templates. * - * @type array $slug__in List of slugs to include. - * @type int $wp_id Post ID of customized template. + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only). + * @type string $post_type Post type to get the templates for. * } - * @param string $template_type Optional. The template type (post type). Default 'wp_template'. - * @return WP_Block_Template[] Block template objects. + * @param array $template_type wp_template or wp_template_part. + * + * @return array Templates. */ function get_block_templates( $query = array(), $template_type = 'wp_template' ) { + /** + * Filters the block templates array before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * @since 5.9 + * + * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query, + * or null to allow WP to run it's normal queries. + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * @type string $post_type Post type to get the templates for. + * } + * @param array $template_type wp_template or wp_template_part. + */ + $templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type ); + if ( ! is_null( $templates ) ) { + return $templates; + } + + $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; $wp_query_args = array( 'post_status' => array( 'auto-draft', 'draft', 'publish' ), 'post_type' => $template_type, @@ -74,11 +587,20 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' ) ), ); + if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'wp_template_part_area', + 'field' => 'name', + 'terms' => $query['area'], + ); + $wp_query_args['tax_query']['relation'] = 'AND'; + } + if ( isset( $query['slug__in'] ) ) { $wp_query_args['post_name__in'] = $query['slug__in']; } - // This is only needed for the regular templates CPT listing and editor. + // This is only needed for the regular templates/template parts CPT listing and editor. if ( isset( $query['wp_id'] ) ) { $wp_query_args['p'] = $query['wp_id']; } else { @@ -88,14 +610,66 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' ) $template_query = new WP_Query( $wp_query_args ); $query_result = array(); foreach ( $template_query->posts as $post ) { - $template = _build_template_result_from_post( $post ); + $template = _build_block_template_result_from_post( $post ); - if ( ! is_wp_error( $template ) ) { - $query_result[] = $template; + if ( is_wp_error( $template ) ) { + continue; + } + + if ( $post_type && ! $template->is_custom ) { + continue; + } + + $query_result[] = $template; + } + + if ( ! isset( $query['wp_id'] ) ) { + $template_files = _get_block_templates_files( $template_type ); + foreach ( $template_files as $template_file ) { + $template = _build_block_template_result_from_file( $template_file, $template_type ); + + if ( $post_type && ! $template->is_custom ) { + continue; + } + + if ( $post_type && + isset( $template->post_types ) && + ! in_array( $post_type, $template->post_types, true ) + ) { + continue; + } + + $is_not_custom = false === array_search( + wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'], + array_column( $query_result, 'id' ), + true + ); + $fits_slug_query = + ! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true ); + $fits_area_query = + ! isset( $query['area'] ) || $template_file['area'] === $query['area']; + $should_include = $is_not_custom && $fits_slug_query && $fits_area_query; + if ( $should_include ) { + $query_result[] = $template; + } } } - return $query_result; + /** + * Filters the array of queried block templates array after they've been fetched. + * + * @since 5.9 + * + * @param WP_Block_Template[] $query_result Array of found block templates. + * @param array $query { + * Optional. Arguments to retrieve templates. + * + * @type array $slug__in List of slugs to include. + * @type int $wp_id Post ID of customized template. + * } + * @param array $template_type wp_template or wp_template_part. + */ + return apply_filters( 'get_block_templates', $query_result, $query, $template_type ); } /** @@ -104,10 +678,29 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' ) * @since 5.8.0 * * @param string $id Template unique identifier (example: theme_slug//template_slug). - * @param string $template_type Optional. The template type (post type). Default 'wp_template'. + * @param array $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`. + * Default `'wp_template'`. + * * @return WP_Block_Template|null Template. */ function get_block_template( $id, $template_type = 'wp_template' ) { + /** + * Filters the block templates array before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * @since 5.9.0 + * + * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query, + * or null to allow WP to run its normal queries. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type Template type: `'wp_template'` or '`wp_template_part'`. + */ + $block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type ); + if ( ! is_null( $block_template ) ) { + return $block_template; + } + $parts = explode( '//', $id, 2 ); if ( count( $parts ) < 2 ) { return null; @@ -131,12 +724,116 @@ function get_block_template( $id, $template_type = 'wp_template' ) { $posts = $template_query->posts; if ( count( $posts ) > 0 ) { - $template = _build_template_result_from_post( $posts[0] ); + $template = _build_block_template_result_from_post( $posts[0] ); if ( ! is_wp_error( $template ) ) { return $template; } } - return null; + $block_template = get_block_file_template( $id, $template_type ); + + /** + * Filters the array of queried block templates array after they've been fetched. + * + * @since 5.9 + * + * @param WP_Block_Template $block_template The found block template. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type Template type: `'wp_template'` or '`wp_template_part'`. + */ + return apply_filters( 'get_block_template', $block_template, $id, $template_type ); +} + +/** + * Retrieves a single unified template object using its id. + * + * @since 5.9.0 + * + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`. + * Default `'wp_template'`. + */ +function get_block_file_template( $id, $template_type = 'wp_template' ) { + /** + * Filters the block templates array before the query takes place. + * + * Return a non-null value to bypass the WordPress queries. + * + * + * @since 5.9.0 + * + * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query, + * or null to allow WP to run its normal queries. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type Template type: `'wp_template'` or '`wp_template_part'`. + */ + $block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type ); + if ( ! is_null( $block_template ) ) { + return $block_template; + } + + $parts = explode( '//', $id, 2 ); + if ( count( $parts ) < 2 ) { + /** This filter is documented at the end of this function */ + return apply_filters( 'get_block_file_template', null, $id, $template_type ); + } + list( $theme, $slug ) = $parts; + + if ( wp_get_theme()->get_stylesheet() !== $theme ) { + /** This filter is documented at the end of this function */ + return apply_filters( 'get_block_file_template', null, $id, $template_type ); + } + + $template_file = _get_block_template_file( $template_type, $slug ); + if ( null === $template_file ) { + /** This filter is documented at the end of this function */ + return apply_filters( 'get_block_file_template', null, $id, $template_type ); + } + + $block_template = _build_block_template_result_from_file( $template_file, $template_type ); + + /** + * Filters the array of queried block templates array after they've been fetched. + * + * @since 5.9.0 + * + * @param WP_Block_Template $block_template The found block template. + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type Template type: `'wp_template'` or '`wp_template_part'`. + */ + return apply_filters( 'get_block_file_template', $block_template, $id, $template_type ); +} + +/** + * Print a template-part. + * + * @since 5.9.0 + * + * @param string $part The template-part to print. Use "header" or "footer". + */ +function block_template_part( $part ) { + $template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' ); + if ( ! $template_part || empty( $template_part->content ) ) { + return; + } + echo do_blocks( $template_part->content ); +} + +/** + * Print the header template-part. + * + * @since 5.9.0 + */ +function block_header_area() { + block_template_part( 'header' ); +} + +/** + * Print the footer template-part. + * + * @since 5.9.0 + */ +function block_footer_area() { + block_template_part( 'footer' ); } diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index 5b855e6502..f94a60d921 100644 --- a/wp-includes/blocks.php +++ b/wp-includes/blocks.php @@ -1154,6 +1154,8 @@ function build_query_vars_from_query_block( $block, $page ) { * * It's used in QueryPaginationNext and QueryPaginationPrevious blocks. * + * @since 5.9.0 + * * @param WP_Block $block Block instance. * @param boolean $is_next Flag for hanlding `next/previous` blocks. * diff --git a/wp-includes/blocks/index.php b/wp-includes/blocks/index.php index b702cdf03c..d3aa3f6d7d 100644 --- a/wp-includes/blocks/index.php +++ b/wp-includes/blocks/index.php @@ -37,6 +37,7 @@ require ABSPATH . WPINC . '/blocks/site-logo.php'; require ABSPATH . WPINC . '/blocks/site-title.php'; require ABSPATH . WPINC . '/blocks/social-link.php'; require ABSPATH . WPINC . '/blocks/tag-cloud.php'; +require ABSPATH . WPINC . '/blocks/template-part.php'; /** * Registers core block types using metadata files. diff --git a/wp-includes/blocks/template-part.php b/wp-includes/blocks/template-part.php new file mode 100644 index 0000000000..36fb948ad3 --- /dev/null +++ b/wp-includes/blocks/template-part.php @@ -0,0 +1,155 @@ +get_stylesheet() === $attributes['theme'] + ) { + $template_part_id = $attributes['theme'] . '//' . $attributes['slug']; + $template_part_query = new WP_Query( + array( + 'post_type' => 'wp_template_part', + 'post_status' => 'publish', + 'post_name__in' => array( $attributes['slug'] ), + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'slug', + 'terms' => $attributes['theme'], + ), + ), + 'posts_per_page' => 1, + 'no_found_rows' => true, + ) + ); + $template_part_post = $template_part_query->have_posts() ? $template_part_query->next_post() : null; + if ( $template_part_post ) { + // A published post might already exist if this template part was customized elsewhere + // or if it's part of a customized template. + $content = $template_part_post->post_content; + $area_terms = get_the_terms( $template_part_post, 'wp_template_part_area' ); + if ( ! is_wp_error( $area_terms ) && false !== $area_terms ) { + $area = $area_terms[0]->name; + } + } else { + // Else, if the template part was provided by the active theme, + // render the corresponding file content. + $template_part_file_path = get_theme_file_path( '/block-template-parts/' . $attributes['slug'] . '.html' ); + if ( 0 === validate_file( $attributes['slug'] ) && file_exists( $template_part_file_path ) ) { + $content = _inject_theme_attribute_in_block_template_content( file_get_contents( $template_part_file_path ) ); + } + } + } + + if ( is_null( $content ) && is_user_logged_in() ) { + if ( ! isset( $attributes['slug'] ) ) { + // If there is no slug this is a placeholder and we dont want to return any message. + return; + } + return sprintf( + /* translators: %s: Template part slug. */ + __( 'Template part has been deleted or is unavailable: %s' ), + $attributes['slug'] + ); + } + + if ( isset( $seen_ids[ $template_part_id ] ) ) { + // WP_DEBUG_DISPLAY must only be honored when WP_DEBUG. This precedent + // is set in `wp_debug_mode()`. + $is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG && + defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY; + + return $is_debug ? + // translators: Visible only in the front end, this warning takes the place of a faulty block. + __( '[block rendering halted]' ) : + ''; + } + + // Run through the actions that are typically taken on the_content. + $seen_ids[ $template_part_id ] = true; + $content = do_blocks( $content ); + unset( $seen_ids[ $template_part_id ] ); + $content = wptexturize( $content ); + $content = convert_smilies( $content ); + $content = shortcode_unautop( $content ); + $content = wp_filter_content_tags( $content ); + $content = do_shortcode( $content ); + + // Handle embeds for block template parts. + global $wp_embed; + $content = $wp_embed->autoembed( $content ); + + if ( empty( $attributes['tagName'] ) ) { + $defined_areas = get_allowed_block_template_part_areas(); + $area_tag = 'div'; + foreach ( $defined_areas as $defined_area ) { + if ( $defined_area['area'] === $area && isset( $defined_area['area_tag'] ) ) { + $area_tag = $defined_area['area_tag']; + } + } + $html_tag = $area_tag; + } else { + $html_tag = esc_attr( $attributes['tagName'] ); + } + $wrapper_attributes = get_block_wrapper_attributes(); + + return "<$html_tag $wrapper_attributes>" . str_replace( ']]>', ']]>', $content ) . ""; +} + +/** + * Returns an array of variation objects for the template part block. + * + * @return array Array containing the block variation objects. + */ +function build_template_part_block_variations() { + $variations = array(); + $defined_areas = get_allowed_block_template_part_areas(); + foreach ( $defined_areas as $area ) { + if ( 'uncategorized' !== $area['area'] ) { + $variations[] = array( + 'name' => $area['area'], + 'title' => $area['label'], + 'description' => $area['description'], + 'attributes' => array( + 'area' => $area['area'], + ), + 'scope' => array( 'inserter' ), + 'icon' => $area['icon'], + ); + } + } + return $variations; +} + +/** + * Registers the `core/template-part` block on the server. + */ +function register_block_core_template_part() { + register_block_type_from_metadata( + __DIR__ . '/template-part', + array( + 'render_callback' => 'render_block_core_template_part', + 'variations' => build_template_part_block_variations(), + ) + ); +} +add_action( 'init', 'register_block_core_template_part' ); diff --git a/wp-includes/blocks/template-part/block.json b/wp-includes/blocks/template-part/block.json new file mode 100644 index 0000000000..7e85b0f6aa --- /dev/null +++ b/wp-includes/blocks/template-part/block.json @@ -0,0 +1,35 @@ +{ + "apiVersion": 2, + "name": "core/template-part", + "title": "Template Part", + "category": "theme", + "description": "Edit the different global regions of your site, like the header, footer, sidebar, or create your own.", + "textdomain": "default", + "attributes": { + "slug": { + "type": "string" + }, + "theme": { + "type": "string" + }, + "tagName": { + "type": "string" + }, + "area": { + "type": "string" + } + }, + "supports": { + "align": true, + "html": false, + "color": { + "gradients": true, + "link": true + }, + "spacing": { + "padding": true + }, + "__experimentalLayout": true + }, + "editorStyle": "wp-block-template-part-editor" +} diff --git a/wp-includes/blocks/template-part/editor-rtl.css b/wp-includes/blocks/template-part/editor-rtl.css new file mode 100644 index 0000000000..1d6b824aea --- /dev/null +++ b/wp-includes/blocks/template-part/editor-rtl.css @@ -0,0 +1,138 @@ +/** + * Colors + */ +/** + * Breakpoints & Media Queries + */ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + * For example, don't add rules to this sheet that affect block visuals. It's purely for UI. + */ +/** + * Colors + */ +/** + * Fonts & basic variables. + */ +/** + * Grid System. + * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/ + */ +/** + * Dimensions. + */ +/** + * Shadows. + */ +/** + * Editor widths. + */ +/** + * Block & Editor UI. + */ +/** + * Block paddings. + */ +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ +/** +* Converts a hex value into the rgb equivalent. +* +* @param {string} hex - the hexadecimal value to convert +* @return {string} comma separated rgb values +*/ +/** + * Breakpoint mixins + */ +/** + * Long content fade mixin + * + * Creates a fading overlay to signify that the content is longer + * than the space allows. + */ +/** + * Focus styles. + */ +/** + * Applies editor left position to the selector passed as argument + */ +/** + * Styles that are reused verbatim in a few places + */ +/** + * Allows users to opt-out of animations via OS-level preferences. + */ +/** + * Reset default styles for JavaScript UI based pages. + * This is a WP-admin agnostic reset + */ +/** + * Reset the WP Admin page styles for Gutenberg-like pages. + */ +.wp-block-template-part__placeholder-preview-dropdown-content .components-popover__content, +.wp-block-template-part__preview-dropdown-content .components-popover__content { + min-width: 320px; + padding: 0; +} + +.wp-block-template-part__selection-preview-search-form { + border-bottom: 1px solid #ddd; +} + +.wp-block-template-part__selection-preview-container { + background: #fff; + padding-bottom: 16px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item { + border-radius: 2px; + cursor: pointer; + margin-top: 16px; + transition: all 0.05s ease-in-out; + border: 1px solid #ddd; + width: 100%; + background-color: #fff; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:hover { + border: 1px solid var(--wp-admin-theme-color); +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:focus { + box-shadow: inset 0 0 0 1px #fff, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item.is-placeholder { + min-height: 100px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item .block-editor-block-preview__container { + border-bottom: 1px solid #ddd; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item-title { + padding: 4px; + font-size: 12px; + text-align: right; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-header { + padding: 16px 16px 0; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-content { + padding: 0 16px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-title { + color: var(--wp-admin-theme-color); + text-transform: uppercase; + font-size: 11px; + font-weight: 500; +} + +.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions { + padding-top: 12px; + display: flex; + justify-content: flex-end; +} +.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions .components-flex-item { + margin-right: 12px; +} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/editor-rtl.min.css b/wp-includes/blocks/template-part/editor-rtl.min.css new file mode 100644 index 0000000000..91405824dd --- /dev/null +++ b/wp-includes/blocks/template-part/editor-rtl.min.css @@ -0,0 +1 @@ +.wp-block-template-part__placeholder-preview-dropdown-content .components-popover__content,.wp-block-template-part__preview-dropdown-content .components-popover__content{min-width:320px;padding:0}.wp-block-template-part__selection-preview-search-form{border-bottom:1px solid #ddd}.wp-block-template-part__selection-preview-container{background:#fff;padding-bottom:16px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item{border-radius:2px;cursor:pointer;margin-top:16px;transition:all .05s ease-in-out;border:1px solid #ddd;width:100%;background-color:#fff}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:hover{border:1px solid var(--wp-admin-theme-color)}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:focus{box-shadow:inset 0 0 0 1px #fff,0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);outline:2px solid transparent}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item.is-placeholder{min-height:100px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item .block-editor-block-preview__container{border-bottom:1px solid #ddd}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item-title{padding:4px;font-size:12px;text-align:right}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-header{padding:16px 16px 0}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-content{padding:0 16px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-title{color:var(--wp-admin-theme-color);text-transform:uppercase;font-size:11px;font-weight:500}.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions{padding-top:12px;display:flex;justify-content:flex-end}.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions .components-flex-item{margin-right:12px} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/editor.css b/wp-includes/blocks/template-part/editor.css new file mode 100644 index 0000000000..2f67b90e14 --- /dev/null +++ b/wp-includes/blocks/template-part/editor.css @@ -0,0 +1,138 @@ +/** + * Colors + */ +/** + * Breakpoints & Media Queries + */ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + * For example, don't add rules to this sheet that affect block visuals. It's purely for UI. + */ +/** + * Colors + */ +/** + * Fonts & basic variables. + */ +/** + * Grid System. + * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/ + */ +/** + * Dimensions. + */ +/** + * Shadows. + */ +/** + * Editor widths. + */ +/** + * Block & Editor UI. + */ +/** + * Block paddings. + */ +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ +/** +* Converts a hex value into the rgb equivalent. +* +* @param {string} hex - the hexadecimal value to convert +* @return {string} comma separated rgb values +*/ +/** + * Breakpoint mixins + */ +/** + * Long content fade mixin + * + * Creates a fading overlay to signify that the content is longer + * than the space allows. + */ +/** + * Focus styles. + */ +/** + * Applies editor left position to the selector passed as argument + */ +/** + * Styles that are reused verbatim in a few places + */ +/** + * Allows users to opt-out of animations via OS-level preferences. + */ +/** + * Reset default styles for JavaScript UI based pages. + * This is a WP-admin agnostic reset + */ +/** + * Reset the WP Admin page styles for Gutenberg-like pages. + */ +.wp-block-template-part__placeholder-preview-dropdown-content .components-popover__content, +.wp-block-template-part__preview-dropdown-content .components-popover__content { + min-width: 320px; + padding: 0; +} + +.wp-block-template-part__selection-preview-search-form { + border-bottom: 1px solid #ddd; +} + +.wp-block-template-part__selection-preview-container { + background: #fff; + padding-bottom: 16px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item { + border-radius: 2px; + cursor: pointer; + margin-top: 16px; + transition: all 0.05s ease-in-out; + border: 1px solid #ddd; + width: 100%; + background-color: #fff; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:hover { + border: 1px solid var(--wp-admin-theme-color); +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:focus { + box-shadow: inset 0 0 0 1px #fff, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + outline: 2px solid transparent; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item.is-placeholder { + min-height: 100px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item .block-editor-block-preview__container { + border-bottom: 1px solid #ddd; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item-title { + padding: 4px; + font-size: 12px; + text-align: left; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-header { + padding: 16px 16px 0; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-content { + padding: 0 16px; +} +.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-title { + color: var(--wp-admin-theme-color); + text-transform: uppercase; + font-size: 11px; + font-weight: 500; +} + +.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions { + padding-top: 12px; + display: flex; + justify-content: flex-end; +} +.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions .components-flex-item { + margin-left: 12px; +} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/editor.min.css b/wp-includes/blocks/template-part/editor.min.css new file mode 100644 index 0000000000..165a55c0f8 --- /dev/null +++ b/wp-includes/blocks/template-part/editor.min.css @@ -0,0 +1 @@ +.wp-block-template-part__placeholder-preview-dropdown-content .components-popover__content,.wp-block-template-part__preview-dropdown-content .components-popover__content{min-width:320px;padding:0}.wp-block-template-part__selection-preview-search-form{border-bottom:1px solid #ddd}.wp-block-template-part__selection-preview-container{background:#fff;padding-bottom:16px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item{border-radius:2px;cursor:pointer;margin-top:16px;transition:all .05s ease-in-out;border:1px solid #ddd;width:100%;background-color:#fff}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:hover{border:1px solid var(--wp-admin-theme-color)}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item:focus{box-shadow:inset 0 0 0 1px #fff,0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);outline:2px solid transparent}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item.is-placeholder{min-height:100px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item .block-editor-block-preview__container{border-bottom:1px solid #ddd}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-preview-item-title{padding:4px;font-size:12px;text-align:left}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-header{padding:16px 16px 0}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-content{padding:0 16px}.wp-block-template-part__selection-preview-container .wp-block-template-part__selection-panel-group-title{color:var(--wp-admin-theme-color);text-transform:uppercase;font-size:11px;font-weight:500}.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions{padding-top:12px;display:flex;justify-content:flex-end}.wp-block-template-part__placeholder-create-new__title-form .wp-block-template-part__placeholder-create-new__title-form-actions .components-flex-item{margin-left:12px} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/theme-rtl.css b/wp-includes/blocks/template-part/theme-rtl.css new file mode 100644 index 0000000000..975853880e --- /dev/null +++ b/wp-includes/blocks/template-part/theme-rtl.css @@ -0,0 +1,81 @@ +/** + * Colors + */ +/** + * Breakpoints & Media Queries + */ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + * For example, don't add rules to this sheet that affect block visuals. It's purely for UI. + */ +/** + * Colors + */ +/** + * Fonts & basic variables. + */ +/** + * Grid System. + * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/ + */ +/** + * Dimensions. + */ +/** + * Shadows. + */ +/** + * Editor widths. + */ +/** + * Block & Editor UI. + */ +/** + * Block paddings. + */ +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ +/** +* Converts a hex value into the rgb equivalent. +* +* @param {string} hex - the hexadecimal value to convert +* @return {string} comma separated rgb values +*/ +/** + * Breakpoint mixins + */ +/** + * Long content fade mixin + * + * Creates a fading overlay to signify that the content is longer + * than the space allows. + */ +/** + * Focus styles. + */ +/** + * Applies editor left position to the selector passed as argument + */ +/** + * Styles that are reused verbatim in a few places + */ +/** + * Allows users to opt-out of animations via OS-level preferences. + */ +/** + * Reset default styles for JavaScript UI based pages. + * This is a WP-admin agnostic reset + */ +/** + * Reset the WP Admin page styles for Gutenberg-like pages. + */ +.wp-block-template-part.has-background { + padding: 1.25em 2.375em; + margin-top: 0; + margin-bottom: 0; +} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/theme-rtl.min.css b/wp-includes/blocks/template-part/theme-rtl.min.css new file mode 100644 index 0000000000..6bf2ec6242 --- /dev/null +++ b/wp-includes/blocks/template-part/theme-rtl.min.css @@ -0,0 +1 @@ +.wp-block-template-part.has-background{padding:1.25em 2.375em;margin-top:0;margin-bottom:0} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/theme.css b/wp-includes/blocks/template-part/theme.css new file mode 100644 index 0000000000..975853880e --- /dev/null +++ b/wp-includes/blocks/template-part/theme.css @@ -0,0 +1,81 @@ +/** + * Colors + */ +/** + * Breakpoints & Media Queries + */ +/** + * SCSS Variables. + * + * Please use variables from this sheet to ensure consistency across the UI. + * Don't add to this sheet unless you're pretty sure the value will be reused in many places. + * For example, don't add rules to this sheet that affect block visuals. It's purely for UI. + */ +/** + * Colors + */ +/** + * Fonts & basic variables. + */ +/** + * Grid System. + * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/ + */ +/** + * Dimensions. + */ +/** + * Shadows. + */ +/** + * Editor widths. + */ +/** + * Block & Editor UI. + */ +/** + * Block paddings. + */ +/** + * React Native specific. + * These variables do not appear to be used anywhere else. + */ +/** +* Converts a hex value into the rgb equivalent. +* +* @param {string} hex - the hexadecimal value to convert +* @return {string} comma separated rgb values +*/ +/** + * Breakpoint mixins + */ +/** + * Long content fade mixin + * + * Creates a fading overlay to signify that the content is longer + * than the space allows. + */ +/** + * Focus styles. + */ +/** + * Applies editor left position to the selector passed as argument + */ +/** + * Styles that are reused verbatim in a few places + */ +/** + * Allows users to opt-out of animations via OS-level preferences. + */ +/** + * Reset default styles for JavaScript UI based pages. + * This is a WP-admin agnostic reset + */ +/** + * Reset the WP Admin page styles for Gutenberg-like pages. + */ +.wp-block-template-part.has-background { + padding: 1.25em 2.375em; + margin-top: 0; + margin-bottom: 0; +} \ No newline at end of file diff --git a/wp-includes/blocks/template-part/theme.min.css b/wp-includes/blocks/template-part/theme.min.css new file mode 100644 index 0000000000..6bf2ec6242 --- /dev/null +++ b/wp-includes/blocks/template-part/theme.min.css @@ -0,0 +1 @@ +.wp-block-template-part.has-background{padding:1.25em 2.375em;margin-top:0;margin-bottom:0} \ No newline at end of file diff --git a/wp-includes/class-wp-block-template.php b/wp-includes/class-wp-block-template.php index 704a2afc45..a4938c07cf 100644 --- a/wp-includes/class-wp-block-template.php +++ b/wp-includes/class-wp-block-template.php @@ -100,4 +100,13 @@ class WP_Block_Template { * @var boolean */ public $has_theme_file; + + /** + * Whether a template is a custom template. + * + * @since 5.9.0 + * + * @var boolean + */ + public $is_custom = true; } diff --git a/wp-includes/class-wp-theme-json.php b/wp-includes/class-wp-theme-json.php index cd80d24d3c..894e0bfa0f 100644 --- a/wp-includes/class-wp-theme-json.php +++ b/wp-includes/class-wp-theme-json.php @@ -601,7 +601,7 @@ class WP_Theme_JSON { */ public function get_custom_templates() { $custom_templates = array(); - if ( ! isset( $this->theme_json['customTemplates'] ) ) { + if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) { return $custom_templates; } @@ -625,7 +625,7 @@ class WP_Theme_JSON { */ public function get_template_parts() { $template_parts = array(); - if ( ! isset( $this->theme_json['templateParts'] ) ) { + if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) { return $template_parts; } diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index c4a88b69d2..5783d5cd0f 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -664,6 +664,7 @@ add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 ); // Block Templates CPT and Rendering add_filter( 'render_block_context', '_block_template_render_without_post_block_context' ); add_filter( 'pre_wp_unique_post_slug', 'wp_filter_wp_template_unique_post_slug', 10, 5 ); +add_action( 'save_post_wp_template_part', 'wp_set_unique_slug_on_create_template_part' ); add_action( 'wp_footer', 'the_block_template_skip_link' ); add_action( 'setup_theme', 'wp_enable_block_templates' ); diff --git a/wp-includes/post.php b/wp-includes/post.php index f578e4ce35..07ce72b42d 100644 --- a/wp-includes/post.php +++ b/wp-includes/post.php @@ -365,6 +365,64 @@ function create_initial_post_types() { ) ); + register_post_type( + 'wp_template_part', + array( + 'labels' => array( + 'name' => __( 'Template Parts' ), + 'singular_name' => __( 'Template Part' ), + 'add_new' => _x( 'Add New', 'Template Part' ), + 'add_new_item' => __( 'Add New Template Part' ), + 'new_item' => __( 'New Template Part' ), + 'edit_item' => __( 'Edit Template Part' ), + 'view_item' => __( 'View Template Part' ), + 'all_items' => __( 'All Template Parts' ), + 'search_items' => __( 'Search Template Parts' ), + 'parent_item_colon' => __( 'Parent Template Part:' ), + 'not_found' => __( 'No template parts found.' ), + 'not_found_in_trash' => __( 'No template parts found in Trash.' ), + 'archives' => __( 'Template part archives' ), + 'insert_into_item' => __( 'Insert into template part' ), + 'uploaded_to_this_item' => __( 'Uploaded to this template part' ), + 'filter_items_list' => __( 'Filter template parts list' ), + 'items_list_navigation' => __( 'Template parts list navigation' ), + 'items_list' => __( 'Template parts list' ), + ), + 'description' => __( 'Template parts to include in your templates.' ), + 'public' => false, + '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ + 'has_archive' => false, + 'show_ui' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'rewrite' => false, + 'rest_base' => 'template-parts', + 'rest_controller_class' => 'WP_REST_Templates_Controller', + 'map_meta_cap' => true, + 'capabilities' => array( + 'create_posts' => 'edit_theme_options', + 'delete_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + 'delete_private_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'edit_private_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'publish_posts' => 'edit_theme_options', + 'read' => 'edit_theme_options', + 'read_private_posts' => 'edit_theme_options', + ), + 'supports' => array( + 'title', + 'slug', + 'excerpt', + 'editor', + 'revisions', + ), + ) + ); + register_post_type( 'wp_global_styles', array( diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index e7712363f7..f3d406c095 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -156,6 +156,9 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { if ( isset( $request['area'] ) ) { $query['area'] = $request['area']; } + if ( isset( $request['post_type'] ) ) { + $query['post_type'] = $request['post_type']; + } $templates = array(); foreach ( get_block_templates( $query, $this->post_type ) as $template ) { @@ -187,7 +190,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { - $template = get_block_template( $request['id'], $this->post_type ); + if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + $template = get_block_file_template( $request['id'], $this->post_type ); + } else { + $template = get_block_template( $request['id'], $this->post_type ); + } if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); @@ -222,6 +229,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); } + if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + wp_delete_post( $template->wp_id, true ); + return $this->prepare_item_for_response( get_block_file_template( $request['id'], $this->post_type ), $request ); + } + $changes = $this->prepare_item_for_database( $request ); if ( 'custom' === $template->source ) { @@ -395,6 +407,16 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { $changes->post_excerpt = $template->description; } + if ( 'wp_template_part' === $this->post_type ) { + if ( isset( $request['area'] ) ) { + $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); + } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { + $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); + } elseif ( ! $template->area ) { + $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; + } + } + return $changes; } @@ -502,16 +524,25 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { * Retrieves the query params for the posts collection. * * @since 5.8.0 + * @since 5.9.0 Added `'area'` and `'post_type'`. * * @return array Collection parameters. */ public function get_collection_params() { return array( - 'context' => $this->get_context_param(), - 'wp_id' => array( + 'context' => $this->get_context_param(), + 'wp_id' => array( 'description' => __( 'Limit to the specified post id.' ), 'type' => 'integer', ), + 'area' => array( + 'description' => __( 'Limit to the specified template part area.' ), + 'type' => 'string', + ), + 'post_type' => array( + 'description' => __( 'Post type to get the templates for.' ), + 'type' => 'string', + ), ); } @@ -519,6 +550,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { * Retrieves the block type' schema, conforming to JSON Schema. * * @since 5.8.0 + * @since 5.9.0 Added `'area'`. * * @return array Item schema data. */ @@ -596,6 +628,14 @@ class WP_REST_Templates_Controller extends WP_REST_Controller { ), ); + if ( 'wp_template_part' === $this->post_type ) { + $schema['properties']['area'] = array( + 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + ); + } + $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); diff --git a/wp-includes/taxonomy.php b/wp-includes/taxonomy.php index 79a5bdab52..36ec58cd38 100644 --- a/wp-includes/taxonomy.php +++ b/wp-includes/taxonomy.php @@ -18,6 +18,7 @@ * avoid registering rewrite rules before the {@see 'init'} action. * * @since 2.8.0 + * @since 5.9.0 Added `'wp_template_part_area'` taxonomy. * * @global WP_Rewrite $wp_rewrite WordPress rewrite component. */ @@ -175,7 +176,7 @@ function create_initial_taxonomies() { register_taxonomy( 'wp_theme', - array( 'wp_template', 'wp_global_styles' ), + array( 'wp_template', 'wp_template_part', 'wp_global_styles' ), array( 'public' => false, 'hierarchical' => false, @@ -191,6 +192,25 @@ function create_initial_taxonomies() { 'show_in_rest' => false, ) ); + + register_taxonomy( + 'wp_template_part_area', + array( 'wp_template_part' ), + array( + 'public' => false, + 'hierarchical' => false, + 'labels' => array( + 'name' => __( 'Template Part Areas' ), + 'singular_name' => __( 'Template Part Area' ), + ), + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => false, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => false, + ) + ); } /** diff --git a/wp-includes/theme-templates.php b/wp-includes/theme-templates.php index c7466312cb..467dcac941 100644 --- a/wp-includes/theme-templates.php +++ b/wp-includes/theme-templates.php @@ -1,5 +1,35 @@ post_status ) { + return; + } + + if ( ! $post->post_name ) { + wp_update_post( + array( + 'ID' => $post_id, + 'post_name' => 'custom_slug_' . uniqid(), + ) + ); + } + + $terms = get_the_terms( $post_id, 'wp_theme' ); + if ( ! is_array( $terms ) || ! count( $terms ) ) { + wp_set_post_terms( $post_id, wp_get_theme()->get_stylesheet(), 'wp_theme' ); + } +} + /** * Generates a unique slug for templates. * @@ -14,7 +44,7 @@ * @return string The original, desired slug. */ function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) { - if ( 'wp_template' !== $post_type ) { + if ( 'wp_template' !== $post_type && 'wp_template_part' !== $post_type ) { return $override_slug; } diff --git a/wp-includes/version.php b/wp-includes/version.php index 83889536da..1a713f8618 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '5.9-alpha-52061'; +$wp_version = '5.9-alpha-52062'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.