[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Template loading functions.
   4   *
   5   * @package WordPress
   6   * @subpackage Template
   7   */
   8  
   9  /**
  10   * Retrieves path to a template.
  11   *
  12   * Used to quickly retrieve the path of a template without including the file
  13   * extension. It will also check the parent theme, if the file exists, with
  14   * the use of locate_template(). Allows for more generic template location
  15   * without the use of the other get_*_template() functions.
  16   *
  17   * @since 1.5.0
  18   *
  19   * @param string   $type      Filename without extension.
  20   * @param string[] $templates An optional list of template candidates.
  21   * @return string Full path to template file.
  22   */
  23  function get_query_template( $type, $templates = array() ) {
  24      $type = preg_replace( '|[^a-z0-9-]+|', '', $type );
  25  
  26      if ( empty( $templates ) ) {
  27          $templates = array( "{$type}.php" );
  28      }
  29  
  30      /**
  31       * Filters the list of template filenames that are searched for when retrieving a template to use.
  32       *
  33       * The dynamic portion of the hook name, `$type`, refers to the filename -- minus the file
  34       * extension and any non-alphanumeric characters delimiting words -- of the file to load.
  35       * The last element in the array should always be the fallback template for this query type.
  36       *
  37       * Possible hook names include:
  38       *
  39       *  - `404_template_hierarchy`
  40       *  - `archive_template_hierarchy`
  41       *  - `attachment_template_hierarchy`
  42       *  - `author_template_hierarchy`
  43       *  - `category_template_hierarchy`
  44       *  - `date_template_hierarchy`
  45       *  - `embed_template_hierarchy`
  46       *  - `frontpage_template_hierarchy`
  47       *  - `home_template_hierarchy`
  48       *  - `index_template_hierarchy`
  49       *  - `page_template_hierarchy`
  50       *  - `paged_template_hierarchy`
  51       *  - `privacypolicy_template_hierarchy`
  52       *  - `search_template_hierarchy`
  53       *  - `single_template_hierarchy`
  54       *  - `singular_template_hierarchy`
  55       *  - `tag_template_hierarchy`
  56       *  - `taxonomy_template_hierarchy`
  57       *
  58       * @since 4.7.0
  59       *
  60       * @param string[] $templates A list of template candidates, in descending order of priority.
  61       */
  62      $templates = apply_filters( "{$type}_template_hierarchy", $templates );
  63  
  64      $template = locate_template( $templates );
  65  
  66      $template = locate_block_template( $template, $type, $templates );
  67  
  68      /**
  69       * Filters the path of the queried template by type.
  70       *
  71       * The dynamic portion of the hook name, `$type`, refers to the filename -- minus the file
  72       * extension and any non-alphanumeric characters delimiting words -- of the file to load.
  73       * This hook also applies to various types of files loaded as part of the Template Hierarchy.
  74       *
  75       * Possible hook names include:
  76       *
  77       *  - `404_template`
  78       *  - `archive_template`
  79       *  - `attachment_template`
  80       *  - `author_template`
  81       *  - `category_template`
  82       *  - `date_template`
  83       *  - `embed_template`
  84       *  - `frontpage_template`
  85       *  - `home_template`
  86       *  - `index_template`
  87       *  - `page_template`
  88       *  - `paged_template`
  89       *  - `privacypolicy_template`
  90       *  - `search_template`
  91       *  - `single_template`
  92       *  - `singular_template`
  93       *  - `tag_template`
  94       *  - `taxonomy_template`
  95       *
  96       * @since 1.5.0
  97       * @since 4.8.0 The `$type` and `$templates` parameters were added.
  98       *
  99       * @param string   $template  Path to the template. See locate_template().
 100       * @param string   $type      Sanitized filename without extension.
 101       * @param string[] $templates A list of template candidates, in descending order of priority.
 102       */
 103      return apply_filters( "{$type}_template", $template, $type, $templates );
 104  }
 105  
 106  /**
 107   * Retrieves path of index template in current or parent template.
 108   *
 109   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 110   * and {@see '$type_template'} dynamic hooks, where `$type` is 'index'.
 111   *
 112   * @since 3.0.0
 113   *
 114   * @see get_query_template()
 115   *
 116   * @return string Full path to index template file.
 117   */
 118  function get_index_template() {
 119      return get_query_template( 'index' );
 120  }
 121  
 122  /**
 123   * Retrieves path of 404 template in current or parent template.
 124   *
 125   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 126   * and {@see '$type_template'} dynamic hooks, where `$type` is '404'.
 127   *
 128   * @since 1.5.0
 129   *
 130   * @see get_query_template()
 131   *
 132   * @return string Full path to 404 template file.
 133   */
 134  function get_404_template() {
 135      return get_query_template( '404' );
 136  }
 137  
 138  /**
 139   * Retrieves path of archive template in current or parent template.
 140   *
 141   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 142   * and {@see '$type_template'} dynamic hooks, where `$type` is 'archive'.
 143   *
 144   * @since 1.5.0
 145   *
 146   * @see get_query_template()
 147   *
 148   * @return string Full path to archive template file.
 149   */
 150  function get_archive_template() {
 151      $post_types = array_filter( (array) get_query_var( 'post_type' ) );
 152  
 153      $templates = array();
 154  
 155      if ( count( $post_types ) === 1 ) {
 156          $post_type   = reset( $post_types );
 157          $templates[] = "archive-{$post_type}.php";
 158      }
 159      $templates[] = 'archive.php';
 160  
 161      return get_query_template( 'archive', $templates );
 162  }
 163  
 164  /**
 165   * Retrieves path of post type archive template in current or parent template.
 166   *
 167   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 168   * and {@see '$type_template'} dynamic hooks, where `$type` is 'archive'.
 169   *
 170   * @since 3.7.0
 171   *
 172   * @see get_archive_template()
 173   *
 174   * @return string Full path to archive template file.
 175   */
 176  function get_post_type_archive_template() {
 177      $post_type = get_query_var( 'post_type' );
 178      if ( is_array( $post_type ) ) {
 179          $post_type = reset( $post_type );
 180      }
 181  
 182      $obj = get_post_type_object( $post_type );
 183      if ( ! ( $obj instanceof WP_Post_Type ) || ! $obj->has_archive ) {
 184          return '';
 185      }
 186  
 187      return get_archive_template();
 188  }
 189  
 190  /**
 191   * Retrieves path of author template in current or parent template.
 192   *
 193   * The hierarchy for this template looks like:
 194   *
 195   * 1. author-{nicename}.php
 196   * 2. author-{id}.php
 197   * 3. author.php
 198   *
 199   * An example of this is:
 200   *
 201   * 1. author-john.php
 202   * 2. author-1.php
 203   * 3. author.php
 204   *
 205   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 206   * and {@see '$type_template'} dynamic hooks, where `$type` is 'author'.
 207   *
 208   * @since 1.5.0
 209   *
 210   * @see get_query_template()
 211   *
 212   * @return string Full path to author template file.
 213   */
 214  function get_author_template() {
 215      $author = get_queried_object();
 216  
 217      $templates = array();
 218  
 219      if ( $author instanceof WP_User ) {
 220          $templates[] = "author-{$author->user_nicename}.php";
 221          $templates[] = "author-{$author->ID}.php";
 222      }
 223      $templates[] = 'author.php';
 224  
 225      return get_query_template( 'author', $templates );
 226  }
 227  
 228  /**
 229   * Retrieves path of category template in current or parent template.
 230   *
 231   * The hierarchy for this template looks like:
 232   *
 233   * 1. category-{slug}.php
 234   * 2. category-{id}.php
 235   * 3. category.php
 236   *
 237   * An example of this is:
 238   *
 239   * 1. category-news.php
 240   * 2. category-2.php
 241   * 3. category.php
 242   *
 243   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 244   * and {@see '$type_template'} dynamic hooks, where `$type` is 'category'.
 245   *
 246   * @since 1.5.0
 247   * @since 4.7.0 The decoded form of `category-{slug}.php` was added to the top of the
 248   *              template hierarchy when the category slug contains multibyte characters.
 249   *
 250   * @see get_query_template()
 251   *
 252   * @return string Full path to category template file.
 253   */
 254  function get_category_template() {
 255      $category = get_queried_object();
 256  
 257      $templates = array();
 258  
 259      if ( ! empty( $category->slug ) ) {
 260  
 261          $slug_decoded = urldecode( $category->slug );
 262          if ( $slug_decoded !== $category->slug ) {
 263              $templates[] = "category-{$slug_decoded}.php";
 264          }
 265  
 266          $templates[] = "category-{$category->slug}.php";
 267          $templates[] = "category-{$category->term_id}.php";
 268      }
 269      $templates[] = 'category.php';
 270  
 271      return get_query_template( 'category', $templates );
 272  }
 273  
 274  /**
 275   * Retrieves path of tag template in current or parent template.
 276   *
 277   * The hierarchy for this template looks like:
 278   *
 279   * 1. tag-{slug}.php
 280   * 2. tag-{id}.php
 281   * 3. tag.php
 282   *
 283   * An example of this is:
 284   *
 285   * 1. tag-wordpress.php
 286   * 2. tag-3.php
 287   * 3. tag.php
 288   *
 289   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 290   * and {@see '$type_template'} dynamic hooks, where `$type` is 'tag'.
 291   *
 292   * @since 2.3.0
 293   * @since 4.7.0 The decoded form of `tag-{slug}.php` was added to the top of the
 294   *              template hierarchy when the tag slug contains multibyte characters.
 295   *
 296   * @see get_query_template()
 297   *
 298   * @return string Full path to tag template file.
 299   */
 300  function get_tag_template() {
 301      $tag = get_queried_object();
 302  
 303      $templates = array();
 304  
 305      if ( ! empty( $tag->slug ) ) {
 306  
 307          $slug_decoded = urldecode( $tag->slug );
 308          if ( $slug_decoded !== $tag->slug ) {
 309              $templates[] = "tag-{$slug_decoded}.php";
 310          }
 311  
 312          $templates[] = "tag-{$tag->slug}.php";
 313          $templates[] = "tag-{$tag->term_id}.php";
 314      }
 315      $templates[] = 'tag.php';
 316  
 317      return get_query_template( 'tag', $templates );
 318  }
 319  
 320  /**
 321   * Retrieves path of custom taxonomy term template in current or parent template.
 322   *
 323   * The hierarchy for this template looks like:
 324   *
 325   * 1. taxonomy-{taxonomy_slug}-{term_slug}.php
 326   * 2. taxonomy-{taxonomy_slug}-{term_id}.php
 327   * 3. taxonomy-{taxonomy_slug}.php
 328   * 4. taxonomy.php
 329   *
 330   * An example of this is:
 331   *
 332   * 1. taxonomy-location-texas.php
 333   * 2. taxonomy-location-67.php
 334   * 3. taxonomy-location.php
 335   * 4. taxonomy.php
 336   *
 337   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 338   * and {@see '$type_template'} dynamic hooks, where `$type` is 'taxonomy'.
 339   *
 340   * @since 2.5.0
 341   * @since 4.7.0 The decoded form of `taxonomy-{taxonomy_slug}-{term_slug}.php` was added to the top of the
 342   *              template hierarchy when the term slug contains multibyte characters.
 343   * @since 6.9.0 Added `taxonomy-{taxonomy_slug}-{term_id}.php` to the hierarchy.
 344   *
 345   * @see get_query_template()
 346   *
 347   * @return string Full path to custom taxonomy term template file.
 348   */
 349  function get_taxonomy_template() {
 350      $term = get_queried_object();
 351  
 352      $templates = array();
 353  
 354      if ( ! empty( $term->slug ) ) {
 355          $taxonomy = $term->taxonomy;
 356  
 357          $slug_decoded = urldecode( $term->slug );
 358          if ( $slug_decoded !== $term->slug ) {
 359              $templates[] = "taxonomy-$taxonomy-{$slug_decoded}.php";
 360          }
 361  
 362          $templates[] = "taxonomy-$taxonomy-{$term->slug}.php";
 363          $templates[] = "taxonomy-$taxonomy-{$term->term_id}.php";
 364          $templates[] = "taxonomy-$taxonomy.php";
 365      }
 366      $templates[] = 'taxonomy.php';
 367  
 368      return get_query_template( 'taxonomy', $templates );
 369  }
 370  
 371  /**
 372   * Retrieves path of date template in current or parent template.
 373   *
 374   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 375   * and {@see '$type_template'} dynamic hooks, where `$type` is 'date'.
 376   *
 377   * @since 1.5.0
 378   *
 379   * @see get_query_template()
 380   *
 381   * @return string Full path to date template file.
 382   */
 383  function get_date_template() {
 384      return get_query_template( 'date' );
 385  }
 386  
 387  /**
 388   * Retrieves path of home template in current or parent template.
 389   *
 390   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 391   * and {@see '$type_template'} dynamic hooks, where `$type` is 'home'.
 392   *
 393   * @since 1.5.0
 394   *
 395   * @see get_query_template()
 396   *
 397   * @return string Full path to home template file.
 398   */
 399  function get_home_template() {
 400      $templates = array( 'home.php', 'index.php' );
 401  
 402      return get_query_template( 'home', $templates );
 403  }
 404  
 405  /**
 406   * Retrieves path of front page template in current or parent template.
 407   *
 408   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 409   * and {@see '$type_template'} dynamic hooks, where `$type` is 'frontpage'.
 410   *
 411   * @since 3.0.0
 412   *
 413   * @see get_query_template()
 414   *
 415   * @return string Full path to front page template file.
 416   */
 417  function get_front_page_template() {
 418      $templates = array( 'front-page.php' );
 419  
 420      return get_query_template( 'frontpage', $templates );
 421  }
 422  
 423  /**
 424   * Retrieves path of Privacy Policy page template in current or parent template.
 425   *
 426   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 427   * and {@see '$type_template'} dynamic hooks, where `$type` is 'privacypolicy'.
 428   *
 429   * @since 5.2.0
 430   *
 431   * @see get_query_template()
 432   *
 433   * @return string Full path to privacy policy template file.
 434   */
 435  function get_privacy_policy_template() {
 436      $templates = array( 'privacy-policy.php' );
 437  
 438      return get_query_template( 'privacypolicy', $templates );
 439  }
 440  
 441  /**
 442   * Retrieves path of page template in current or parent template.
 443   *
 444   * Note: For block themes, use locate_block_template() function instead.
 445   *
 446   * The hierarchy for this template looks like:
 447   *
 448   * 1. {Page Template}.php
 449   * 2. page-{page_name}.php
 450   * 3. page-{id}.php
 451   * 4. page.php
 452   *
 453   * An example of this is:
 454   *
 455   * 1. page-templates/full-width.php
 456   * 2. page-about.php
 457   * 3. page-4.php
 458   * 4. page.php
 459   *
 460   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 461   * and {@see '$type_template'} dynamic hooks, where `$type` is 'page'.
 462   *
 463   * @since 1.5.0
 464   * @since 4.7.0 The decoded form of `page-{page_name}.php` was added to the top of the
 465   *              template hierarchy when the page name contains multibyte characters.
 466   *
 467   * @see get_query_template()
 468   *
 469   * @return string Full path to page template file.
 470   */
 471  function get_page_template() {
 472      $id       = get_queried_object_id();
 473      $template = get_page_template_slug();
 474      $pagename = get_query_var( 'pagename' );
 475  
 476      if ( ! $pagename && $id ) {
 477          /*
 478           * If a static page is set as the front page, $pagename will not be set.
 479           * Retrieve it from the queried object.
 480           */
 481          $post = get_queried_object();
 482          if ( $post ) {
 483              $pagename = $post->post_name;
 484          }
 485      }
 486  
 487      $templates = array();
 488      if ( $template && 0 === validate_file( $template ) ) {
 489          $templates[] = $template;
 490      }
 491      if ( $pagename ) {
 492          $pagename_decoded = urldecode( $pagename );
 493          if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
 494              $templates[] = "page-{$pagename_decoded}.php";
 495          }
 496          $templates[] = "page-{$pagename}.php";
 497      }
 498      if ( $id ) {
 499          $templates[] = "page-{$id}.php";
 500      }
 501      $templates[] = 'page.php';
 502  
 503      return get_query_template( 'page', $templates );
 504  }
 505  
 506  /**
 507   * Retrieves path of search template in current or parent template.
 508   *
 509   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 510   * and {@see '$type_template'} dynamic hooks, where `$type` is 'search'.
 511   *
 512   * @since 1.5.0
 513   *
 514   * @see get_query_template()
 515   *
 516   * @return string Full path to search template file.
 517   */
 518  function get_search_template() {
 519      return get_query_template( 'search' );
 520  }
 521  
 522  /**
 523   * Retrieves path of single template in current or parent template. Applies to single Posts,
 524   * single Attachments, and single custom post types.
 525   *
 526   * The hierarchy for this template looks like:
 527   *
 528   * 1. {Post Type Template}.php
 529   * 2. single-{post_type}-{post_name}.php
 530   * 3. single-{post_type}.php
 531   * 4. single.php
 532   *
 533   * An example of this is:
 534   *
 535   * 1. templates/full-width.php
 536   * 2. single-post-hello-world.php
 537   * 3. single-post.php
 538   * 4. single.php
 539   *
 540   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 541   * and {@see '$type_template'} dynamic hooks, where `$type` is 'single'.
 542   *
 543   * @since 1.5.0
 544   * @since 4.4.0 `single-{post_type}-{post_name}.php` was added to the top of the template hierarchy.
 545   * @since 4.7.0 The decoded form of `single-{post_type}-{post_name}.php` was added to the top of the
 546   *              template hierarchy when the post name contains multibyte characters.
 547   * @since 4.7.0 `{Post Type Template}.php` was added to the top of the template hierarchy.
 548   *
 549   * @see get_query_template()
 550   *
 551   * @return string Full path to single template file.
 552   */
 553  function get_single_template() {
 554      $object = get_queried_object();
 555  
 556      $templates = array();
 557  
 558      if ( ! empty( $object->post_type ) ) {
 559          $template = get_page_template_slug( $object );
 560          if ( $template && 0 === validate_file( $template ) ) {
 561              $templates[] = $template;
 562          }
 563  
 564          $name_decoded = urldecode( $object->post_name );
 565          if ( $name_decoded !== $object->post_name ) {
 566              $templates[] = "single-{$object->post_type}-{$name_decoded}.php";
 567          }
 568  
 569          $templates[] = "single-{$object->post_type}-{$object->post_name}.php";
 570          $templates[] = "single-{$object->post_type}.php";
 571      }
 572  
 573      $templates[] = 'single.php';
 574  
 575      return get_query_template( 'single', $templates );
 576  }
 577  
 578  /**
 579   * Retrieves an embed template path in the current or parent template.
 580   *
 581   * The hierarchy for this template looks like:
 582   *
 583   * 1. embed-{post_type}-{post_format}.php
 584   * 2. embed-{post_type}.php
 585   * 3. embed.php
 586   *
 587   * An example of this is:
 588   *
 589   * 1. embed-post-audio.php
 590   * 2. embed-post.php
 591   * 3. embed.php
 592   *
 593   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 594   * and {@see '$type_template'} dynamic hooks, where `$type` is 'embed'.
 595   *
 596   * @since 4.5.0
 597   *
 598   * @see get_query_template()
 599   *
 600   * @return string Full path to embed template file.
 601   */
 602  function get_embed_template() {
 603      $object = get_queried_object();
 604  
 605      $templates = array();
 606  
 607      if ( ! empty( $object->post_type ) ) {
 608          $post_format = get_post_format( $object );
 609          if ( $post_format ) {
 610              $templates[] = "embed-{$object->post_type}-{$post_format}.php";
 611          }
 612          $templates[] = "embed-{$object->post_type}.php";
 613      }
 614  
 615      $templates[] = 'embed.php';
 616  
 617      return get_query_template( 'embed', $templates );
 618  }
 619  
 620  /**
 621   * Retrieves the path of the singular template in current or parent template.
 622   *
 623   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 624   * and {@see '$type_template'} dynamic hooks, where `$type` is 'singular'.
 625   *
 626   * @since 4.3.0
 627   *
 628   * @see get_query_template()
 629   *
 630   * @return string Full path to singular template file.
 631   */
 632  function get_singular_template() {
 633      return get_query_template( 'singular' );
 634  }
 635  
 636  /**
 637   * Retrieves path of attachment template in current or parent template.
 638   *
 639   * The hierarchy for this template looks like:
 640   *
 641   * 1. {mime_type}-{sub_type}.php
 642   * 2. {sub_type}.php
 643   * 3. {mime_type}.php
 644   * 4. attachment.php
 645   *
 646   * An example of this is:
 647   *
 648   * 1. image-jpeg.php
 649   * 2. jpeg.php
 650   * 3. image.php
 651   * 4. attachment.php
 652   *
 653   * The template hierarchy and template path are filterable via the {@see '$type_template_hierarchy'}
 654   * and {@see '$type_template'} dynamic hooks, where `$type` is 'attachment'.
 655   *
 656   * @since 2.0.0
 657   * @since 4.3.0 The order of the mime type logic was reversed so the hierarchy is more logical.
 658   *
 659   * @see get_query_template()
 660   *
 661   * @return string Full path to attachment template file.
 662   */
 663  function get_attachment_template() {
 664      $attachment = get_queried_object();
 665  
 666      $templates = array();
 667  
 668      if ( $attachment ) {
 669          if ( str_contains( $attachment->post_mime_type, '/' ) ) {
 670              list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
 671          } else {
 672              list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
 673          }
 674  
 675          if ( ! empty( $subtype ) ) {
 676              $templates[] = "{$type}-{$subtype}.php";
 677              $templates[] = "{$subtype}.php";
 678          }
 679          $templates[] = "{$type}.php";
 680      }
 681      $templates[] = 'attachment.php';
 682  
 683      return get_query_template( 'attachment', $templates );
 684  }
 685  
 686  /**
 687   * Set up the globals used for template loading.
 688   *
 689   * @since 6.5.0
 690   *
 691   * @global string $wp_stylesheet_path Path to current theme's stylesheet directory.
 692   * @global string $wp_template_path   Path to current theme's template directory.
 693   */
 694  function wp_set_template_globals() {
 695      global $wp_stylesheet_path, $wp_template_path;
 696  
 697      $wp_stylesheet_path = get_stylesheet_directory();
 698      $wp_template_path   = get_template_directory();
 699  }
 700  
 701  /**
 702   * Determines whether a template found by locate_template() may be loaded.
 703   *
 704   * @since 7.1.2
 705   * @access private
 706   *
 707   * @global string $wp_stylesheet_path Path to current theme's stylesheet directory.
 708   * @global string $wp_template_path   Path to current theme's template directory.
 709   *
 710   * @param string $path Path to an existing template file.
 711   * @return bool Whether the template may be loaded.
 712   */
 713  function _wp_is_template_path_allowed( $path ) {
 714      global $wp_stylesheet_path, $wp_template_path;
 715  
 716      // A file path that exists and does not contain `..` is allowed.
 717      if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
 718          return true;
 719      }
 720  
 721      // Resolve the true location of the requested file for later comparison.
 722      $real_path = realpath( $path );
 723  
 724      if ( false === $real_path ) {
 725          return false;
 726      }
 727  
 728      $real_path = trailingslashit( wp_normalize_path( $real_path ) );
 729  
 730      $directories = array(
 731          $wp_stylesheet_path,
 732          $wp_template_path,
 733          ABSPATH . WPINC . '/theme-compat',
 734      );
 735  
 736      // If a theme is in a subdirectory, accept templates from its direct parent directory.
 737      if ( str_contains( get_stylesheet(), '/' ) ) {
 738          $directories[] = dirname( $wp_stylesheet_path );
 739      }
 740  
 741      // If a parent theme is in a subdirectory, accept templates from its direct parent directory.
 742      if ( str_contains( get_template(), '/' ) ) {
 743          $directories[] = dirname( $wp_template_path );
 744      }
 745  
 746      foreach ( $directories as $directory ) {
 747          $real_directory = realpath( $directory );
 748  
 749          if ( false === $real_directory ) {
 750              continue;
 751          }
 752  
 753          // The true location of the requested file must be inside one of the allowed directories.
 754          if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
 755              return true;
 756          }
 757      }
 758  
 759      return false;
 760  }
 761  
 762  /**
 763   * Retrieves the name of the highest priority template file that exists.
 764   *
 765   * Searches in the stylesheet directory before the template directory and
 766   * wp-includes/theme-compat so that themes which inherit from a parent theme
 767   * can just overload one file.
 768   *
 769   * @since 2.7.0
 770   * @since 5.5.0 The `$args` parameter was added.
 771   * @since 7.1.2 A template name containing `..` is only located if it resolves inside the theme.
 772   *
 773   * @global string $wp_stylesheet_path Path to current theme's stylesheet directory.
 774   * @global string $wp_template_path   Path to current theme's template directory.
 775   *
 776   * @param string|array $template_names Template file(s) to search for, in order.
 777   * @param bool         $load           If true the template file will be loaded if it is found.
 778   * @param bool         $load_once      Whether to require_once or require. Has no effect if `$load` is false.
 779   *                                     Default true.
 780   * @param array        $args           Optional. Additional arguments passed to the template.
 781   *                                     Default empty array.
 782   * @return string The template filename if one is located.
 783   */
 784  function locate_template( $template_names, $load = false, $load_once = true, $args = array() ) {
 785      global $wp_stylesheet_path, $wp_template_path;
 786  
 787      if ( ! isset( $wp_stylesheet_path ) || ! isset( $wp_template_path ) ) {
 788          wp_set_template_globals();
 789      }
 790  
 791      $is_child_theme = is_child_theme();
 792  
 793      $located = '';
 794      foreach ( (array) $template_names as $template_name ) {
 795          if ( ! $template_name ) {
 796              continue;
 797          }
 798          if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) {
 799              $candidate = $wp_stylesheet_path . '/' . $template_name;
 800          } elseif ( $is_child_theme && file_exists( $wp_template_path . '/' . $template_name ) ) {
 801              $candidate = $wp_template_path . '/' . $template_name;
 802          } elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
 803              $candidate = ABSPATH . WPINC . '/theme-compat/' . $template_name;
 804          } else {
 805              continue;
 806          }
 807  
 808          if ( _wp_is_template_path_allowed( $candidate ) ) {
 809              $located = $candidate;
 810              break;
 811          }
 812      }
 813  
 814      if ( $load && '' !== $located ) {
 815          load_template( $located, $load_once, $args );
 816      }
 817  
 818      return $located;
 819  }
 820  
 821  /**
 822   * Requires the template file with WordPress environment.
 823   *
 824   * The globals are set up for the template file to ensure that the WordPress
 825   * environment is available from within the function. The query variables are
 826   * also available.
 827   *
 828   * @since 1.5.0
 829   * @since 5.5.0 The `$args` parameter was added.
 830   *
 831   * @global array      $posts
 832   * @global WP_Post    $post          Global post object.
 833   * @global bool       $wp_did_header
 834   * @global WP_Query   $wp_query      WordPress Query object.
 835   * @global WP_Rewrite $wp_rewrite    WordPress rewrite component.
 836   * @global wpdb       $wpdb          WordPress database abstraction object.
 837   * @global string     $wp_version
 838   * @global WP         $wp            Current WordPress environment instance.
 839   * @global int        $id
 840   * @global WP_Comment $comment       Global comment object.
 841   * @global int        $user_ID
 842   *
 843   * @param string $_template_file Path to template file.
 844   * @param bool   $load_once      Whether to require_once or require. Default true.
 845   * @param array  $args           Optional. Additional arguments passed to the template.
 846   *                               Default empty array.
 847   */
 848  function load_template( $_template_file, $load_once = true, $args = array() ) {
 849      global $posts, $post, $wp_did_header, $wp_query, $wp_rewrite, $wpdb, $wp_version, $wp, $id, $comment, $user_ID;
 850  
 851      /** @var array{ s?: scalar, ... } $query_vars */
 852      $query_vars = $wp_query->query_vars;
 853      if ( is_array( $query_vars ) ) {
 854          /*
 855           * This use of extract() cannot be removed. There are many possible ways that
 856           * templates could depend on variables that it creates existing, and no way to
 857           * detect and deprecate it.
 858           *
 859           * Passing the EXTR_SKIP flag is the safest option, ensuring globals and
 860           * function variables cannot be overwritten.
 861           */
 862          // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
 863          extract( $query_vars, EXTR_SKIP );
 864      }
 865  
 866      if ( isset( $s ) ) {
 867          $s = esc_attr( (string) $s );
 868      }
 869  
 870      /**
 871       * Fires before a template file is loaded.
 872       *
 873       * @since 6.1.0
 874       *
 875       * @param string $_template_file The full path to the template file.
 876       * @param bool   $load_once      Whether to require_once or require.
 877       * @param array  $args           Additional arguments passed to the template.
 878       */
 879      do_action( 'wp_before_load_template', $_template_file, $load_once, $args );
 880  
 881      if ( $load_once ) {
 882          require_once $_template_file;
 883      } else {
 884          require $_template_file;
 885      }
 886  
 887      /**
 888       * Fires after a template file is loaded.
 889       *
 890       * @since 6.1.0
 891       *
 892       * @param string $_template_file The full path to the template file.
 893       * @param bool   $load_once      Whether to require_once or require.
 894       * @param array  $args           Additional arguments passed to the template.
 895       */
 896      do_action( 'wp_after_load_template', $_template_file, $load_once, $args );
 897  }
 898  
 899  /**
 900   * Checks whether the template should be output buffered for enhancement.
 901   *
 902   * By default, an output buffer is only started if a {@see 'wp_template_enhancement_output_buffer'} filter has been
 903   * added by the time a template is included at the {@see 'wp_before_include_template'} action. This allows template
 904   * responses to be streamed as much as possible when no template enhancements are registered to apply.
 905   *
 906   * @since 6.9.0
 907   *
 908   * @return bool Whether the template should be output-buffered for enhancement.
 909   */
 910  function wp_should_output_buffer_template_for_enhancement(): bool {
 911      /**
 912       * Filters whether the template should be output-buffered for enhancement.
 913       *
 914       * By default, an output buffer is only started if a {@see 'wp_template_enhancement_output_buffer'} filter has been
 915       * added or if a plugin has added a {@see 'wp_finalized_template_enhancement_output_buffer'} action. For this
 916       * default to apply, either of the hooks must be added by the time the template is included at the
 917       * {@see 'wp_before_include_template'} action. This allows template responses to be streamed unless the there is
 918       * code which depends on an output buffer being opened. This filter allows a site to opt in to adding such template
 919       * enhancement filters later during the rendering of the template.
 920       *
 921       * @since 6.9.0
 922       *
 923       * @param bool $use_output_buffer Whether an output buffer is started.
 924       */
 925      return (bool) apply_filters( 'wp_should_output_buffer_template_for_enhancement', has_filter( 'wp_template_enhancement_output_buffer' ) || has_action( 'wp_finalized_template_enhancement_output_buffer' ) );
 926  }
 927  
 928  /**
 929   * Starts the template enhancement output buffer.
 930   *
 931   * This function is called immediately before the template is included.
 932   *
 933   * @since 6.9.0
 934   *
 935   * @return bool Whether the output buffer successfully started.
 936   */
 937  function wp_start_template_enhancement_output_buffer(): bool {
 938      if ( ! wp_should_output_buffer_template_for_enhancement() ) {
 939          return false;
 940      }
 941  
 942      $started = ob_start(
 943          'wp_finalize_template_enhancement_output_buffer',
 944          0, // Unlimited buffer size so that entire output is passed to the filter.
 945          /*
 946           * Instead of the default PHP_OUTPUT_HANDLER_STDFLAGS (cleanable, flushable, and removable) being used for
 947           * flags, the PHP_OUTPUT_HANDLER_FLUSHABLE flag must be omitted. If the buffer were flushable, then each time
 948           * that ob_flush() is called, a fragment of the output would be sent into the output buffer callback. This
 949           * output buffer is intended to capture the entire response for processing, as indicated by the chunk size of 0.
 950           * So the buffer does not allow flushing to ensure the entire buffer can be processed, such as for optimizing an
 951           * entire HTML document, where markup in the HEAD may need to be adjusted based on markup that appears late in
 952           * the BODY.
 953           *
 954           * If this ends up being problematic, then PHP_OUTPUT_HANDLER_FLUSHABLE could be added to the $flags and the
 955           * output buffer callback could check if the phase is PHP_OUTPUT_HANDLER_FLUSH and abort any subsequent
 956           * processing while also emitting a _doing_it_wrong().
 957           *
 958           * The output buffer needs to be removable because WordPress calls wp_ob_end_flush_all() and then calls
 959           * wp_cache_close(). If the buffers are not all flushed before wp_cache_close() is closed, then some output buffer
 960           * handlers (e.g. for caching plugins) may fail to be able to store the page output in the object cache.
 961           * See <https://github.com/WordPress/performance/pull/1317#issuecomment-2271955356>.
 962           */
 963          PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE
 964      );
 965  
 966      if ( $started ) {
 967          /**
 968           * Fires when the template enhancement output buffer has started.
 969           *
 970           * @since 6.9.0
 971           */
 972          do_action( 'wp_template_enhancement_output_buffer_started' );
 973      }
 974  
 975      return $started;
 976  }
 977  
 978  /**
 979   * Finalizes the template enhancement output buffer.
 980   *
 981   * Checks to see if the output buffer is complete and contains HTML. If so, runs the content through
 982   * the `wp_template_enhancement_output_buffer` filter.  If not, the original content is returned.
 983   *
 984   * @since 6.9.0
 985   *
 986   * @see wp_start_template_enhancement_output_buffer()
 987   *
 988   * @param string $output Output buffer.
 989   * @param int    $phase  Phase.
 990   * @return string Finalized output buffer.
 991   */
 992  function wp_finalize_template_enhancement_output_buffer( string $output, int $phase ): string {
 993      // When the output is being cleaned (e.g. pending template is replaced with error page), do not send it through the filter.
 994      if ( ( $phase & PHP_OUTPUT_HANDLER_CLEAN ) !== 0 ) {
 995          return $output;
 996      }
 997  
 998      // Detect if the response is an HTML content type.
 999      $is_html_content_type = null;
1000      $html_content_types   = array( 'text/html', 'application/xhtml+xml' );
1001      foreach ( headers_list() as $header ) {
1002          $header_parts = explode( ':', strtolower( $header ), 2 );
1003          if (
1004              count( $header_parts ) === 2 &&
1005              'content-type' === $header_parts[0]
1006          ) {
1007              /*
1008               * This is looking for very specific content types, therefore it
1009               * doesn’t need to fully parse the header’s value. Instead, it needs
1010               * only assert that the content type is one of the static HTML types.
1011               *
1012               * Example:
1013               *
1014               *     Content-Type: text/html; charset=utf8
1015               *     Content-Type: text/html  ;charset=latin4
1016               *     Content-Type:application/xhtml+xml
1017               */
1018              $media_type           = trim( strtok( $header_parts[1], ';' ), " \t" );
1019              $is_html_content_type = in_array( $media_type, $html_content_types, true );
1020              break; // PHP only sends the first Content-Type header in the list.
1021          }
1022      }
1023      if ( null === $is_html_content_type ) {
1024          $is_html_content_type = in_array( ini_get( 'default_mimetype' ), $html_content_types, true );
1025      }
1026  
1027      // If the content type is not HTML, short-circuit since it is not relevant for enhancement.
1028      if ( ! $is_html_content_type ) {
1029          /** This action is documented in wp-includes/template.php */
1030          do_action( 'wp_finalized_template_enhancement_output_buffer', $output );
1031          return $output;
1032      }
1033  
1034      $filtered_output = $output;
1035  
1036      $did_just_catch = false;
1037  
1038      $error_log = array();
1039      set_error_handler(
1040          static function ( int $level, string $message, ?string $file = null, ?int $line = null ) use ( &$error_log, &$did_just_catch ) {
1041              // Switch a user error to an exception so that it can be caught and the buffer can be returned.
1042              if ( E_USER_ERROR === $level ) {
1043                  throw new Exception( __( 'User error triggered:' ) . ' ' . $message );
1044              }
1045  
1046              // Display a caught exception as an error since it prevents any of the output buffer filters from applying.
1047              if ( $did_just_catch ) {
1048                  $level = E_USER_ERROR;
1049              }
1050  
1051              // Capture a reported error to be displayed by appending to the processed output buffer if display_errors is enabled.
1052              if ( error_reporting() & $level ) {
1053                  $error_log[] = compact( 'level', 'message', 'file', 'line' );
1054              }
1055              return false;
1056          }
1057      );
1058      $original_display_errors = ini_get( 'display_errors' );
1059      if ( $original_display_errors ) {
1060          ini_set( 'display_errors', 0 );
1061      }
1062  
1063      try {
1064          /**
1065           * Filters the template enhancement output buffer prior to sending to the client.
1066           *
1067           * This filter only applies the HTML output of an included template. This filter is a progressive enhancement
1068           * intended for applications such as optimizing markup to improve frontend page load performance. Sites must not
1069           * depend on this filter applying since they may opt to stream the responses instead. Callbacks for this filter
1070           * are highly discouraged from using regular expressions to do any kind of replacement on the output. Use the
1071           * HTML API (either `WP_HTML_Tag_Processor` or `WP_HTML_Processor`), or else use {@see DOM\HtmlDocument} as of
1072           * PHP 8.4 which fully supports HTML5.
1073           *
1074           * Do not print any output during this filter. While filters normally don't print anything, this is especially
1075           * important since this applies during an output buffer callback. Prior to PHP 8.5, the output will be silently
1076           * omitted, whereas afterward a deprecation notice will be emitted.
1077           *
1078           * Important: Because this filter is applied inside an output buffer callback (i.e. display handler), any
1079           * callbacks added to the filter must not attempt to start their own output buffers. Otherwise, PHP will raise a
1080           * fatal error: "Cannot use output buffering in output buffering display handlers."
1081           *
1082           * @since 6.9.0
1083           *
1084           * @param string $filtered_output HTML template enhancement output buffer.
1085           * @param string $output          Original HTML template output buffer.
1086           */
1087          $filtered_output = (string) apply_filters( 'wp_template_enhancement_output_buffer', $filtered_output, $output );
1088      } catch ( Throwable $throwable ) {
1089          // Emit to the error log as a warning not as an error to prevent halting execution.
1090          $did_just_catch = true;
1091          trigger_error(
1092              sprintf(
1093                  /* translators: %s is the throwable class name */
1094                  __( 'Uncaught "%s" thrown:' ),
1095                  get_class( $throwable )
1096              ) . ' ' . $throwable->getMessage(),
1097              E_USER_WARNING
1098          );
1099          $did_just_catch = false;
1100      }
1101  
1102      try {
1103          /**
1104           * Fires after the template enhancement output buffer has been finalized.
1105           *
1106           * This happens immediately before the template enhancement output buffer is flushed. No output may be printed
1107           * at this action; prior to PHP 8.5, the output will be silently omitted, whereas afterward a deprecation notice
1108           * will be emitted. Nevertheless, HTTP headers may be sent, which makes this action complimentary to the
1109           * {@see 'send_headers'} action, in which headers may be sent before the template has started rendering. In
1110           * contrast, this `wp_finalized_template_enhancement_output_buffer` action is the possible point at which HTTP
1111           * headers can be sent. This action does not fire if the "template enhancement output buffer" was not started.
1112           * This output buffer is automatically started if this action is added before
1113           * {@see wp_start_template_enhancement_output_buffer()} runs at the {@see 'wp_before_include_template'} action
1114           * with priority 1000. Before this point, the output buffer will also be started automatically if there was a
1115           * {@see 'wp_template_enhancement_output_buffer'} filter added, or if the
1116           * {@see 'wp_should_output_buffer_template_for_enhancement'} filter is made to return `true`.
1117           *
1118           * Important: Because this action fires inside an output buffer callback (i.e. display handler), any callbacks
1119           * added to the action must not attempt to start their own output buffers. Otherwise, PHP will raise a fatal
1120           * error: "Cannot use output buffering in output buffering display handlers."
1121           *
1122           * @since 6.9.0
1123           *
1124           * @param string $output Finalized output buffer.
1125           */
1126          do_action( 'wp_finalized_template_enhancement_output_buffer', $filtered_output );
1127      } catch ( Throwable $throwable ) {
1128          // Emit to the error log as a warning not as an error to prevent halting execution.
1129          $did_just_catch = true;
1130          trigger_error(
1131              sprintf(
1132                  /* translators: %s is the class name */
1133                  __( 'Uncaught "%s" thrown:' ),
1134                  get_class( $throwable )
1135              ) . ' ' . $throwable->getMessage(),
1136              E_USER_WARNING
1137          );
1138          $did_just_catch = false;
1139      }
1140  
1141      // Append any errors to be displayed before returning flushing the buffer.
1142      if ( $original_display_errors && 'stderr' !== $original_display_errors ) {
1143          foreach ( $error_log as $error ) {
1144              switch ( $error['level'] ) {
1145                  case E_USER_NOTICE:
1146                      $type = 'Notice';
1147                      break;
1148                  case E_USER_DEPRECATED:
1149                      $type = 'Deprecated';
1150                      break;
1151                  case E_USER_WARNING:
1152                      $type = 'Warning';
1153                      break;
1154                  default:
1155                      $type = 'Error';
1156              }
1157  
1158              if ( ini_get( 'html_errors' ) ) {
1159                  /*
1160                   * Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1478>.
1161                   * The self-closing tags are a vestige of the XHTML past!
1162                   */
1163                  $format = "%s<br />\n<b>%s</b>:  %s in <b>%s</b> on line <b>%s</b><br />\n%s";
1164              } else {
1165                  // Adapted from PHP internals: <https://github.com/php/php-src/blob/a979e9f897a90a580e883b1f39ce5673686ffc67/main/main.c#L1492>.
1166                  $format = "%s\n%s: %s in %s on line %s\n%s";
1167              }
1168              $filtered_output .= sprintf(
1169                  $format,
1170                  ini_get( 'error_prepend_string' ),
1171                  $type,
1172                  $error['message'],
1173                  $error['file'],
1174                  $error['line'],
1175                  ini_get( 'error_append_string' )
1176              );
1177          }
1178  
1179          ini_set( 'display_errors', $original_display_errors );
1180      }
1181  
1182      restore_error_handler();
1183  
1184      return $filtered_output;
1185  }


Generated : Fri Oct 9 08:20:33 2026 Cross-referenced by PHPXref