wp/wp-includes/block-template-utils.php
changeset 19 3d72ae0968f4
parent 18 be944660c56a
child 21 48c4eec2b7e6
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
     1 <?php
     1 <?php
     2 /**
     2 /**
     3  * Utilities used to fetch and create templates.
     3  * Utilities used to fetch and create templates and template parts.
     4  *
     4  *
     5  * @package WordPress
     5  * @package WordPress
     6  * @since 5.8.0
     6  * @since 5.8.0
     7  */
     7  */
     8 
     8 
       
     9 // Define constants for supported wp_template_part_area taxonomy.
       
    10 if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) {
       
    11 	define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' );
       
    12 }
       
    13 if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) {
       
    14 	define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' );
       
    15 }
       
    16 if ( ! defined( 'WP_TEMPLATE_PART_AREA_SIDEBAR' ) ) {
       
    17 	define( 'WP_TEMPLATE_PART_AREA_SIDEBAR', 'sidebar' );
       
    18 }
       
    19 if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) {
       
    20 	define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' );
       
    21 }
       
    22 
       
    23 /**
       
    24  * For backward compatibility reasons,
       
    25  * block themes might be using block-templates or block-template-parts,
       
    26  * this function ensures we fallback to these folders properly.
       
    27  *
       
    28  * @since 5.9.0
       
    29  *
       
    30  * @param string $theme_stylesheet The stylesheet. Default is to leverage the main theme root.
       
    31  *
       
    32  * @return string[] {
       
    33  *     Folder names used by block themes.
       
    34  *
       
    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.
       
    37  * }
       
    38  */
       
    39 function get_block_theme_folders( $theme_stylesheet = null ) {
       
    40 	$theme_name = null === $theme_stylesheet ? get_stylesheet() : $theme_stylesheet;
       
    41 	$root_dir   = get_theme_root( $theme_name );
       
    42 	$theme_dir  = "$root_dir/$theme_name";
       
    43 
       
    44 	if ( file_exists( $theme_dir . '/block-templates' ) || file_exists( $theme_dir . '/block-template-parts' ) ) {
       
    45 		return array(
       
    46 			'wp_template'      => 'block-templates',
       
    47 			'wp_template_part' => 'block-template-parts',
       
    48 		);
       
    49 	}
       
    50 
       
    51 	return array(
       
    52 		'wp_template'      => 'templates',
       
    53 		'wp_template_part' => 'parts',
       
    54 	);
       
    55 }
       
    56 
       
    57 /**
       
    58  * Returns a filtered list of allowed area values for template parts.
       
    59  *
       
    60  * @since 5.9.0
       
    61  *
       
    62  * @return array The supported template part area values.
       
    63  */
       
    64 function get_allowed_block_template_part_areas() {
       
    65 	$default_area_definitions = array(
       
    66 		array(
       
    67 			'area'        => WP_TEMPLATE_PART_AREA_UNCATEGORIZED,
       
    68 			'label'       => __( 'General' ),
       
    69 			'description' => __(
       
    70 				'General templates often perform a specific role like displaying post content, and are not tied to any particular area.'
       
    71 			),
       
    72 			'icon'        => 'layout',
       
    73 			'area_tag'    => 'div',
       
    74 		),
       
    75 		array(
       
    76 			'area'        => WP_TEMPLATE_PART_AREA_HEADER,
       
    77 			'label'       => __( 'Header' ),
       
    78 			'description' => __(
       
    79 				'The Header template defines a page area that typically contains a title, logo, and main navigation.'
       
    80 			),
       
    81 			'icon'        => 'header',
       
    82 			'area_tag'    => 'header',
       
    83 		),
       
    84 		array(
       
    85 			'area'        => WP_TEMPLATE_PART_AREA_FOOTER,
       
    86 			'label'       => __( 'Footer' ),
       
    87 			'description' => __(
       
    88 				'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.'
       
    89 			),
       
    90 			'icon'        => 'footer',
       
    91 			'area_tag'    => 'footer',
       
    92 		),
       
    93 	);
       
    94 
       
    95 	/**
       
    96 	 * Filters the list of allowed template part area values.
       
    97 	 *
       
    98 	 * @since 5.9.0
       
    99 	 *
       
   100 	 * @param array $default_area_definitions An array of supported area objects.
       
   101 	 */
       
   102 	return apply_filters( 'default_wp_template_part_areas', $default_area_definitions );
       
   103 }
       
   104 
       
   105 
       
   106 /**
       
   107  * Returns a filtered list of default template types, containing their
       
   108  * localized titles and descriptions.
       
   109  *
       
   110  * @since 5.9.0
       
   111  *
       
   112  * @return array The default template types.
       
   113  */
       
   114 function get_default_block_template_types() {
       
   115 	$default_template_types = array(
       
   116 		'index'          => array(
       
   117 			'title'       => _x( 'Index', 'Template name' ),
       
   118 			'description' => __( 'Displays posts.' ),
       
   119 		),
       
   120 		'home'           => array(
       
   121 			'title'       => _x( 'Home', 'Template name' ),
       
   122 			'description' => __( 'Displays posts on the homepage, or on the Posts page if a static homepage is set.' ),
       
   123 		),
       
   124 		'front-page'     => array(
       
   125 			'title'       => _x( 'Front Page', 'Template name' ),
       
   126 			'description' => __( 'Displays the homepage.' ),
       
   127 		),
       
   128 		'singular'       => array(
       
   129 			'title'       => _x( 'Singular', 'Template name' ),
       
   130 			'description' => __( 'Displays a single post or page.' ),
       
   131 		),
       
   132 		'single'         => array(
       
   133 			'title'       => _x( 'Single Post', 'Template name' ),
       
   134 			'description' => __( 'Displays a single post.' ),
       
   135 		),
       
   136 		'page'           => array(
       
   137 			'title'       => _x( 'Page', 'Template name' ),
       
   138 			'description' => __( 'Displays a single page.' ),
       
   139 		),
       
   140 		'archive'        => array(
       
   141 			'title'       => _x( 'Archive', 'Template name' ),
       
   142 			'description' => __( 'Displays post categories, tags, and other archives.' ),
       
   143 		),
       
   144 		'author'         => array(
       
   145 			'title'       => _x( 'Author', 'Template name' ),
       
   146 			'description' => __( 'Displays latest posts written by a single author.' ),
       
   147 		),
       
   148 		'category'       => array(
       
   149 			'title'       => _x( 'Category', 'Template name' ),
       
   150 			'description' => __( 'Displays latest posts in single post category.' ),
       
   151 		),
       
   152 		'taxonomy'       => array(
       
   153 			'title'       => _x( 'Taxonomy', 'Template name' ),
       
   154 			'description' => __( 'Displays latest posts from a single post taxonomy.' ),
       
   155 		),
       
   156 		'date'           => array(
       
   157 			'title'       => _x( 'Date', 'Template name' ),
       
   158 			'description' => __( 'Displays posts from a specific date.' ),
       
   159 		),
       
   160 		'tag'            => array(
       
   161 			'title'       => _x( 'Tag', 'Template name' ),
       
   162 			'description' => __( 'Displays latest posts with a single post tag.' ),
       
   163 		),
       
   164 		'attachment'     => array(
       
   165 			'title'       => __( 'Media' ),
       
   166 			'description' => __( 'Displays individual media items or attachments.' ),
       
   167 		),
       
   168 		'search'         => array(
       
   169 			'title'       => _x( 'Search', 'Template name' ),
       
   170 			'description' => __( 'Displays search results.' ),
       
   171 		),
       
   172 		'privacy-policy' => array(
       
   173 			'title'       => __( 'Privacy Policy' ),
       
   174 			'description' => __( 'Displays the privacy policy page.' ),
       
   175 		),
       
   176 		'404'            => array(
       
   177 			'title'       => _x( '404', 'Template name' ),
       
   178 			'description' => __( 'Displays when no content is found.' ),
       
   179 		),
       
   180 	);
       
   181 
       
   182 	/**
       
   183 	 * Filters the list of template types.
       
   184 	 *
       
   185 	 * @since 5.9.0
       
   186 	 *
       
   187 	 * @param array $default_template_types An array of template types, formatted as [ slug => [ title, description ] ].
       
   188 	 */
       
   189 	return apply_filters( 'default_template_types', $default_template_types );
       
   190 }
       
   191 
       
   192 /**
       
   193  * Checks whether the input 'area' is a supported value.
       
   194  * Returns the input if supported, otherwise returns the 'uncategorized' value.
       
   195  *
       
   196  * @since 5.9.0
       
   197  * @access private
       
   198  *
       
   199  * @param string $type Template part area name.
       
   200  *
       
   201  * @return string Input if supported, else the uncategorized value.
       
   202  */
       
   203 function _filter_block_template_part_area( $type ) {
       
   204 	$allowed_areas = array_map(
       
   205 		static function ( $item ) {
       
   206 			return $item['area'];
       
   207 		},
       
   208 		get_allowed_block_template_part_areas()
       
   209 	);
       
   210 	if ( in_array( $type, $allowed_areas, true ) ) {
       
   211 		return $type;
       
   212 	}
       
   213 
       
   214 	$warning_message = sprintf(
       
   215 		/* 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".' ),
       
   217 		$type,
       
   218 		WP_TEMPLATE_PART_AREA_UNCATEGORIZED
       
   219 	);
       
   220 	trigger_error( $warning_message, E_USER_NOTICE );
       
   221 	return WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
       
   222 }
       
   223 
       
   224 /**
       
   225  * Finds all nested template part file paths in a theme's directory.
       
   226  *
       
   227  * @since 5.9.0
       
   228  * @access private
       
   229  *
       
   230  * @param string $base_directory The theme's file path.
       
   231  * @return array A list of paths to all template part files.
       
   232  */
       
   233 function _get_block_templates_paths( $base_directory ) {
       
   234 	$path_list = array();
       
   235 	if ( file_exists( $base_directory ) ) {
       
   236 		$nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
       
   237 		$nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH );
       
   238 		foreach ( $nested_html_files as $path => $file ) {
       
   239 			$path_list[] = $path;
       
   240 		}
       
   241 	}
       
   242 	return $path_list;
       
   243 }
       
   244 
       
   245 /**
       
   246  * Retrieves the template file from the theme for a given slug.
       
   247  *
       
   248  * @since 5.9.0
       
   249  * @access private
       
   250  *
       
   251  * @param string $template_type 'wp_template' or 'wp_template_part'.
       
   252  * @param string $slug          Template slug.
       
   253  *
       
   254  * @return array|null Template.
       
   255  */
       
   256 function _get_block_template_file( $template_type, $slug ) {
       
   257 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
       
   258 		return null;
       
   259 	}
       
   260 
       
   261 	$themes = array(
       
   262 		get_stylesheet() => get_stylesheet_directory(),
       
   263 		get_template()   => get_template_directory(),
       
   264 	);
       
   265 	foreach ( $themes as $theme_slug => $theme_dir ) {
       
   266 		$template_base_paths = get_block_theme_folders( $theme_slug );
       
   267 		$file_path           = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html';
       
   268 		if ( file_exists( $file_path ) ) {
       
   269 			$new_template_item = array(
       
   270 				'slug'  => $slug,
       
   271 				'path'  => $file_path,
       
   272 				'theme' => $theme_slug,
       
   273 				'type'  => $template_type,
       
   274 			);
       
   275 
       
   276 			if ( 'wp_template_part' === $template_type ) {
       
   277 				return _add_block_template_part_area_info( $new_template_item );
       
   278 			}
       
   279 
       
   280 			if ( 'wp_template' === $template_type ) {
       
   281 				return _add_block_template_info( $new_template_item );
       
   282 			}
       
   283 
       
   284 			return $new_template_item;
       
   285 		}
       
   286 	}
       
   287 
       
   288 	return null;
       
   289 }
       
   290 
       
   291 /**
       
   292  * Retrieves the template files from the theme.
       
   293  *
       
   294  * @since 5.9.0
       
   295  * @access private
       
   296  *
       
   297  * @param string $template_type 'wp_template' or 'wp_template_part'.
       
   298  *
       
   299  * @return array Template.
       
   300  */
       
   301 function _get_block_templates_files( $template_type ) {
       
   302 	if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
       
   303 		return null;
       
   304 	}
       
   305 
       
   306 	$themes         = array(
       
   307 		get_stylesheet() => get_stylesheet_directory(),
       
   308 		get_template()   => get_template_directory(),
       
   309 	);
       
   310 	$template_files = array();
       
   311 	foreach ( $themes as $theme_slug => $theme_dir ) {
       
   312 		$template_base_paths  = get_block_theme_folders( $theme_slug );
       
   313 		$theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
       
   314 		foreach ( $theme_template_files as $template_file ) {
       
   315 			$template_base_path = $template_base_paths[ $template_type ];
       
   316 			$template_slug      = substr(
       
   317 				$template_file,
       
   318 				// Starting position of slug.
       
   319 				strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
       
   320 				// Subtract ending '.html'.
       
   321 				-5
       
   322 			);
       
   323 			$new_template_item = array(
       
   324 				'slug'  => $template_slug,
       
   325 				'path'  => $template_file,
       
   326 				'theme' => $theme_slug,
       
   327 				'type'  => $template_type,
       
   328 			);
       
   329 
       
   330 			if ( 'wp_template_part' === $template_type ) {
       
   331 				$template_files[] = _add_block_template_part_area_info( $new_template_item );
       
   332 			}
       
   333 
       
   334 			if ( 'wp_template' === $template_type ) {
       
   335 				$template_files[] = _add_block_template_info( $new_template_item );
       
   336 			}
       
   337 		}
       
   338 	}
       
   339 
       
   340 	return $template_files;
       
   341 }
       
   342 
       
   343 /**
       
   344  * Attempts to add custom template information to the template item.
       
   345  *
       
   346  * @since 5.9.0
       
   347  * @access private
       
   348  *
       
   349  * @param array $template_item Template to add information to (requires 'slug' field).
       
   350  * @return array Template item.
       
   351  */
       
   352 function _add_block_template_info( $template_item ) {
       
   353 	if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
       
   354 		return $template_item;
       
   355 	}
       
   356 
       
   357 	$theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates();
       
   358 	if ( isset( $theme_data[ $template_item['slug'] ] ) ) {
       
   359 		$template_item['title']     = $theme_data[ $template_item['slug'] ]['title'];
       
   360 		$template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes'];
       
   361 	}
       
   362 
       
   363 	return $template_item;
       
   364 }
       
   365 
       
   366 /**
       
   367  * Attempts to add the template part's area information to the input template.
       
   368  *
       
   369  * @since 5.9.0
       
   370  * @access private
       
   371  *
       
   372  * @param array $template_info Template to add information to (requires 'type' and 'slug' fields).
       
   373  *
       
   374  * @return array Template info.
       
   375  */
       
   376 function _add_block_template_part_area_info( $template_info ) {
       
   377 	if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
       
   378 		$theme_data = WP_Theme_JSON_Resolver::get_theme_data()->get_template_parts();
       
   379 	}
       
   380 
       
   381 	if ( isset( $theme_data[ $template_info['slug'] ]['area'] ) ) {
       
   382 		$template_info['title'] = $theme_data[ $template_info['slug'] ]['title'];
       
   383 		$template_info['area']  = _filter_block_template_part_area( $theme_data[ $template_info['slug'] ]['area'] );
       
   384 	} else {
       
   385 		$template_info['area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
       
   386 	}
       
   387 
       
   388 	return $template_info;
       
   389 }
       
   390 
       
   391 /**
       
   392  * Returns an array containing the references of
       
   393  * the passed blocks and their inner blocks.
       
   394  *
       
   395  * @since 5.9.0
       
   396  * @access private
       
   397  *
       
   398  * @param array $blocks array of blocks.
       
   399  *
       
   400  * @return array block references to the passed blocks and their inner blocks.
       
   401  */
       
   402 function _flatten_blocks( &$blocks ) {
       
   403 	$all_blocks = array();
       
   404 	$queue      = array();
       
   405 	foreach ( $blocks as &$block ) {
       
   406 		$queue[] = &$block;
       
   407 	}
       
   408 
       
   409 	while ( count( $queue ) > 0 ) {
       
   410 		$block = &$queue[0];
       
   411 		array_shift( $queue );
       
   412 		$all_blocks[] = &$block;
       
   413 
       
   414 		if ( ! empty( $block['innerBlocks'] ) ) {
       
   415 			foreach ( $block['innerBlocks'] as &$inner_block ) {
       
   416 				$queue[] = &$inner_block;
       
   417 			}
       
   418 		}
       
   419 	}
       
   420 
       
   421 	return $all_blocks;
       
   422 }
       
   423 
       
   424 /**
       
   425  * Parses wp_template content and injects the active theme's
       
   426  * stylesheet as a theme attribute into each wp_template_part
       
   427  *
       
   428  * @since 5.9.0
       
   429  * @access private
       
   430  *
       
   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.
       
   502  * @param string $template_type 'wp_template' or 'wp_template_part'.
       
   503  *
       
   504  * @return WP_Block_Template Template.
       
   505  */
       
   506 function _build_block_template_result_from_file( $template_file, $template_type ) {
       
   507 	$default_template_types = get_default_block_template_types();
       
   508 	$template_content       = file_get_contents( $template_file['path'] );
       
   509 	$theme                  = wp_get_theme()->get_stylesheet();
       
   510 
       
   511 	$template                 = new WP_Block_Template();
       
   512 	$template->id             = $theme . '//' . $template_file['slug'];
       
   513 	$template->theme          = $theme;
       
   514 	$template->content        = _inject_theme_attribute_in_block_template_content( $template_content );
       
   515 	$template->slug           = $template_file['slug'];
       
   516 	$template->source         = 'theme';
       
   517 	$template->type           = $template_type;
       
   518 	$template->title          = ! empty( $template_file['title'] ) ? $template_file['title'] : $template_file['slug'];
       
   519 	$template->status         = 'publish';
       
   520 	$template->has_theme_file = true;
       
   521 	$template->is_custom      = true;
       
   522 
       
   523 	if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) {
       
   524 		$template->description = $default_template_types[ $template_file['slug'] ]['description'];
       
   525 		$template->title       = $default_template_types[ $template_file['slug'] ]['title'];
       
   526 		$template->is_custom   = false;
       
   527 	}
       
   528 
       
   529 	if ( 'wp_template' === $template_type && isset( $template_file['postTypes'] ) ) {
       
   530 		$template->post_types = $template_file['postTypes'];
       
   531 	}
       
   532 
       
   533 	if ( 'wp_template_part' === $template_type && isset( $template_file['area'] ) ) {
       
   534 		$template->area = $template_file['area'];
       
   535 	}
       
   536 
       
   537 	return $template;
       
   538 }
       
   539 
     9 /**
   540 /**
    10  * Build a unified template object based a post Object.
   541  * Build a unified template object based a post Object.
    11  *
   542  *
       
   543  * @since 5.9.0
    12  * @access private
   544  * @access private
    13  * @since 5.8.0
       
    14  *
   545  *
    15  * @param WP_Post $post Template post.
   546  * @param WP_Post $post Template post.
    16  *
   547  *
    17  * @return WP_Block_Template|WP_Error Template.
   548  * @return WP_Block_Template|WP_Error Template.
    18  */
   549  */
    19 function _build_template_result_from_post( $post ) {
   550 function _build_block_template_result_from_post( $post ) {
    20 	$terms = get_the_terms( $post, 'wp_theme' );
   551 	$default_template_types = get_default_block_template_types();
       
   552 	$terms                  = get_the_terms( $post, 'wp_theme' );
    21 
   553 
    22 	if ( is_wp_error( $terms ) ) {
   554 	if ( is_wp_error( $terms ) ) {
    23 		return $terms;
   555 		return $terms;
    24 	}
   556 	}
    25 
   557 
    26 	if ( ! $terms ) {
   558 	if ( ! $terms ) {
    27 		return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
   559 		return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
    28 	}
   560 	}
    29 
   561 
    30 	$theme = $terms[0]->name;
   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 );
    31 
   567 
    32 	$template                 = new WP_Block_Template();
   568 	$template                 = new WP_Block_Template();
    33 	$template->wp_id          = $post->ID;
   569 	$template->wp_id          = $post->ID;
    34 	$template->id             = $theme . '//' . $post->post_name;
   570 	$template->id             = $theme . '//' . $post->post_name;
    35 	$template->theme          = $theme;
   571 	$template->theme          = $theme;
    36 	$template->content        = $post->post_content;
   572 	$template->content        = $post->post_content;
    37 	$template->slug           = $post->post_name;
   573 	$template->slug           = $post->post_name;
    38 	$template->source         = 'custom';
   574 	$template->source         = 'custom';
       
   575 	$template->origin         = ! empty( $origin ) ? $origin : null;
    39 	$template->type           = $post->post_type;
   576 	$template->type           = $post->post_type;
    40 	$template->description    = $post->post_excerpt;
   577 	$template->description    = $post->post_excerpt;
    41 	$template->title          = $post->post_title;
   578 	$template->title          = $post->post_title;
    42 	$template->status         = $post->post_status;
   579 	$template->status         = $post->post_status;
    43 	$template->has_theme_file = false;
   580 	$template->has_theme_file = $has_theme_file;
       
   581 	$template->is_custom      = true;
       
   582 	$template->author         = $post->post_author;
       
   583 
       
   584 	if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
       
   585 		$template->is_custom = false;
       
   586 	}
       
   587 
       
   588 	if ( 'wp_template_part' === $post->post_type ) {
       
   589 		$type_terms = get_the_terms( $post, 'wp_template_part_area' );
       
   590 		if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
       
   591 			$template->area = $type_terms[0]->name;
       
   592 		}
       
   593 	}
    44 
   594 
    45 	return $template;
   595 	return $template;
    46 }
   596 }
    47 
   597 
    48 /**
   598 /**
    49  * Retrieves a list of unified template objects based on a query.
   599  * Retrieves a list of unified template objects based on a query.
    50  *
   600  *
    51  * @since 5.8.0
   601  * @since 5.8.0
    52  *
   602  *
    53  * @param array $query {
   603  * @param array  $query {
    54  *     Optional. Arguments to retrieve templates.
   604  *     Optional. Arguments to retrieve templates.
    55  *
   605  *
    56  *     @type array  $slug__in List of slugs to include.
   606  *     @type array  $slug__in  List of slugs to include.
    57  *     @type int    $wp_id Post ID of customized template.
   607  *     @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).
       
   609  *     @type string $post_type Post type to get the templates for.
    58  * }
   610  * }
    59  * @param string $template_type Optional. The template type (post type). Default 'wp_template'.
   611  * @param string $template_type 'wp_template' or 'wp_template_part'.
    60  * @return WP_Block_Template[] Block template objects.
   612  *
       
   613  * @return array Templates.
    61  */
   614  */
    62 function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
   615 function get_block_templates( $query = array(), $template_type = 'wp_template' ) {
       
   616 	/**
       
   617 	 * Filters the block templates array before the query takes place.
       
   618 	 *
       
   619 	 * Return a non-null value to bypass the WordPress queries.
       
   620 	 *
       
   621 	 * @since 5.9.0
       
   622 	 *
       
   623 	 * @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.
       
   625 	 * @param array  $query {
       
   626 	 *     Optional. Arguments to retrieve templates.
       
   627 	 *
       
   628 	 *     @type array  $slug__in List of slugs to include.
       
   629 	 *     @type int    $wp_id Post ID of customized template.
       
   630 	 *     @type string $post_type Post type to get the templates for.
       
   631 	 * }
       
   632 	 * @param string $template_type wp_template or wp_template_part.
       
   633 	 */
       
   634 	$templates = apply_filters( 'pre_get_block_templates', null, $query, $template_type );
       
   635 	if ( ! is_null( $templates ) ) {
       
   636 		return $templates;
       
   637 	}
       
   638 
       
   639 	$post_type     = isset( $query['post_type'] ) ? $query['post_type'] : '';
    63 	$wp_query_args = array(
   640 	$wp_query_args = array(
    64 		'post_status'    => array( 'auto-draft', 'draft', 'publish' ),
   641 		'post_status'    => array( 'auto-draft', 'draft', 'publish' ),
    65 		'post_type'      => $template_type,
   642 		'post_type'      => $template_type,
    66 		'posts_per_page' => -1,
   643 		'posts_per_page' => -1,
    67 		'no_found_rows'  => true,
   644 		'no_found_rows'  => true,
    72 				'terms'    => wp_get_theme()->get_stylesheet(),
   649 				'terms'    => wp_get_theme()->get_stylesheet(),
    73 			),
   650 			),
    74 		),
   651 		),
    75 	);
   652 	);
    76 
   653 
       
   654 	if ( 'wp_template_part' === $template_type && isset( $query['area'] ) ) {
       
   655 		$wp_query_args['tax_query'][]           = array(
       
   656 			'taxonomy' => 'wp_template_part_area',
       
   657 			'field'    => 'name',
       
   658 			'terms'    => $query['area'],
       
   659 		);
       
   660 		$wp_query_args['tax_query']['relation'] = 'AND';
       
   661 	}
       
   662 
    77 	if ( isset( $query['slug__in'] ) ) {
   663 	if ( isset( $query['slug__in'] ) ) {
    78 		$wp_query_args['post_name__in'] = $query['slug__in'];
   664 		$wp_query_args['post_name__in'] = $query['slug__in'];
    79 	}
   665 	}
    80 
   666 
    81 	// This is only needed for the regular templates CPT listing and editor.
   667 	// This is only needed for the regular templates/template parts post type listing and editor.
    82 	if ( isset( $query['wp_id'] ) ) {
   668 	if ( isset( $query['wp_id'] ) ) {
    83 		$wp_query_args['p'] = $query['wp_id'];
   669 		$wp_query_args['p'] = $query['wp_id'];
    84 	} else {
   670 	} else {
    85 		$wp_query_args['post_status'] = 'publish';
   671 		$wp_query_args['post_status'] = 'publish';
    86 	}
   672 	}
    87 
   673 
    88 	$template_query = new WP_Query( $wp_query_args );
   674 	$template_query = new WP_Query( $wp_query_args );
    89 	$query_result   = array();
   675 	$query_result   = array();
    90 	foreach ( $template_query->posts as $post ) {
   676 	foreach ( $template_query->posts as $post ) {
    91 		$template = _build_template_result_from_post( $post );
   677 		$template = _build_block_template_result_from_post( $post );
    92 
   678 
    93 		if ( ! is_wp_error( $template ) ) {
   679 		if ( is_wp_error( $template ) ) {
    94 			$query_result[] = $template;
   680 			continue;
    95 		}
   681 		}
    96 	}
   682 
    97 
   683 		if ( $post_type && ! $template->is_custom ) {
    98 	return $query_result;
   684 			continue;
       
   685 		}
       
   686 
       
   687 		$query_result[] = $template;
       
   688 	}
       
   689 
       
   690 	if ( ! isset( $query['wp_id'] ) ) {
       
   691 		$template_files = _get_block_templates_files( $template_type );
       
   692 		foreach ( $template_files as $template_file ) {
       
   693 			$template = _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 		}
       
   720 	}
       
   721 
       
   722 	/**
       
   723 	 * Filters the array of queried block templates array after they've been fetched.
       
   724 	 *
       
   725 	 * @since 5.9.0
       
   726 	 *
       
   727 	 * @param WP_Block_Template[] $query_result Array of found block templates.
       
   728 	 * @param array  $query {
       
   729 	 *     Optional. Arguments to retrieve templates.
       
   730 	 *
       
   731 	 *     @type array  $slug__in List of slugs to include.
       
   732 	 *     @type int    $wp_id Post ID of customized template.
       
   733 	 * }
       
   734 	 * @param string $template_type wp_template or wp_template_part.
       
   735 	 */
       
   736 	return apply_filters( 'get_block_templates', $query_result, $query, $template_type );
    99 }
   737 }
   100 
   738 
   101 /**
   739 /**
   102  * Retrieves a single unified template object using its id.
   740  * Retrieves a single unified template object using its id.
   103  *
   741  *
   104  * @since 5.8.0
   742  * @since 5.8.0
   105  *
   743  *
   106  * @param string $id Template unique identifier (example: theme_slug//template_slug).
   744  * @param string $id            Template unique identifier (example: theme_slug//template_slug).
   107  * @param string $template_type wp_template.
   745  * @param string $template_type Optional. Template type: `'wp_template'` or '`wp_template_part'`.
       
   746  *                              Default `'wp_template'`.
   108  *
   747  *
   109  * @return WP_Block_Template|null Template.
   748  * @return WP_Block_Template|null Template.
   110  */
   749  */
   111 function get_block_template( $id, $template_type = 'wp_template' ) {
   750 function get_block_template( $id, $template_type = 'wp_template' ) {
       
   751 	/**
       
   752 	 *Filters the block template object before the query takes place.
       
   753 	 *
       
   754 	 * Return a non-null value to bypass the WordPress queries.
       
   755 	 *
       
   756 	 * @since 5.9.0
       
   757 	 *
       
   758 	 * @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.
       
   760 	 * @param string $id                             Template unique identifier (example: theme_slug//template_slug).
       
   761 	 * @param string $template_type                  Template type: `'wp_template'` or '`wp_template_part'`.
       
   762 	 */
       
   763 	$block_template = apply_filters( 'pre_get_block_template', null, $id, $template_type );
       
   764 	if ( ! is_null( $block_template ) ) {
       
   765 		return $block_template;
       
   766 	}
       
   767 
   112 	$parts = explode( '//', $id, 2 );
   768 	$parts = explode( '//', $id, 2 );
   113 	if ( count( $parts ) < 2 ) {
   769 	if ( count( $parts ) < 2 ) {
   114 		return null;
   770 		return null;
   115 	}
   771 	}
   116 	list( $theme, $slug ) = $parts;
   772 	list( $theme, $slug ) = $parts;
   130 	);
   786 	);
   131 	$template_query       = new WP_Query( $wp_query_args );
   787 	$template_query       = new WP_Query( $wp_query_args );
   132 	$posts                = $template_query->posts;
   788 	$posts                = $template_query->posts;
   133 
   789 
   134 	if ( count( $posts ) > 0 ) {
   790 	if ( count( $posts ) > 0 ) {
   135 		$template = _build_template_result_from_post( $posts[0] );
   791 		$template = _build_block_template_result_from_post( $posts[0] );
   136 
   792 
   137 		if ( ! is_wp_error( $template ) ) {
   793 		if ( ! is_wp_error( $template ) ) {
   138 			return $template;
   794 			return $template;
   139 		}
   795 		}
   140 	}
   796 	}
   141 
   797 
   142 	return null;
   798 	$block_template = get_block_file_template( $id, $template_type );
   143 }
   799 
       
   800 	/**
       
   801 	 * Filters the queried block template object after it's been fetched.
       
   802 	 *
       
   803 	 * @since 5.9.0
       
   804 	 *
       
   805 	 * @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).
       
   807 	 * @param array                  $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
       
   808 	 */
       
   809 	return apply_filters( 'get_block_template', $block_template, $id, $template_type );
       
   810 }
       
   811 
       
   812 /**
       
   813  * Retrieves a single unified template object using its id.
       
   814  *
       
   815  * @since 5.9.0
       
   816  *
       
   817  * @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'`.
       
   819  *                              Default `'wp_template'`.
       
   820  * @return WP_Block_Template|null The found block template, or null if there isn't one.
       
   821  */
       
   822 function get_block_file_template( $id, $template_type = 'wp_template' ) {
       
   823 	/**
       
   824 	 * Filters the block templates array before the query takes place.
       
   825 	 *
       
   826 	 * Return a non-null value to bypass the WordPress queries.
       
   827 	 *
       
   828 	 * @since 5.9.0
       
   829 	 *
       
   830 	 * @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.
       
   832 	 * @param string                 $id             Template unique identifier (example: theme_slug//template_slug).
       
   833 	 * @param string                 $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
       
   834 	 */
       
   835 	$block_template = apply_filters( 'pre_get_block_file_template', null, $id, $template_type );
       
   836 	if ( ! is_null( $block_template ) ) {
       
   837 		return $block_template;
       
   838 	}
       
   839 
       
   840 	$parts = explode( '//', $id, 2 );
       
   841 	if ( count( $parts ) < 2 ) {
       
   842 		/** This filter is documented in wp-includes/block-template-utils.php */
       
   843 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
       
   844 	}
       
   845 	list( $theme, $slug ) = $parts;
       
   846 
       
   847 	if ( wp_get_theme()->get_stylesheet() !== $theme ) {
       
   848 		/** This filter is documented in wp-includes/block-template-utils.php */
       
   849 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
       
   850 	}
       
   851 
       
   852 	$template_file = _get_block_template_file( $template_type, $slug );
       
   853 	if ( null === $template_file ) {
       
   854 		/** This filter is documented in wp-includes/block-template-utils.php */
       
   855 		return apply_filters( 'get_block_file_template', null, $id, $template_type );
       
   856 	}
       
   857 
       
   858 	$block_template = _build_block_template_result_from_file( $template_file, $template_type );
       
   859 
       
   860 	/**
       
   861 	 * Filters the array of queried block templates array after they've been fetched.
       
   862 	 *
       
   863 	 * @since 5.9.0
       
   864 	 *
       
   865 	 * @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).
       
   867 	 * @param string                 $template_type  Template type: `'wp_template'` or '`wp_template_part'`.
       
   868 	 */
       
   869 	return apply_filters( 'get_block_file_template', $block_template, $id, $template_type );
       
   870 }
       
   871 
       
   872 /**
       
   873  * Print a template-part.
       
   874  *
       
   875  * @since 5.9.0
       
   876  *
       
   877  * @param string $part The template-part to print. Use "header" or "footer".
       
   878  */
       
   879 function block_template_part( $part ) {
       
   880 	$template_part = get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
       
   881 	if ( ! $template_part || empty( $template_part->content ) ) {
       
   882 		return;
       
   883 	}
       
   884 	echo do_blocks( $template_part->content );
       
   885 }
       
   886 
       
   887 /**
       
   888  * Print the header template-part.
       
   889  *
       
   890  * @since 5.9.0
       
   891  */
       
   892 function block_header_area() {
       
   893 	block_template_part( 'header' );
       
   894 }
       
   895 
       
   896 /**
       
   897  * Print the footer template-part.
       
   898  *
       
   899  * @since 5.9.0
       
   900  */
       
   901 function block_footer_area() {
       
   902 	block_template_part( 'footer' );
       
   903 }
       
   904 
       
   905 /**
       
   906  * Filters theme directories that should be ignored during export.
       
   907  *
       
   908  * @since 6.0.0
       
   909  *
       
   910  * @param string $path The path of the file in the theme.
       
   911  * @return Bool Whether this file is in an ignored directory.
       
   912  */
       
   913 function wp_is_theme_directory_ignored( $path ) {
       
   914 	$directories_to_ignore = array( '.svn', '.git', '.hg', '.bzr', 'node_modules', 'vendor' );
       
   915 	foreach ( $directories_to_ignore as $directory ) {
       
   916 		if ( strpos( $path, $directory ) === 0 ) {
       
   917 			return true;
       
   918 		}
       
   919 	}
       
   920 
       
   921 	return false;
       
   922 }
       
   923 
       
   924 /**
       
   925  * Creates an export of the current templates and
       
   926  * template parts from the site editor at the
       
   927  * specified path in a ZIP file.
       
   928  *
       
   929  * @since 5.9.0
       
   930  * @since 6.0.0 Adds the whole theme to the export archive.
       
   931  *
       
   932  * @return WP_Error|string Path of the ZIP file or error on failure.
       
   933  */
       
   934 function wp_generate_block_templates_export_file() {
       
   935 	if ( ! class_exists( 'ZipArchive' ) ) {
       
   936 		return new WP_Error( 'missing_zip_package', __( 'Zip Export not supported.' ) );
       
   937 	}
       
   938 
       
   939 	$obscura    = wp_generate_password( 12, false, false );
       
   940 	$theme_name = basename( get_stylesheet() );
       
   941 	$filename   = get_temp_dir() . $theme_name . $obscura . '.zip';
       
   942 
       
   943 	$zip = new ZipArchive();
       
   944 	if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) {
       
   945 		return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.' ) );
       
   946 	}
       
   947 
       
   948 	$zip->addEmptyDir( 'templates' );
       
   949 	$zip->addEmptyDir( 'parts' );
       
   950 
       
   951 	// Get path of the theme.
       
   952 	$theme_path = wp_normalize_path( get_stylesheet_directory() );
       
   953 
       
   954 	// Create recursive directory iterator.
       
   955 	$theme_files = new RecursiveIteratorIterator(
       
   956 		new RecursiveDirectoryIterator( $theme_path ),
       
   957 		RecursiveIteratorIterator::LEAVES_ONLY
       
   958 	);
       
   959 
       
   960 	// Make a copy of the current theme.
       
   961 	foreach ( $theme_files as $file ) {
       
   962 		// Skip directories as they are added automatically.
       
   963 		if ( ! $file->isDir() ) {
       
   964 			// Get real and relative path for current file.
       
   965 			$file_path     = wp_normalize_path( $file );
       
   966 			$relative_path = substr( $file_path, strlen( $theme_path ) + 1 );
       
   967 
       
   968 			if ( ! wp_is_theme_directory_ignored( $relative_path ) ) {
       
   969 				$zip->addFile( $file_path, $relative_path );
       
   970 			}
       
   971 		}
       
   972 	}
       
   973 
       
   974 	// Load templates into the zip file.
       
   975 	$templates = get_block_templates();
       
   976 	foreach ( $templates as $template ) {
       
   977 		$template->content = _remove_theme_attribute_in_block_template_content( $template->content );
       
   978 
       
   979 		$zip->addFromString(
       
   980 			'templates/' . $template->slug . '.html',
       
   981 			$template->content
       
   982 		);
       
   983 	}
       
   984 
       
   985 	// Load template parts into the zip file.
       
   986 	$template_parts = get_block_templates( array(), 'wp_template_part' );
       
   987 	foreach ( $template_parts as $template_part ) {
       
   988 		$zip->addFromString(
       
   989 			'parts/' . $template_part->slug . '.html',
       
   990 			$template_part->content
       
   991 		);
       
   992 	}
       
   993 
       
   994 	// Load theme.json into the zip file.
       
   995 	$tree = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) );
       
   996 	// Merge with user data.
       
   997 	$tree->merge( WP_Theme_JSON_Resolver::get_user_data() );
       
   998 
       
   999 	$theme_json_raw = $tree->get_data();
       
  1000 	// If a version is defined, add a schema.
       
  1001 	if ( $theme_json_raw['version'] ) {
       
  1002 		global $wp_version;
       
  1003 		$theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
       
  1004 		$schema             = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' );
       
  1005 		$theme_json_raw     = array_merge( $schema, $theme_json_raw );
       
  1006 	}
       
  1007 
       
  1008 	// Convert to a string.
       
  1009 	$theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
       
  1010 
       
  1011 	// Replace 4 spaces with a tab.
       
  1012 	$theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded );
       
  1013 
       
  1014 	// Add the theme.json file to the zip.
       
  1015 	$zip->addFromString(
       
  1016 		'theme.json',
       
  1017 		$theme_json_tabbed
       
  1018 	);
       
  1019 
       
  1020 	// Save changes to the zip file.
       
  1021 	$zip->close();
       
  1022 
       
  1023 	return $filename;
       
  1024 }