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


Generated : Thu Apr 25 08:20:02 2024 Cross-referenced by PHPXref