[ 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                      if ( ! isset( $post_templates[ $type ] ) ) {
1399                          $post_templates[ $type ] = array();
1400                      }
1401  
1402                      $post_templates[ $type ][ $file ] = $headers['TemplateName'];
1403                  }
1404              }
1405  
1406              $this->cache_add( 'post_templates', $post_templates );
1407          }
1408  
1409          if ( current_theme_supports( 'block-templates' ) ) {
1410              $block_templates = get_block_templates( array(), 'wp_template' );
1411              foreach ( get_post_types( array( 'public' => true ) ) as $type ) {
1412                  foreach ( $block_templates as $block_template ) {
1413                      if ( ! $block_template->is_custom ) {
1414                          continue;
1415                      }
1416  
1417                      if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) {
1418                          continue;
1419                      }
1420  
1421                      $post_templates[ $type ][ $block_template->slug ] = $block_template->title;
1422                  }
1423              }
1424          }
1425  
1426          if ( $this->load_textdomain() ) {
1427              foreach ( $post_templates as &$post_type ) {
1428                  foreach ( $post_type as &$post_template ) {
1429                      $post_template = $this->translate_header( 'Template Name', $post_template );
1430                  }
1431              }
1432          }
1433  
1434          return $post_templates;
1435      }
1436  
1437      /**
1438       * Returns the theme's post templates for a given post type.
1439       *
1440       * @since 3.4.0
1441       * @since 4.7.0 Added the `$post_type` parameter.
1442       *
1443       * @param WP_Post|null $post      Optional. The post being edited, provided for context.
1444       * @param string       $post_type Optional. Post type to get the templates for. Default 'page'.
1445       *                                If a post is provided, its post type is used.
1446       * @return string[] Array of template header names keyed by the template file name.
1447       */
1448  	public function get_page_templates( $post = null, $post_type = 'page' ) {
1449          if ( $post ) {
1450              $post_type = get_post_type( $post );
1451          }
1452  
1453          $post_templates = $this->get_post_templates();
1454          $post_templates = $post_templates[ $post_type ] ?? array();
1455  
1456          /**
1457           * Filters list of page templates for a theme.
1458           *
1459           * @since 4.9.6
1460           *
1461           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1462           * @param WP_Theme     $theme          The theme object.
1463           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1464           * @param string       $post_type      Post type to get the templates for.
1465           */
1466          $post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type );
1467  
1468          /**
1469           * Filters list of page templates for a theme.
1470           *
1471           * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1472           *
1473           * Possible hook names include:
1474           *
1475           *  - `theme_post_templates`
1476           *  - `theme_page_templates`
1477           *  - `theme_attachment_templates`
1478           *
1479           * @since 3.9.0
1480           * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1481           * @since 4.7.0 Added the `$post_type` parameter.
1482           *
1483           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1484           * @param WP_Theme     $theme          The theme object.
1485           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1486           * @param string       $post_type      Post type to get the templates for.
1487           */
1488          $post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1489  
1490          return $post_templates;
1491      }
1492  
1493      /**
1494       * Scans a directory for files of a certain extension.
1495       *
1496       * @since 3.4.0
1497       *
1498       * @param string            $path          Absolute path to search.
1499       * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1500       *                                         or null for all extensions. Default null.
1501       * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1502       *                                         -1 (infinite depth). Default 0.
1503       * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1504       *                                         returned path for the found files, particularly when this function
1505       *                                         recurses to lower depths. Default empty.
1506       * @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1507       *                        with `$relative_path`, with the values being absolute paths. False otherwise.
1508       */
1509  	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1510          if ( ! is_dir( $path ) ) {
1511              return false;
1512          }
1513  
1514          if ( $extensions ) {
1515              $extensions  = (array) $extensions;
1516              $_extensions = implode( '|', $extensions );
1517          }
1518  
1519          $relative_path = trailingslashit( $relative_path );
1520          if ( '/' === $relative_path ) {
1521              $relative_path = '';
1522          }
1523  
1524          $results = scandir( $path );
1525          $files   = array();
1526  
1527          /**
1528           * Filters the array of excluded directories and files while scanning theme folder.
1529           *
1530           * @since 4.7.4
1531           *
1532           * @param string[] $exclusions Array of excluded directories and files.
1533           */
1534          $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
1535  
1536          foreach ( $results as $result ) {
1537              if ( '.' === $result[0] || in_array( $result, $exclusions, true ) ) {
1538                  continue;
1539              }
1540              if ( is_dir( $path . '/' . $result ) ) {
1541                  if ( ! $depth ) {
1542                      continue;
1543                  }
1544                  $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result );
1545                  $files = array_merge_recursive( $files, $found );
1546              } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1547                  $files[ $relative_path . $result ] = $path . '/' . $result;
1548              }
1549          }
1550  
1551          return $files;
1552      }
1553  
1554      /**
1555       * Loads the theme's textdomain.
1556       *
1557       * Translation files are not inherited from the parent theme. TODO: If this fails for the
1558       * child theme, it should probably try to load the parent theme's translations.
1559       *
1560       * @since 3.4.0
1561       *
1562       * @return bool True if the textdomain was successfully loaded or has already been loaded.
1563       *  False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1564       */
1565  	public function load_textdomain() {
1566          if ( isset( $this->textdomain_loaded ) ) {
1567              return $this->textdomain_loaded;
1568          }
1569  
1570          $textdomain = $this->get( 'TextDomain' );
1571          if ( ! $textdomain ) {
1572              $this->textdomain_loaded = false;
1573              return false;
1574          }
1575  
1576          if ( is_textdomain_loaded( $textdomain ) ) {
1577              $this->textdomain_loaded = true;
1578              return true;
1579          }
1580  
1581          $path       = $this->get_stylesheet_directory();
1582          $domainpath = $this->get( 'DomainPath' );
1583          if ( $domainpath ) {
1584              $path .= $domainpath;
1585          } else {
1586              $path .= '/languages';
1587          }
1588  
1589          $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1590          return $this->textdomain_loaded;
1591      }
1592  
1593      /**
1594       * Determines whether the theme is allowed (multisite only).
1595       *
1596       * @since 3.4.0
1597       *
1598       * @param string $check   Optional. Whether to check only the 'network'-wide settings, the 'site'
1599       *                        settings, or 'both'. Defaults to 'both'.
1600       * @param int    $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1601       * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1602       */
1603  	public function is_allowed( $check = 'both', $blog_id = null ) {
1604          if ( ! is_multisite() ) {
1605              return true;
1606          }
1607  
1608          if ( 'both' === $check || 'network' === $check ) {
1609              $allowed = self::get_allowed_on_network();
1610              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1611                  return true;
1612              }
1613          }
1614  
1615          if ( 'both' === $check || 'site' === $check ) {
1616              $allowed = self::get_allowed_on_site( $blog_id );
1617              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1618                  return true;
1619              }
1620          }
1621  
1622          return false;
1623      }
1624  
1625      /**
1626       * Returns whether this theme is a block-based theme or not.
1627       *
1628       * @since 5.9.0
1629       *
1630       * @return bool
1631       */
1632  	public function is_block_theme() {
1633          if ( isset( $this->block_theme ) ) {
1634              return $this->block_theme;
1635          }
1636  
1637          $paths_to_index_block_template = array(
1638              $this->get_file_path( '/templates/index.html' ),
1639              $this->get_file_path( '/block-templates/index.html' ),
1640          );
1641  
1642          $this->block_theme = false;
1643  
1644          foreach ( $paths_to_index_block_template as $path_to_index_block_template ) {
1645              if ( is_file( $path_to_index_block_template ) && is_readable( $path_to_index_block_template ) ) {
1646                  $this->block_theme = true;
1647                  break;
1648              }
1649          }
1650  
1651          return $this->block_theme;
1652      }
1653  
1654      /**
1655       * Retrieves the path of a file in the theme.
1656       *
1657       * Searches in the stylesheet directory before the template directory so themes
1658       * which inherit from a parent theme can just override one file.
1659       *
1660       * @since 5.9.0
1661       *
1662       * @param string $file Optional. File to search for in the stylesheet directory.
1663       * @return string The path of the file.
1664       */
1665  	public function get_file_path( $file = '' ) {
1666          $file = ltrim( $file, '/' );
1667  
1668          $stylesheet_directory = $this->get_stylesheet_directory();
1669          $template_directory   = $this->get_template_directory();
1670  
1671          if ( empty( $file ) ) {
1672              $path = $stylesheet_directory;
1673          } elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
1674              $path = $stylesheet_directory . '/' . $file;
1675          } else {
1676              $path = $template_directory . '/' . $file;
1677          }
1678  
1679          /** This filter is documented in wp-includes/link-template.php */
1680          return apply_filters( 'theme_file_path', $path, $file );
1681      }
1682  
1683      /**
1684       * Determines the latest WordPress default theme that is installed.
1685       *
1686       * This hits the filesystem.
1687       *
1688       * @since 4.4.0
1689       *
1690       * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1691       */
1692  	public static function get_core_default_theme() {
1693          foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1694              $theme = wp_get_theme( $slug );
1695              if ( $theme->exists() ) {
1696                  return $theme;
1697              }
1698          }
1699          return false;
1700      }
1701  
1702      /**
1703       * Returns array of stylesheet names of themes allowed on the site or network.
1704       *
1705       * @since 3.4.0
1706       *
1707       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1708       * @return string[] Array of stylesheet names.
1709       */
1710  	public static function get_allowed( $blog_id = null ) {
1711          /**
1712           * Filters the array of themes allowed on the network.
1713           *
1714           * Site is provided as context so that a list of network allowed themes can
1715           * be filtered further.
1716           *
1717           * @since 4.5.0
1718           *
1719           * @param string[] $allowed_themes An array of theme stylesheet names.
1720           * @param int      $blog_id        ID of the site.
1721           */
1722          $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1723          return $network + self::get_allowed_on_site( $blog_id );
1724      }
1725  
1726      /**
1727       * Returns array of stylesheet names of themes allowed on the network.
1728       *
1729       * @since 3.4.0
1730       *
1731       * @return string[] Array of stylesheet names.
1732       */
1733  	public static function get_allowed_on_network() {
1734          static $allowed_themes;
1735          if ( ! isset( $allowed_themes ) ) {
1736              $allowed_themes = (array) get_site_option( 'allowedthemes' );
1737          }
1738  
1739          /**
1740           * Filters the array of themes allowed on the network.
1741           *
1742           * @since MU (3.0.0)
1743           *
1744           * @param string[] $allowed_themes An array of theme stylesheet names.
1745           */
1746          $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1747  
1748          return $allowed_themes;
1749      }
1750  
1751      /**
1752       * Returns array of stylesheet names of themes allowed on the site.
1753       *
1754       * @since 3.4.0
1755       *
1756       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1757       * @return string[] Array of stylesheet names.
1758       */
1759  	public static function get_allowed_on_site( $blog_id = null ) {
1760          static $allowed_themes = array();
1761  
1762          if ( ! $blog_id || ! is_multisite() ) {
1763              $blog_id = get_current_blog_id();
1764          }
1765  
1766          if ( isset( $allowed_themes[ $blog_id ] ) ) {
1767              /**
1768               * Filters the array of themes allowed on the site.
1769               *
1770               * @since 4.5.0
1771               *
1772               * @param string[] $allowed_themes An array of theme stylesheet names.
1773               * @param int      $blog_id        ID of the site. Defaults to current site.
1774               */
1775              return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1776          }
1777  
1778          $current = get_current_blog_id() === $blog_id;
1779  
1780          if ( $current ) {
1781              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1782          } else {
1783              switch_to_blog( $blog_id );
1784              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1785              restore_current_blog();
1786          }
1787  
1788          /*
1789           * This is all super old MU back compat joy.
1790           * 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1791           */
1792          if ( false === $allowed_themes[ $blog_id ] ) {
1793              if ( $current ) {
1794                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1795              } else {
1796                  switch_to_blog( $blog_id );
1797                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1798                  restore_current_blog();
1799              }
1800  
1801              if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1802                  $allowed_themes[ $blog_id ] = array();
1803              } else {
1804                  $converted = array();
1805                  $themes    = wp_get_themes();
1806                  foreach ( $themes as $stylesheet => $theme_data ) {
1807                      if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) {
1808                          $converted[ $stylesheet ] = true;
1809                      }
1810                  }
1811                  $allowed_themes[ $blog_id ] = $converted;
1812              }
1813              // Set the option so we never have to go through this pain again.
1814              if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1815                  if ( $current ) {
1816                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false );
1817                      delete_option( 'allowed_themes' );
1818                  } else {
1819                      switch_to_blog( $blog_id );
1820                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ], false );
1821                      delete_option( 'allowed_themes' );
1822                      restore_current_blog();
1823                  }
1824              }
1825          }
1826  
1827          /** This filter is documented in wp-includes/class-wp-theme.php */
1828          return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1829      }
1830  
1831      /**
1832       * Returns the folder names of the block template directories.
1833       *
1834       * @since 6.4.0
1835       *
1836       * @return string[] {
1837       *     Folder names used by block themes.
1838       *
1839       *     @type string $wp_template      Theme-relative directory name for block templates.
1840       *     @type string $wp_template_part Theme-relative directory name for block template parts.
1841       * }
1842       */
1843  	public function get_block_template_folders() {
1844          // Return set/cached value if available.
1845          if ( isset( $this->block_template_folders ) ) {
1846              return $this->block_template_folders;
1847          }
1848  
1849          $this->block_template_folders = $this->default_template_folders;
1850  
1851          $stylesheet_directory = $this->get_stylesheet_directory();
1852          // If the theme uses deprecated block template folders.
1853          if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) {
1854              $this->block_template_folders = array(
1855                  'wp_template'      => 'block-templates',
1856                  'wp_template_part' => 'block-template-parts',
1857              );
1858          }
1859          return $this->block_template_folders;
1860      }
1861  
1862      /**
1863       * Gets block pattern data for a specified theme.
1864       * Each pattern is defined as a PHP file and defines
1865       * its metadata using plugin-style headers. The minimum required definition is:
1866       *
1867       *     /**
1868       *      * Title: My Pattern
1869       *      * Slug: my-theme/my-pattern
1870       *      *
1871       *
1872       * The output of the PHP source corresponds to the content of the pattern, e.g.:
1873       *
1874       *     <main><p><?php echo "Hello"; ?></p></main>
1875       *
1876       * If applicable, this will collect from both parent and child theme.
1877       *
1878       * Other settable fields include:
1879       *
1880       *     - Description
1881       *     - Viewport Width
1882       *     - Inserter         (yes/no)
1883       *     - Categories       (comma-separated values)
1884       *     - Keywords         (comma-separated values)
1885       *     - Block Types      (comma-separated values)
1886       *     - Post Types       (comma-separated values)
1887       *     - Template Types   (comma-separated values)
1888       *
1889       * @since 6.4.0
1890       *
1891       * @return array Block pattern data.
1892       */
1893  	public function get_block_patterns() {
1894          $can_use_cached = ! wp_is_development_mode( 'theme' );
1895  
1896          $pattern_data = $this->get_pattern_cache();
1897          if ( is_array( $pattern_data ) ) {
1898              if ( $can_use_cached ) {
1899                  return $pattern_data;
1900              }
1901              // If in development mode, clear pattern cache.
1902              $this->delete_pattern_cache();
1903          }
1904  
1905          $dirpath      = $this->get_stylesheet_directory() . '/patterns';
1906          $pattern_data = array();
1907  
1908          if ( ! file_exists( $dirpath ) ) {
1909              if ( $can_use_cached ) {
1910                  $this->set_pattern_cache( $pattern_data );
1911              }
1912              return $pattern_data;
1913          }
1914  
1915          $files = (array) self::scandir( $dirpath, 'php', -1 );
1916  
1917          /**
1918           * Filters list of block pattern files for a theme.
1919           *
1920           * @since 6.8.0
1921           *
1922           * @param array  $files   Array of theme files found within `patterns` directory.
1923           * @param string $dirpath Path of theme `patterns` directory being scanned.
1924           */
1925          $files = apply_filters( 'theme_block_pattern_files', $files, $dirpath );
1926  
1927          $dirpath = trailingslashit( $dirpath );
1928  
1929          if ( ! $files ) {
1930              if ( $can_use_cached ) {
1931                  $this->set_pattern_cache( $pattern_data );
1932              }
1933              return $pattern_data;
1934          }
1935  
1936          $default_headers = array(
1937              'title'         => 'Title',
1938              'slug'          => 'Slug',
1939              'description'   => 'Description',
1940              'viewportWidth' => 'Viewport Width',
1941              'inserter'      => 'Inserter',
1942              'categories'    => 'Categories',
1943              'keywords'      => 'Keywords',
1944              'blockTypes'    => 'Block Types',
1945              'postTypes'     => 'Post Types',
1946              'templateTypes' => 'Template Types',
1947          );
1948  
1949          $properties_to_parse = array(
1950              'categories',
1951              'keywords',
1952              'blockTypes',
1953              'postTypes',
1954              'templateTypes',
1955          );
1956  
1957          foreach ( $files as $file ) {
1958              $pattern = get_file_data( $file, $default_headers );
1959  
1960              if ( empty( $pattern['slug'] ) ) {
1961                  _doing_it_wrong(
1962                      __FUNCTION__,
1963                      sprintf(
1964                          /* translators: 1: file name. */
1965                          __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
1966                          $file
1967                      ),
1968                      '6.0.0'
1969                  );
1970                  continue;
1971              }
1972  
1973              if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
1974                  _doing_it_wrong(
1975                      __FUNCTION__,
1976                      sprintf(
1977                          /* translators: 1: file name; 2: slug value found. */
1978                          __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
1979                          $file,
1980                          $pattern['slug']
1981                      ),
1982                      '6.0.0'
1983                  );
1984              }
1985  
1986              // Title is a required property.
1987              if ( ! $pattern['title'] ) {
1988                  _doing_it_wrong(
1989                      __FUNCTION__,
1990                      sprintf(
1991                          /* translators: 1: file name. */
1992                          __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
1993                          $file
1994                      ),
1995                      '6.0.0'
1996                  );
1997                  continue;
1998              }
1999  
2000              // For properties of type array, parse data as comma-separated.
2001              foreach ( $properties_to_parse as $property ) {
2002                  if ( ! empty( $pattern[ $property ] ) ) {
2003                      $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
2004                  } else {
2005                      unset( $pattern[ $property ] );
2006                  }
2007              }
2008  
2009              // Parse properties of type int.
2010              $property = 'viewportWidth';
2011              if ( ! empty( $pattern[ $property ] ) ) {
2012                  $pattern[ $property ] = (int) $pattern[ $property ];
2013              } else {
2014                  unset( $pattern[ $property ] );
2015              }
2016  
2017              // Parse properties of type bool.
2018              $property = 'inserter';
2019              if ( ! empty( $pattern[ $property ] ) ) {
2020                  $pattern[ $property ] = in_array(
2021                      strtolower( $pattern[ $property ] ),
2022                      array( 'yes', 'true' ),
2023                      true
2024                  );
2025              } else {
2026                  unset( $pattern[ $property ] );
2027              }
2028  
2029              $key = str_replace( $dirpath, '', $file );
2030  
2031              $pattern_data[ $key ] = $pattern;
2032          }
2033  
2034          if ( $can_use_cached ) {
2035              $this->set_pattern_cache( $pattern_data );
2036          }
2037  
2038          return $pattern_data;
2039      }
2040  
2041      /**
2042       * Gets block pattern cache.
2043       *
2044       * @since 6.4.0
2045       * @since 6.6.0 Uses transients to cache regardless of site environment.
2046       *
2047       * @return array|false Returns an array of patterns if cache is found, otherwise false.
2048       */
2049  	private function get_pattern_cache() {
2050          if ( ! $this->exists() ) {
2051              return false;
2052          }
2053  
2054          $pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
2055  
2056          if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
2057              return $pattern_data['patterns'];
2058          }
2059          return false;
2060      }
2061  
2062      /**
2063       * Sets block pattern cache.
2064       *
2065       * @since 6.4.0
2066       * @since 6.6.0 Uses transients to cache regardless of site environment.
2067       *
2068       * @param array $patterns Block patterns data to set in cache.
2069       */
2070  	private function set_pattern_cache( array $patterns ) {
2071          $pattern_data = array(
2072              'version'  => $this->get( 'Version' ),
2073              'patterns' => $patterns,
2074          );
2075  
2076          /**
2077           * Filters the cache expiration time for theme files.
2078           *
2079           * @since 6.6.0
2080           *
2081           * @param int    $cache_expiration Cache expiration time in seconds.
2082           * @param string $cache_type       Type of cache being set.
2083           */
2084          $cache_expiration = (int) apply_filters( 'wp_theme_files_cache_ttl', self::$cache_expiration, 'theme_block_patterns' );
2085  
2086          // We don't want to cache patterns infinitely.
2087          if ( $cache_expiration <= 0 ) {
2088              _doing_it_wrong(
2089                  __METHOD__,
2090                  sprintf(
2091                      /* translators: %1$s: The filter name.*/
2092                      __( 'The %1$s filter must return an integer value greater than 0.' ),
2093                      '<code>wp_theme_files_cache_ttl</code>'
2094                  ),
2095                  '6.6.0'
2096              );
2097  
2098              $cache_expiration = self::$cache_expiration;
2099          }
2100  
2101          set_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash, $pattern_data, $cache_expiration );
2102      }
2103  
2104      /**
2105       * Clears block pattern cache.
2106       *
2107       * @since 6.4.0
2108       * @since 6.6.0 Uses transients to cache regardless of site environment.
2109       */
2110  	public function delete_pattern_cache() {
2111          delete_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
2112      }
2113  
2114      /**
2115       * Enables a theme for all sites on the current network.
2116       *
2117       * @since 4.6.0
2118       *
2119       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
2120       */
2121  	public static function network_enable_theme( $stylesheets ) {
2122          if ( ! is_multisite() ) {
2123              return;
2124          }
2125  
2126          if ( ! is_array( $stylesheets ) ) {
2127              $stylesheets = array( $stylesheets );
2128          }
2129  
2130          $allowed_themes = get_site_option( 'allowedthemes' );
2131          foreach ( $stylesheets as $stylesheet ) {
2132              $allowed_themes[ $stylesheet ] = true;
2133          }
2134  
2135          update_site_option( 'allowedthemes', $allowed_themes );
2136      }
2137  
2138      /**
2139       * Disables a theme for all sites on the current network.
2140       *
2141       * @since 4.6.0
2142       *
2143       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
2144       */
2145  	public static function network_disable_theme( $stylesheets ) {
2146          if ( ! is_multisite() ) {
2147              return;
2148          }
2149  
2150          if ( ! is_array( $stylesheets ) ) {
2151              $stylesheets = array( $stylesheets );
2152          }
2153  
2154          $allowed_themes = get_site_option( 'allowedthemes' );
2155          foreach ( $stylesheets as $stylesheet ) {
2156              if ( isset( $allowed_themes[ $stylesheet ] ) ) {
2157                  unset( $allowed_themes[ $stylesheet ] );
2158              }
2159          }
2160  
2161          update_site_option( 'allowedthemes', $allowed_themes );
2162      }
2163  
2164      /**
2165       * Sorts themes by name.
2166       *
2167       * @since 3.4.0
2168       *
2169       * @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
2170       */
2171  	public static function sort_by_name( &$themes ) {
2172          if ( str_starts_with( get_user_locale(), 'en_' ) ) {
2173              uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
2174          } else {
2175              foreach ( $themes as $key => $theme ) {
2176                  $theme->translate_header( 'Name', $theme->headers['Name'] );
2177              }
2178              uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
2179          }
2180      }
2181  
2182      /**
2183       * Callback function for usort() to naturally sort themes by name.
2184       *
2185       * Accesses the Name header directly from the class for maximum speed.
2186       * Would choke on HTML but we don't care enough to slow it down with strip_tags().
2187       *
2188       * @since 3.4.0
2189       *
2190       * @param WP_Theme $a First theme.
2191       * @param WP_Theme $b Second theme.
2192       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
2193       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
2194       */
2195  	private static function _name_sort( $a, $b ) {
2196          return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
2197      }
2198  
2199      /**
2200       * Callback function for usort() to naturally sort themes by translated name.
2201       *
2202       * @since 3.4.0
2203       *
2204       * @param WP_Theme $a First theme.
2205       * @param WP_Theme $b Second theme.
2206       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
2207       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
2208       */
2209  	private static function _name_sort_i18n( $a, $b ) {
2210          return strnatcasecmp( $a->name_translated, $b->name_translated );
2211      }
2212  }


Generated : Mon Nov 2 08:20:36 2026 Cross-referenced by PHPXref