[ 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  final class WP_Theme implements ArrayAccess {
  10  
  11      /**
  12       * Whether the theme has been marked as updateable.
  13       *
  14       * @since 4.4.0
  15       * @var bool
  16       *
  17       * @see WP_MS_Themes_List_Table
  18       */
  19      public $update = false;
  20  
  21      /**
  22       * Headers for style.css files.
  23       *
  24       * @since 3.4.0
  25       * @since 5.4.0 Added `Requires at least` and `Requires PHP` headers.
  26       * @var array
  27       */
  28      private static $file_headers = array(
  29          'Name'        => 'Theme Name',
  30          'ThemeURI'    => 'Theme URI',
  31          'Description' => 'Description',
  32          'Author'      => 'Author',
  33          'AuthorURI'   => 'Author URI',
  34          'Version'     => 'Version',
  35          'Template'    => 'Template',
  36          'Status'      => 'Status',
  37          'Tags'        => 'Tags',
  38          'TextDomain'  => 'Text Domain',
  39          'DomainPath'  => 'Domain Path',
  40          'RequiresWP'  => 'Requires at least',
  41          'RequiresPHP' => 'Requires PHP',
  42      );
  43  
  44      /**
  45       * Default themes.
  46       *
  47       * @var array
  48       */
  49      private static $default_themes = array(
  50          'classic'         => 'WordPress Classic',
  51          'default'         => 'WordPress Default',
  52          'twentyten'       => 'Twenty Ten',
  53          'twentyeleven'    => 'Twenty Eleven',
  54          'twentytwelve'    => 'Twenty Twelve',
  55          'twentythirteen'  => 'Twenty Thirteen',
  56          'twentyfourteen'  => 'Twenty Fourteen',
  57          'twentyfifteen'   => 'Twenty Fifteen',
  58          'twentysixteen'   => 'Twenty Sixteen',
  59          'twentyseventeen' => 'Twenty Seventeen',
  60          'twentynineteen'  => 'Twenty Nineteen',
  61          'twentytwenty'    => 'Twenty Twenty',
  62          'twentytwentyone' => 'Twenty Twenty-One',
  63      );
  64  
  65      /**
  66       * Renamed theme tags.
  67       *
  68       * @var array
  69       */
  70      private static $tag_map = array(
  71          'fixed-width'    => 'fixed-layout',
  72          'flexible-width' => 'fluid-layout',
  73      );
  74  
  75      /**
  76       * Absolute path to the theme root, usually wp-content/themes
  77       *
  78       * @var string
  79       */
  80      private $theme_root;
  81  
  82      /**
  83       * Header data from the theme's style.css file.
  84       *
  85       * @var array
  86       */
  87      private $headers = array();
  88  
  89      /**
  90       * Header data from the theme's style.css file after being sanitized.
  91       *
  92       * @var array
  93       */
  94      private $headers_sanitized;
  95  
  96      /**
  97       * Header name from the theme's style.css after being translated.
  98       *
  99       * Cached due to sorting functions running over the translated name.
 100       *
 101       * @var string
 102       */
 103      private $name_translated;
 104  
 105      /**
 106       * Errors encountered when initializing the theme.
 107       *
 108       * @var WP_Error
 109       */
 110      private $errors;
 111  
 112      /**
 113       * The directory name of the theme's files, inside the theme root.
 114       *
 115       * In the case of a child theme, this is directory name of the child theme.
 116       * Otherwise, 'stylesheet' is the same as 'template'.
 117       *
 118       * @var string
 119       */
 120      private $stylesheet;
 121  
 122      /**
 123       * The directory name of the theme's files, inside the theme root.
 124       *
 125       * In the case of a child theme, this is the directory name of the parent theme.
 126       * Otherwise, 'template' is the same as 'stylesheet'.
 127       *
 128       * @var string
 129       */
 130      private $template;
 131  
 132      /**
 133       * A reference to the parent theme, in the case of a child theme.
 134       *
 135       * @var WP_Theme
 136       */
 137      private $parent;
 138  
 139      /**
 140       * URL to the theme root, usually an absolute URL to wp-content/themes
 141       *
 142       * @var string
 143       */
 144      private $theme_root_uri;
 145  
 146      /**
 147       * Flag for whether the theme's textdomain is loaded.
 148       *
 149       * @var bool
 150       */
 151      private $textdomain_loaded;
 152  
 153      /**
 154       * Stores an md5 hash of the theme root, to function as the cache key.
 155       *
 156       * @var string
 157       */
 158      private $cache_hash;
 159  
 160      /**
 161       * Flag for whether the themes cache bucket should be persistently cached.
 162       *
 163       * Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter.
 164       *
 165       * @var bool
 166       */
 167      private static $persistently_cache;
 168  
 169      /**
 170       * Expiration time for the themes cache bucket.
 171       *
 172       * By default the bucket is not cached, so this value is useless.
 173       *
 174       * @var bool
 175       */
 176      private static $cache_expiration = 1800;
 177  
 178      /**
 179       * Constructor for WP_Theme.
 180       *
 181       * @since 3.4.0
 182       *
 183       * @global array $wp_theme_directories
 184       *
 185       * @param string        $theme_dir  Directory of the theme within the theme_root.
 186       * @param string        $theme_root Theme root.
 187       * @param WP_Theme|null $_child If this theme is a parent theme, the child may be passed for validation purposes.
 188       */
 189  	public function __construct( $theme_dir, $theme_root, $_child = null ) {
 190          global $wp_theme_directories;
 191  
 192          // Initialize caching on first run.
 193          if ( ! isset( self::$persistently_cache ) ) {
 194              /** This action is documented in wp-includes/theme.php */
 195              self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
 196              if ( self::$persistently_cache ) {
 197                  wp_cache_add_global_groups( 'themes' );
 198                  if ( is_int( self::$persistently_cache ) ) {
 199                      self::$cache_expiration = self::$persistently_cache;
 200                  }
 201              } else {
 202                  wp_cache_add_non_persistent_groups( 'themes' );
 203              }
 204          }
 205  
 206          $this->theme_root = $theme_root;
 207          $this->stylesheet = $theme_dir;
 208  
 209          // Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
 210          if ( ! in_array( $theme_root, (array) $wp_theme_directories, true )
 211              && in_array( dirname( $theme_root ), (array) $wp_theme_directories, true )
 212          ) {
 213              $this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
 214              $this->theme_root = dirname( $theme_root );
 215          }
 216  
 217          $this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
 218          $theme_file       = $this->stylesheet . '/style.css';
 219  
 220          $cache = $this->cache_get( 'theme' );
 221  
 222          if ( is_array( $cache ) ) {
 223              foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
 224                  if ( isset( $cache[ $key ] ) ) {
 225                      $this->$key = $cache[ $key ];
 226                  }
 227              }
 228              if ( $this->errors ) {
 229                  return;
 230              }
 231              if ( isset( $cache['theme_root_template'] ) ) {
 232                  $theme_root_template = $cache['theme_root_template'];
 233              }
 234          } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
 235              $this->headers['Name'] = $this->stylesheet;
 236              if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) ) {
 237                  $this->errors = new WP_Error(
 238                      'theme_not_found',
 239                      sprintf(
 240                          /* translators: %s: Theme directory name. */
 241                          __( 'The theme directory "%s" does not exist.' ),
 242                          esc_html( $this->stylesheet )
 243                      )
 244                  );
 245              } else {
 246                  $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
 247              }
 248              $this->template = $this->stylesheet;
 249              $this->cache_add(
 250                  'theme',
 251                  array(
 252                      'headers'    => $this->headers,
 253                      'errors'     => $this->errors,
 254                      'stylesheet' => $this->stylesheet,
 255                      'template'   => $this->template,
 256                  )
 257              );
 258              if ( ! file_exists( $this->theme_root ) ) { // Don't cache this one.
 259                  $this->errors->add( 'theme_root_missing', __( 'Error: The themes directory is either empty or doesn&#8217;t exist. Please check your installation.' ) );
 260              }
 261              return;
 262          } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
 263              $this->headers['Name'] = $this->stylesheet;
 264              $this->errors          = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
 265              $this->template        = $this->stylesheet;
 266              $this->cache_add(
 267                  'theme',
 268                  array(
 269                      'headers'    => $this->headers,
 270                      'errors'     => $this->errors,
 271                      'stylesheet' => $this->stylesheet,
 272                      'template'   => $this->template,
 273                  )
 274              );
 275              return;
 276          } else {
 277              $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
 278              // Default themes always trump their pretenders.
 279              // Properly identify default themes that are inside a directory within wp-content/themes.
 280              $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes, true );
 281              if ( $default_theme_slug ) {
 282                  if ( basename( $this->stylesheet ) != $default_theme_slug ) {
 283                      $this->headers['Name'] .= '/' . $this->stylesheet;
 284                  }
 285              }
 286          }
 287  
 288          if ( ! $this->template && $this->stylesheet === $this->headers['Template'] ) {
 289              $this->errors = new WP_Error(
 290                  'theme_child_invalid',
 291                  sprintf(
 292                      /* translators: %s: Template. */
 293                      __( 'The theme defines itself as its parent theme. Please check the %s header.' ),
 294                      '<code>Template</code>'
 295                  )
 296              );
 297              $this->cache_add(
 298                  'theme',
 299                  array(
 300                      'headers'    => $this->headers,
 301                      'errors'     => $this->errors,
 302                      'stylesheet' => $this->stylesheet,
 303                  )
 304              );
 305  
 306              return;
 307          }
 308  
 309          // (If template is set from cache [and there are no errors], we know it's good.)
 310          if ( ! $this->template ) {
 311              $this->template = $this->headers['Template'];
 312          }
 313  
 314          if ( ! $this->template ) {
 315              $this->template = $this->stylesheet;
 316              if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) {
 317                  $error_message = sprintf(
 318                      /* translators: 1: index.php, 2: Documentation URL, 3: style.css */
 319                      __( 'Template is missing. Standalone themes need to have a %1$s template file. <a href="%2$s">Child themes</a> need to have a Template header in the %3$s stylesheet.' ),
 320                      '<code>index.php</code>',
 321                      __( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
 322                      '<code>style.css</code>'
 323                  );
 324                  $this->errors = new WP_Error( 'theme_no_index', $error_message );
 325                  $this->cache_add(
 326                      'theme',
 327                      array(
 328                          'headers'    => $this->headers,
 329                          'errors'     => $this->errors,
 330                          'stylesheet' => $this->stylesheet,
 331                          'template'   => $this->template,
 332                      )
 333                  );
 334                  return;
 335              }
 336          }
 337  
 338          // If we got our data from cache, we can assume that 'template' is pointing to the right place.
 339          if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) {
 340              // If we're in a directory of themes inside /themes, look for the parent nearby.
 341              // wp-content/themes/directory-of-themes/*
 342              $parent_dir  = dirname( $this->stylesheet );
 343              $directories = search_theme_directories();
 344  
 345              if ( '.' !== $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) {
 346                  $this->template = $parent_dir . '/' . $this->template;
 347              } elseif ( $directories && isset( $directories[ $this->template ] ) ) {
 348                  // Look for the template in the search_theme_directories() results, in case it is in another theme root.
 349                  // We don't look into directories of themes, just the theme root.
 350                  $theme_root_template = $directories[ $this->template ]['theme_root'];
 351              } else {
 352                  // Parent theme is missing.
 353                  $this->errors = new WP_Error(
 354                      'theme_no_parent',
 355                      sprintf(
 356                          /* translators: %s: Theme directory name. */
 357                          __( 'The parent theme is missing. Please install the "%s" parent theme.' ),
 358                          esc_html( $this->template )
 359                      )
 360                  );
 361                  $this->cache_add(
 362                      'theme',
 363                      array(
 364                          'headers'    => $this->headers,
 365                          'errors'     => $this->errors,
 366                          'stylesheet' => $this->stylesheet,
 367                          'template'   => $this->template,
 368                      )
 369                  );
 370                  $this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
 371                  return;
 372              }
 373          }
 374  
 375          // Set the parent, if we're a child theme.
 376          if ( $this->template != $this->stylesheet ) {
 377              // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
 378              if ( $_child instanceof WP_Theme && $_child->template == $this->stylesheet ) {
 379                  $_child->parent = null;
 380                  $_child->errors = new WP_Error(
 381                      'theme_parent_invalid',
 382                      sprintf(
 383                          /* translators: %s: Theme directory name. */
 384                          __( 'The "%s" theme is not a valid parent theme.' ),
 385                          esc_html( $_child->template )
 386                      )
 387                  );
 388                  $_child->cache_add(
 389                      'theme',
 390                      array(
 391                          'headers'    => $_child->headers,
 392                          'errors'     => $_child->errors,
 393                          'stylesheet' => $_child->stylesheet,
 394                          'template'   => $_child->template,
 395                      )
 396                  );
 397                  // The two themes actually reference each other with the Template header.
 398                  if ( $_child->stylesheet == $this->template ) {
 399                      $this->errors = new WP_Error(
 400                          'theme_parent_invalid',
 401                          sprintf(
 402                              /* translators: %s: Theme directory name. */
 403                              __( 'The "%s" theme is not a valid parent theme.' ),
 404                              esc_html( $this->template )
 405                          )
 406                      );
 407                      $this->cache_add(
 408                          'theme',
 409                          array(
 410                              'headers'    => $this->headers,
 411                              'errors'     => $this->errors,
 412                              'stylesheet' => $this->stylesheet,
 413                              'template'   => $this->template,
 414                          )
 415                      );
 416                  }
 417                  return;
 418              }
 419              // Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
 420              $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
 421          }
 422  
 423          if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) {
 424              $this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) );
 425          }
 426  
 427          // We're good. If we didn't retrieve from cache, set it.
 428          if ( ! is_array( $cache ) ) {
 429              $cache = array(
 430                  'headers'    => $this->headers,
 431                  'errors'     => $this->errors,
 432                  'stylesheet' => $this->stylesheet,
 433                  'template'   => $this->template,
 434              );
 435              // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
 436              if ( isset( $theme_root_template ) ) {
 437                  $cache['theme_root_template'] = $theme_root_template;
 438              }
 439              $this->cache_add( 'theme', $cache );
 440          }
 441      }
 442  
 443      /**
 444       * When converting the object to a string, the theme name is returned.
 445       *
 446       * @since 3.4.0
 447       *
 448       * @return string Theme name, ready for display (translated)
 449       */
 450  	public function __toString() {
 451          return (string) $this->display( 'Name' );
 452      }
 453  
 454      /**
 455       * __isset() magic method for properties formerly returned by current_theme_info()
 456       *
 457       * @since 3.4.0
 458       *
 459       * @param string $offset Property to check if set.
 460       * @return bool Whether the given property is set.
 461       */
 462  	public function __isset( $offset ) {
 463          static $properties = array(
 464              'name',
 465              'title',
 466              'version',
 467              'parent_theme',
 468              'template_dir',
 469              'stylesheet_dir',
 470              'template',
 471              'stylesheet',
 472              'screenshot',
 473              'description',
 474              'author',
 475              'tags',
 476              'theme_root',
 477              'theme_root_uri',
 478          );
 479  
 480          return in_array( $offset, $properties, true );
 481      }
 482  
 483      /**
 484       * __get() magic method for properties formerly returned by current_theme_info()
 485       *
 486       * @since 3.4.0
 487       *
 488       * @param string $offset Property to get.
 489       * @return mixed Property value.
 490       */
 491  	public function __get( $offset ) {
 492          switch ( $offset ) {
 493              case 'name':
 494              case 'title':
 495                  return $this->get( 'Name' );
 496              case 'version':
 497                  return $this->get( 'Version' );
 498              case 'parent_theme':
 499                  return $this->parent() ? $this->parent()->get( 'Name' ) : '';
 500              case 'template_dir':
 501                  return $this->get_template_directory();
 502              case 'stylesheet_dir':
 503                  return $this->get_stylesheet_directory();
 504              case 'template':
 505                  return $this->get_template();
 506              case 'stylesheet':
 507                  return $this->get_stylesheet();
 508              case 'screenshot':
 509                  return $this->get_screenshot( 'relative' );
 510              // 'author' and 'description' did not previously return translated data.
 511              case 'description':
 512                  return $this->display( 'Description' );
 513              case 'author':
 514                  return $this->display( 'Author' );
 515              case 'tags':
 516                  return $this->get( 'Tags' );
 517              case 'theme_root':
 518                  return $this->get_theme_root();
 519              case 'theme_root_uri':
 520                  return $this->get_theme_root_uri();
 521              // For cases where the array was converted to an object.
 522              default:
 523                  return $this->offsetGet( $offset );
 524          }
 525      }
 526  
 527      /**
 528       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 529       *
 530       * @since 3.4.0
 531       *
 532       * @param mixed $offset
 533       * @param mixed $value
 534       */
 535  	public function offsetSet( $offset, $value ) {}
 536  
 537      /**
 538       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 539       *
 540       * @since 3.4.0
 541       *
 542       * @param mixed $offset
 543       */
 544  	public function offsetUnset( $offset ) {}
 545  
 546      /**
 547       * Method to implement ArrayAccess for keys formerly returned by get_themes()
 548       *
 549       * @since 3.4.0
 550       *
 551       * @param mixed $offset
 552       * @return bool
 553       */
 554  	public function offsetExists( $offset ) {
 555          static $keys = array(
 556              'Name',
 557              'Version',
 558              'Status',
 559              'Title',
 560              'Author',
 561              'Author Name',
 562              'Author URI',
 563              'Description',
 564              'Template',
 565              'Stylesheet',
 566              'Template Files',
 567              'Stylesheet Files',
 568              'Template Dir',
 569              'Stylesheet Dir',
 570              'Screenshot',
 571              'Tags',
 572              'Theme Root',
 573              'Theme Root URI',
 574              'Parent Theme',
 575          );
 576  
 577          return in_array( $offset, $keys, true );
 578      }
 579  
 580      /**
 581       * Method to implement ArrayAccess for keys formerly returned by get_themes().
 582       *
 583       * Author, Author Name, Author URI, and Description did not previously return
 584       * translated data. We are doing so now as it is safe to do. However, as
 585       * Name and Title could have been used as the key for get_themes(), both remain
 586       * untranslated for back compatibility. This means that ['Name'] is not ideal,
 587       * and care should be taken to use `$theme::display( 'Name' )` to get a properly
 588       * translated header.
 589       *
 590       * @since 3.4.0
 591       *
 592       * @param mixed $offset
 593       * @return mixed
 594       */
 595  	public function offsetGet( $offset ) {
 596          switch ( $offset ) {
 597              case 'Name':
 598              case 'Title':
 599                  /*
 600                   * See note above about using translated data. get() is not ideal.
 601                   * It is only for backward compatibility. Use display().
 602                   */
 603                  return $this->get( 'Name' );
 604              case 'Author':
 605                  return $this->display( 'Author' );
 606              case 'Author Name':
 607                  return $this->display( 'Author', false );
 608              case 'Author URI':
 609                  return $this->display( 'AuthorURI' );
 610              case 'Description':
 611                  return $this->display( 'Description' );
 612              case 'Version':
 613              case 'Status':
 614                  return $this->get( $offset );
 615              case 'Template':
 616                  return $this->get_template();
 617              case 'Stylesheet':
 618                  return $this->get_stylesheet();
 619              case 'Template Files':
 620                  return $this->get_files( 'php', 1, true );
 621              case 'Stylesheet Files':
 622                  return $this->get_files( 'css', 0, false );
 623              case 'Template Dir':
 624                  return $this->get_template_directory();
 625              case 'Stylesheet Dir':
 626                  return $this->get_stylesheet_directory();
 627              case 'Screenshot':
 628                  return $this->get_screenshot( 'relative' );
 629              case 'Tags':
 630                  return $this->get( 'Tags' );
 631              case 'Theme Root':
 632                  return $this->get_theme_root();
 633              case 'Theme Root URI':
 634                  return $this->get_theme_root_uri();
 635              case 'Parent Theme':
 636                  return $this->parent() ? $this->parent()->get( 'Name' ) : '';
 637              default:
 638                  return null;
 639          }
 640      }
 641  
 642      /**
 643       * Returns errors property.
 644       *
 645       * @since 3.4.0
 646       *
 647       * @return WP_Error|false WP_Error if there are errors, or false.
 648       */
 649  	public function errors() {
 650          return is_wp_error( $this->errors ) ? $this->errors : false;
 651      }
 652  
 653      /**
 654       * Whether the theme exists.
 655       *
 656       * A theme with errors exists. A theme with the error of 'theme_not_found',
 657       * meaning that the theme's directory was not found, does not exist.
 658       *
 659       * @since 3.4.0
 660       *
 661       * @return bool Whether the theme exists.
 662       */
 663  	public function exists() {
 664          return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes(), true ) );
 665      }
 666  
 667      /**
 668       * Returns reference to the parent theme.
 669       *
 670       * @since 3.4.0
 671       *
 672       * @return WP_Theme|false Parent theme, or false if the current theme is not a child theme.
 673       */
 674  	public function parent() {
 675          return isset( $this->parent ) ? $this->parent : false;
 676      }
 677  
 678      /**
 679       * Adds theme data to cache.
 680       *
 681       * Cache entries keyed by the theme and the type of data.
 682       *
 683       * @since 3.4.0
 684       *
 685       * @param string       $key  Type of data to store (theme, screenshot, headers, post_templates)
 686       * @param array|string $data Data to store
 687       * @return bool Return value from wp_cache_add()
 688       */
 689  	private function cache_add( $key, $data ) {
 690          return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration );
 691      }
 692  
 693      /**
 694       * Gets theme data from cache.
 695       *
 696       * Cache entries are keyed by the theme and the type of data.
 697       *
 698       * @since 3.4.0
 699       *
 700       * @param string $key Type of data to retrieve (theme, screenshot, headers, post_templates)
 701       * @return mixed Retrieved data
 702       */
 703  	private function cache_get( $key ) {
 704          return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' );
 705      }
 706  
 707      /**
 708       * Clears the cache for the theme.
 709       *
 710       * @since 3.4.0
 711       */
 712  	public function cache_delete() {
 713          foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) {
 714              wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
 715          }
 716          $this->template          = null;
 717          $this->textdomain_loaded = null;
 718          $this->theme_root_uri    = null;
 719          $this->parent            = null;
 720          $this->errors            = null;
 721          $this->headers_sanitized = null;
 722          $this->name_translated   = null;
 723          $this->headers           = array();
 724          $this->__construct( $this->stylesheet, $this->theme_root );
 725      }
 726  
 727      /**
 728       * Get a raw, unformatted theme header.
 729       *
 730       * The header is sanitized, but is not translated, and is not marked up for display.
 731       * To get a theme header for display, use the display() method.
 732       *
 733       * Use the get_template() method, not the 'Template' header, for finding the template.
 734       * The 'Template' header is only good for what was written in the style.css, while
 735       * get_template() takes into account where WordPress actually located the theme and
 736       * whether it is actually valid.
 737       *
 738       * @since 3.4.0
 739       *
 740       * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 741       * @return string|array|false String or array (for Tags header) on success, false on failure.
 742       */
 743  	public function get( $header ) {
 744          if ( ! isset( $this->headers[ $header ] ) ) {
 745              return false;
 746          }
 747  
 748          if ( ! isset( $this->headers_sanitized ) ) {
 749              $this->headers_sanitized = $this->cache_get( 'headers' );
 750              if ( ! is_array( $this->headers_sanitized ) ) {
 751                  $this->headers_sanitized = array();
 752              }
 753          }
 754  
 755          if ( isset( $this->headers_sanitized[ $header ] ) ) {
 756              return $this->headers_sanitized[ $header ];
 757          }
 758  
 759          // If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets.
 760          if ( self::$persistently_cache ) {
 761              foreach ( array_keys( $this->headers ) as $_header ) {
 762                  $this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] );
 763              }
 764              $this->cache_add( 'headers', $this->headers_sanitized );
 765          } else {
 766              $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] );
 767          }
 768  
 769          return $this->headers_sanitized[ $header ];
 770      }
 771  
 772      /**
 773       * Gets a theme header, formatted and translated for display.
 774       *
 775       * @since 3.4.0
 776       *
 777       * @param string $header    Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 778       * @param bool   $markup    Optional. Whether to mark up the header. Defaults to true.
 779       * @param bool   $translate Optional. Whether to translate the header. Defaults to true.
 780       * @return string|array|false Processed header. An array for Tags if `$markup` is false, string otherwise.
 781       *                            False on failure.
 782       */
 783  	public function display( $header, $markup = true, $translate = true ) {
 784          $value = $this->get( $header );
 785          if ( false === $value ) {
 786              return false;
 787          }
 788  
 789          if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) {
 790              $translate = false;
 791          }
 792  
 793          if ( $translate ) {
 794              $value = $this->translate_header( $header, $value );
 795          }
 796  
 797          if ( $markup ) {
 798              $value = $this->markup_header( $header, $value, $translate );
 799          }
 800  
 801          return $value;
 802      }
 803  
 804      /**
 805       * Sanitize a theme header.
 806       *
 807       * @since 3.4.0
 808       * @since 5.4.0 Added support for `Requires at least` and `Requires PHP` headers.
 809       *
 810       * @param string $header Theme header. Accepts 'Name', 'Description', 'Author', 'Version',
 811       *                       'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP'.
 812       * @param string $value  Value to sanitize.
 813       * @return string|array An array for Tags header, string otherwise.
 814       */
 815  	private function sanitize_header( $header, $value ) {
 816          switch ( $header ) {
 817              case 'Status':
 818                  if ( ! $value ) {
 819                      $value = 'publish';
 820                      break;
 821                  }
 822                  // Fall through otherwise.
 823              case 'Name':
 824                  static $header_tags = array(
 825                      'abbr'    => array( 'title' => true ),
 826                      'acronym' => array( 'title' => true ),
 827                      'code'    => true,
 828                      'em'      => true,
 829                      'strong'  => true,
 830                  );
 831  
 832                  $value = wp_kses( $value, $header_tags );
 833                  break;
 834              case 'Author':
 835                  // There shouldn't be anchor tags in Author, but some themes like to be challenging.
 836              case 'Description':
 837                  static $header_tags_with_a = array(
 838                      'a'       => array(
 839                          'href'  => true,
 840                          'title' => true,
 841                      ),
 842                      'abbr'    => array( 'title' => true ),
 843                      'acronym' => array( 'title' => true ),
 844                      'code'    => true,
 845                      'em'      => true,
 846                      'strong'  => true,
 847                  );
 848  
 849                  $value = wp_kses( $value, $header_tags_with_a );
 850                  break;
 851              case 'ThemeURI':
 852              case 'AuthorURI':
 853                  $value = esc_url_raw( $value );
 854                  break;
 855              case 'Tags':
 856                  $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
 857                  break;
 858              case 'Version':
 859              case 'RequiresWP':
 860              case 'RequiresPHP':
 861                  $value = strip_tags( $value );
 862                  break;
 863          }
 864  
 865          return $value;
 866      }
 867  
 868      /**
 869       * Mark up a theme header.
 870       *
 871       * @since 3.4.0
 872       *
 873       * @param string       $header    Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 874       * @param string|array $value     Value to mark up. An array for Tags header, string otherwise.
 875       * @param string       $translate Whether the header has been translated.
 876       * @return string Value, marked up.
 877       */
 878  	private function markup_header( $header, $value, $translate ) {
 879          switch ( $header ) {
 880              case 'Name':
 881                  if ( empty( $value ) ) {
 882                      $value = esc_html( $this->get_stylesheet() );
 883                  }
 884                  break;
 885              case 'Description':
 886                  $value = wptexturize( $value );
 887                  break;
 888              case 'Author':
 889                  if ( $this->get( 'AuthorURI' ) ) {
 890                      $value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value );
 891                  } elseif ( ! $value ) {
 892                      $value = __( 'Anonymous' );
 893                  }
 894                  break;
 895              case 'Tags':
 896                  static $comma = null;
 897                  if ( ! isset( $comma ) ) {
 898                      /* translators: Used between list items, there is a space after the comma. */
 899                      $comma = __( ', ' );
 900                  }
 901                  $value = implode( $comma, $value );
 902                  break;
 903              case 'ThemeURI':
 904              case 'AuthorURI':
 905                  $value = esc_url( $value );
 906                  break;
 907          }
 908  
 909          return $value;
 910      }
 911  
 912      /**
 913       * Translate a theme header.
 914       *
 915       * @since 3.4.0
 916       *
 917       * @param string       $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
 918       * @param string|array $value  Value to translate. An array for Tags header, string otherwise.
 919       * @return string|array Translated value. An array for Tags header, string otherwise.
 920       */
 921  	private function translate_header( $header, $value ) {
 922          switch ( $header ) {
 923              case 'Name':
 924                  // Cached for sorting reasons.
 925                  if ( isset( $this->name_translated ) ) {
 926                      return $this->name_translated;
 927                  }
 928  
 929                  // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
 930                  $this->name_translated = translate( $value, $this->get( 'TextDomain' ) );
 931  
 932                  return $this->name_translated;
 933              case 'Tags':
 934                  if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) {
 935                      return $value;
 936                  }
 937  
 938                  static $tags_list;
 939                  if ( ! isset( $tags_list ) ) {
 940                      $tags_list = array(
 941                          // As of 4.6, deprecated tags which are only used to provide translation for older themes.
 942                          'black'             => __( 'Black' ),
 943                          'blue'              => __( 'Blue' ),
 944                          'brown'             => __( 'Brown' ),
 945                          'gray'              => __( 'Gray' ),
 946                          'green'             => __( 'Green' ),
 947                          'orange'            => __( 'Orange' ),
 948                          'pink'              => __( 'Pink' ),
 949                          'purple'            => __( 'Purple' ),
 950                          'red'               => __( 'Red' ),
 951                          'silver'            => __( 'Silver' ),
 952                          'tan'               => __( 'Tan' ),
 953                          'white'             => __( 'White' ),
 954                          'yellow'            => __( 'Yellow' ),
 955                          'dark'              => __( 'Dark' ),
 956                          'light'             => __( 'Light' ),
 957                          'fixed-layout'      => __( 'Fixed Layout' ),
 958                          'fluid-layout'      => __( 'Fluid Layout' ),
 959                          'responsive-layout' => __( 'Responsive Layout' ),
 960                          'blavatar'          => __( 'Blavatar' ),
 961                          'photoblogging'     => __( 'Photoblogging' ),
 962                          'seasonal'          => __( 'Seasonal' ),
 963                      );
 964  
 965                      $feature_list = get_theme_feature_list( false ); // No API.
 966  
 967                      foreach ( $feature_list as $tags ) {
 968                          $tags_list += $tags;
 969                      }
 970                  }
 971  
 972                  foreach ( $value as &$tag ) {
 973                      if ( isset( $tags_list[ $tag ] ) ) {
 974                          $tag = $tags_list[ $tag ];
 975                      } elseif ( isset( self::$tag_map[ $tag ] ) ) {
 976                          $tag = $tags_list[ self::$tag_map[ $tag ] ];
 977                      }
 978                  }
 979  
 980                  return $value;
 981  
 982              default:
 983                  // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
 984                  $value = translate( $value, $this->get( 'TextDomain' ) );
 985          }
 986          return $value;
 987      }
 988  
 989      /**
 990       * The directory name of the theme's "stylesheet" files, inside the theme root.
 991       *
 992       * In the case of a child theme, this is directory name of the child theme.
 993       * Otherwise, get_stylesheet() is the same as get_template().
 994       *
 995       * @since 3.4.0
 996       *
 997       * @return string Stylesheet
 998       */
 999  	public function get_stylesheet() {
1000          return $this->stylesheet;
1001      }
1002  
1003      /**
1004       * The directory name of the theme's "template" files, inside the theme root.
1005       *
1006       * In the case of a child theme, this is the directory name of the parent theme.
1007       * Otherwise, the get_template() is the same as get_stylesheet().
1008       *
1009       * @since 3.4.0
1010       *
1011       * @return string Template
1012       */
1013  	public function get_template() {
1014          return $this->template;
1015      }
1016  
1017      /**
1018       * Returns the absolute path to the directory of a theme's "stylesheet" files.
1019       *
1020       * In the case of a child theme, this is the absolute path to the directory
1021       * of the child theme's files.
1022       *
1023       * @since 3.4.0
1024       *
1025       * @return string Absolute path of the stylesheet directory.
1026       */
1027  	public function get_stylesheet_directory() {
1028          if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes(), true ) ) {
1029              return '';
1030          }
1031  
1032          return $this->theme_root . '/' . $this->stylesheet;
1033      }
1034  
1035      /**
1036       * Returns the absolute path to the directory of a theme's "template" files.
1037       *
1038       * In the case of a child theme, this is the absolute path to the directory
1039       * of the parent theme's files.
1040       *
1041       * @since 3.4.0
1042       *
1043       * @return string Absolute path of the template directory.
1044       */
1045  	public function get_template_directory() {
1046          if ( $this->parent() ) {
1047              $theme_root = $this->parent()->theme_root;
1048          } else {
1049              $theme_root = $this->theme_root;
1050          }
1051  
1052          return $theme_root . '/' . $this->template;
1053      }
1054  
1055      /**
1056       * Returns the URL to the directory of a theme's "stylesheet" files.
1057       *
1058       * In the case of a child theme, this is the URL to the directory of the
1059       * child theme's files.
1060       *
1061       * @since 3.4.0
1062       *
1063       * @return string URL to the stylesheet directory.
1064       */
1065  	public function get_stylesheet_directory_uri() {
1066          return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
1067      }
1068  
1069      /**
1070       * Returns the URL to the directory of a theme's "template" files.
1071       *
1072       * In the case of a child theme, this is the URL to the directory of the
1073       * parent theme's files.
1074       *
1075       * @since 3.4.0
1076       *
1077       * @return string URL to the template directory.
1078       */
1079  	public function get_template_directory_uri() {
1080          if ( $this->parent() ) {
1081              $theme_root_uri = $this->parent()->get_theme_root_uri();
1082          } else {
1083              $theme_root_uri = $this->get_theme_root_uri();
1084          }
1085  
1086          return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
1087      }
1088  
1089      /**
1090       * The absolute path to the directory of the theme root.
1091       *
1092       * This is typically the absolute path to wp-content/themes.
1093       *
1094       * @since 3.4.0
1095       *
1096       * @return string Theme root.
1097       */
1098  	public function get_theme_root() {
1099          return $this->theme_root;
1100      }
1101  
1102      /**
1103       * Returns the URL to the directory of the theme root.
1104       *
1105       * This is typically the absolute URL to wp-content/themes. This forms the basis
1106       * for all other URLs returned by WP_Theme, so we pass it to the public function
1107       * get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter.
1108       *
1109       * @since 3.4.0
1110       *
1111       * @return string Theme root URI.
1112       */
1113  	public function get_theme_root_uri() {
1114          if ( ! isset( $this->theme_root_uri ) ) {
1115              $this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
1116          }
1117          return $this->theme_root_uri;
1118      }
1119  
1120      /**
1121       * Returns the main screenshot file for the theme.
1122       *
1123       * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
1124       *
1125       * Screenshots for a theme must be in the stylesheet directory. (In the case of child
1126       * themes, parent theme screenshots are not inherited.)
1127       *
1128       * @since 3.4.0
1129       *
1130       * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
1131       * @return string|false Screenshot file. False if the theme does not have a screenshot.
1132       */
1133  	public function get_screenshot( $uri = 'uri' ) {
1134          $screenshot = $this->cache_get( 'screenshot' );
1135          if ( $screenshot ) {
1136              if ( 'relative' === $uri ) {
1137                  return $screenshot;
1138              }
1139              return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
1140          } elseif ( 0 === $screenshot ) {
1141              return false;
1142          }
1143  
1144          foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) {
1145              if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
1146                  $this->cache_add( 'screenshot', 'screenshot.' . $ext );
1147                  if ( 'relative' === $uri ) {
1148                      return 'screenshot.' . $ext;
1149                  }
1150                  return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
1151              }
1152          }
1153  
1154          $this->cache_add( 'screenshot', 0 );
1155          return false;
1156      }
1157  
1158      /**
1159       * Return files in the theme's directory.
1160       *
1161       * @since 3.4.0
1162       *
1163       * @param string[]|string $type          Optional. Array of extensions to find, string of a single extension,
1164       *                                       or null for all extensions. Default null.
1165       * @param int             $depth         Optional. How deep to search for files. Defaults to a flat scan (0 depth).
1166       *                                       -1 depth is infinite.
1167       * @param bool            $search_parent Optional. Whether to return parent files. Default false.
1168       * @return string[] Array of files, keyed by the path to the file relative to the theme's directory, with the values
1169       *                  being absolute paths.
1170       */
1171  	public function get_files( $type = null, $depth = 0, $search_parent = false ) {
1172          $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
1173  
1174          if ( $search_parent && $this->parent() ) {
1175              $files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
1176          }
1177  
1178          return $files;
1179      }
1180  
1181      /**
1182       * Returns the theme's post templates.
1183       *
1184       * @since 4.7.0
1185       *
1186       * @return string[] Array of page templates, keyed by filename and post type,
1187       *                  with the value of the translated header name.
1188       */
1189  	public function get_post_templates() {
1190          // If you screw up your current theme and we invalidate your parent, most things still work. Let it slide.
1191          if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) {
1192              return array();
1193          }
1194  
1195          $post_templates = $this->cache_get( 'post_templates' );
1196  
1197          if ( ! is_array( $post_templates ) ) {
1198              $post_templates = array();
1199  
1200              $files = (array) $this->get_files( 'php', 1, true );
1201  
1202              foreach ( $files as $file => $full_path ) {
1203                  if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) {
1204                      continue;
1205                  }
1206  
1207                  $types = array( 'page' );
1208                  if ( preg_match( '|Template Post Type:(.*)$|mi', file_get_contents( $full_path ), $type ) ) {
1209                      $types = explode( ',', _cleanup_header_comment( $type[1] ) );
1210                  }
1211  
1212                  foreach ( $types as $type ) {
1213                      $type = sanitize_key( $type );
1214                      if ( ! isset( $post_templates[ $type ] ) ) {
1215                          $post_templates[ $type ] = array();
1216                      }
1217  
1218                      $post_templates[ $type ][ $file ] = _cleanup_header_comment( $header[1] );
1219                  }
1220              }
1221  
1222              $this->cache_add( 'post_templates', $post_templates );
1223          }
1224  
1225          if ( $this->load_textdomain() ) {
1226              foreach ( $post_templates as &$post_type ) {
1227                  foreach ( $post_type as &$post_template ) {
1228                      $post_template = $this->translate_header( 'Template Name', $post_template );
1229                  }
1230              }
1231          }
1232  
1233          return $post_templates;
1234      }
1235  
1236      /**
1237       * Returns the theme's post templates for a given post type.
1238       *
1239       * @since 3.4.0
1240       * @since 4.7.0 Added the `$post_type` parameter.
1241       *
1242       * @param WP_Post|null $post      Optional. The post being edited, provided for context.
1243       * @param string       $post_type Optional. Post type to get the templates for. Default 'page'.
1244       *                                If a post is provided, its post type is used.
1245       * @return string[] Array of template header names keyed by the template file name.
1246       */
1247  	public function get_page_templates( $post = null, $post_type = 'page' ) {
1248          if ( $post ) {
1249              $post_type = get_post_type( $post );
1250          }
1251  
1252          $post_templates = $this->get_post_templates();
1253          $post_templates = isset( $post_templates[ $post_type ] ) ? $post_templates[ $post_type ] : array();
1254  
1255          /**
1256           * Filters list of page templates for a theme.
1257           *
1258           * @since 4.9.6
1259           *
1260           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1261           * @param WP_Theme     $this           The theme object.
1262           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1263           * @param string       $post_type      Post type to get the templates for.
1264           */
1265          $post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type );
1266  
1267          /**
1268           * Filters list of page templates for a theme.
1269           *
1270           * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1271           *
1272           * @since 3.9.0
1273           * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1274           * @since 4.7.0 Added the `$post_type` parameter.
1275           *
1276           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1277           * @param WP_Theme     $this           The theme object.
1278           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1279           * @param string       $post_type      Post type to get the templates for.
1280           */
1281          $post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1282  
1283          return $post_templates;
1284      }
1285  
1286      /**
1287       * Scans a directory for files of a certain extension.
1288       *
1289       * @since 3.4.0
1290       *
1291       * @param string            $path          Absolute path to search.
1292       * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1293       *                                         or null for all extensions. Default null.
1294       * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1295       *                                         -1 (infinite depth). Default 0.
1296       * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1297       *                                         returned path for the found files, particularly when this function
1298       *                                         recurses to lower depths. Default empty.
1299       * @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1300       *                        with `$relative_path`, with the values being absolute paths. False otherwise.
1301       */
1302  	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1303          if ( ! is_dir( $path ) ) {
1304              return false;
1305          }
1306  
1307          if ( $extensions ) {
1308              $extensions  = (array) $extensions;
1309              $_extensions = implode( '|', $extensions );
1310          }
1311  
1312          $relative_path = trailingslashit( $relative_path );
1313          if ( '/' === $relative_path ) {
1314              $relative_path = '';
1315          }
1316  
1317          $results = scandir( $path );
1318          $files   = array();
1319  
1320          /**
1321           * Filters the array of excluded directories and files while scanning theme folder.
1322           *
1323           * @since 4.7.4
1324           *
1325           * @param string[] $exclusions Array of excluded directories and files.
1326           */
1327          $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
1328  
1329          foreach ( $results as $result ) {
1330              if ( '.' === $result[0] || in_array( $result, $exclusions, true ) ) {
1331                  continue;
1332              }
1333              if ( is_dir( $path . '/' . $result ) ) {
1334                  if ( ! $depth ) {
1335                      continue;
1336                  }
1337                  $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result );
1338                  $files = array_merge_recursive( $files, $found );
1339              } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1340                  $files[ $relative_path . $result ] = $path . '/' . $result;
1341              }
1342          }
1343  
1344          return $files;
1345      }
1346  
1347      /**
1348       * Loads the theme's textdomain.
1349       *
1350       * Translation files are not inherited from the parent theme. TODO: If this fails for the
1351       * child theme, it should probably try to load the parent theme's translations.
1352       *
1353       * @since 3.4.0
1354       *
1355       * @return bool True if the textdomain was successfully loaded or has already been loaded.
1356       *  False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1357       */
1358  	public function load_textdomain() {
1359          if ( isset( $this->textdomain_loaded ) ) {
1360              return $this->textdomain_loaded;
1361          }
1362  
1363          $textdomain = $this->get( 'TextDomain' );
1364          if ( ! $textdomain ) {
1365              $this->textdomain_loaded = false;
1366              return false;
1367          }
1368  
1369          if ( is_textdomain_loaded( $textdomain ) ) {
1370              $this->textdomain_loaded = true;
1371              return true;
1372          }
1373  
1374          $path       = $this->get_stylesheet_directory();
1375          $domainpath = $this->get( 'DomainPath' );
1376          if ( $domainpath ) {
1377              $path .= $domainpath;
1378          } else {
1379              $path .= '/languages';
1380          }
1381  
1382          $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1383          return $this->textdomain_loaded;
1384      }
1385  
1386      /**
1387       * Whether the theme is allowed (multisite only).
1388       *
1389       * @since 3.4.0
1390       *
1391       * @param string $check   Optional. Whether to check only the 'network'-wide settings, the 'site'
1392       *                        settings, or 'both'. Defaults to 'both'.
1393       * @param int    $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1394       * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1395       */
1396  	public function is_allowed( $check = 'both', $blog_id = null ) {
1397          if ( ! is_multisite() ) {
1398              return true;
1399          }
1400  
1401          if ( 'both' === $check || 'network' === $check ) {
1402              $allowed = self::get_allowed_on_network();
1403              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1404                  return true;
1405              }
1406          }
1407  
1408          if ( 'both' === $check || 'site' === $check ) {
1409              $allowed = self::get_allowed_on_site( $blog_id );
1410              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1411                  return true;
1412              }
1413          }
1414  
1415          return false;
1416      }
1417  
1418      /**
1419       * Determines the latest WordPress default theme that is installed.
1420       *
1421       * This hits the filesystem.
1422       *
1423       * @since 4.4.0
1424       *
1425       * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1426       */
1427  	public static function get_core_default_theme() {
1428          foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1429              $theme = wp_get_theme( $slug );
1430              if ( $theme->exists() ) {
1431                  return $theme;
1432              }
1433          }
1434          return false;
1435      }
1436  
1437      /**
1438       * Returns array of stylesheet names of themes allowed on the site or network.
1439       *
1440       * @since 3.4.0
1441       *
1442       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1443       * @return string[] Array of stylesheet names.
1444       */
1445  	public static function get_allowed( $blog_id = null ) {
1446          /**
1447           * Filters the array of themes allowed on the network.
1448           *
1449           * Site is provided as context so that a list of network allowed themes can
1450           * be filtered further.
1451           *
1452           * @since 4.5.0
1453           *
1454           * @param string[] $allowed_themes An array of theme stylesheet names.
1455           * @param int      $blog_id        ID of the site.
1456           */
1457          $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1458          return $network + self::get_allowed_on_site( $blog_id );
1459      }
1460  
1461      /**
1462       * Returns array of stylesheet names of themes allowed on the network.
1463       *
1464       * @since 3.4.0
1465       *
1466       * @return string[] Array of stylesheet names.
1467       */
1468  	public static function get_allowed_on_network() {
1469          static $allowed_themes;
1470          if ( ! isset( $allowed_themes ) ) {
1471              $allowed_themes = (array) get_site_option( 'allowedthemes' );
1472          }
1473  
1474          /**
1475           * Filters the array of themes allowed on the network.
1476           *
1477           * @since MU (3.0.0)
1478           *
1479           * @param string[] $allowed_themes An array of theme stylesheet names.
1480           */
1481          $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1482  
1483          return $allowed_themes;
1484      }
1485  
1486      /**
1487       * Returns array of stylesheet names of themes allowed on the site.
1488       *
1489       * @since 3.4.0
1490       *
1491       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1492       * @return string[] Array of stylesheet names.
1493       */
1494  	public static function get_allowed_on_site( $blog_id = null ) {
1495          static $allowed_themes = array();
1496  
1497          if ( ! $blog_id || ! is_multisite() ) {
1498              $blog_id = get_current_blog_id();
1499          }
1500  
1501          if ( isset( $allowed_themes[ $blog_id ] ) ) {
1502              /**
1503               * Filters the array of themes allowed on the site.
1504               *
1505               * @since 4.5.0
1506               *
1507               * @param string[] $allowed_themes An array of theme stylesheet names.
1508               * @param int      $blog_id        ID of the site. Defaults to current site.
1509               */
1510              return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1511          }
1512  
1513          $current = get_current_blog_id() == $blog_id;
1514  
1515          if ( $current ) {
1516              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1517          } else {
1518              switch_to_blog( $blog_id );
1519              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1520              restore_current_blog();
1521          }
1522  
1523          // This is all super old MU back compat joy.
1524          // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1525          if ( false === $allowed_themes[ $blog_id ] ) {
1526              if ( $current ) {
1527                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1528              } else {
1529                  switch_to_blog( $blog_id );
1530                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1531                  restore_current_blog();
1532              }
1533  
1534              if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1535                  $allowed_themes[ $blog_id ] = array();
1536              } else {
1537                  $converted = array();
1538                  $themes    = wp_get_themes();
1539                  foreach ( $themes as $stylesheet => $theme_data ) {
1540                      if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) {
1541                          $converted[ $stylesheet ] = true;
1542                      }
1543                  }
1544                  $allowed_themes[ $blog_id ] = $converted;
1545              }
1546              // Set the option so we never have to go through this pain again.
1547              if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1548                  if ( $current ) {
1549                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1550                      delete_option( 'allowed_themes' );
1551                  } else {
1552                      switch_to_blog( $blog_id );
1553                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1554                      delete_option( 'allowed_themes' );
1555                      restore_current_blog();
1556                  }
1557              }
1558          }
1559  
1560          /** This filter is documented in wp-includes/class-wp-theme.php */
1561          return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1562      }
1563  
1564      /**
1565       * Enables a theme for all sites on the current network.
1566       *
1567       * @since 4.6.0
1568       *
1569       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
1570       */
1571  	public static function network_enable_theme( $stylesheets ) {
1572          if ( ! is_multisite() ) {
1573              return;
1574          }
1575  
1576          if ( ! is_array( $stylesheets ) ) {
1577              $stylesheets = array( $stylesheets );
1578          }
1579  
1580          $allowed_themes = get_site_option( 'allowedthemes' );
1581          foreach ( $stylesheets as $stylesheet ) {
1582              $allowed_themes[ $stylesheet ] = true;
1583          }
1584  
1585          update_site_option( 'allowedthemes', $allowed_themes );
1586      }
1587  
1588      /**
1589       * Disables a theme for all sites on the current network.
1590       *
1591       * @since 4.6.0
1592       *
1593       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
1594       */
1595  	public static function network_disable_theme( $stylesheets ) {
1596          if ( ! is_multisite() ) {
1597              return;
1598          }
1599  
1600          if ( ! is_array( $stylesheets ) ) {
1601              $stylesheets = array( $stylesheets );
1602          }
1603  
1604          $allowed_themes = get_site_option( 'allowedthemes' );
1605          foreach ( $stylesheets as $stylesheet ) {
1606              if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1607                  unset( $allowed_themes[ $stylesheet ] );
1608              }
1609          }
1610  
1611          update_site_option( 'allowedthemes', $allowed_themes );
1612      }
1613  
1614      /**
1615       * Sorts themes by name.
1616       *
1617       * @since 3.4.0
1618       *
1619       * @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
1620       */
1621  	public static function sort_by_name( &$themes ) {
1622          if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
1623              uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1624          } else {
1625              foreach ( $themes as $key => $theme ) {
1626                  $theme->translate_header( 'Name', $theme->headers['Name'] );
1627              }
1628              uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1629          }
1630      }
1631  
1632      /**
1633       * Callback function for usort() to naturally sort themes by name.
1634       *
1635       * Accesses the Name header directly from the class for maximum speed.
1636       * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1637       *
1638       * @since 3.4.0
1639       *
1640       * @param WP_Theme $a First theme.
1641       * @param WP_Theme $b Second theme.
1642       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1643       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1644       */
1645  	private static function _name_sort( $a, $b ) {
1646          return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1647      }
1648  
1649      /**
1650       * Callback function for usort() to naturally sort themes by translated name.
1651       *
1652       * @since 3.4.0
1653       *
1654       * @param WP_Theme $a First theme.
1655       * @param WP_Theme $b Second theme.
1656       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1657       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1658       */
1659  	private static function _name_sort_i18n( $a, $b ) {
1660          return strnatcasecmp( $a->name_translated, $b->name_translated );
1661      }
1662  }


Generated : Thu Oct 22 08:20:02 2020 Cross-referenced by PHPXref