[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> block-template-utils.php (source)

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


Generated : Fri Apr 26 08:20:02 2024 Cross-referenced by PHPXref