wp/wp-includes/block-template-utils.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    35  *     @type string $wp_template      Theme-relative directory name for block templates.
    35  *     @type string $wp_template      Theme-relative directory name for block templates.
    36  *     @type string $wp_template_part Theme-relative directory name for block template parts.
    36  *     @type string $wp_template_part Theme-relative directory name for block template parts.
    37  * }
    37  * }
    38  */
    38  */
    39 function get_block_theme_folders( $theme_stylesheet = null ) {
    39 function get_block_theme_folders( $theme_stylesheet = null ) {
    40 	$theme_name = null === $theme_stylesheet ? get_stylesheet() : $theme_stylesheet;
    40 	$theme = wp_get_theme( (string) $theme_stylesheet );
    41 	$root_dir   = get_theme_root( $theme_name );
    41 	if ( ! $theme->exists() ) {
    42 	$theme_dir  = "$root_dir/$theme_name";
    42 		// Return the default folders if the theme doesn't exist.
    43 
       
    44 	if ( file_exists( $theme_dir . '/block-templates' ) || file_exists( $theme_dir . '/block-template-parts' ) ) {
       
    45 		return array(
    43 		return array(
    46 			'wp_template'      => 'block-templates',
    44 			'wp_template'      => 'templates',
    47 			'wp_template_part' => 'block-template-parts',
    45 			'wp_template_part' => 'parts',
    48 		);
    46 		);
    49 	}
    47 	}
    50 
    48 	return $theme->get_block_template_folders();
    51 	return array(
       
    52 		'wp_template'      => 'templates',
       
    53 		'wp_template_part' => 'parts',
       
    54 	);
       
    55 }
    49 }
    56 
    50 
    57 /**
    51 /**
    58  * Returns a filtered list of allowed area values for template parts.
    52  * Returns a filtered list of allowed area values for template parts.
    59  *
    53  *
    60  * @since 5.9.0
    54  * @since 5.9.0
    61  *
    55  *
    62  * @return array The supported template part area values.
    56  * @return array[] {
       
    57  *     The allowed template part area values.
       
    58  *
       
    59  *     @type array ...$0 {
       
    60  *         Data for the allowed template part area.
       
    61  *
       
    62  *         @type string $area        Template part area name.
       
    63  *         @type string $label       Template part area label.
       
    64  *         @type string $description Template part area description.
       
    65  *         @type string $icon        Template part area icon.
       
    66  *         @type string $area_tag    Template part area tag.
       
    67  *     }
       
    68  * }
    63  */
    69  */
    64 function get_allowed_block_template_part_areas() {
    70 function get_allowed_block_template_part_areas() {
    65 	$default_area_definitions = array(
    71 	$default_area_definitions = array(
    66 		array(
    72 		array(
    67 			'area'        => WP_TEMPLATE_PART_AREA_UNCATEGORIZED,
    73 			'area'        => WP_TEMPLATE_PART_AREA_UNCATEGORIZED,
    68 			'label'       => __( 'General' ),
    74 			'label'       => _x( 'General', 'template part area' ),
    69 			'description' => __(
    75 			'description' => __(
    70 				'General templates often perform a specific role like displaying post content, and are not tied to any particular area.'
    76 				'General templates often perform a specific role like displaying post content, and are not tied to any particular area.'
    71 			),
    77 			),
    72 			'icon'        => 'layout',
    78 			'icon'        => 'layout',
    73 			'area_tag'    => 'div',
    79 			'area_tag'    => 'div',
    74 		),
    80 		),
    75 		array(
    81 		array(
    76 			'area'        => WP_TEMPLATE_PART_AREA_HEADER,
    82 			'area'        => WP_TEMPLATE_PART_AREA_HEADER,
    77 			'label'       => __( 'Header' ),
    83 			'label'       => _x( 'Header', 'template part area' ),
    78 			'description' => __(
    84 			'description' => __(
    79 				'The Header template defines a page area that typically contains a title, logo, and main navigation.'
    85 				'The Header template defines a page area that typically contains a title, logo, and main navigation.'
    80 			),
    86 			),
    81 			'icon'        => 'header',
    87 			'icon'        => 'header',
    82 			'area_tag'    => 'header',
    88 			'area_tag'    => 'header',
    83 		),
    89 		),
    84 		array(
    90 		array(
    85 			'area'        => WP_TEMPLATE_PART_AREA_FOOTER,
    91 			'area'        => WP_TEMPLATE_PART_AREA_FOOTER,
    86 			'label'       => __( 'Footer' ),
    92 			'label'       => _x( 'Footer', 'template part area' ),
    87 			'description' => __(
    93 			'description' => __(
    88 				'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.'
    94 				'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.'
    89 			),
    95 			),
    90 			'icon'        => 'footer',
    96 			'icon'        => 'footer',
    91 			'area_tag'    => 'footer',
    97 			'area_tag'    => 'footer',
    95 	/**
   101 	/**
    96 	 * Filters the list of allowed template part area values.
   102 	 * Filters the list of allowed template part area values.
    97 	 *
   103 	 *
    98 	 * @since 5.9.0
   104 	 * @since 5.9.0
    99 	 *
   105 	 *
   100 	 * @param array $default_area_definitions An array of supported area objects.
   106 	 * @param array[] $default_area_definitions {
       
   107 	 *     The allowed template part area values.
       
   108 	 *
       
   109 	 *     @type array ...$0 {
       
   110 	 *         Data for the template part area.
       
   111 	 *
       
   112 	 *         @type string $area        Template part area name.
       
   113 	 *         @type string $label       Template part area label.
       
   114 	 *         @type string $description Template part area description.
       
   115 	 *         @type string $icon        Template part area icon.
       
   116 	 *         @type string $area_tag    Template part area tag.
       
   117 	 *     }
       
   118 	 * }
   101 	 */
   119 	 */
   102 	return apply_filters( 'default_wp_template_part_areas', $default_area_definitions );
   120 	return apply_filters( 'default_wp_template_part_areas', $default_area_definitions );
   103 }
   121 }
   104 
   122 
   105 
   123 
   107  * Returns a filtered list of default template types, containing their
   125  * Returns a filtered list of default template types, containing their
   108  * localized titles and descriptions.
   126  * localized titles and descriptions.
   109  *
   127  *
   110  * @since 5.9.0
   128  * @since 5.9.0
   111  *
   129  *
   112  * @return array The default template types.
   130  * @return array[] {
       
   131  *     The default template types.
       
   132  *
       
   133  *     @type array ...$0 {
       
   134  *         Data for the template type.
       
   135  *
       
   136  *         @type string $title       Template type title.
       
   137  *         @type string $description Template type description.
       
   138  *    }
       
   139  * }
   113  */
   140  */
   114 function get_default_block_template_types() {
   141 function get_default_block_template_types() {
   115 	$default_template_types = array(
   142 	$default_template_types = array(
   116 		'index'          => array(
   143 		'index'          => array(
   117 			'title'       => _x( 'Index', 'Template name' ),
   144 			'title'       => _x( 'Index', 'Template name' ),
   118 			'description' => __( 'Displays posts.' ),
   145 			'description' => __( 'Used as a fallback template for all pages when a more specific template is not defined.' ),
   119 		),
   146 		),
   120 		'home'           => array(
   147 		'home'           => array(
   121 			'title'       => _x( 'Home', 'Template name' ),
   148 			'title'       => _x( 'Blog Home', 'Template name' ),
   122 			'description' => __( 'Displays posts on the homepage, or on the Posts page if a static homepage is set.' ),
   149 			'description' => __( 'Displays the latest posts as either the site homepage or as the "Posts page" as defined under reading settings. If it exists, the Front Page template overrides this template when posts are shown on the homepage.' ),
   123 		),
   150 		),
   124 		'front-page'     => array(
   151 		'front-page'     => array(
   125 			'title'       => _x( 'Front Page', 'Template name' ),
   152 			'title'       => _x( 'Front Page', 'Template name' ),
   126 			'description' => __( 'Displays the homepage.' ),
   153 			'description' => __( 'Displays your site\'s homepage, whether it is set to display latest posts or a static page. The Front Page template takes precedence over all templates.' ),
   127 		),
   154 		),
   128 		'singular'       => array(
   155 		'singular'       => array(
   129 			'title'       => _x( 'Singular', 'Template name' ),
   156 			'title'       => _x( 'Single Entries', 'Template name' ),
   130 			'description' => __( 'Displays a single post or page.' ),
   157 			'description' => __( 'Displays any single entry, such as a post or a page. This template will serve as a fallback when a more specific template (e.g. Single Post, Page, or Attachment) cannot be found.' ),
   131 		),
   158 		),
   132 		'single'         => array(
   159 		'single'         => array(
   133 			'title'       => _x( 'Single Post', 'Template name' ),
   160 			'title'       => _x( 'Single Posts', 'Template name' ),
   134 			'description' => __( 'Displays a single post.' ),
   161 			'description' => __( 'Displays a single post on your website unless a custom template has been applied to that post or a dedicated template exists.' ),
   135 		),
   162 		),
   136 		'page'           => array(
   163 		'page'           => array(
   137 			'title'       => _x( 'Page', 'Template name' ),
   164 			'title'       => _x( 'Pages', 'Template name' ),
   138 			'description' => __( 'Displays a single page.' ),
   165 			'description' => __( 'Displays a static page unless a custom template has been applied to that page or a dedicated template exists.' ),
   139 		),
   166 		),
   140 		'archive'        => array(
   167 		'archive'        => array(
   141 			'title'       => _x( 'Archive', 'Template name' ),
   168 			'title'       => _x( 'All Archives', 'Template name' ),
   142 			'description' => __( 'Displays post categories, tags, and other archives.' ),
   169 			'description' => __( 'Displays any archive, including posts by a single author, category, tag, taxonomy, custom post type, and date. This template will serve as a fallback when more specific templates (e.g. Category or Tag) cannot be found.' ),
   143 		),
   170 		),
   144 		'author'         => array(
   171 		'author'         => array(
   145 			'title'       => _x( 'Author', 'Template name' ),
   172 			'title'       => _x( 'Author Archives', 'Template name' ),
   146 			'description' => __( 'Displays latest posts written by a single author.' ),
   173 			'description' => __( 'Displays a single author\'s post archive. This template will serve as a fallback when a more specific template (e.g. Author: Admin) cannot be found.' ),
   147 		),
   174 		),
   148 		'category'       => array(
   175 		'category'       => array(
   149 			'title'       => _x( 'Category', 'Template name' ),
   176 			'title'       => _x( 'Category Archives', 'Template name' ),
   150 			'description' => __( 'Displays latest posts in single post category.' ),
   177 			'description' => __( 'Displays a post category archive. This template will serve as a fallback when a more specific template (e.g. Category: Recipes) cannot be found.' ),
   151 		),
   178 		),
   152 		'taxonomy'       => array(
   179 		'taxonomy'       => array(
   153 			'title'       => _x( 'Taxonomy', 'Template name' ),
   180 			'title'       => _x( 'Taxonomy', 'Template name' ),
   154 			'description' => __( 'Displays latest posts from a single post taxonomy.' ),
   181 			'description' => __( 'Displays a custom taxonomy archive. Like categories and tags, taxonomies have terms which you use to classify things. For example: a taxonomy named "Art" can have multiple terms, such as "Modern" and "18th Century." This template will serve as a fallback when a more specific template (e.g. Taxonomy: Art) cannot be found.' ),
   155 		),
   182 		),
   156 		'date'           => array(
   183 		'date'           => array(
   157 			'title'       => _x( 'Date', 'Template name' ),
   184 			'title'       => _x( 'Date Archives', 'Template name' ),
   158 			'description' => __( 'Displays posts from a specific date.' ),
   185 			'description' => __( 'Displays a post archive when a specific date is visited (e.g., example.com/2023/).' ),
   159 		),
   186 		),
   160 		'tag'            => array(
   187 		'tag'            => array(
   161 			'title'       => _x( 'Tag', 'Template name' ),
   188 			'title'       => _x( 'Tag Archives', 'Template name' ),
   162 			'description' => __( 'Displays latest posts with a single post tag.' ),
   189 			'description' => __( 'Displays a post tag archive. This template will serve as a fallback when a more specific template (e.g. Tag: Pizza) cannot be found.' ),
   163 		),
   190 		),
   164 		'attachment'     => array(
   191 		'attachment'     => array(
   165 			'title'       => __( 'Media' ),
   192 			'title'       => __( 'Attachment Pages' ),
   166 			'description' => __( 'Displays individual media items or attachments.' ),
   193 			'description' => __( 'Displays when a visitor views the dedicated page that exists for any media attachment.' ),
   167 		),
   194 		),
   168 		'search'         => array(
   195 		'search'         => array(
   169 			'title'       => _x( 'Search', 'Template name' ),
   196 			'title'       => _x( 'Search Results', 'Template name' ),
   170 			'description' => __( 'Displays search results.' ),
   197 			'description' => __( 'Displays when a visitor performs a search on your website.' ),
   171 		),
   198 		),
   172 		'privacy-policy' => array(
   199 		'privacy-policy' => array(
   173 			'title'       => __( 'Privacy Policy' ),
   200 			'title'       => __( 'Privacy Policy' ),
   174 			'description' => __( 'Displays the privacy policy page.' ),
   201 			'description' => __( 'Displays your site\'s Privacy Policy page.' ),
   175 		),
   202 		),
   176 		'404'            => array(
   203 		'404'            => array(
   177 			'title'       => _x( '404', 'Template name' ),
   204 			'title'       => _x( 'Page: 404', 'Template name' ),
   178 			'description' => __( 'Displays when no content is found.' ),
   205 			'description' => __( 'Displays when a visitor views a non-existent page, such as a dead link or a mistyped URL.' ),
   179 		),
   206 		),
   180 	);
   207 	);
   181 
   208 
   182 	/**
   209 	/**
   183 	 * Filters the list of template types.
   210 	 * Filters the list of default template types.
   184 	 *
   211 	 *
   185 	 * @since 5.9.0
   212 	 * @since 5.9.0
   186 	 *
   213 	 *
   187 	 * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ].
   214 	 * @param array[] $default_template_types {
       
   215 	 *     The default template types.
       
   216 	 *
       
   217 	 *     @type array ...$0 {
       
   218 	 *         Data for the template type.
       
   219 	 *
       
   220 	 *         @type string $title       Template type title.
       
   221 	 *         @type string $description Template type description.
       
   222 	 *    }
       
   223 	 * }
   188 	 */
   224 	 */
   189 	return apply_filters( 'default_template_types', $default_template_types );
   225 	return apply_filters( 'default_template_types', $default_template_types );
   190 }
   226 }
   191 
   227 
   192 /**
   228 /**
   195  *
   231  *
   196  * @since 5.9.0
   232  * @since 5.9.0
   197  * @access private
   233  * @access private
   198  *
   234  *
   199  * @param string $type Template part area name.
   235  * @param string $type Template part area name.
   200  *
       
   201  * @return string Input if supported, else the uncategorized value.
   236  * @return string Input if supported, else the uncategorized value.
   202  */
   237  */
   203 function _filter_block_template_part_area( $type ) {
   238 function _filter_block_template_part_area( $type ) {
   204 	$allowed_areas = array_map(
   239 	$allowed_areas = array_map(
   205 		static function ( $item ) {
   240 		static function ( $item ) {
   215 		/* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */
   250 		/* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */
   216 		__( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ),
   251 		__( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ),
   217 		$type,
   252 		$type,
   218 		WP_TEMPLATE_PART_AREA_UNCATEGORIZED
   253 		WP_TEMPLATE_PART_AREA_UNCATEGORIZED
   219 	);
   254 	);
   220 	trigger_error( $warning_message, E_USER_NOTICE );
   255 	wp_trigger_error( __FUNCTION__, $warning_message );
   221 	return WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
   256 	return WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
   222 }
   257 }
   223 
   258 
   224 /**
   259 /**
   225  * Finds all nested template part file paths in a theme's directory.
   260  * Finds all nested template part file paths in a theme's directory.
   226  *
   261  *
   227  * @since 5.9.0
   262  * @since 5.9.0
   228  * @access private
   263  * @access private
   229  *
   264  *
   230  * @param string $base_directory The theme's file path.
   265  * @param string $base_directory The theme's file path.
   231  * @return array A list of paths to all template part files.
   266  * @return string[] A list of paths to all template part files.
   232  */
   267  */
   233 function _get_block_templates_paths( $base_directory ) {
   268 function _get_block_templates_paths( $base_directory ) {
       
   269 	static $template_path_list = array();
       
   270 	if ( isset( $template_path_list[ $base_directory ] ) ) {
       
   271 		return $template_path_list[ $base_directory ];
       
   272 	}
   234 	$path_list = array();
   273 	$path_list = array();
   235 	if ( file_exists( $base_directory ) ) {
   274 	if ( is_dir( $base_directory ) ) {
   236 		$nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
   275 		$nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
   237 		$nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH );
   276 		$nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH );
   238 		foreach ( $nested_html_files as $path => $file ) {
   277 		foreach ( $nested_html_files as $path => $file ) {
   239 			$path_list[] = $path;
   278 			$path_list[] = $path;
   240 		}
   279 		}
   241 	}
   280 	}
       
   281 	$template_path_list[ $base_directory ] = $path_list;
   242 	return $path_list;
   282 	return $path_list;
   243 }
   283 }
   244 
   284 
   245 /**
   285 /**
   246  * Retrieves the template file from the theme for a given slug.
   286  * Retrieves the template file from the theme for a given slug.
   247  *
   287  *
   248  * @since 5.9.0
   288  * @since 5.9.0
   249  * @access private
   289  * @access private
   250  *
   290  *
   251  * @param string $template_type 'wp_template' or 'wp_template_part'.
   291  * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
   252  * @param string $slug          Template slug.
   292  * @param string $slug          Template slug.
   253  *
   293  * @return array|null {
   254  * @return array|null Template.
   294  *     Array with template metadata if $template_type is one of 'wp_template' or 'wp_template_part',
       
   295  *     null otherwise.
       
   296  *
       
   297  *     @type string   $slug      Template slug.
       
   298  *     @type string   $path      Template file path.
       
   299  *     @type string   $theme     Theme slug.
       
   300  *     @type string   $type      Template type.
       
   301  *     @type string   $area      Template area. Only for 'wp_template_part'.
       
   302  *     @type string   $title     Optional. Template title.
       
   303  *     @type string[] $postTypes Optional. List of post types that the template supports. Only for 'wp_template'.
       
   304  * }
   255  */
   305  */
   256 function _get_block_template_file( $template_type, $slug ) {
   306 function _get_block_template_file( $template_type, $slug ) {
   257 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
   307 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
   258 		return null;
   308 		return null;
   259 	}
   309 	}
   290 
   340 
   291 /**
   341 /**
   292  * Retrieves the template files from the theme.
   342  * Retrieves the template files from the theme.
   293  *
   343  *
   294  * @since 5.9.0
   344  * @since 5.9.0
       
   345  * @since 6.3.0 Added the `$query` parameter.
   295  * @access private
   346  * @access private
   296  *
   347  *
   297  * @param string $template_type 'wp_template' or 'wp_template_part'.
   348  * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
   298  *
   349  * @param array  $query {
   299  * @return array Template.
   350  *     Arguments to retrieve templates. Optional, empty by default.
   300  */
   351  *
   301 function _get_block_templates_files( $template_type ) {
   352  *     @type string[] $slug__in     List of slugs to include.
       
   353  *     @type string[] $slug__not_in List of slugs to skip.
       
   354  *     @type string   $area         A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
       
   355  *     @type string   $post_type    Post type to get the templates for.
       
   356  * }
       
   357  *
       
   358  * @return array Template
       
   359  */
       
   360 function _get_block_templates_files( $template_type, $query = array() ) {
   302 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
   361 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
   303 		return null;
   362 		return null;
   304 	}
   363 	}
   305 
   364 
   306 	$themes         = array(
   365 	$default_template_types = array();
   307 		get_stylesheet() => get_stylesheet_directory(),
   366 	if ( 'wp_template' === $template_type ) {
   308 		get_template()   => get_template_directory(),
   367 		$default_template_types = get_default_block_template_types();
   309 	);
   368 	}
       
   369 
       
   370 	// Prepare metadata from $query.
       
   371 	$slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
       
   372 	$slugs_to_skip    = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array();
       
   373 	$area             = isset( $query['area'] ) ? $query['area'] : null;
       
   374 	$post_type        = isset( $query['post_type'] ) ? $query['post_type'] : '';
       
   375 
       
   376 	$stylesheet = get_stylesheet();
       
   377 	$template   = get_template();
       
   378 	$themes     = array(
       
   379 		$stylesheet => get_stylesheet_directory(),
       
   380 	);
       
   381 	// Add the parent theme if it's not the same as the current theme.
       
   382 	if ( $stylesheet !== $template ) {
       
   383 		$themes[ $template ] = get_template_directory();
       
   384 	}
   310 	$template_files = array();
   385 	$template_files = array();
   311 	foreach ( $themes as $theme_slug => $theme_dir ) {
   386 	foreach ( $themes as $theme_slug => $theme_dir ) {
   312 		$template_base_paths  = get_block_theme_folders( $theme_slug );
   387 		$template_base_paths  = get_block_theme_folders( $theme_slug );
   313 		$theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
   388 		$theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
   314 		foreach ( $theme_template_files as $template_file ) {
   389 		foreach ( $theme_template_files as $template_file ) {
   318 				// Starting position of slug.
   393 				// Starting position of slug.
   319 				strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
   394 				strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
   320 				// Subtract ending '.html'.
   395 				// Subtract ending '.html'.
   321 				-5
   396 				-5
   322 			);
   397 			);
       
   398 
       
   399 			// Skip this item if its slug doesn't match any of the slugs to include.
       
   400 			if ( ! empty( $slugs_to_include ) && ! in_array( $template_slug, $slugs_to_include, true ) ) {
       
   401 				continue;
       
   402 			}
       
   403 
       
   404 			// Skip this item if its slug matches any of the slugs to skip.
       
   405 			if ( ! empty( $slugs_to_skip ) && in_array( $template_slug, $slugs_to_skip, true ) ) {
       
   406 				continue;
       
   407 			}
       
   408 
       
   409 			/*
       
   410 			 * The child theme items (stylesheet) are processed before the parent theme's (template).
       
   411 			 * If a child theme defines a template, prevent the parent template from being added to the list as well.
       
   412 			 */
       
   413 			if ( isset( $template_files[ $template_slug ] ) ) {
       
   414 				continue;
       
   415 			}
       
   416 
   323 			$new_template_item = array(
   417 			$new_template_item = array(
   324 				'slug'  => $template_slug,
   418 				'slug'  => $template_slug,
   325 				'path'  => $template_file,
   419 				'path'  => $template_file,
   326 				'theme' => $theme_slug,
   420 				'theme' => $theme_slug,
   327 				'type'  => $template_type,
   421 				'type'  => $template_type,
   328 			);
   422 			);
   329 
   423 
   330 			if ( 'wp_template_part' === $template_type ) {
   424 			if ( 'wp_template_part' === $template_type ) {
   331 				$template_files[] = _add_block_template_part_area_info( $new_template_item );
   425 				$candidate = _add_block_template_part_area_info( $new_template_item );
       
   426 				if ( ! isset( $area ) || ( isset( $area ) && $area === $candidate['area'] ) ) {
       
   427 					$template_files[ $template_slug ] = $candidate;
       
   428 				}
   332 			}
   429 			}
   333 
   430 
   334 			if ( 'wp_template' === $template_type ) {
   431 			if ( 'wp_template' === $template_type ) {
   335 				$template_files[] = _add_block_template_info( $new_template_item );
   432 				$candidate = _add_block_template_info( $new_template_item );
       
   433 				$is_custom = ! isset( $default_template_types[ $candidate['slug'] ] );
       
   434 
       
   435 				if (
       
   436 					! $post_type ||
       
   437 					( $post_type && isset( $candidate['postTypes'] ) && in_array( $post_type, $candidate['postTypes'], true ) )
       
   438 				) {
       
   439 					$template_files[ $template_slug ] = $candidate;
       
   440 				}
       
   441 
       
   442 				// The custom templates with no associated post types are available for all post types.
       
   443 				if ( $post_type && ! isset( $candidate['postTypes'] ) && $is_custom ) {
       
   444 					$template_files[ $template_slug ] = $candidate;
       
   445 				}
   336 			}
   446 			}
   337 		}
   447 		}
   338 	}
   448 	}
   339 
   449 
   340 	return $template_files;
   450 	return array_values( $template_files );
   341 }
   451 }
   342 
   452 
   343 /**
   453 /**
   344  * Attempts to add custom template information to the template item.
   454  * Attempts to add custom template information to the template item.
   345  *
   455  *
   348  *
   458  *
   349  * @param array $template_item Template to add information to (requires 'slug' field).
   459  * @param array $template_item Template to add information to (requires 'slug' field).
   350  * @return array Template item.
   460  * @return array Template item.
   351  */
   461  */
   352 function _add_block_template_info( $template_item ) {
   462 function _add_block_template_info( $template_item ) {
   353 	if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
   463 	if ( ! wp_theme_has_theme_json() ) {
   354 		return $template_item;
   464 		return $template_item;
   355 	}
   465 	}
   356 
   466 
   357 	$theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates();
   467 	$theme_data = wp_get_theme_data_custom_templates();
   358 	if ( isset( $theme_data[ $template_item['slug'] ] ) ) {
   468 	if ( isset( $theme_data[ $template_item['slug'] ] ) ) {
   359 		$template_item['title']     = $theme_data[ $template_item['slug'] ]['title'];
   469 		$template_item['title']     = $theme_data[ $template_item['slug'] ]['title'];
   360 		$template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes'];
   470 		$template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes'];
   361 	}
   471 	}
   362 
   472 
   368  *
   478  *
   369  * @since 5.9.0
   479  * @since 5.9.0
   370  * @access private
   480  * @access private
   371  *
   481  *
   372  * @param array $template_info Template to add information to (requires 'type' and 'slug' fields).
   482  * @param array $template_info Template to add information to (requires 'type' and 'slug' fields).
   373  *
       
   374  * @return array Template info.
   483  * @return array Template info.
   375  */
   484  */
   376 function _add_block_template_part_area_info( $template_info ) {
   485 function _add_block_template_part_area_info( $template_info ) {
   377 	if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
   486 	if ( wp_theme_has_theme_json() ) {
   378 		$theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts();
   487 		$theme_data = wp_get_theme_data_template_parts();
   379 	}
   488 	}
   380 
   489 
   381 	if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) {
   490 	if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) {
   382 		$template_info['title'] = $theme_data[ $template_info['slug'] ]['title'];
   491 		$template_info['title'] = $theme_data[ $template_info['slug'] ]['title'];
   383 		$template_info['area']  = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] );
   492 		$template_info['area']  = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] );
   394  *
   503  *
   395  * @since 5.9.0
   504  * @since 5.9.0
   396  * @access private
   505  * @access private
   397  *
   506  *
   398  * @param array $blocks array of blocks.
   507  * @param array $blocks array of blocks.
   399  *
       
   400  * @return array block references to the passed blocks and their inner blocks.
   508  * @return array block references to the passed blocks and their inner blocks.
   401  */
   509  */
   402 function _flatten_blocks( &$blocks ) {
   510 function _flatten_blocks( &$blocks ) {
   403 	$all_blocks = array();
   511 	$all_blocks = array();
   404 	$queue      = array();
   512 	$queue      = array();
   420 
   528 
   421 	return $all_blocks;
   529 	return $all_blocks;
   422 }
   530 }
   423 
   531 
   424 /**
   532 /**
   425  * Parses wp_template content and injects the active theme's
   533  * Injects the active theme's stylesheet as a `theme` attribute
   426  * stylesheet as a theme attribute into each wp_template_part
   534  * into a given template part block.
       
   535  *
       
   536  * @since 6.4.0
       
   537  * @access private
       
   538  *
       
   539  * @param array $block a parsed block.
       
   540  */
       
   541 function _inject_theme_attribute_in_template_part_block( &$block ) {
       
   542 	if (
       
   543 		'core/template-part' === $block['blockName'] &&
       
   544 		! isset( $block['attrs']['theme'] )
       
   545 	) {
       
   546 		$block['attrs']['theme'] = get_stylesheet();
       
   547 	}
       
   548 }
       
   549 
       
   550 /**
       
   551  * Removes the `theme` attribute from a given template part block.
       
   552  *
       
   553  * @since 6.4.0
       
   554  * @access private
       
   555  *
       
   556  * @param array $block a parsed block.
       
   557  */
       
   558 function _remove_theme_attribute_from_template_part_block( &$block ) {
       
   559 	if (
       
   560 		'core/template-part' === $block['blockName'] &&
       
   561 		isset( $block['attrs']['theme'] )
       
   562 	) {
       
   563 		unset( $block['attrs']['theme'] );
       
   564 	}
       
   565 }
       
   566 
       
   567 /**
       
   568  * Builds a unified template object based on a theme file.
   427  *
   569  *
   428  * @since 5.9.0
   570  * @since 5.9.0
       
   571  * @since 6.3.0 Added `modified` property to template objects.
   429  * @access private
   572  * @access private
   430  *
   573  *
   431  * @param string $template_content serialized wp_template content.
       
   432  *
       
   433  * @return string Updated 'wp_template' content.
       
   434  */
       
   435 function _inject_theme_attribute_in_block_template_content( $template_content ) {
       
   436 	$has_updated_content = false;
       
   437 	$new_content         = '';
       
   438 	$template_blocks     = parse_blocks( $template_content );
       
   439 
       
   440 	$blocks = _flatten_blocks( $template_blocks );
       
   441 	foreach ( $blocks as &$block ) {
       
   442 		if (
       
   443 			'core/template-part' === $block['blockName'] &&
       
   444 			! isset( $block['attrs']['theme'] )
       
   445 		) {
       
   446 			$block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
       
   447 			$has_updated_content     = true;
       
   448 		}
       
   449 	}
       
   450 
       
   451 	if ( $has_updated_content ) {
       
   452 		foreach ( $template_blocks as &$block ) {
       
   453 			$new_content .= serialize_block( $block );
       
   454 		}
       
   455 
       
   456 		return $new_content;
       
   457 	}
       
   458 
       
   459 	return $template_content;
       
   460 }
       
   461 
       
   462 /**
       
   463  * Parses a block template and removes the theme attribute from each template part.
       
   464  *
       
   465  * @since 5.9.0
       
   466  * @access private
       
   467  *
       
   468  * @param string $template_content Serialized block template content.
       
   469  * @return string Updated block template content.
       
   470  */
       
   471 function _remove_theme_attribute_in_block_template_content( $template_content ) {
       
   472 	$has_updated_content = false;
       
   473 	$new_content         = '';
       
   474 	$template_blocks     = parse_blocks( $template_content );
       
   475 
       
   476 	$blocks = _flatten_blocks( $template_blocks );
       
   477 	foreach ( $blocks as $key => $block ) {
       
   478 		if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['theme'] ) ) {
       
   479 			unset( $blocks[ $key ]['attrs']['theme'] );
       
   480 			$has_updated_content = true;
       
   481 		}
       
   482 	}
       
   483 
       
   484 	if ( ! $has_updated_content ) {
       
   485 		return $template_content;
       
   486 	}
       
   487 
       
   488 	foreach ( $template_blocks as $block ) {
       
   489 		$new_content .= serialize_block( $block );
       
   490 	}
       
   491 
       
   492 	return $new_content;
       
   493 }
       
   494 
       
   495 /**
       
   496  * Build a unified template object based on a theme file.
       
   497  *
       
   498  * @since 5.9.0
       
   499  * @access private
       
   500  *
       
   501  * @param array  $template_file Theme file.
   574  * @param array  $template_file Theme file.
   502  * @param string $template_type 'wp_template' or 'wp_template_part'.
   575  * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
   503  *
       
   504  * @return WP_Block_Template Template.
   576  * @return WP_Block_Template Template.
   505  */
   577  */
   506 function _build_block_template_result_from_file( $template_file, $template_type ) {
   578 function _build_block_template_result_from_file( $template_file, $template_type ) {
   507 	$default_template_types = get_default_block_template_types();
   579 	$default_template_types = get_default_block_template_types();
   508 	$template_content       = file_get_contents( $template_file['path'] );
   580 	$theme                  = get_stylesheet();
   509 	$theme                  = wp_get_theme()->get_stylesheet();
       
   510 
   581 
   511 	$template                 = new WP_Block_Template();
   582 	$template                 = new WP_Block_Template();
   512 	$template->id             = $theme . '//' . $template_file['slug'];
   583 	$template->id             = $theme . '//' . $template_file['slug'];
   513 	$template->theme          = $theme;
   584 	$template->theme          = $theme;
   514 	$template->content        = _inject_theme_attribute_in_block_template_content( $template_content );
   585 	$template->content        = file_get_contents( $template_file['path'] );
   515 	$template->slug           = $template_file['slug'];
   586 	$template->slug           = $template_file['slug'];
   516 	$template->source         = 'theme';
   587 	$template->source         = 'theme';
   517 	$template->type           = $template_type;
   588 	$template->type           = $template_type;
   518 	$template->title          = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug'];
   589 	$template->title          = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug'];
   519 	$template->status         = 'publish';
   590 	$template->status         = 'publish';
   520 	$template->has_theme_file = true;
   591 	$template->has_theme_file = true;
   521 	$template->is_custom      = true;
   592 	$template->is_custom      = true;
       
   593 	$template->modified       = null;
   522 
   594 
   523 	if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
   595 	if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
   524 		$template->description = $default_template_types[ $template_file['slug'] ]['description'];
   596 		$template->description = $default_template_types[ $template_file['slug'] ]['description'];
   525 		$template->title       = $default_template_types[ $template_file['slug'] ]['title'];
   597 		$template->title       = $default_template_types[ $template_file['slug'] ]['title'];
   526 		$template->is_custom   = false;
   598 		$template->is_custom   = false;
   532 
   604 
   533 	if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) {
   605 	if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) {
   534 		$template->area = $template_file['area'];
   606 		$template->area = $template_file['area'];
   535 	}
   607 	}
   536 
   608 
       
   609 	$before_block_visitor = '_inject_theme_attribute_in_template_part_block';
       
   610 	$after_block_visitor  = null;
       
   611 	$hooked_blocks        = get_hooked_blocks();
       
   612 	if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
       
   613 		$before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
       
   614 		$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
       
   615 	}
       
   616 	$blocks            = parse_blocks( $template->content );
       
   617 	$template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
       
   618 
   537 	return $template;
   619 	return $template;
   538 }
   620 }
   539 
   621 
   540 /**
   622 /**
   541  * Build a unified template object based a post Object.
   623  * Builds the title and description of a post-specific template based on the underlying referenced post.
   542  *
   624  *
   543  * @since 5.9.0
   625  * Mutates the underlying template object.
       
   626  *
       
   627  * @since 6.1.0
   544  * @access private
   628  * @access private
   545  *
   629  *
   546  * @param WP_Post $post Template post.
   630  * @param string            $post_type Post type, e.g. page, post, product.
   547  *
   631  * @param string            $slug      Slug of the post, e.g. a-story-about-shoes.
   548  * @return WP_Block_Template|WP_Error Template.
   632  * @param WP_Block_Template $template  Template to mutate adding the description and title computed.
   549  */
   633  * @return bool Returns true if the referenced post was found and false otherwise.
   550 function _build_block_template_result_from_post( $post ) {
   634  */
       
   635 function _wp_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, WP_Block_Template $template ) {
       
   636 	$post_type_object = get_post_type_object( $post_type );
       
   637 
       
   638 	$default_args = array(
       
   639 		'post_type'              => $post_type,
       
   640 		'post_status'            => 'publish',
       
   641 		'posts_per_page'         => 1,
       
   642 		'update_post_meta_cache' => false,
       
   643 		'update_post_term_cache' => false,
       
   644 		'ignore_sticky_posts'    => true,
       
   645 		'no_found_rows'          => true,
       
   646 	);
       
   647 
       
   648 	$args = array(
       
   649 		'name' => $slug,
       
   650 	);
       
   651 	$args = wp_parse_args( $args, $default_args );
       
   652 
       
   653 	$posts_query = new WP_Query( $args );
       
   654 
       
   655 	if ( empty( $posts_query->posts ) ) {
       
   656 		$template->title = sprintf(
       
   657 			/* translators: Custom template title in the Site Editor referencing a post that was not found. 1: Post type singular name, 2: Post type slug. */
       
   658 			__( 'Not found: %1$s (%2$s)' ),
       
   659 			$post_type_object->labels->singular_name,
       
   660 			$slug
       
   661 		);
       
   662 
       
   663 		return false;
       
   664 	}
       
   665 
       
   666 	$post_title = $posts_query->posts[0]->post_title;
       
   667 
       
   668 	$template->title = sprintf(
       
   669 		/* translators: Custom template title in the Site Editor. 1: Post type singular name, 2: Post title. */
       
   670 		__( '%1$s: %2$s' ),
       
   671 		$post_type_object->labels->singular_name,
       
   672 		$post_title
       
   673 	);
       
   674 
       
   675 	$template->description = sprintf(
       
   676 		/* translators: Custom template description in the Site Editor. %s: Post title. */
       
   677 		__( 'Template for %s' ),
       
   678 		$post_title
       
   679 	);
       
   680 
       
   681 	$args = array(
       
   682 		'title' => $post_title,
       
   683 	);
       
   684 	$args = wp_parse_args( $args, $default_args );
       
   685 
       
   686 	$posts_with_same_title_query = new WP_Query( $args );
       
   687 
       
   688 	if ( count( $posts_with_same_title_query->posts ) > 1 ) {
       
   689 		$template->title = sprintf(
       
   690 			/* translators: Custom template title in the Site Editor. 1: Template title, 2: Post type slug. */
       
   691 			__( '%1$s (%2$s)' ),
       
   692 			$template->title,
       
   693 			$slug
       
   694 		);
       
   695 	}
       
   696 
       
   697 	return true;
       
   698 }
       
   699 
       
   700 /**
       
   701  * Builds the title and description of a taxonomy-specific template based on the underlying entity referenced.
       
   702  *
       
   703  * Mutates the underlying template object.
       
   704  *
       
   705  * @since 6.1.0
       
   706  * @access private
       
   707  *
       
   708  * @param string            $taxonomy Identifier of the taxonomy, e.g. category.
       
   709  * @param string            $slug     Slug of the term, e.g. shoes.
       
   710  * @param WP_Block_Template $template Template to mutate adding the description and title computed.
       
   711  * @return bool True if the term referenced was found and false otherwise.
       
   712  */
       
   713 function _wp_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, WP_Block_Template $template ) {
       
   714 	$taxonomy_object = get_taxonomy( $taxonomy );
       
   715 
       
   716 	$default_args = array(
       
   717 		'taxonomy'               => $taxonomy,
       
   718 		'hide_empty'             => false,
       
   719 		'update_term_meta_cache' => false,
       
   720 	);
       
   721 
       
   722 	$term_query = new WP_Term_Query();
       
   723 
       
   724 	$args = array(
       
   725 		'number' => 1,
       
   726 		'slug'   => $slug,
       
   727 	);
       
   728 	$args = wp_parse_args( $args, $default_args );
       
   729 
       
   730 	$terms_query = $term_query->query( $args );
       
   731 
       
   732 	if ( empty( $terms_query ) ) {
       
   733 		$template->title = sprintf(
       
   734 			/* translators: Custom template title in the Site Editor, referencing a taxonomy term that was not found. 1: Taxonomy singular name, 2: Term slug. */
       
   735 			__( 'Not found: %1$s (%2$s)' ),
       
   736 			$taxonomy_object->labels->singular_name,
       
   737 			$slug
       
   738 		);
       
   739 		return false;
       
   740 	}
       
   741 
       
   742 	$term_title = $terms_query[0]->name;
       
   743 
       
   744 	$template->title = sprintf(
       
   745 		/* translators: Custom template title in the Site Editor. 1: Taxonomy singular name, 2: Term title. */
       
   746 		__( '%1$s: %2$s' ),
       
   747 		$taxonomy_object->labels->singular_name,
       
   748 		$term_title
       
   749 	);
       
   750 
       
   751 	$template->description = sprintf(
       
   752 		/* translators: Custom template description in the Site Editor. %s: Term title. */
       
   753 		__( 'Template for %s' ),
       
   754 		$term_title
       
   755 	);
       
   756 
       
   757 	$term_query = new WP_Term_Query();
       
   758 
       
   759 	$args = array(
       
   760 		'number' => 2,
       
   761 		'name'   => $term_title,
       
   762 	);
       
   763 	$args = wp_parse_args( $args, $default_args );
       
   764 
       
   765 	$terms_with_same_title_query = $term_query->query( $args );
       
   766 
       
   767 	if ( count( $terms_with_same_title_query ) > 1 ) {
       
   768 		$template->title = sprintf(
       
   769 			/* translators: Custom template title in the Site Editor. 1: Template title, 2: Term slug. */
       
   770 			__( '%1$s (%2$s)' ),
       
   771 			$template->title,
       
   772 			$slug
       
   773 		);
       
   774 	}
       
   775 
       
   776 	return true;
       
   777 }
       
   778 
       
   779 /**
       
   780  * Builds a block template object from a post object.
       
   781  *
       
   782  * This is a helper function that creates a block template object from a given post object.
       
   783  * It is self-sufficient in that it only uses information passed as arguments; it does not
       
   784  * query the database for additional information.
       
   785  *
       
   786  * @since 6.5.3
       
   787  * @access private
       
   788  *
       
   789  * @param WP_Post $post  Template post.
       
   790  * @param array   $terms Additional terms to inform the template object.
       
   791  * @param array   $meta  Additional meta fields to inform the template object.
       
   792  * @return WP_Block_Template|WP_Error Template or error object.
       
   793  */
       
   794 function _build_block_template_object_from_post_object( $post, $terms = array(), $meta = array() ) {
       
   795 	if ( empty( $terms['wp_theme'] ) ) {
       
   796 		return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
       
   797 	}
       
   798 	$theme = $terms['wp_theme'];
       
   799 
   551 	$default_template_types = get_default_block_template_types();
   800 	$default_template_types = get_default_block_template_types();
   552 	$terms                  = get_the_terms( $post, 'wp_theme' );
   801 
   553 
   802 	$template_file  = _get_block_template_file( $post->post_type, $post->post_name );
   554 	if ( is_wp_error( $terms ) ) {
   803 	$has_theme_file = get_stylesheet() === $theme && null !== $template_file;
   555 		return $terms;
       
   556 	}
       
   557 
       
   558 	if ( ! $terms ) {
       
   559 		return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
       
   560 	}
       
   561 
       
   562 	$theme          = $terms[0]->name;
       
   563 	$has_theme_file = wp_get_theme()->get_stylesheet() === $theme &&
       
   564 		null !== _get_block_template_file( $post->post_type, $post->post_name );
       
   565 
       
   566 	$origin = get_post_meta( $post->ID, 'origin', true );
       
   567 
   804 
   568 	$template                 = new WP_Block_Template();
   805 	$template                 = new WP_Block_Template();
   569 	$template->wp_id          = $post->ID;
   806 	$template->wp_id          = $post->ID;
   570 	$template->id             = $theme . '//' . $post->post_name;
   807 	$template->id             = $theme . '//' . $post->post_name;
   571 	$template->theme          = $theme;
   808 	$template->theme          = $theme;
   572 	$template->content        = $post->post_content;
   809 	$template->content        = $post->post_content;
   573 	$template->slug           = $post->post_name;
   810 	$template->slug           = $post->post_name;
   574 	$template->source         = 'custom';
   811 	$template->source         = 'custom';
   575 	$template->origin         = ! empty( $origin ) ? $origin : null;
   812 	$template->origin         = ! empty( $meta['origin'] ) ? $meta['origin'] : null;
   576 	$template->type           = $post->post_type;
   813 	$template->type           = $post->post_type;
   577 	$template->description    = $post->post_excerpt;
   814 	$template->description    = $post->post_excerpt;
   578 	$template->title          = $post->post_title;
   815 	$template->title          = $post->post_title;
   579 	$template->status         = $post->post_status;
   816 	$template->status         = $post->post_status;
   580 	$template->has_theme_file = $has_theme_file;
   817 	$template->has_theme_file = $has_theme_file;
   581 	$template->is_custom      = true;
   818 	$template->is_custom      = empty( $meta['is_wp_suggestion'] );
   582 	$template->author         = $post->post_author;
   819 	$template->author         = $post->post_author;
       
   820 	$template->modified       = $post->post_modified;
       
   821 
       
   822 	if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
       
   823 		$template->post_types = $template_file['postTypes'];
       
   824 	}
   583 
   825 
   584 	if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
   826 	if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
   585 		$template->is_custom = false;
   827 		$template->is_custom = false;
   586 	}
   828 	}
   587 
   829 
   588 	if ( 'wp_template_part' === $post->post_type ) {
   830 	if ( 'wp_template_part' === $post->post_type && isset( $terms['wp_template_part_area'] ) ) {
   589 		$type_terms = get_the_terms( $post, 'wp_template_part_area' );
   831 		$template->area = $terms['wp_template_part_area'];
       
   832 	}
       
   833 
       
   834 	return $template;
       
   835 }
       
   836 
       
   837 /**
       
   838  * Builds a unified template object based a post Object.
       
   839  *
       
   840  * @since 5.9.0
       
   841  * @since 6.3.0 Added `modified` property to template objects.
       
   842  * @since 6.4.0 Added support for a revision post to be passed to this function.
       
   843  * @access private
       
   844  *
       
   845  * @param WP_Post $post Template post.
       
   846  * @return WP_Block_Template|WP_Error Template or error object.
       
   847  */
       
   848 function _build_block_template_result_from_post( $post ) {
       
   849 	$post_id = wp_is_post_revision( $post );
       
   850 	if ( ! $post_id ) {
       
   851 		$post_id = $post;
       
   852 	}
       
   853 	$parent_post     = get_post( $post_id );
       
   854 	$post->post_name = $parent_post->post_name;
       
   855 	$post->post_type = $parent_post->post_type;
       
   856 
       
   857 	$terms = get_the_terms( $parent_post, 'wp_theme' );
       
   858 
       
   859 	if ( is_wp_error( $terms ) ) {
       
   860 		return $terms;
       
   861 	}
       
   862 
       
   863 	if ( ! $terms ) {
       
   864 		return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
       
   865 	}
       
   866 
       
   867 	$terms = array(
       
   868 		'wp_theme' => $terms[0]->name,
       
   869 	);
       
   870 
       
   871 	if ( 'wp_template_part' === $parent_post->post_type ) {
       
   872 		$type_terms = get_the_terms( $parent_post, 'wp_template_part_area' );
   590 		if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
   873 		if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
   591 			$template->area = $type_terms[0]->name;
   874 			$terms['wp_template_part_area'] = $type_terms[0]->name;
   592 		}
   875 		}
       
   876 	}
       
   877 
       
   878 	$meta = array(
       
   879 		'origin'           => get_post_meta( $parent_post->ID, 'origin', true ),
       
   880 		'is_wp_suggestion' => get_post_meta( $parent_post->ID, 'is_wp_suggestion', true ),
       
   881 	);
       
   882 
       
   883 	$template = _build_block_template_object_from_post_object( $post, $terms, $meta );
       
   884 
       
   885 	if ( is_wp_error( $template ) ) {
       
   886 		return $template;
       
   887 	}
       
   888 
       
   889 	// Check for a block template without a description and title or with a title equal to the slug.
       
   890 	if ( 'wp_template' === $parent_post->post_type && empty( $template->description ) && ( empty( $template->title ) || $template->title === $template->slug ) ) {
       
   891 		$matches = array();
       
   892 
       
   893 		// Check for a block template for a single author, page, post, tag, category, custom post type, or custom taxonomy.
       
   894 		if ( preg_match( '/(author|page|single|tag|category|taxonomy)-(.+)/', $template->slug, $matches ) ) {
       
   895 			$type           = $matches[1];
       
   896 			$slug_remaining = $matches[2];
       
   897 
       
   898 			switch ( $type ) {
       
   899 				case 'author':
       
   900 					$nice_name = $slug_remaining;
       
   901 					$users     = get_users(
       
   902 						array(
       
   903 							'capability'     => 'edit_posts',
       
   904 							'search'         => $nice_name,
       
   905 							'search_columns' => array( 'user_nicename' ),
       
   906 							'fields'         => 'display_name',
       
   907 						)
       
   908 					);
       
   909 
       
   910 					if ( empty( $users ) ) {
       
   911 						$template->title = sprintf(
       
   912 							/* translators: Custom template title in the Site Editor, referencing a deleted author. %s: Author nicename. */
       
   913 							__( 'Deleted author: %s' ),
       
   914 							$nice_name
       
   915 						);
       
   916 					} else {
       
   917 						$author_name = $users[0];
       
   918 
       
   919 						$template->title = sprintf(
       
   920 							/* translators: Custom template title in the Site Editor. %s: Author name. */
       
   921 							__( 'Author: %s' ),
       
   922 							$author_name
       
   923 						);
       
   924 
       
   925 						$template->description = sprintf(
       
   926 							/* translators: Custom template description in the Site Editor. %s: Author name. */
       
   927 							__( 'Template for %s' ),
       
   928 							$author_name
       
   929 						);
       
   930 
       
   931 						$users_with_same_name = get_users(
       
   932 							array(
       
   933 								'capability'     => 'edit_posts',
       
   934 								'search'         => $author_name,
       
   935 								'search_columns' => array( 'display_name' ),
       
   936 								'fields'         => 'display_name',
       
   937 							)
       
   938 						);
       
   939 
       
   940 						if ( count( $users_with_same_name ) > 1 ) {
       
   941 							$template->title = sprintf(
       
   942 								/* translators: Custom template title in the Site Editor. 1: Template title of an author template, 2: Author nicename. */
       
   943 								__( '%1$s (%2$s)' ),
       
   944 								$template->title,
       
   945 								$nice_name
       
   946 							);
       
   947 						}
       
   948 					}
       
   949 					break;
       
   950 				case 'page':
       
   951 					_wp_build_title_and_description_for_single_post_type_block_template( 'page', $slug_remaining, $template );
       
   952 					break;
       
   953 				case 'single':
       
   954 					$post_types = get_post_types();
       
   955 
       
   956 					foreach ( $post_types as $post_type ) {
       
   957 						$post_type_length = strlen( $post_type ) + 1;
       
   958 
       
   959 						// If $slug_remaining starts with $post_type followed by a hyphen.
       
   960 						if ( 0 === strncmp( $slug_remaining, $post_type . '-', $post_type_length ) ) {
       
   961 							$slug  = substr( $slug_remaining, $post_type_length, strlen( $slug_remaining ) );
       
   962 							$found = _wp_build_title_and_description_for_single_post_type_block_template( $post_type, $slug, $template );
       
   963 
       
   964 							if ( $found ) {
       
   965 								break;
       
   966 							}
       
   967 						}
       
   968 					}
       
   969 					break;
       
   970 				case 'tag':
       
   971 					_wp_build_title_and_description_for_taxonomy_block_template( 'post_tag', $slug_remaining, $template );
       
   972 					break;
       
   973 				case 'category':
       
   974 					_wp_build_title_and_description_for_taxonomy_block_template( 'category', $slug_remaining, $template );
       
   975 					break;
       
   976 				case 'taxonomy':
       
   977 					$taxonomies = get_taxonomies();
       
   978 
       
   979 					foreach ( $taxonomies as $taxonomy ) {
       
   980 						$taxonomy_length = strlen( $taxonomy ) + 1;
       
   981 
       
   982 						// If $slug_remaining starts with $taxonomy followed by a hyphen.
       
   983 						if ( 0 === strncmp( $slug_remaining, $taxonomy . '-', $taxonomy_length ) ) {
       
   984 							$slug  = substr( $slug_remaining, $taxonomy_length, strlen( $slug_remaining ) );
       
   985 							$found = _wp_build_title_and_description_for_taxonomy_block_template( $taxonomy, $slug, $template );
       
   986 
       
   987 							if ( $found ) {
       
   988 								break;
       
   989 							}
       
   990 						}
       
   991 					}
       
   992 					break;
       
   993 			}
       
   994 		}
       
   995 	}
       
   996 
       
   997 	$hooked_blocks = get_hooked_blocks();
       
   998 	if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
       
   999 		$before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
       
  1000 		$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
       
  1001 		$blocks               = parse_blocks( $template->content );
       
  1002 		$template->content    = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
   593 	}
  1003 	}
   594 
  1004 
   595 	return $template;
  1005 	return $template;
   596 }
  1006 }
   597 
  1007 
   601  * @since 5.8.0
  1011  * @since 5.8.0
   602  *
  1012  *
   603  * @param array  $query {
  1013  * @param array  $query {
   604  *     Optional. Arguments to retrieve templates.
  1014  *     Optional. Arguments to retrieve templates.
   605  *
  1015  *
   606  *     @type array  $slug__in  List of slugs to include.
  1016  *     @type string[] $slug__in  List of slugs to include.
   607  *     @type int    $wp_id     Post ID of customized template.
  1017  *     @type int      $wp_id     Post ID of customized template.
   608  *     @type string $area      A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only).
  1018  *     @type string   $area      A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
   609  *     @type string $post_type Post type to get the templates for.
  1019  *     @type string   $post_type Post type to get the templates for.
   610  * }
  1020  * }
   611  * @param string $template_type 'wp_template' or 'wp_template_part'.
  1021  * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
   612  *
  1022  * @return WP_Block_Template[] Array of block templates.
   613  * @return array Templates.
       
   614  */
  1023  */
   615 function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
  1024 function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
   616 	/**
  1025 	/**
   617 	 * Filters the block templates array before the query takes place.
  1026 	 * Filters the block templates array before the query takes place.
   618 	 *
  1027 	 *
   619 	 * Return a non-null value to bypass the WordPress queries.
  1028 	 * Return a non-null value to bypass the WordPress queries.
   620 	 *
  1029 	 *
   621 	 * @since 5.9.0
  1030 	 * @since 5.9.0
   622 	 *
  1031 	 *
   623 	 * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query,
  1032 	 * @param WP_Block_Template[]|null $block_templates Return an array of block templates to short-circuit the default query,
   624 	 *                                                  or null to allow WP to run it's normal queries.
  1033 	 *                                                  or null to allow WP to run its normal queries.
   625 	 * @param array  $query {
  1034 	 * @param array  $query {
   626 	 *     Optional. Arguments to retrieve templates.
  1035 	 *     Arguments to retrieve templates. All arguments are optional.
   627 	 *
  1036 	 *
   628 	 *     @type array  $slug__in List of slugs to include.
  1037 	 *     @type string[] $slug__in  List of slugs to include.
   629 	 *     @type int    $wp_id Post ID of customized template.
  1038 	 *     @type int      $wp_id     Post ID of customized template.
   630 	 *     @type string $post_type Post type to get the templates for.
  1039 	 *     @type string   $area      A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
       
  1040 	 *     @type string   $post_type Post type to get the templates for.
   631 	 * }
  1041 	 * }
   632 	 * @param string $template_type wp_template or wp_template_part.
  1042 	 * @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
   633 	 */
  1043 	 */
   634 	$templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
  1044 	$templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
   635 	if ( ! is_null( $templates ) ) {
  1045 	if ( ! is_null( $templates ) ) {
   636 		return $templates;
  1046 		return $templates;
   637 	}
  1047 	}
   638 
  1048 
   639 	$post_type     = isset( $query['post_type'] ) ? $query['post_type'] : '';
  1049 	$post_type     = isset( $query['post_type'] ) ? $query['post_type'] : '';
   640 	$wp_query_args = array(
  1050 	$wp_query_args = array(
   641 		'post_status'    => array( 'auto-draft', 'draft', 'publish' ),
  1051 		'post_status'         => array( 'auto-draft', 'draft', 'publish' ),
   642 		'post_type'      => $template_type,
  1052 		'post_type'           => $template_type,
   643 		'posts_per_page' => -1,
  1053 		'posts_per_page'      => -1,
   644 		'no_found_rows'  => true,
  1054 		'no_found_rows'       => true,
   645 		'tax_query'      => array(
  1055 		'lazy_load_term_meta' => false,
       
  1056 		'tax_query'           => array(
   646 			array(
  1057 			array(
   647 				'taxonomy' => 'wp_theme',
  1058 				'taxonomy' => 'wp_theme',
   648 				'field'    => 'name',
  1059 				'field'    => 'name',
   649 				'terms'    => wp_get_theme()->get_stylesheet(),
  1060 				'terms'    => get_stylesheet(),
   650 			),
  1061 			),
   651 		),
  1062 		),
   652 	);
  1063 	);
   653 
  1064 
   654 	if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
  1065 	if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
   658 			'terms'    => $query['area'],
  1069 			'terms'    => $query['area'],
   659 		);
  1070 		);
   660 		$wp_query_args['tax_query']['relation'] = 'AND';
  1071 		$wp_query_args['tax_query']['relation'] = 'AND';
   661 	}
  1072 	}
   662 
  1073 
   663 	if ( isset( $query['slug__in'] ) ) {
  1074 	if ( ! empty( $query['slug__in'] ) ) {
   664 		$wp_query_args['post_name__in'] = $query['slug__in'];
  1075 		$wp_query_args['post_name__in']  = $query['slug__in'];
       
  1076 		$wp_query_args['posts_per_page'] = count( array_unique( $query['slug__in'] ) );
   665 	}
  1077 	}
   666 
  1078 
   667 	// This is only needed for the regular templates/template parts post type listing and editor.
  1079 	// This is only needed for the regular templates/template parts post type listing and editor.
   668 	if ( isset( $query['wp_id'] ) ) {
  1080 	if ( isset( $query['wp_id'] ) ) {
   669 		$wp_query_args['p'] = $query['wp_id'];
  1081 		$wp_query_args['p'] = $query['wp_id'];
   682 
  1094 
   683 		if ( $post_type && ! $template->is_custom ) {
  1095 		if ( $post_type && ! $template->is_custom ) {
   684 			continue;
  1096 			continue;
   685 		}
  1097 		}
   686 
  1098 
       
  1099 		if (
       
  1100 			$post_type &&
       
  1101 			isset( $template->post_types ) &&
       
  1102 			! in_array( $post_type, $template->post_types, true )
       
  1103 		) {
       
  1104 			continue;
       
  1105 		}
       
  1106 
   687 		$query_result[] = $template;
  1107 		$query_result[] = $template;
   688 	}
  1108 	}
   689 
  1109 
   690 	if ( ! isset( $query['wp_id'] ) ) {
  1110 	if ( ! isset( $query['wp_id'] ) ) {
   691 		$template_files = _get_block_templates_files( $template_type );
  1111 		/*
       
  1112 		 * If the query has found some use templates, those have priority
       
  1113 		 * over the theme-provided ones, so we skip querying and building them.
       
  1114 		 */
       
  1115 		$query['slug__not_in'] = wp_list_pluck( $query_result, 'slug' );
       
  1116 		$template_files        = _get_block_templates_files( $template_type, $query );
   692 		foreach ( $template_files as $template_file ) {
  1117 		foreach ( $template_files as $template_file ) {
   693 			$template = _build_block_template_result_from_file( $template_file, $template_type );
  1118 			$query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
   694 
       
   695 			if ( $post_type && ! $template->is_custom ) {
       
   696 				continue;
       
   697 			}
       
   698 
       
   699 			if ( $post_type &&
       
   700 				isset( $template->post_types ) &&
       
   701 				! in_array( $post_type, $template->post_types, true )
       
   702 			) {
       
   703 				continue;
       
   704 			}
       
   705 
       
   706 			$is_not_custom   = false === array_search(
       
   707 				wp_get_theme()->get_stylesheet() . '//' . $template_file['slug'],
       
   708 				wp_list_pluck( $query_result, 'id' ),
       
   709 				true
       
   710 			);
       
   711 			$fits_slug_query =
       
   712 				! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true );
       
   713 			$fits_area_query =
       
   714 				! isset( $query['area'] ) || $template_file['area'] === $query['area'];
       
   715 			$should_include  = $is_not_custom && $fits_slug_query && $fits_area_query;
       
   716 			if ( $should_include ) {
       
   717 				$query_result[] = $template;
       
   718 			}
       
   719 		}
  1119 		}
   720 	}
  1120 	}
   721 
  1121 
   722 	/**
  1122 	/**
   723 	 * Filters the array of queried block templates array after they've been fetched.
  1123 	 * Filters the array of queried block templates array after they've been fetched.
   724 	 *
  1124 	 *
   725 	 * @since 5.9.0
  1125 	 * @since 5.9.0
   726 	 *
  1126 	 *
   727 	 * @param WP_Block_Template[] $query_result Array of found block templates.
  1127 	 * @param WP_Block_Template[] $query_result Array of found block templates.
   728 	 * @param array  $query {
  1128 	 * @param array               $query {
   729 	 *     Optional. Arguments to retrieve templates.
  1129 	 *     Arguments to retrieve templates. All arguments are optional.
   730 	 *
  1130 	 *
   731 	 *     @type array  $slug__in List of slugs to include.
  1131 	 *     @type string[] $slug__in  List of slugs to include.
   732 	 *     @type int    $wp_id Post ID of customized template.
  1132 	 *     @type int      $wp_id     Post ID of customized template.
       
  1133 	 *     @type string   $area      A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
       
  1134 	 *     @type string   $post_type Post type to get the templates for.
   733 	 * }
  1135 	 * }
   734 	 * @param string $template_type wp_template or wp_template_part.
  1136 	 * @param string              $template_type wp_template or wp_template_part.
   735 	 */
  1137 	 */
   736 	return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
  1138 	return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
   737 }
  1139 }
   738 
  1140 
   739 /**
  1141 /**
   740  * Retrieves a single unified template object using its id.
  1142  * Retrieves a single unified template object using its id.
   741  *
  1143  *
   742  * @since 5.8.0
  1144  * @since 5.8.0
   743  *
  1145  *
   744  * @param string $id            Template unique identifier (example: theme_slug//template_slug).
  1146  * @param string $id            Template unique identifier (example: 'theme_slug//template_slug').
   745  * @param string $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`.
  1147  * @param string $template_type Optional. Template type. Either 'wp_template' or 'wp_template_part'.
   746  *                              Default `'wp_template'`.
  1148  *                              Default 'wp_template'.
   747  *
       
   748  * @return WP_Block_Template|null Template.
  1149  * @return WP_Block_Template|null Template.
   749  */
  1150  */
   750 function get_block_template( $id, $template_type = 'wp_template' ) {
  1151 function get_block_template( $id, $template_type = 'wp_template' ) {
   751 	/**
  1152 	/**
   752 	 *Filters the block template object before the query takes place.
  1153 	 * Filters the block template object before the query takes place.
   753 	 *
  1154 	 *
   754 	 * Return a non-null value to bypass the WordPress queries.
  1155 	 * Return a non-null value to bypass the WordPress queries.
   755 	 *
  1156 	 *
   756 	 * @since 5.9.0
  1157 	 * @since 5.9.0
   757 	 *
  1158 	 *
   758 	 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
  1159 	 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
   759 	 *                                               or null to allow WP to run its normal queries.
  1160 	 *                                               or null to allow WP to run its normal queries.
   760 	 * @param string $id                             Template unique identifier (example: theme_slug//template_slug).
  1161 	 * @param string                 $id             Template unique identifier (example: 'theme_slug//template_slug').
   761 	 * @param string $template_type                  Template type: `'wp_template'` or '`wp_template_part'`.
  1162 	 * @param string                 $template_type  Template type. Either 'wp_template' or 'wp_template_part'.
   762 	 */
  1163 	 */
   763 	$block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type );
  1164 	$block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type );
   764 	if ( ! is_null( $block_template ) ) {
  1165 	if ( ! is_null( $block_template ) ) {
   765 		return $block_template;
  1166 		return $block_template;
   766 	}
  1167 	}
   801 	 * Filters the queried block template object after it's been fetched.
  1202 	 * Filters the queried block template object after it's been fetched.
   802 	 *
  1203 	 *
   803 	 * @since 5.9.0
  1204 	 * @since 5.9.0
   804 	 *
  1205 	 *
   805 	 * @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
  1206 	 * @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
   806 	 * @param string                 $id             Template unique identifier (example: theme_slug//template_slug).
  1207 	 * @param string                 $id             Template unique identifier (example: 'theme_slug//template_slug').
   807 	 * @param array                  $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
  1208 	 * @param string                 $template_type  Template type. Either 'wp_template' or 'wp_template_part'.
   808 	 */
  1209 	 */
   809 	return apply_filters( 'get_block_template', $block_template, $id, $template_type );
  1210 	return apply_filters( 'get_block_template', $block_template, $id, $template_type );
   810 }
  1211 }
   811 
  1212 
   812 /**
  1213 /**
   813  * Retrieves a single unified template object using its id.
  1214  * Retrieves a unified template object based on a theme file.
       
  1215  *
       
  1216  * This is a fallback of get_block_template(), used when no templates are found in the database.
   814  *
  1217  *
   815  * @since 5.9.0
  1218  * @since 5.9.0
   816  *
  1219  *
   817  * @param string $id            Template unique identifier (example: theme_slug//template_slug).
  1220  * @param string $id            Template unique identifier (example: 'theme_slug//template_slug').
   818  * @param string $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`.
  1221  * @param string $template_type Optional. Template type. Either 'wp_template' or 'wp_template_part'.
   819  *                              Default `'wp_template'`.
  1222  *                              Default 'wp_template'.
   820  * @return WP_Block_Template|null The found block template, or null if there isn't one.
  1223  * @return WP_Block_Template|null The found block template, or null if there isn't one.
   821  */
  1224  */
   822 function get_block_file_template( $id, $template_type = 'wp_template' ) {
  1225 function get_block_file_template( $id, $template_type = 'wp_template' ) {
   823 	/**
  1226 	/**
   824 	 * Filters the block templates array before the query takes place.
  1227 	 * Filters the block template object before the theme file discovery takes place.
   825 	 *
  1228 	 *
   826 	 * Return a non-null value to bypass the WordPress queries.
  1229 	 * Return a non-null value to bypass the WordPress theme file discovery.
   827 	 *
  1230 	 *
   828 	 * @since 5.9.0
  1231 	 * @since 5.9.0
   829 	 *
  1232 	 *
   830 	 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
  1233 	 * @param WP_Block_Template|null $block_template Return block template object to short-circuit the default query,
   831 	 *                                               or null to allow WP to run its normal queries.
  1234 	 *                                               or null to allow WP to run its normal queries.
   832 	 * @param string                 $id             Template unique identifier (example: theme_slug//template_slug).
  1235 	 * @param string                 $id             Template unique identifier (example: 'theme_slug//template_slug').
   833 	 * @param string                 $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
  1236 	 * @param string                 $template_type  Template type. Either 'wp_template' or 'wp_template_part'.
   834 	 */
  1237 	 */
   835 	$block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type );
  1238 	$block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type );
   836 	if ( ! is_null( $block_template ) ) {
  1239 	if ( ! is_null( $block_template ) ) {
   837 		return $block_template;
  1240 		return $block_template;
   838 	}
  1241 	}
   842 		/** This filter is documented in wp-includes/block-template-utils.php */
  1245 		/** This filter is documented in wp-includes/block-template-utils.php */
   843 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
  1246 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
   844 	}
  1247 	}
   845 	list( $theme, $slug ) = $parts;
  1248 	list( $theme, $slug ) = $parts;
   846 
  1249 
   847 	if ( wp_get_theme()->get_stylesheet() !== $theme ) {
  1250 	if ( get_stylesheet() !== $theme ) {
   848 		/** This filter is documented in wp-includes/block-template-utils.php */
  1251 		/** This filter is documented in wp-includes/block-template-utils.php */
   849 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
  1252 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
   850 	}
  1253 	}
   851 
  1254 
   852 	$template_file = _get_block_template_file( $template_type, $slug );
  1255 	$template_file = _get_block_template_file( $template_type, $slug );
   856 	}
  1259 	}
   857 
  1260 
   858 	$block_template = _build_block_template_result_from_file( $template_file, $template_type );
  1261 	$block_template = _build_block_template_result_from_file( $template_file, $template_type );
   859 
  1262 
   860 	/**
  1263 	/**
   861 	 * Filters the array of queried block templates array after they've been fetched.
  1264 	 * Filters the block template object after it has been (potentially) fetched from the theme file.
   862 	 *
  1265 	 *
   863 	 * @since 5.9.0
  1266 	 * @since 5.9.0
   864 	 *
  1267 	 *
   865 	 * @param WP_Block_Template|null $block_template The found block template, or null if there is none.
  1268 	 * @param WP_Block_Template|null $block_template The found block template, or null if there is none.
   866 	 * @param string                 $id             Template unique identifier (example: theme_slug//template_slug).
  1269 	 * @param string                 $id             Template unique identifier (example: 'theme_slug//template_slug').
   867 	 * @param string                 $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
  1270 	 * @param string                 $template_type  Template type. Either 'wp_template' or 'wp_template_part'.
   868 	 */
  1271 	 */
   869 	return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
  1272 	return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
   870 }
  1273 }
   871 
  1274 
   872 /**
  1275 /**
   873  * Print a template-part.
  1276  * Prints a block template part.
   874  *
  1277  *
   875  * @since 5.9.0
  1278  * @since 5.9.0
   876  *
  1279  *
   877  * @param string $part The template-part to print. Use "header" or "footer".
  1280  * @param string $part The block template part to print, for example 'header' or 'footer'.
   878  */
  1281  */
   879 function block_template_part( $part ) {
  1282 function block_template_part( $part ) {
   880 	$template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
  1283 	$template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
   881 	if ( ! $template_part || empty( $template_part->content ) ) {
  1284 	if ( ! $template_part || empty( $template_part->content ) ) {
   882 		return;
  1285 		return;
   883 	}
  1286 	}
   884 	echo do_blocks( $template_part->content );
  1287 	echo do_blocks( $template_part->content );
   885 }
  1288 }
   886 
  1289 
   887 /**
  1290 /**
   888  * Print the header template-part.
  1291  * Prints the header block template part.
   889  *
  1292  *
   890  * @since 5.9.0
  1293  * @since 5.9.0
   891  */
  1294  */
   892 function block_header_area() {
  1295 function block_header_area() {
   893 	block_template_part( 'header' );
  1296 	block_template_part( 'header' );
   894 }
  1297 }
   895 
  1298 
   896 /**
  1299 /**
   897  * Print the footer template-part.
  1300  * Prints the footer block template part.
   898  *
  1301  *
   899  * @since 5.9.0
  1302  * @since 5.9.0
   900  */
  1303  */
   901 function block_footer_area() {
  1304 function block_footer_area() {
   902 	block_template_part( 'footer' );
  1305 	block_template_part( 'footer' );
   903 }
  1306 }
   904 
  1307 
   905 /**
  1308 /**
   906  * Filters theme directories that should be ignored during export.
  1309  * Determines whether a theme directory should be ignored during export.
   907  *
  1310  *
   908  * @since 6.0.0
  1311  * @since 6.0.0
   909  *
  1312  *
   910  * @param string $path The path of the file in the theme.
  1313  * @param string $path The path of the file in the theme.
   911  * @return Bool Whether this file is in an ignored directory.
  1314  * @return bool Whether this file is in an ignored directory.
   912  */
  1315  */
   913 function wp_is_theme_directory_ignored( $path ) {
  1316 function wp_is_theme_directory_ignored( $path ) {
   914 	$directories_to_ignore = array( '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' );
  1317 	$directories_to_ignore = array( '.DS_Store', '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' );
       
  1318 
   915 	foreach ( $directories_to_ignore as $directory ) {
  1319 	foreach ( $directories_to_ignore as $directory ) {
   916 		if ( strpos( $path, $directory ) === 0 ) {
  1320 		if ( str_starts_with( $path, $directory ) ) {
   917 			return true;
  1321 			return true;
   918 		}
  1322 		}
   919 	}
  1323 	}
   920 
  1324 
   921 	return false;
  1325 	return false;
   927  * specified path in a ZIP file.
  1331  * specified path in a ZIP file.
   928  *
  1332  *
   929  * @since 5.9.0
  1333  * @since 5.9.0
   930  * @since 6.0.0 Adds the whole theme to the export archive.
  1334  * @since 6.0.0 Adds the whole theme to the export archive.
   931  *
  1335  *
       
  1336  * @global string $wp_version The WordPress version string.
       
  1337  *
   932  * @return WP_Error|string Path of the ZIP file or error on failure.
  1338  * @return WP_Error|string Path of the ZIP file or error on failure.
   933  */
  1339  */
   934 function wp_generate_block_templates_export_file() {
  1340 function wp_generate_block_templates_export_file() {
       
  1341 	global $wp_version;
       
  1342 
   935 	if ( ! class_exists( 'ZipArchive' ) ) {
  1343 	if ( ! class_exists( 'ZipArchive' ) ) {
   936 		return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) );
  1344 		return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) );
   937 	}
  1345 	}
   938 
  1346 
   939 	$obscura    = wp_generate_password( 12, false, false );
  1347 	$obscura    = wp_generate_password( 12, false, false );
   972 	}
  1380 	}
   973 
  1381 
   974 	// Load templates into the zip file.
  1382 	// Load templates into the zip file.
   975 	$templates = get_block_templates();
  1383 	$templates = get_block_templates();
   976 	foreach ( $templates as $template ) {
  1384 	foreach ( $templates as $template ) {
   977 		$template->content = _remove_theme_attribute_in_block_template_content( $template->content );
  1385 		$template->content = traverse_and_serialize_blocks(
       
  1386 			parse_blocks( $template->content ),
       
  1387 			'_remove_theme_attribute_from_template_part_block'
       
  1388 		);
   978 
  1389 
   979 		$zip->addFromString(
  1390 		$zip->addFromString(
   980 			'templates/' . $template->slug . '.html',
  1391 			'templates/' . $template->slug . '.html',
   981 			$template->content
  1392 			$template->content
   982 		);
  1393 		);
   997 	$tree->merge( WP_Theme_JSON_Resolver::get_user_data() );
  1408 	$tree->merge( WP_Theme_JSON_Resolver::get_user_data() );
   998 
  1409 
   999 	$theme_json_raw = $tree->get_data();
  1410 	$theme_json_raw = $tree->get_data();
  1000 	// If a version is defined, add a schema.
  1411 	// If a version is defined, add a schema.
  1001 	if ( $theme_json_raw['version'] ) {
  1412 	if ( $theme_json_raw['version'] ) {
  1002 		global $wp_version;
       
  1003 		$theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
  1413 		$theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
  1004 		$schema             = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' );
  1414 		$schema             = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' );
  1005 		$theme_json_raw     = array_merge( $schema, $theme_json_raw );
  1415 		$theme_json_raw     = array_merge( $schema, $theme_json_raw );
  1006 	}
  1416 	}
  1007 
  1417 
  1020 	// Save changes to the zip file.
  1430 	// Save changes to the zip file.
  1021 	$zip->close();
  1431 	$zip->close();
  1022 
  1432 
  1023 	return $filename;
  1433 	return $filename;
  1024 }
  1434 }
       
  1435 
       
  1436 /**
       
  1437  * Gets the template hierarchy for the given template slug to be created.
       
  1438  *
       
  1439  * Note: Always add `index` as the last fallback template.
       
  1440  *
       
  1441  * @since 6.1.0
       
  1442  *
       
  1443  * @param string $slug            The template slug to be created.
       
  1444  * @param bool   $is_custom       Optional. Indicates if a template is custom or
       
  1445  *                                part of the template hierarchy. Default false.
       
  1446  * @param string $template_prefix Optional. The template prefix for the created template.
       
  1447  *                                Used to extract the main template type, e.g.
       
  1448  *                                in `taxonomy-books` the `taxonomy` is extracted.
       
  1449  *                                Default empty string.
       
  1450  * @return string[] The template hierarchy.
       
  1451  */
       
  1452 function get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) {
       
  1453 	if ( 'index' === $slug ) {
       
  1454 		/** This filter is documented in wp-includes/template.php */
       
  1455 		return apply_filters( 'index_template_hierarchy', array( 'index' ) );
       
  1456 	}
       
  1457 	if ( $is_custom ) {
       
  1458 		/** This filter is documented in wp-includes/template.php */
       
  1459 		return apply_filters( 'page_template_hierarchy', array( 'page', 'singular', 'index' ) );
       
  1460 	}
       
  1461 	if ( 'front-page' === $slug ) {
       
  1462 		/** This filter is documented in wp-includes/template.php */
       
  1463 		return apply_filters( 'frontpage_template_hierarchy', array( 'front-page', 'home', 'index' ) );
       
  1464 	}
       
  1465 
       
  1466 	$matches = array();
       
  1467 
       
  1468 	$template_hierarchy = array( $slug );
       
  1469 	// Most default templates don't have `$template_prefix` assigned.
       
  1470 	if ( ! empty( $template_prefix ) ) {
       
  1471 		list( $type ) = explode( '-', $template_prefix );
       
  1472 		// We need these checks because we always add the `$slug` above.
       
  1473 		if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) {
       
  1474 			$template_hierarchy[] = $template_prefix;
       
  1475 		}
       
  1476 		if ( $slug !== $type ) {
       
  1477 			$template_hierarchy[] = $type;
       
  1478 		}
       
  1479 	} elseif ( preg_match( '/^(author|category|archive|tag|page)-.+$/', $slug, $matches ) ) {
       
  1480 		$template_hierarchy[] = $matches[1];
       
  1481 	} elseif ( preg_match( '/^(taxonomy|single)-(.+)$/', $slug, $matches ) ) {
       
  1482 		$type           = $matches[1];
       
  1483 		$slug_remaining = $matches[2];
       
  1484 
       
  1485 		$items = 'single' === $type ? get_post_types() : get_taxonomies();
       
  1486 		foreach ( $items as $item ) {
       
  1487 			if ( ! str_starts_with( $slug_remaining, $item ) ) {
       
  1488 					continue;
       
  1489 			}
       
  1490 
       
  1491 			// If $slug_remaining is equal to $post_type or $taxonomy we have
       
  1492 			// the single-$post_type template or the taxonomy-$taxonomy template.
       
  1493 			if ( $slug_remaining === $item ) {
       
  1494 				$template_hierarchy[] = $type;
       
  1495 				break;
       
  1496 			}
       
  1497 
       
  1498 			// If $slug_remaining is single-$post_type-$slug template.
       
  1499 			if ( strlen( $slug_remaining ) > strlen( $item ) + 1 ) {
       
  1500 				$template_hierarchy[] = "$type-$item";
       
  1501 				$template_hierarchy[] = $type;
       
  1502 				break;
       
  1503 			}
       
  1504 		}
       
  1505 	}
       
  1506 	// Handle `archive` template.
       
  1507 	if (
       
  1508 		str_starts_with( $slug, 'author' ) ||
       
  1509 		str_starts_with( $slug, 'taxonomy' ) ||
       
  1510 		str_starts_with( $slug, 'category' ) ||
       
  1511 		str_starts_with( $slug, 'tag' ) ||
       
  1512 		'date' === $slug
       
  1513 	) {
       
  1514 		$template_hierarchy[] = 'archive';
       
  1515 	}
       
  1516 	// Handle `single` template.
       
  1517 	if ( 'attachment' === $slug ) {
       
  1518 		$template_hierarchy[] = 'single';
       
  1519 	}
       
  1520 	// Handle `singular` template.
       
  1521 	if (
       
  1522 		str_starts_with( $slug, 'single' ) ||
       
  1523 		str_starts_with( $slug, 'page' ) ||
       
  1524 		'attachment' === $slug
       
  1525 	) {
       
  1526 		$template_hierarchy[] = 'singular';
       
  1527 	}
       
  1528 	$template_hierarchy[] = 'index';
       
  1529 
       
  1530 	$template_type = '';
       
  1531 	if ( ! empty( $template_prefix ) ) {
       
  1532 		list( $template_type ) = explode( '-', $template_prefix );
       
  1533 	} else {
       
  1534 		list( $template_type ) = explode( '-', $slug );
       
  1535 	}
       
  1536 	$valid_template_types = array( '404', 'archive', 'attachment', 'author', 'category', 'date', 'embed', 'frontpage', 'home', 'index', 'page', 'paged', 'privacypolicy', 'search', 'single', 'singular', 'tag', 'taxonomy' );
       
  1537 	if ( in_array( $template_type, $valid_template_types, true ) ) {
       
  1538 		/** This filter is documented in wp-includes/template.php */
       
  1539 		return apply_filters( "{$template_type}_template_hierarchy", $template_hierarchy );
       
  1540 	}
       
  1541 	return $template_hierarchy;
       
  1542 }
       
  1543 
       
  1544 /**
       
  1545  * Inject ignoredHookedBlocks metadata attributes into a template or template part.
       
  1546  *
       
  1547  * Given an object that represents a `wp_template` or `wp_template_part` post object
       
  1548  * prepared for inserting or updating the database, locate all blocks that have
       
  1549  * hooked blocks, and inject a `metadata.ignoredHookedBlocks` attribute into the anchor
       
  1550  * blocks to reflect the latter.
       
  1551  *
       
  1552  * @since 6.5.0
       
  1553  * @access private
       
  1554  *
       
  1555  * @param stdClass        $changes    An object representing a template or template part
       
  1556  *                                    prepared for inserting or updating the database.
       
  1557  * @param WP_REST_Request $deprecated Deprecated. Not used.
       
  1558  * @return stdClass|WP_Error The updated object representing a template or template part.
       
  1559  */
       
  1560 function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated = null ) {
       
  1561 	if ( null !== $deprecated ) {
       
  1562 		_deprecated_argument( __FUNCTION__, '6.5.3' );
       
  1563 	}
       
  1564 
       
  1565 	if ( ! isset( $changes->post_content ) ) {
       
  1566 		return $changes;
       
  1567 	}
       
  1568 
       
  1569 	$hooked_blocks = get_hooked_blocks();
       
  1570 	if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
       
  1571 		return $changes;
       
  1572 	}
       
  1573 
       
  1574 	$meta  = isset( $changes->meta_input ) ? $changes->meta_input : array();
       
  1575 	$terms = isset( $changes->tax_input ) ? $changes->tax_input : array();
       
  1576 
       
  1577 	if ( empty( $changes->ID ) ) {
       
  1578 		// There's no post object for this template in the database for this template yet.
       
  1579 		$post = $changes;
       
  1580 	} else {
       
  1581 		// Find the existing post object.
       
  1582 		$post = get_post( $changes->ID );
       
  1583 
       
  1584 		// If the post is a revision, use the parent post's post_name and post_type.
       
  1585 		$post_id = wp_is_post_revision( $post );
       
  1586 		if ( $post_id ) {
       
  1587 			$parent_post     = get_post( $post_id );
       
  1588 			$post->post_name = $parent_post->post_name;
       
  1589 			$post->post_type = $parent_post->post_type;
       
  1590 		}
       
  1591 
       
  1592 		// Apply the changes to the existing post object.
       
  1593 		$post = (object) array_merge( (array) $post, (array) $changes );
       
  1594 
       
  1595 		$type_terms        = get_the_terms( $changes->ID, 'wp_theme' );
       
  1596 		$terms['wp_theme'] = ! is_wp_error( $type_terms ) && ! empty( $type_terms ) ? $type_terms[0]->name : null;
       
  1597 	}
       
  1598 
       
  1599 	// Required for the WP_Block_Template. Update the post object with the current time.
       
  1600 	$post->post_modified = current_time( 'mysql' );
       
  1601 
       
  1602 	// If the post_author is empty, set it to the current user.
       
  1603 	if ( empty( $post->post_author ) ) {
       
  1604 		$post->post_author = get_current_user_id();
       
  1605 	}
       
  1606 
       
  1607 	if ( 'wp_template_part' === $post->post_type && ! isset( $terms['wp_template_part_area'] ) ) {
       
  1608 		$area_terms                     = get_the_terms( $changes->ID, 'wp_template_part_area' );
       
  1609 		$terms['wp_template_part_area'] = ! is_wp_error( $area_terms ) && ! empty( $area_terms ) ? $area_terms[0]->name : null;
       
  1610 	}
       
  1611 
       
  1612 	$template = _build_block_template_object_from_post_object( new WP_Post( $post ), $terms, $meta );
       
  1613 
       
  1614 	if ( is_wp_error( $template ) ) {
       
  1615 		return $template;
       
  1616 	}
       
  1617 
       
  1618 	$changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );
       
  1619 
       
  1620 	return $changes;
       
  1621 }