[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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


Generated: Fri Oct 25 08:20:01 2019 Cross-referenced by PHPXref 0.7