[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-theme.php (source)

   1  <?php
   2  /**
   3   * WP_Theme Class
   4   *
   5   * @package WordPress
   6   * @subpackage Theme
   7   * @since 3.4.0
   8   *
   9   * @property-read string|false   $name
  10   * @property-read string|false   $title
  11   * @property-read string|false   $version
  12   * @property-read string|false   $parent_theme
  13   * @property-read string         $template_dir
  14   * @property-read string         $stylesheet_dir
  15   * @property-read string         $template
  16   * @property-read string         $stylesheet
  17   * @property-read string|false   $screenshot
  18   * @property-read string|false   $description
  19   * @property-read string|false   $author
  20   * @property-read string[]|false $tags
  21   * @property-read string         $theme_root
  22   * @property-read string         $theme_root_uri
  23   *
  24   * @phpstan-type Theme_Key 'Name'|'Version'|'Status'|'Title'|'Author'|'Author Name'|'Author URI'|'Description'|'Template'|'Stylesheet'|'Template Files'|'Stylesheet Files'|'Template Dir'|'Stylesheet Dir'|'Screenshot'|'Tags'|'Theme Root'|'Theme Root URI'|'Parent Theme'
  25   */
  26  #[AllowDynamicProperties]
  27  final class WP_Theme implements ArrayAccess {
  28  
  29      /**
  30       * Whether the theme has been marked as updateable.
  31       *
  32       * @since 4.4.0
  33       * @var bool
  34       *
  35       * @see WP_MS_Themes_List_Table
  36       */
  37      public $update = false;
  38  
  39      /**
  40       * Headers for style.css files.
  41       *
  42       * @since 3.4.0
  43       * @since 5.4.0 Added `Requires at least` and `Requires PHP` headers.
  44       * @since 6.1.0 Added `Update URI` header.
  45       * @var string[]
  46       */
  47      private static $file_headers = array(
  48          'Name'        => 'Theme Name',
  49          'ThemeURI'    => 'Theme URI',
  50          'Description' => 'Description',
  51          'Author'      => 'Author',
  52          'AuthorURI'   => 'Author URI',
  53          'Version'     => 'Version',
  54          'Template'    => 'Template',
  55          'Status'      => 'Status',
  56          'Tags'        => 'Tags',
  57          'TextDomain'  => 'Text Domain',
  58          'DomainPath'  => 'Domain Path',
  59          'RequiresWP'  => 'Requires at least',
  60          'RequiresPHP' => 'Requires PHP',
  61          'UpdateURI'   => 'Update URI',
  62      );
  63  
  64      /**
  65       * Default themes.
  66       *
  67       * @since 3.4.0
  68       * @since 3.5.0 Added the Twenty Twelve theme.
  69       * @since 3.6.0 Added the Twenty Thirteen theme.
  70       * @since 3.8.0 Added the Twenty Fourteen theme.
  71       * @since 4.1.0 Added the Twenty Fifteen theme.
  72       * @since 4.4.0 Added the Twenty Sixteen theme.
  73       * @since 4.7.0 Added the Twenty Seventeen theme.
  74       * @since 5.0.0 Added the Twenty Nineteen theme.
  75       * @since 5.3.0 Added the Twenty Twenty theme.
  76       * @since 5.6.0 Added the Twenty Twenty-One theme.
  77       * @since 5.9.0 Added the Twenty Twenty-Two theme.
  78       * @since 6.1.0 Added the Twenty Twenty-Three theme.
  79       * @since 6.4.0 Added the Twenty Twenty-Four theme.
  80       * @since 6.7.0 Added the Twenty Twenty-Five theme.
  81       * @since 7.2.0 Added the Ipsum theme.
  82       * @var string[]
  83       */
  84      private static $default_themes = array(
  85          'classic'           => 'WordPress Classic',
  86          'default'           => 'WordPress Default',
  87          'twentyten'         => 'Twenty Ten',
  88          'twentyeleven'      => 'Twenty Eleven',
  89          'twentytwelve'      => 'Twenty Twelve',
  90          'twentythirteen'    => 'Twenty Thirteen',
  91          'twentyfourteen'    => 'Twenty Fourteen',
  92          'twentyfifteen'     => 'Twenty Fifteen',
  93          'twentysixteen'     => 'Twenty Sixteen',
  94          'twentyseventeen'   => 'Twenty Seventeen',
  95          'twentynineteen'    => 'Twenty Nineteen',
  96          'twentytwenty'      => 'Twenty Twenty',
  97          'twentytwentyone'   => 'Twenty Twenty-One',
  98          'twentytwentytwo'   => 'Twenty Twenty-Two',
  99          'twentytwentythree' => 'Twenty Twenty-Three',
 100          'twentytwentyfour'  => 'Twenty Twenty-Four',
 101          'twentytwentyfive'  => 'Twenty Twenty-Five',
 102          'ipsum'             => 'Ipsum',
 103      );
 104  
 105      /**
 106       * Renamed theme tags.
 107       *
 108       * @since 3.8.0
 109       * @var string[]
 110       */
 111      private static $tag_map = array(
 112          'fixed-width'    => 'fixed-layout',
 113          'flexible-width' => 'fluid-layout',
 114      );
 115  
 116      /**
 117       * Absolute path to the theme root, usually wp-content/themes
 118       *
 119       * @since 3.4.0
 120       * @var string
 121       */
 122      private $theme_root;
 123  
 124      /**
 125       * Header data from the theme's style.css file.
 126       *
 127       * @since 3.4.0
 128       * @var array
 129       */
 130      private $headers = array();
 131  
 132      /**
 133       * Header data from the theme's style.css file after being sanitized.
 134       *
 135       * @since 3.4.0
 136       * @var ?array
 137       */
 138      private $headers_sanitized;
 139  
 140      /**
 141       * Is this theme a block theme.
 142       *
 143       * @since 6.2.0
 144       * @var ?bool
 145       */
 146      private $block_theme;
 147  
 148      /**
 149       * Header name from the theme's style.css after being translated.
 150       *
 151       * Cached due to sorting functions running over the translated name.
 152       *
 153       * @since 3.4.0
 154       * @var ?string
 155       */
 156      private $name_translated;
 157  
 158      /**
 159       * Errors encountered when initializing the theme.
 160       *
 161       * @since 3.4.0
 162       * @var ?WP_Error
 163       */
 164      private $errors;
 165  
 166      /**
 167       * The directory name of the theme's files, inside the theme root.
 168       *
 169       * In the case of a child theme, this is directory name of the child theme.
 170       * Otherwise, 'stylesheet' is the same as 'template'.
 171       *
 172       * @since 3.4.0
 173       * @var string
 174       */
 175      private $stylesheet;
 176  
 177      /**
 178       * The directory name of the theme's files, inside the theme root.
 179       *
 180       * In the case of a child theme, this is the directory name of the parent theme.
 181       * Otherwise, 'template' is the same as 'stylesheet'.
 182       *
 183       * @since 3.4.0
 184       * @var ?string
 185       */
 186      private $template;
 187  
 188      /**
 189       * A reference to the parent theme, in the case of a child theme.
 190       *
 191       * @since 3.4.0
 192       * @var ?WP_Theme
 193       */
 194      private $parent;
 195  
 196      /**
 197       * URL to the theme root, usually an absolute URL to wp-content/themes
 198       *
 199       * @since 3.4.0
 200       * @var ?string
 201       */
 202      private $theme_root_uri;
 203  
 204      /**
 205       * Flag for whether the theme's textdomain is loaded.
 206       *
 207       * @since 3.4.0
 208       * @var ?bool
 209       */
 210      private $textdomain_loaded;
 211  
 212      /**
 213       * Stores an md5 hash of the theme root, to function as the cache key.
 214       *
 215       * @since 3.4.0
 216       * @var string
 217       */
 218      private $cache_hash;
 219  
 220      /**
 221       * Block template folders.
 222       *
 223       * @since 6.4.0
 224       * @var ?string[]
 225       */
 226      private $block_template_folders;
 227  
 228      /**
 229       * Default values for template folders.
 230       *
 231       * @since 6.4.0
 232       * @var string[]
 233       */
 234      private $default_template_folders = array(
 235          'wp_template'      => 'templates',
 236          'wp_template_part' => 'parts',
 237      );
 238  
 239      /**
 240       * Flag for whether the themes cache bucket should be persistently cached.
 241       *
 242       * Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter.
 243       *
 244       * @since 3.4.0
 245       * @var bool
 246       */
 247      private static $persistently_cache;
 248  
 249      /**
 250       * Expiration time for the themes cache bucket.
 251       *
 252       * By default the bucket is not cached, so this value is useless.
 253       *
 254       * @since 3.4.0
 255       * @var int
 256       */
 257      private static $cache_expiration = 1800;
 258  
 259      /**
 260       * Constructor for WP_Theme.
 261       *
 262       * @since 3.4.0
 263       *
 264       * @global string[] $wp_theme_directories
 265       *
 266       * @param string        $theme_dir  Directory of the theme within the theme_root.
 267       * @param string        $theme_root Theme root.
 268       * @param WP_Theme|null $_child     If this theme is a parent theme, the child may be passed for validation purposes.
 269       */
 270  	public function __construct( $theme_dir, $theme_root, $_child = null ) {
 271          global $wp_theme_directories;
 272  
 273          // Initialize caching on first run.
 274          if ( ! isset( self::$persistently_cache ) ) {
 275              /** This action is documented in wp-includes/theme.php */
 276              self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
 277              if ( self::$persistently_cache ) {
 278                  wp_cache_add_global_groups( 'themes' );
 279                  if ( is_int( self::$persistently_cache ) ) {
 280                      self::$cache_expiration = self::$persistently_cache;
 281                  }
 282              } else {
 283                  wp_cache_add_non_persistent_groups( 'themes' );
 284              }
 285          }
 286  
 287          // Handle a numeric theme directory as a string.
 288          $theme_dir = (string) $theme_dir;
 289  
 290          $this->theme_root = $theme_root;
 291          $this->stylesheet = $theme_dir;
 292  
 293          // Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
 294          if ( ! in_array( $theme_root, (array) $wp_theme_directories, true )
 295              && in_array( dirname( $theme_root ), (array) $wp_theme_directories, true )
 296          ) {
 297              $this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
 298              $this->theme_root = dirname( $theme_root );
 299          }
 300  
 301          $this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
 302          $theme_file       = $this->stylesheet . '/style.css';
 303  
 304          $cache = $this->cache_get( 'theme' );
 305  
 306          if ( is_array( $cache ) ) {
 307              foreach ( array( 'block_template_folders', 'block_theme', 'errors', 'headers', 'template' ) as $key ) {
 308                  if ( isset( $cache[ $key ] ) ) {
 309                      $this->$key = $cache[ $key ];
 310                  }
 311              }
 312              if ( $this->errors ) {
 313                  return;
 314              }
 315              if ( isset( $cache['theme_root_template'] ) ) {
 316                  $theme_root_template = $cache['theme_root_template'];
 317              }
 318          } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
 319              $this->headers['Name'] = $this->stylesheet;
 320              if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) ) {
 321                  $this->errors = new WP_Error(
 322                      'theme_not_found',
 323                      sprintf(
 324                          /* translators: %s: Theme directory name. */
 325                          __( 'The theme directory "%s" does not exist.' ),
 326                          esc_html( $this->stylesheet )
 327                      )
 328                  );
 329              } else {
 330                  $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
 331              }
 332              $this->template               = $this->stylesheet;
 333              $this->block_theme            = false;
 334              $this->block_template_folders = $this->default_template_folders;
 335              $this->cache_add(
 336                  'theme',
 337                  array(
 338                      'block_template_folders' => $this->block_template_folders,
 339                      'block_theme'            => $this->block_theme,
 340                      'headers'                => $this->headers,
 341                      'errors'                 => $this->errors,
 342                      'stylesheet'             => $this->stylesheet,
 343                      'template'               => $this->template,
 344                  )
 345              );
 346              if ( ! file_exists( $this->theme_root ) ) { // Don't cache this one.
 347                  $this->errors->add( 'theme_root_missing', __( '<strong>Error:</strong> The themes directory is either empty or does not exist. Please check your installation.' ) );
 348              }
 349              return;
 350          } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
 351              $this->headers['Name']        = $this->stylesheet;
 352              $this->errors                 = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
 353              $this->template               = $this->stylesheet;
 354              $this->block_theme            = false;
 355              $this->block_template_folders = $this->default_template_folders;
 356              $this->cache_add(
 357                  'theme',
 358                  array(
 359                      'block_template_folders' => $this->block_template_folders,
 360                      'block_theme'            => $this->block_theme,
 361                      'headers'                => $this->headers,
 362                      'errors'                 => $this->errors,
 363                      'stylesheet'             => $this->stylesheet,
 364                      'template'               => $this->template,
 365                  )
 366              );
 367              return;
 368          } else {
 369              $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
 370              /*
 371               * Default themes always trump their pretenders.
 372               * Properly identify default themes that are inside a directory within wp-content/themes.
 373               */
 374              $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes, true );
 375              if ( $default_theme_slug ) {
 376                  if ( basename( $this->stylesheet ) !== $default_theme_slug ) {
 377                      $this->headers['Name'] .= '/' . $this->stylesheet;
 378                  }
 379              }
 380          }
 381  
 382          if ( ! $this->template && $this->stylesheet === $this->headers['Template'] ) {
 383              $this->errors   = new WP_Error(
 384                  'theme_child_invalid',
 385                  sprintf(
 386                      /* translators: %s: Template. */
 387                      __( 'The theme defines itself as its parent theme. Please check the %s header.' ),
 388                      '<code>Template</code>'
 389                  )
 390              );
 391              $this->template = $this->stylesheet;
 392              $this->cache_add(
 393                  'theme',
 394                  array(
 395                      'block_template_folders' => $this->get_block_template_folders(),
 396                      'block_theme'            => $this->is_block_theme(),
 397                      'headers'                => $this->headers,
 398                      'errors'                 => $this->errors,
 399                      'stylesheet'             => $this->stylesheet,
 400                      'template'               => $this->template,
 401                  )
 402              );
 403  
 404              return;
 405          }
 406  
 407          // (If template is set from cache [and there are no errors], we know it's good.)
 408          if ( ! $this->template ) {
 409              $this->template = $this->headers['Template'];
 410          }
 411  
 412          if ( ! $this->template ) {
 413              $this->template = $this->stylesheet;
 414              $theme_path     = $this->theme_root . '/' . $this->stylesheet;
 415  
 416              if ( ! $this->is_block_theme() && ! file_exists( $theme_path . '/index.php' ) ) {
 417                  $error_message = sprintf(
 418                      /* translators: 1: templates/index.html, 2: index.php, 3: Documentation URL, 4: Template, 5: style.css */
 419                      __( 'Template is missing. Standalone themes need to have a %1$s or %2$s template file. <a href="%3$s">Child themes</a> need to have a %4$s header in the %5$s stylesheet.' ),
 420                      '<code>templates/index.html</code>',
 421                      '<code>index.php</code>',
 422                      __( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
 423                      '<code>Template</code>',
 424                      '<code>style.css</code>'
 425                  );
 426                  $this->errors = new WP_Error( 'theme_no_index', $error_message );
 427                  $this->cache_add(
 428                      'theme',
 429                      array(
 430                          'block_template_folders' => $this->get_block_template_folders(),
 431                          'block_theme'            => $this->block_theme,
 432                          'headers'                => $this->headers,
 433                          'errors'                 => $this->errors,
 434                          'stylesheet'             => $this->stylesheet,
 435                          'template'               => $this->template,
 436                      )
 437                  );
 438                  return;
 439              }
 440          }
 441  
 442          // If we got our data from cache, we can assume that 'template' is pointing to the right place.
 443          if ( ! is_array( $cache )
 444              && $this->template !== $this->stylesheet
 445              && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' )
 446          ) {
 447              /*
 448               * If we're in a directory of themes inside /themes, look for the parent nearby.
 449               * wp-content/themes/directory-of-themes/*
 450               */
 451              $parent_dir  = dirname( $this->stylesheet );
 452              $directories = search_theme_directories();
 453  
 454              if ( '.' !== $parent_dir
 455                  && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' )
 456              ) {
 457                  $this->template = $parent_dir . '/' . $this->template;
 458              } elseif ( $directories && isset( $directories[ $this->template ] ) ) {
 459                  /*
 460                   * Look for the template in the search_theme_directories() results, in case it is in another theme root.
 461                   * We don't look into directories of themes, just the theme root.
 462                   */
 463                  $theme_root_template = $directories[ $this->template ]['theme_root'];
 464              } else {
 465                  // Parent theme is missing.
 466                  $this->errors = new WP_Error(
 467                      'theme_no_parent',
 468                      sprintf(
 469                          /* translators: %s: Theme directory name. */
 470                          __( 'The parent theme is missing. Please install the "%s" parent theme.' ),
 471                          esc_html( $this->template )
 472                      )
 473                  );
 474                  $this->cache_add(
 475                      'theme',
 476                      array(
 477                          'block_template_folders' => $this->get_block_template_folders(),
 478                          'block_theme'            => $this->is_block_theme(),
 479                          'headers'                => $this->headers,
 480                          'errors'                 => $this->errors,
 481                          'stylesheet'             => $this->stylesheet,
 482                          'template'               => $this->template,
 483                      )
 484                  );
 485                  $this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
 486                  return;
 487              }
 488          }
 489  
 490          // Set the parent, if we're a child theme.
 491          if ( $this->template !== $this->stylesheet ) {
 492              // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
 493              if ( $_child instanceof WP_Theme && $_child->template === $this->stylesheet ) {
 494                  $_child->parent = null;
 495                  $_child->errors = new WP_Error(
 496                      'theme_parent_invalid',
 497                      sprintf(
 498                          /* translators: %s: Theme directory name. */
 499                          __( 'The "%s" theme is not a valid parent theme.' ),
 500                          esc_html( $_child->template )
 501                      )
 502                  );
 503                  $_child->cache_add(
 504                      'theme',
 505                      array(
 506                          'block_template_folders' => $_child->get_block_template_folders(),
 507                          'block_theme'            => $_child->is_block_theme(),
 508                          'headers'                => $_child->headers,
 509                          'errors'                 => $_child->errors,
 510                          'stylesheet'             => $_child->stylesheet,
 511                          'template'               => $_child->template,
 512                      )
 513                  );
 514                  // The two themes actually reference each other with the Template header.
 515                  if ( $_child->stylesheet === $this->template ) {
 516                      $this->errors = new WP_Error(
 517                          'theme_parent_invalid',
 518                          sprintf(
 519                              /* translators: %s: Theme directory name. */
 520                              __( 'The "%s" theme is not a valid parent theme.' ),
 521                              esc_html( $this->template )
 522                          )
 523                      );
 524                      $this->cache_add(
 525                          'theme',
 526                          array(
 527                              'block_template_folders' => $this->get_block_template_folders(),
 528                              'block_theme'            => $this->is_block_theme(),
 529                              'headers'                => $this->headers,
 530                              'errors'                 => $this->errors,
 531                              'stylesheet'             => $this->stylesheet,
 532                              'template'               => $this->template,
 533                          )
 534                      );
 535                  }
 536                  return;
 537              }
 538              // Set the parent. Pass the current instance so we can do the checks above and assess errors.
 539              $this->parent = new WP_Theme( $this->template, $theme_root_template ?? $this->theme_root, $this );
 540          }
 541  
 542          if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) {
 543              $this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) );
 544          }
 545  
 546          // We're good. If we didn't retrieve from cache, set it.
 547          if ( ! is_array( $cache ) ) {
 548              $cache = array(
 549                  'block_theme'            => $this->is_block_theme(),
 550                  'block_template_folders' => $this->get_block_template_folders(),
 551                  'headers'                => $this->headers,
 552                  'errors'                 => $this->errors,
 553                  'stylesheet'             => $this->stylesheet,
 554                  'template'               => $this->template,
 555              );
 556              // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
 557              if ( isset( $theme_root_template ) ) {
 558                  $cache['theme_root_template'] = $theme_root_template;
 559              }
 560              $this->cache_add( 'theme', $cache );
 561          }
 562      }
 563  
 564      /**
 565       * When converting the object to a string, the theme name is returned.
 566       *
 567       * @since 3.4.0
 568       *
 569       * @return string Theme name, ready for display (translated)
 570       */
 571  	public function __toString() {
 572          return (string) $this->display( 'Name' );
 573      }
 574  
 575      /**
 576       * __isset() magic method for properties formerly returned by current_theme_info()
 577       *
 578       * @since 3.4.0
 579       *
 580       * @param string $offset Property to check if set.
 581       * @return bool Whether the given property is set.
 582       */
 583  	public function __isset( $offset ) {
 584          static $properties = array(
 585              'name',
 586              'title',
 587              'version',
 588              'parent_theme',
 589              'template_dir',
 590              'stylesheet_dir',
 591              'template',
 592              'stylesheet',
 593              'screenshot',
 594              'description',
 595              'author',
 596              'tags',
 597              'theme_root',
 598              'theme_root_uri',
 599          );
 600  
 601          return in_array( $offset, $properties, true );
 602      }
 603  
 604      /**
 605       * __get() magic method for properties formerly returned by current_theme_info()
 606       *
 607       * @since 3.4.0
 608       *
 609       * @param string $offset Property to get.
 610       * @return mixed Property value.
 611       */
 612  	public function __get( $offset ) {
 613          switch ( $offset ) {
 614              case 'name':
 615              case 'title':
 616                  return $this->get( 'Name' );
 617              case 'version':
 618                  return $this->get( 'Version' );
 619              case 'parent_theme':
 620                  return $this->parent() ? $this->parent()->get( 'Name' ) : '';
 621              case 'template_dir':
 622                  return $this->get_template_directory();
 623              case 'stylesheet_dir':
 624                  return $this->get_stylesheet_directory();
 625              case 'template':
 626                  return $this->get_template();
 627              case 'stylesheet':
 628                  return $this->get_stylesheet();
 629              case 'screenshot':
 630                  return $this->get_screenshot( 'relative' );
 631              // 'author' and 'description' did not previously return translated data.
 632              case 'description':
 633                  return $this->display( 'Description' );
 634              case 'author':
 635                  return $this->display( 'Author' );
 636              case 'tags':
 637                  return $this->get( 'Tags' );
 638              case 'theme_root':
 639                  return $this->get_theme_root();
 640              case 'theme_root_uri':
 641                  return $this->get_theme_root_uri();
 642              // For cases where the array was converted to an object.
 643              default:
 644                  return $this->offsetGet( $offset );
 645          }
 646      }
 647  
 648      /**
 649       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 650       *
 651       * @since 3.4.0
 652       *
 653       * @param mixed $offset
 654       * @param mixed $value
 655       */
 656      #[ReturnTypeWillChange]
 657  	public function offsetSet( $offset, $value ) {}
 658  
 659      /**
 660       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 661       *
 662       * @since 3.4.0
 663       *
 664       * @param mixed $offset
 665       */
 666      #[ReturnTypeWillChange]
 667  	public function offsetUnset( $offset ) {}
 668  
 669      /**
 670       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 671       *
 672       * @since 3.4.0
 673       *
 674       * @param mixed $offset
 675       * @return bool
 676       *
 677       * @phpstan-return ( $offset is Theme_Key ? true : false )
 678       */
 679      #[ReturnTypeWillChange]
 680  	public function offsetExists( $offset ) {
 681          static $keys = array(
 682              'Name',
 683              'Version',
 684              'Status',
 685              'Title',
 686              'Author',
 687              'Author Name',
 688              'Author URI',
 689              'Description',
 690              'Template',
 691              'Stylesheet',
 692              'Template Files',
 693              'Stylesheet Files',
 694              'Template Dir',
 695              'Stylesheet Dir',
 696              'Screenshot',
 697              'Tags',
 698              'Theme Root',
 699              'Theme Root URI',
 700              'Parent Theme',
 701          );
 702  
 703          return in_array( $offset, $keys, true );
 704      }
 705  
 706      /**
 707       * Method to implement ArrayAccess for keys formerly returned by get_themes().
 708       *
 709       * Author, Author Name, Author URI, and Description did not previously return
 710       * translated data. We are doing so now as it is safe to do. However, as
 711       * Name and Title could have been used as the key for get_themes(), both remain
 712       * untranslated for back compatibility. This means that ['Name'] is not ideal,
 713       * and care should be taken to use `$theme::display( 'Name' )` to get a properly
 714       * translated header.
 715       *
 716       * @since 3.4.0
 717       *
 718       * @param mixed $offset
 719       * @return mixed
 720       *
 721       * @phpstan-return ( $offset is Theme_Key ? mixed : null )
 722       */
 723      #[ReturnTypeWillChange]
 724  	public function offsetGet( $offset ) {
 725          switch ( $offset ) {
 726              case 'Name':
 727              case 'Title':
 728                  /*
 729                   * See note above about using translated data. get() is not ideal.
 730                   * It is only for backward compatibility. Use display().
 731                   */
 732                  return $this->get( 'Name' );
 733              case 'Author':
 734                  return $this->display( 'Author' );
 735              case 'Author Name':
 736                  return $this->display( 'Author', false );
 737              case 'Author URI':
 738                  return $this->display( 'AuthorURI' );
 739              case 'Description':
 740                  return $this->display( 'Description' );
 741              case 'Version':
 742              case 'Status':
 743                  return $this->get( $offset );
 744              case 'Template':
 745                  return $this->get_template();
 746              case 'Stylesheet':
 747                  return $this->get_stylesheet();
 748              case 'Template Files':
 749                  return $this->get_files( 'php', 1, true );
 750              case 'Stylesheet Files':
 751                  return $this->get_files( 'css', 0, false );
 752              case 'Template Dir':
 753                  return $this->get_template_directory();
 754              case 'Stylesheet Dir':
 755                  return $this->get_stylesheet_directory();
 756              case 'Screenshot':
 757                  return $this->get_screenshot( 'relative' );
 758              case 'Tags':
 759                  return $this->get( 'Tags' );
 760              case 'Theme Root':
 761                  return $this->get_theme_root();
 762              case 'Theme Root URI':
 763                  return $this->get_theme_root_uri();
 764              case 'Parent Theme':
 765                  return $this->parent() ? $this->parent()->get( 'Name' ) : '';
 766              default:
 767                  return null;
 768          }
 769      }
 770  
 771      /**
 772       * Returns errors property.
 773       *
 774       * @since 3.4.0
 775       *
 776       * @return WP_Error|false WP_Error if there are errors, or false.
 777       */
 778  	public function errors() {
 779          return is_wp_error( $this->errors ) ? $this->errors : false;
 780      }
 781  
 782      /**
 783       * Determines whether the theme exists.
 784       *
 785       * A theme with errors exists. A theme with the error of 'theme_not_found',
 786       * meaning that the theme's directory was not found, does not exist.
 787       *
 788       * @since 3.4.0
 789       *
 790       * @return bool Whether the theme exists.
 791       */
 792  	public function exists() {
 793          return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes(), true ) );
 794      }
 795  
 796      /**
 797       * Returns reference to the parent theme.
 798       *
 799       * @since 3.4.0
 800       *
 801       * @return WP_Theme|false Parent theme, or false if the active theme is not a child theme.
 802       */
 803  	public function parent() {
 804          return $this->parent ?? false;
 805      }
 806  
 807      /**
 808       * Perform reinitialization tasks.
 809       *
 810       * Prevents a callback from being injected during unserialization of an object.
 811       */
 812  	public function __wakeup() {
 813          if ( $this->parent && ! $this->parent instanceof self ) {
 814              throw new UnexpectedValueException();
 815          }
 816          if ( $this->headers && ! is_array( $this->headers ) ) {
 817              throw new UnexpectedValueException();
 818          }
 819          foreach ( $this->headers as $value ) {
 820              if ( ! is_string( $value ) ) {
 821                  throw new UnexpectedValueException();
 822              }
 823          }
 824          $this->headers_sanitized = array();
 825      }
 826  
 827      /**
 828       * Adds theme data to cache.
 829       *
 830       * Cache entries keyed by the theme and the type of data.
 831       *
 832       * @since 3.4.0
 833       *
 834       * @param string       $key  Type of data to store (theme, screenshot, headers, post_templates)
 835       * @param array|string $data Data to store
 836       * @return bool Return value from wp_cache_add()
 837       */
 838  	private function cache_add( $key, $data ) {
 839          return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration );
 840      }
 841  
 842      /**
 843       * Gets theme data from cache.
 844       *
 845       * Cache entries are keyed by the theme and the type of data.
 846       *
 847       * @since 3.4.0
 848       *
 849       * @param string $key Type of data to retrieve (theme, screenshot, headers, post_templates)
 850       * @return mixed Retrieved data
 851       */
 852  	private function cache_get( $key ) {
 853          return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' );
 854      }
 855  
 856      /**
 857       * Clears the cache for the theme.
 858       *
 859       * @since 3.4.0
 860       */
 861  	public function cache_delete() {
 862          foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) {
 863              wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
 864          }
 865          $this->template               = null;
 866          $this->textdomain_loaded      = null;
 867          $this->theme_root_uri         = null;
 868          $this->parent                 = null;
 869          $this->errors                 = null;
 870          $this->headers_sanitized      = null;
 871          $this->name_translated        = null;
 872          $this->block_theme            = null;
 873          $this->block_template_folders = null;
 874          $this->headers                = array();
 875          $this->__construct( $this->stylesheet, $this->theme_root );
 876          $this->delete_pattern_cache();
 877      }
 878  
 879      /**
 880       * Gets a raw, unformatted theme header.
 881       *
 882       * The header is sanitized, but is not translated, and is not marked up for display.
 883       * To get a theme header for display, use the display() method.
 884       *
 885       * Use the get_template() method, not the 'Template' header, for finding the template.
 886       * The 'Template' header is only good for what was written in the style.css, while
 887       * get_template() takes into account where WordPress actually located the theme and
 888       * whether it is actually valid.
 889       *
 890       * @since 3.4.0
 891       *
 892       * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 893       * @return string|array|false String or array (for Tags header) on success, false on failure.
 894       *
 895       * @phpstan-return (
 896       *     $header is 'Tags'
 897       *         ? string[]|false
 898       *         : ( $header is 'Name'|'ThemeURI'|'Description'|'Author'|'AuthorURI'|'Version'|'Template'|'Status'|'TextDomain'|'DomainPath'|'RequiresWP'|'RequiresPHP'|'UpdateURI'
 899       *             ? string|false
 900       *             : false )
 901       * )
 902       */
 903  	public function get( $header ) {
 904          if ( ! isset( $this->headers[ $header ] ) ) {
 905              return false;
 906          }
 907  
 908          if ( ! isset( $this->headers_sanitized ) ) {
 909              $this->headers_sanitized = $this->cache_get( 'headers' );
 910              if ( ! is_array( $this->headers_sanitized ) ) {
 911                  $this->headers_sanitized = array();
 912              }
 913          }
 914  
 915          if ( isset( $this->headers_sanitized[ $header ] ) ) {
 916              return $this->headers_sanitized[ $header ];
 917          }
 918  
 919          // If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets.
 920          if ( self::$persistently_cache ) {
 921              foreach ( array_keys( $this->headers ) as $_header ) {
 922                  $this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] );
 923              }
 924              $this->cache_add( 'headers', $this->headers_sanitized );
 925          } else {
 926              $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] );
 927          }
 928  
 929          return $this->headers_sanitized[ $header ];
 930      }
 931  
 932      /**
 933       * Gets a theme header, formatted and translated for display.
 934       *
 935       * @since 3.4.0
 936       *
 937       * @param string $header    Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 938       * @param bool   $markup    Optional. Whether to mark up the header. Defaults to true.
 939       * @param bool   $translate Optional. Whether to translate the header. Defaults to true.
 940       * @return string|array|false Processed header. An array for Tags if `$markup` is false, string otherwise.
 941       *                            False on failure.
 942       *
 943       * @phpstan-return (
 944       *     $markup is false
 945       *         ? ( $header is 'Tags'
 946       *             ? string[]|false
 947       *             : string|false )
 948       *         : string|false
 949       * )
 950       */
 951  	public function display( $header, $markup = true, $translate = true ) {
 952          $value = $this->get( $header );
 953          if ( false === $value ) {
 954              return false;
 955          }
 956  
 957          if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) {
 958              $translate = false;
 959          }
 960  
 961          if ( $translate ) {
 962              $value = $this->translate_header( $header, $value );
 963          }
 964  
 965          if ( $markup ) {
 966              $value = $this->markup_header( $header, $value, $translate );
 967          }
 968  
 969          return $value;
 970      }
 971  
 972      /**
 973       * Sanitizes a theme header.
 974       *
 975       * @since 3.4.0
 976       * @since 5.4.0 Added support for `Requires at least` and `Requires PHP` headers.
 977       * @since 6.1.0 Added support for `Update URI` header.
 978       *
 979       * @param string $header Theme header. Accepts 'Name', 'Description', 'Author', 'Version',
 980       *                       'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP',
 981       *                       'UpdateURI'.
 982       * @param string $value  Value to sanitize.
 983       * @return string|string[] An array for Tags header, string otherwise.
 984       *
 985       * @phpstan-return ( $header is 'Tags' ? string[] : string )
 986       */
 987  	private function sanitize_header( $header, $value ) {
 988          switch ( $header ) {
 989              case 'Status':
 990                  if ( ! $value ) {
 991                      $value = 'publish';
 992                      break;
 993                  }
 994                  // Fall through otherwise.
 995              case 'Name':
 996                  static $header_tags = array(
 997                      'abbr'    => array( 'title' => true ),
 998                      'acronym' => array( 'title' => true ),
 999                      'code'    => true,
1000                      'em'      => true,
1001                      'strong'  => true,
1002                  );
1003  
1004                  $value = wp_kses( $value, $header_tags );
1005                  break;
1006              case 'Author':
1007                  // There shouldn't be anchor tags in Author, but some themes like to be challenging.
1008              case 'Description':
1009                  static $header_tags_with_a = array(
1010                      'a'       => array(
1011                          'href'  => true,
1012                          'title' => true,
1013                      ),
1014                      'abbr'    => array( 'title' => true ),
1015                      'acronym' => array( 'title' => true ),
1016                      'code'    => true,
1017                      'em'      => true,
1018                      'strong'  => true,
1019                  );
1020  
1021                  $value = wp_kses( $value, $header_tags_with_a );
1022                  break;
1023              case 'ThemeURI':
1024              case 'AuthorURI':
1025                  $value = sanitize_url( $value );
1026                  break;
1027              case 'Tags':
1028                  $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
1029                  break;
1030              case 'Version':
1031              case 'RequiresWP':
1032              case 'RequiresPHP':
1033              case 'UpdateURI':
1034                  $value = strip_tags( $value );
1035                  break;
1036          }
1037  
1038          return $value;
1039      }
1040  
1041      /**
1042       * Marks up a theme header.
1043       *
1044       * @since 3.4.0
1045       *
1046       * @param string       $header    Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
1047       * @param string|array $value     Value to mark up. An array for Tags header, string otherwise.
1048       * @param bool         $translate Whether the header has been translated.
1049       * @return string Value, marked up.
1050       */
1051  	private function markup_header( $header, $value, $translate ) {
1052          switch ( $header ) {
1053              case 'Name':
1054                  if ( empty( $value ) ) {
1055                      $value = esc_html( $this->get_stylesheet() );
1056                  }
1057                  break;
1058              case 'Description':
1059                  $value = wptexturize( $value );
1060                  break;
1061              case 'Author':
1062                  if ( $this->get( 'AuthorURI' ) ) {
1063                      $value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value );
1064                  } elseif ( ! $value ) {
1065                      $value = __( 'Anonymous' );
1066                  }
1067                  break;
1068              case 'Tags':
1069                  static $comma = null;
1070                  if ( ! isset( $comma ) ) {
1071                      $comma = wp_get_list_item_separator();
1072                  }
1073                  $value = implode( $comma, $value );
1074                  break;
1075              case 'ThemeURI':
1076              case 'AuthorURI':
1077                  $value = esc_url( $value );
1078                  break;
1079          }
1080  
1081          return $value;
1082      }
1083  
1084      /**
1085       * Translates a theme header.
1086       *
1087       * @since 3.4.0
1088       *
1089       * @param string          $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
1090       * @param string|string[] $value  Value to translate. An array for Tags header, string otherwise.
1091       * @return string|string[] Translated value. An array for Tags header, string otherwise.
1092       *
1093       * @phpstan-return ( $value is string ? string : string[] )
1094       */
1095  	private function translate_header( $header, $value ) {
1096          switch ( $header ) {
1097              case 'Name':
1098                  // Cached for sorting reasons.
1099                  if ( isset( $this->name_translated ) ) {
1100                      return $this->name_translated;
1101                  }
1102  
1103                  // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
1104                  $this->name_translated = translate( $value, $this->get( 'TextDomain' ) );
1105  
1106                  return $this->name_translated;
1107              case 'Tags':
1108                  if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) {
1109                      return $value;
1110                  }
1111  
1112                  static $tags_list;
1113                  if ( ! isset( $tags_list ) ) {
1114                      $tags_list = array(
1115                          // As of 4.6, deprecated tags which are only used to provide translation for older themes.
1116                          'black'             => __( 'Black' ),
1117                          'blue'              => __( 'Blue' ),
1118                          'brown'             => __( 'Brown' ),
1119                          'gray'              => __( 'Gray' ),
1120                          'green'             => __( 'Green' ),
1121                          'orange'            => __( 'Orange' ),
1122                          'pink'              => __( 'Pink' ),
1123                          'purple'            => __( 'Purple' ),
1124                          'red'               => __( 'Red' ),
1125                          'silver'            => __( 'Silver' ),
1126                          'tan'               => __( 'Tan' ),
1127                          'white'             => __( 'White' ),
1128                          'yellow'            => __( 'Yellow' ),
1129                          'dark'              => _x( 'Dark', 'color scheme' ),
1130                          'light'             => _x( 'Light', 'color scheme' ),
1131                          'fixed-layout'      => __( 'Fixed Layout' ),
1132                          'fluid-layout'      => __( 'Fluid Layout' ),
1133                          'responsive-layout' => __( 'Responsive Layout' ),
1134                          'blavatar'          => __( 'Blavatar' ),
1135                          'photoblogging'     => __( 'Photoblogging' ),
1136                          'seasonal'          => __( 'Seasonal' ),
1137                      );
1138  
1139                      $feature_list = get_theme_feature_list( false ); // No API.
1140  
1141                      foreach ( $feature_list as $tags ) {
1142                          $tags_list += $tags;
1143                      }
1144                  }
1145  
1146                  foreach ( $value as &$tag ) {
1147                      if ( isset( $tags_list[ $tag ] ) ) {
1148                          $tag = $tags_list[ $tag ];
1149                      } elseif ( isset( self::$tag_map[ $tag ] ) ) {
1150                          $tag = $tags_list[ self::$tag_map[ $tag ] ];
1151                      }
1152                  }
1153  
1154                  return $value;
1155  
1156              default:
1157                  // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
1158                  $value = translate( $value, $this->get( 'TextDomain' ) );
1159          }
1160          return $value;
1161      }
1162  
1163      /**
1164       * Returns the directory name of the theme's "stylesheet" files, inside the theme root.
1165       *
1166       * In the case of a child theme, this is directory name of the child theme.
1167       * Otherwise, get_stylesheet() is the same as get_template().
1168       *
1169       * @since 3.4.0
1170       *
1171       * @return string Stylesheet
1172       */
1173  	public function get_stylesheet() {
1174          return $this->stylesheet;
1175      }
1176  
1177      /**
1178       * Returns the directory name of the theme's "template" files, inside the theme root.
1179       *
1180       * In the case of a child theme, this is the directory name of the parent theme.
1181       * Otherwise, the get_template() is the same as get_stylesheet().
1182       *
1183       * @since 3.4.0
1184       *
1185       * @return string Template
1186       */
1187  	public function get_template() {
1188          return $this->template;
1189      }
1190  
1191      /**
1192       * Returns the absolute path to the directory of a theme's "stylesheet" files.
1193       *
1194       * In the case of a child theme, this is the absolute path to the directory
1195       * of the child theme's files.
1196       *
1197       * @since 3.4.0
1198       *
1199       * @return string Absolute path of the stylesheet directory.
1200       */
1201  	public function get_stylesheet_directory() {
1202          if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes(), true ) ) {
1203              return '';
1204          }
1205  
1206          return $this->theme_root . '/' . $this->stylesheet;
1207      }
1208  
1209      /**
1210       * Returns the absolute path to the directory of a theme's "template" files.
1211       *
1212       * In the case of a child theme, this is the absolute path to the directory
1213       * of the parent theme's files.
1214       *
1215       * @since 3.4.0
1216       *
1217       * @return string Absolute path of the template directory.
1218       */
1219  	public function get_template_directory() {
1220          if ( $this->parent() ) {
1221              $theme_root = $this->parent()->theme_root;
1222          } else {
1223              $theme_root = $this->theme_root;
1224          }
1225  
1226          return $theme_root . '/' . $this->template;
1227      }
1228  
1229      /**
1230       * Returns the URL to the directory of a theme's "stylesheet" files.
1231       *
1232       * In the case of a child theme, this is the URL to the directory of the
1233       * child theme's files.
1234       *
1235       * @since 3.4.0
1236       *
1237       * @return string URL to the stylesheet directory.
1238       */
1239  	public function get_stylesheet_directory_uri() {
1240          return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
1241      }
1242  
1243      /**
1244       * Returns the URL to the directory of a theme's "template" files.
1245       *
1246       * In the case of a child theme, this is the URL to the directory of the
1247       * parent theme's files.
1248       *
1249       * @since 3.4.0
1250       *
1251       * @return string URL to the template directory.
1252       */
1253  	public function get_template_directory_uri() {
1254          if ( $this->parent() ) {
1255              $theme_root_uri = $this->parent()->get_theme_root_uri();
1256          } else {
1257              $theme_root_uri = $this->get_theme_root_uri();
1258          }
1259  
1260          return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
1261      }
1262  
1263      /**
1264       * Returns the absolute path to the directory of the theme root.
1265       *
1266       * This is typically the absolute path to wp-content/themes.
1267       *
1268       * @since 3.4.0
1269       *
1270       * @return string Theme root.
1271       */
1272  	public function get_theme_root() {
1273          return $this->theme_root;
1274      }
1275  
1276      /**
1277       * Returns the URL to the directory of the theme root.
1278       *
1279       * This is typically the absolute URL to wp-content/themes. This forms the basis
1280       * for all other URLs returned by WP_Theme, so we pass it to the public function
1281       * get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter.
1282       *
1283       * @since 3.4.0
1284       *
1285       * @return string Theme root URI.
1286       */
1287  	public function get_theme_root_uri() {
1288          if ( ! isset( $this->theme_root_uri ) ) {
1289              $this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
1290          }
1291          return $this->theme_root_uri;
1292      }
1293  
1294      /**
1295       * Returns the main screenshot file for the theme.
1296       *
1297       * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
1298       *
1299       * Screenshots for a theme must be in the stylesheet directory. (In the case of child
1300       * themes, parent theme screenshots are not inherited.)
1301       *
1302       * @since 3.4.0
1303       *
1304       * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
1305       * @return string|false Screenshot file. False if the theme does not have a screenshot.
1306       */
1307  	public function get_screenshot( $uri = 'uri' ) {
1308          $screenshot = $this->cache_get( 'screenshot' );
1309          if ( $screenshot ) {
1310              if ( 'relative' === $uri ) {
1311                  return $screenshot;
1312              }
1313              return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
1314          } elseif ( 0 === $screenshot ) {
1315              return false;
1316          }
1317  
1318          foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) {
1319              if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
1320                  $this->cache_add( 'screenshot', 'screenshot.' . $ext );
1321                  if ( 'relative' === $uri ) {
1322                      return 'screenshot.' . $ext;
1323                  }
1324                  return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
1325              }
1326          }
1327  
1328          $this->cache_add( 'screenshot', 0 );
1329          return false;
1330      }
1331  
1332      /**
1333       * Returns files in the theme's directory.
1334       *
1335       * @since 3.4.0
1336       *
1337       * @param string[]|string $type          Optional. Array of extensions to find, string of a single extension,
1338       *                                       or null for all extensions. Default null.
1339       * @param int             $depth         Optional. How deep to search for files. Defaults to a flat scan (0 depth).
1340       *                                       -1 depth is infinite.
1341       * @param bool            $search_parent Optional. Whether to return parent files. Default false.
1342       * @return string[] Array of files, keyed by the path to the file relative to the theme's directory, with the values
1343       *                  being absolute paths.
1344       */
1345  	public function get_files( $type = null, $depth = 0, $search_parent = false ) {
1346          $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
1347  
1348          if ( $search_parent && $this->parent() ) {
1349              $files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
1350          }
1351  
1352          return array_filter( $files );
1353      }
1354  
1355      /**
1356       * Returns the theme's post templates.
1357       *
1358       * @since 4.7.0
1359       * @since 5.8.0 Include block templates.
1360       *
1361       * @return array[] Array of page template arrays, keyed by post type and filename,
1362       *                 with the value of the translated header name.
1363       */
1364  	public function get_post_templates() {
1365          // If you screw up your active theme and we invalidate your parent, most things still work. Let it slide.
1366          if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) {
1367              return array();
1368          }
1369  
1370          $post_templates = $this->cache_get( 'post_templates' );
1371  
1372          if ( ! is_array( $post_templates ) ) {
1373              $post_templates = array();
1374  
1375              $files = (array) $this->get_files( 'php', 1, true );
1376  
1377              foreach ( $files as $file => $full_path ) {
1378                  $headers = get_file_data(
1379                      $full_path,
1380                      array(
1381                          'TemplateName'     => 'Template Name',
1382                          'TemplatePostType' => 'Template Post Type',
1383                      ),
1384                      'theme'
1385                  );
1386  
1387                  if ( ! $headers['TemplateName'] ) {
1388                      continue;
1389                  }
1390  
1391                  $types = array( 'page' );
1392                  if ( $headers['TemplatePostType'] ) {
1393                      $types = explode( ',', $headers['TemplatePostType'] );
1394                  }
1395  
1396                  foreach ( $types as $type ) {
1397                      $type                      = sanitize_key( $type );
1398                      $post_templates[ $type ] ??= array();
1399  
1400                      $post_templates[ $type ][ $file ] = $headers['TemplateName'];
1401                  }
1402              }
1403  
1404              $this->cache_add( 'post_templates', $post_templates );
1405          }
1406  
1407          if ( current_theme_supports( 'block-templates' ) ) {
1408              $block_templates = get_block_templates( array(), 'wp_template' );
1409              foreach ( get_post_types( array( 'public' => true ) ) as $type ) {
1410                  foreach ( $block_templates as $block_template ) {
1411                      if ( ! $block_template->is_custom ) {
1412                          continue;
1413                      }
1414  
1415                      if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) {
1416                          continue;
1417                      }
1418  
1419                      $post_templates[ $type ][ $block_template->slug ] = $block_template->title;
1420                  }
1421              }
1422          }
1423  
1424          if ( $this->load_textdomain() ) {
1425              foreach ( $post_templates as &$post_type ) {
1426                  foreach ( $post_type as &$post_template ) {
1427                      $post_template = $this->translate_header( 'Template Name', $post_template );
1428                  }
1429              }
1430          }
1431  
1432          return $post_templates;
1433      }
1434  
1435      /**
1436       * Returns the theme's post templates for a given post type.
1437       *
1438       * @since 3.4.0
1439       * @since 4.7.0 Added the `$post_type` parameter.
1440       *
1441       * @param WP_Post|null $post      Optional. The post being edited, provided for context.
1442       * @param string       $post_type Optional. Post type to get the templates for. Default 'page'.
1443       *                                If a post is provided, its post type is used.
1444       * @return string[] Array of template header names keyed by the template file name.
1445       */
1446  	public function get_page_templates( $post = null, $post_type = 'page' ) {
1447          if ( $post ) {
1448              $post_type = get_post_type( $post );
1449          }
1450  
1451          $post_templates = $this->get_post_templates();
1452          $post_templates = $post_templates[ $post_type ] ?? array();
1453  
1454          /**
1455           * Filters list of page templates for a theme.
1456           *
1457           * @since 4.9.6
1458           *
1459           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1460           * @param WP_Theme     $theme          The theme object.
1461           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1462           * @param string       $post_type      Post type to get the templates for.
1463           */
1464          $post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type );
1465  
1466          /**
1467           * Filters list of page templates for a theme.
1468           *
1469           * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1470           *
1471           * Possible hook names include:
1472           *
1473           *  - `theme_post_templates`
1474           *  - `theme_page_templates`
1475           *  - `theme_attachment_templates`
1476           *
1477           * @since 3.9.0
1478           * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1479           * @since 4.7.0 Added the `$post_type` parameter.
1480           *
1481           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1482           * @param WP_Theme     $theme          The theme object.
1483           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1484           * @param string       $post_type      Post type to get the templates for.
1485           */
1486          $post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1487  
1488          return $post_templates;
1489      }
1490  
1491      /**
1492       * Scans a directory for files of a certain extension.
1493       *
1494       * @since 3.4.0
1495       *
1496       * @param string            $path          Absolute path to search.
1497       * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1498       *                                         or null for all extensions. Default null.
1499       * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1500       *                                         -1 (infinite depth). Default 0.
1501       * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1502       *                                         returned path for the found files, particularly when this function
1503       *                                         recurses to lower depths. Default empty.
1504       * @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1505       *                        with `$relative_path`, with the values being absolute paths. False otherwise.
1506       */
1507  	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1508          if ( ! is_dir( $path ) ) {
1509              return false;
1510          }
1511  
1512          if ( $extensions ) {
1513              $extensions  = (array) $extensions;
1514              $_extensions = implode( '|', $extensions );
1515          }
1516  
1517          $relative_path = trailingslashit( $relative_path );
1518          if ( '/' === $relative_path ) {
1519              $relative_path = '';
1520          }
1521  
1522          $results = scandir( $path );
1523          $files   = array();
1524  
1525          /**
1526           * Filters the array of excluded directories and files while scanning theme folder.
1527           *
1528           * @since 4.7.4
1529           *
1530           * @param string[] $exclusions Array of excluded directories and files.
1531           */
1532          $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
1533  
1534          foreach ( $results as $result ) {
1535              if ( '.' === $result[0] || in_array( $result, $exclusions, true ) ) {
1536                  continue;
1537              }
1538              if ( is_dir( $path . '/' . $result ) ) {
1539                  if ( ! $depth ) {
1540                      continue;
1541                  }
1542                  $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result );
1543                  $files = array_merge_recursive( $files, $found );
1544              } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1545                  $files[ $relative_path . $result ] = $path . '/' . $result;
1546              }
1547          }
1548  
1549          return $files;
1550      }
1551  
1552      /**
1553       * Loads the theme's textdomain.
1554       *
1555       * Translation files are not inherited from the parent theme. TODO: If this fails for the
1556       * child theme, it should probably try to load the parent theme's translations.
1557       *
1558       * @since 3.4.0
1559       *
1560       * @return bool True if the textdomain was successfully loaded or has already been loaded.
1561       *  False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1562       */
1563  	public function load_textdomain() {
1564          if ( isset( $this->textdomain_loaded ) ) {
1565              return $this->textdomain_loaded;
1566          }
1567  
1568          $textdomain = $this->get( 'TextDomain' );
1569          if ( ! $textdomain ) {
1570              $this->textdomain_loaded = false;
1571              return false;
1572          }
1573  
1574          if ( is_textdomain_loaded( $textdomain ) ) {
1575              $this->textdomain_loaded = true;
1576              return true;
1577          }
1578  
1579          $path       = $this->get_stylesheet_directory();
1580          $domainpath = $this->get( 'DomainPath' );
1581          if ( $domainpath ) {
1582              $path .= $domainpath;
1583          } else {
1584              $path .= '/languages';
1585          }
1586  
1587          $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1588          return $this->textdomain_loaded;
1589      }
1590  
1591      /**
1592       * Determines whether the theme is allowed (multisite only).
1593       *
1594       * @since 3.4.0
1595       *
1596       * @param string $check   Optional. Whether to check only the 'network'-wide settings, the 'site'
1597       *                        settings, or 'both'. Defaults to 'both'.
1598       * @param int    $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1599       * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1600       */
1601  	public function is_allowed( $check = 'both', $blog_id = null ) {
1602          if ( ! is_multisite() ) {
1603              return true;
1604          }
1605  
1606          if ( 'both' === $check || 'network' === $check ) {
1607              $allowed = self::get_allowed_on_network();
1608              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1609                  return true;
1610              }
1611          }
1612  
1613          if ( 'both' === $check || 'site' === $check ) {
1614              $allowed = self::get_allowed_on_site( $blog_id );
1615              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1616                  return true;
1617              }
1618          }
1619  
1620          return false;
1621      }
1622  
1623      /**
1624       * Returns whether this theme is a block-based theme or not.
1625       *
1626       * @since 5.9.0
1627       *
1628       * @return bool
1629       */
1630  	public function is_block_theme() {
1631          if ( isset( $this->block_theme ) ) {
1632              return $this->block_theme;
1633          }
1634  
1635          $paths_to_index_block_template = array(
1636              $this->get_file_path( '/templates/index.html' ),
1637              $this->get_file_path( '/block-templates/index.html' ),
1638          );
1639  
1640          $this->block_theme = false;
1641  
1642          foreach ( $paths_to_index_block_template as $path_to_index_block_template ) {
1643              if ( is_file( $path_to_index_block_template ) && is_readable( $path_to_index_block_template ) ) {
1644                  $this->block_theme = true;
1645                  break;
1646              }
1647          }
1648  
1649          return $this->block_theme;
1650      }
1651  
1652      /**
1653       * Retrieves the path of a file in the theme.
1654       *
1655       * Searches in the stylesheet directory before the template directory so themes
1656       * which inherit from a parent theme can just override one file.
1657       *
1658       * @since 5.9.0
1659       *
1660       * @param string $file Optional. File to search for in the stylesheet directory.
1661       * @return string The path of the file.
1662       */
1663  	public function get_file_path( $file = '' ) {
1664          $file = ltrim( $file, '/' );
1665  
1666          $stylesheet_directory = $this->get_stylesheet_directory();
1667          $template_directory   = $this->get_template_directory();
1668  
1669          if ( empty( $file ) ) {
1670              $path = $stylesheet_directory;
1671          } elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
1672              $path = $stylesheet_directory . '/' . $file;
1673          } else {
1674              $path = $template_directory . '/' . $file;
1675          }
1676  
1677          /** This filter is documented in wp-includes/link-template.php */
1678          return apply_filters( 'theme_file_path', $path, $file );
1679      }
1680  
1681      /**
1682       * Determines the latest WordPress default theme that is installed.
1683       *
1684       * This hits the filesystem.
1685       *
1686       * @since 4.4.0
1687       *
1688       * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1689       */
1690  	public static function get_core_default_theme() {
1691          foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1692              $theme = wp_get_theme( $slug );
1693              if ( $theme->exists() ) {
1694                  return $theme;
1695              }
1696          }
1697          return false;
1698      }
1699  
1700      /**
1701       * Returns array of stylesheet names of themes allowed on the site or network.
1702       *
1703       * @since 3.4.0
1704       *
1705       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1706       * @return string[] Array of stylesheet names.
1707       */
1708  	public static function get_allowed( $blog_id = null ) {
1709          /**
1710           * Filters the array of themes allowed on the network.
1711           *
1712           * Site is provided as context so that a list of network allowed themes can
1713           * be filtered further.
1714           *
1715           * @since 4.5.0
1716           *
1717           * @param string[] $allowed_themes An array of theme stylesheet names.
1718           * @param int      $blog_id        ID of the site.
1719           */
1720          $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1721          return $network + self::get_allowed_on_site( $blog_id );
1722      }
1723  
1724      /**
1725       * Returns array of stylesheet names of themes allowed on the network.
1726       *
1727       * @since 3.4.0
1728       *
1729       * @return string[] Array of stylesheet names.
1730       */
1731  	public static function get_allowed_on_network() {
1732          static $allowed_themes;
1733          if ( ! isset( $allowed_themes ) ) {
1734              $allowed_themes = (array) get_site_option( 'allowedthemes' );
1735          }
1736  
1737          /**
1738           * Filters the array of themes allowed on the network.
1739           *
1740           * @since MU (3.0.0)
1741           *
1742           * @param string[] $allowed_themes An array of theme stylesheet names.
1743           */
1744          $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1745  
1746          return $allowed_themes;
1747      }
1748  
1749      /**
1750       * Returns array of stylesheet names of themes allowed on the site.
1751       *
1752       * @since 3.4.0
1753       *
1754       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1755       * @return string[] Array of stylesheet names.
1756       */
1757  	public static function get_allowed_on_site( $blog_id = null ) {
1758          static $allowed_themes = array();
1759  
1760          if ( ! $blog_id || ! is_multisite() ) {
1761              $blog_id = get_current_blog_id();
1762          }
1763  
1764          if ( isset( $allowed_themes[ $blog_id ] ) ) {
1765              /**
1766               * Filters the array of themes allowed on the site.
1767               *
1768               * @since 4.5.0
1769               *
1770               * @param string[] $allowed_themes An array of theme stylesheet names.
1771               * @param int      $blog_id        ID of the site. Defaults to current site.
1772               */
1773              return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1774          }
1775  
1776          $current = get_current_blog_id() === $blog_id;
1777  
1778          if ( $current ) {
1779              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1780          } else {
1781              switch_to_blog( $blog_id );
1782              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1783              restore_current_blog();
1784          }
1785  
1786          /*
1787           * This is all super old MU back compat joy.
1788           * 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1789           */
1790          if ( false === $allowed_themes[ $blog_id ] ) {
1791              if ( $current ) {
1792                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1793              } else {
1794                  switch_to_blog( $blog_id );
1795                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1796                  restore_current_blog();
1797              }
1798  
1799              if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1800                  $allowed_themes[ $blog_id ] = array();
1801              } else {
1802                  $converted = array();
1803                  $themes    = wp_get_themes();
1804                  foreach ( $themes as $stylesheet => $theme_data ) {
1805                      if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) {
1806                          $converted[ $stylesheet ] = true;
1807                      }
1808                  }
1809                  $allowed_themes[ $blog_id ] = $converted;
1810              }
1811              // Set the option so we never have to go through this pain again.
1812              if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1813                  if ( $current ) {
1814                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false );
1815                      delete_option( 'allowed_themes' );
1816                  } else {
1817                      switch_to_blog( $blog_id );
1818                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false );
1819                      delete_option( 'allowed_themes' );
1820                      restore_current_blog();
1821                  }
1822              }
1823          }
1824  
1825          /** This filter is documented in wp-includes/class-wp-theme.php */
1826          return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1827      }
1828  
1829      /**
1830       * Returns the folder names of the block template directories.
1831       *
1832       * @since 6.4.0
1833       *
1834       * @return string[] {
1835       *     Folder names used by block themes.
1836       *
1837       *     @type string $wp_template      Theme-relative directory name for block templates.
1838       *     @type string $wp_template_part Theme-relative directory name for block template parts.
1839       * }
1840       */
1841  	public function get_block_template_folders() {
1842          // Return set/cached value if available.
1843          if ( isset( $this->block_template_folders ) ) {
1844              return $this->block_template_folders;
1845          }
1846  
1847          $this->block_template_folders = $this->default_template_folders;
1848  
1849          $stylesheet_directory = $this->get_stylesheet_directory();
1850          // If the theme uses deprecated block template folders.
1851          if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) {
1852              $this->block_template_folders = array(
1853                  'wp_template'      => 'block-templates',
1854                  'wp_template_part' => 'block-template-parts',
1855              );
1856          }
1857          return $this->block_template_folders;
1858      }
1859  
1860      /**
1861       * Gets block pattern data for a specified theme.
1862       * Each pattern is defined as a PHP file and defines
1863       * its metadata using plugin-style headers. The minimum required definition is:
1864       *
1865       *     /**
1866       *      * Title: My Pattern
1867       *      * Slug: my-theme/my-pattern
1868       *      *
1869       *
1870       * The output of the PHP source corresponds to the content of the pattern, e.g.:
1871       *
1872       *     <main><p><?php echo "Hello"; ?></p></main>
1873       *
1874       * If applicable, this will collect from both parent and child theme.
1875       *
1876       * Other settable fields include:
1877       *
1878       *     - Description
1879       *     - Viewport Width
1880       *     - Inserter         (yes/no)
1881       *     - Categories       (comma-separated values)
1882       *     - Keywords         (comma-separated values)
1883       *     - Block Types      (comma-separated values)
1884       *     - Post Types       (comma-separated values)
1885       *     - Template Types   (comma-separated values)
1886       *
1887       * @since 6.4.0
1888       *
1889       * @return array Block pattern data.
1890       */
1891  	public function get_block_patterns() {
1892          $can_use_cached = ! wp_is_development_mode( 'theme' );
1893  
1894          $pattern_data = $this->get_pattern_cache();
1895          if ( is_array( $pattern_data ) ) {
1896              if ( $can_use_cached ) {
1897                  return $pattern_data;
1898              }
1899              // If in development mode, clear pattern cache.
1900              $this->delete_pattern_cache();
1901          }
1902  
1903          $dirpath      = $this->get_stylesheet_directory() . '/patterns';
1904          $pattern_data = array();
1905  
1906          if ( ! file_exists( $dirpath ) ) {
1907              if ( $can_use_cached ) {
1908                  $this->set_pattern_cache( $pattern_data );
1909              }
1910              return $pattern_data;
1911          }
1912  
1913          $files = (array) self::scandir( $dirpath, 'php', -1 );
1914  
1915          /**
1916           * Filters list of block pattern files for a theme.
1917           *
1918           * @since 6.8.0
1919           *
1920           * @param array  $files   Array of theme files found within `patterns` directory.
1921           * @param string $dirpath Path of theme `patterns` directory being scanned.
1922           */
1923          $files = apply_filters( 'theme_block_pattern_files', $files, $dirpath );
1924  
1925          $dirpath = trailingslashit( $dirpath );
1926  
1927          if ( ! $files ) {
1928              if ( $can_use_cached ) {
1929                  $this->set_pattern_cache( $pattern_data );
1930              }
1931              return $pattern_data;
1932          }
1933  
1934          $default_headers = array(
1935              'title'         => 'Title',
1936              'slug'          => 'Slug',
1937              'description'   => 'Description',
1938              'viewportWidth' => 'Viewport Width',
1939              'inserter'      => 'Inserter',
1940              'categories'    => 'Categories',
1941              'keywords'      => 'Keywords',
1942              'blockTypes'    => 'Block Types',
1943              'postTypes'     => 'Post Types',
1944              'templateTypes' => 'Template Types',
1945          );
1946  
1947          $properties_to_parse = array(
1948              'categories',
1949              'keywords',
1950              'blockTypes',
1951              'postTypes',
1952              'templateTypes',
1953          );
1954  
1955          foreach ( $files as $file ) {
1956              $pattern = get_file_data( $file, $default_headers );
1957  
1958              if ( empty( $pattern['slug'] ) ) {
1959                  _doing_it_wrong(
1960                      __FUNCTION__,
1961                      sprintf(
1962                          /* translators: 1: file name. */
1963                          __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
1964                          $file
1965                      ),
1966                      '6.0.0'
1967                  );
1968                  continue;
1969              }
1970  
1971              if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
1972                  _doing_it_wrong(
1973                      __FUNCTION__,
1974                      sprintf(
1975                          /* translators: 1: file name; 2: slug value found. */
1976                          __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
1977                          $file,
1978                          $pattern['slug']
1979                      ),
1980                      '6.0.0'
1981                  );
1982              }
1983  
1984              // Title is a required property.
1985              if ( ! $pattern['title'] ) {
1986                  _doing_it_wrong(
1987                      __FUNCTION__,
1988                      sprintf(
1989                          /* translators: 1: file name. */
1990                          __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
1991                          $file
1992                      ),
1993                      '6.0.0'
1994                  );
1995                  continue;
1996              }
1997  
1998              // For properties of type array, parse data as comma-separated.
1999              foreach ( $properties_to_parse as $property ) {
2000                  if ( ! empty( $pattern[ $property ] ) ) {
2001                      $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
2002                  } else {
2003                      unset( $pattern[ $property ] );
2004                  }
2005              }
2006  
2007              // Parse properties of type int.
2008              $property = 'viewportWidth';
2009              if ( ! empty( $pattern[ $property ] ) ) {
2010                  $pattern[ $property ] = (int) $pattern[ $property ];
2011              } else {
2012                  unset( $pattern[ $property ] );
2013              }
2014  
2015              // Parse properties of type bool.
2016              $property = 'inserter';
2017              if ( ! empty( $pattern[ $property ] ) ) {
2018                  $pattern[ $property ] = in_array(
2019                      strtolower( $pattern[ $property ] ),
2020                      array( 'yes', 'true' ),
2021                      true
2022                  );
2023              } else {
2024                  unset( $pattern[ $property ] );
2025              }
2026  
2027              $key = str_replace( $dirpath, '', $file );
2028  
2029              $pattern_data[ $key ] = $pattern;
2030          }
2031  
2032          if ( $can_use_cached ) {
2033              $this->set_pattern_cache( $pattern_data );
2034          }
2035  
2036          return $pattern_data;
2037      }
2038  
2039      /**
2040       * Gets block pattern cache.
2041       *
2042       * @since 6.4.0
2043       * @since 6.6.0 Uses transients to cache regardless of site environment.
2044       *
2045       * @return array|false Returns an array of patterns if cache is found, otherwise false.
2046       */
2047  	private function get_pattern_cache() {
2048          if ( ! $this->exists() ) {
2049              return false;
2050          }
2051  
2052          $pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
2053  
2054          if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
2055              return $pattern_data['patterns'];
2056          }
2057          return false;
2058      }
2059  
2060      /**
2061       * Sets block pattern cache.
2062       *
2063       * @since 6.4.0
2064       * @since 6.6.0 Uses transients to cache regardless of site environment.
2065       *
2066       * @param array $patterns Block patterns data to set in cache.
2067       */
2068  	private function set_pattern_cache( array $patterns ) {
2069          $pattern_data = array(
2070              'version'  => $this->get( 'Version' ),
2071              'patterns' => $patterns,
2072          );
2073  
2074          /**
2075           * Filters the cache expiration time for theme files.
2076           *
2077           * @since 6.6.0
2078           *
2079           * @param int    $cache_expiration Cache expiration time in seconds.
2080           * @param string $cache_type       Type of cache being set.
2081           */
2082          $cache_expiration = (int) apply_filters( 'wp_theme_files_cache_ttl', self::$cache_expiration, 'theme_block_patterns' );
2083  
2084          // We don't want to cache patterns infinitely.
2085          if ( $cache_expiration <= 0 ) {
2086              _doing_it_wrong(
2087                  __METHOD__,
2088                  sprintf(
2089                      /* translators: %1$s: The filter name.*/
2090                      __( 'The %1$s filter must return an integer value greater than 0.' ),
2091                      '<code>wp_theme_files_cache_ttl</code>'
2092                  ),
2093                  '6.6.0'
2094              );
2095  
2096              $cache_expiration = self::$cache_expiration;
2097          }
2098  
2099          set_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash, $pattern_data, $cache_expiration );
2100      }
2101  
2102      /**
2103       * Clears block pattern cache.
2104       *
2105       * @since 6.4.0
2106       * @since 6.6.0 Uses transients to cache regardless of site environment.
2107       */
2108  	public function delete_pattern_cache() {
2109          delete_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
2110      }
2111  
2112      /**
2113       * Enables a theme for all sites on the current network.
2114       *
2115       * @since 4.6.0
2116       *
2117       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
2118       */
2119  	public static function network_enable_theme( $stylesheets ) {
2120          if ( ! is_multisite() ) {
2121              return;
2122          }
2123  
2124          if ( ! is_array( $stylesheets ) ) {
2125              $stylesheets = array( $stylesheets );
2126          }
2127  
2128          $allowed_themes = get_site_option( 'allowedthemes' );
2129          foreach ( $stylesheets as $stylesheet ) {
2130              $allowed_themes[ $stylesheet ] = true;
2131          }
2132  
2133          update_site_option( 'allowedthemes', $allowed_themes );
2134      }
2135  
2136      /**
2137       * Disables a theme for all sites on the current network.
2138       *
2139       * @since 4.6.0
2140       *
2141       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
2142       */
2143  	public static function network_disable_theme( $stylesheets ) {
2144          if ( ! is_multisite() ) {
2145              return;
2146          }
2147  
2148          if ( ! is_array( $stylesheets ) ) {
2149              $stylesheets = array( $stylesheets );
2150          }
2151  
2152          $allowed_themes = get_site_option( 'allowedthemes' );
2153          foreach ( $stylesheets as $stylesheet ) {
2154              if ( isset( $allowed_themes[ $stylesheet ] ) ) {
2155                  unset( $allowed_themes[ $stylesheet ] );
2156              }
2157          }
2158  
2159          update_site_option( 'allowedthemes', $allowed_themes );
2160      }
2161  
2162      /**
2163       * Sorts themes by name.
2164       *
2165       * @since 3.4.0
2166       *
2167       * @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
2168       */
2169  	public static function sort_by_name( &$themes ) {
2170          if ( str_starts_with( get_user_locale(), 'en_' ) ) {
2171              uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
2172          } else {
2173              foreach ( $themes as $key => $theme ) {
2174                  $theme->translate_header( 'Name', $theme->headers['Name'] );
2175              }
2176              uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
2177          }
2178      }
2179  
2180      /**
2181       * Callback function for usort() to naturally sort themes by name.
2182       *
2183       * Accesses the Name header directly from the class for maximum speed.
2184       * Would choke on HTML but we don't care enough to slow it down with strip_tags().
2185       *
2186       * @since 3.4.0
2187       *
2188       * @param WP_Theme $a First theme.
2189       * @param WP_Theme $b Second theme.
2190       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
2191       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
2192       */
2193  	private static function _name_sort( $a, $b ) {
2194          return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
2195      }
2196  
2197      /**
2198       * Callback function for usort() to naturally sort themes by translated name.
2199       *
2200       * @since 3.4.0
2201       *
2202       * @param WP_Theme $a First theme.
2203       * @param WP_Theme $b Second theme.
2204       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
2205       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
2206       */
2207  	private static function _name_sort_i18n( $a, $b ) {
2208          return strnatcasecmp( $a->name_translated, $b->name_translated );
2209      }
2210  }


Generated : Mon Nov 9 08:20:01 2026 Cross-referenced by PHPXref