[ Index ]

PHP Cross Reference of WordPress

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_Theme|null $_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 string[] 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 string 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 string[] Array of template header names keyed by the template file 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 template header names keyed by the template file name.
1252           * @param WP_Theme     $this           The theme object.
1253           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1254           * @param string       $post_type      Post type to get the templates for.
1255           */
1256          $post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type );
1257  
1258          /**
1259           * Filters list of page templates for a theme.
1260           *
1261           * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1262           *
1263           * @since 3.9.0
1264           * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1265           * @since 4.7.0 Added the `$post_type` parameter.
1266           *
1267           * @param string[]     $post_templates Array of template header names keyed by the template file name.
1268           * @param WP_Theme     $this           The theme object.
1269           * @param WP_Post|null $post           The post being edited, provided for context, or null.
1270           * @param string       $post_type      Post type to get the templates for.
1271           */
1272          $post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1273  
1274          return $post_templates;
1275      }
1276  
1277      /**
1278       * Scans a directory for files of a certain extension.
1279       *
1280       * @since 3.4.0
1281       *
1282       * @param string            $path          Absolute path to search.
1283       * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1284       *                                         or null for all extensions. Default null.
1285       * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1286       *                                         -1 (infinite depth). Default 0.
1287       * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1288       *                                         returned path for the found files, particularly when this function
1289       *                                         recurses to lower depths. Default empty.
1290       * @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1291       *                        with `$relative_path`, with the values being absolute paths. False otherwise.
1292       */
1293  	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1294          if ( ! is_dir( $path ) ) {
1295              return false;
1296          }
1297  
1298          if ( $extensions ) {
1299              $extensions  = (array) $extensions;
1300              $_extensions = implode( '|', $extensions );
1301          }
1302  
1303          $relative_path = trailingslashit( $relative_path );
1304          if ( '/' == $relative_path ) {
1305              $relative_path = '';
1306          }
1307  
1308          $results = scandir( $path );
1309          $files   = array();
1310  
1311          /**
1312           * Filters the array of excluded directories and files while scanning theme folder.
1313           *
1314           * @since 4.7.4
1315           *
1316           * @param string[] $exclusions Array of excluded directories and files.
1317           */
1318          $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
1319  
1320          foreach ( $results as $result ) {
1321              if ( '.' == $result[0] || in_array( $result, $exclusions, true ) ) {
1322                  continue;
1323              }
1324              if ( is_dir( $path . '/' . $result ) ) {
1325                  if ( ! $depth ) {
1326                      continue;
1327                  }
1328                  $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result );
1329                  $files = array_merge_recursive( $files, $found );
1330              } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1331                  $files[ $relative_path . $result ] = $path . '/' . $result;
1332              }
1333          }
1334  
1335          return $files;
1336      }
1337  
1338      /**
1339       * Loads the theme's textdomain.
1340       *
1341       * Translation files are not inherited from the parent theme. Todo: if this fails for the
1342       * child theme, it should probably try to load the parent theme's translations.
1343       *
1344       * @since 3.4.0
1345       *
1346       * @return bool True if the textdomain was successfully loaded or has already been loaded.
1347       *  False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1348       */
1349  	public function load_textdomain() {
1350          if ( isset( $this->textdomain_loaded ) ) {
1351              return $this->textdomain_loaded;
1352          }
1353  
1354          $textdomain = $this->get( 'TextDomain' );
1355          if ( ! $textdomain ) {
1356              $this->textdomain_loaded = false;
1357              return false;
1358          }
1359  
1360          if ( is_textdomain_loaded( $textdomain ) ) {
1361              $this->textdomain_loaded = true;
1362              return true;
1363          }
1364  
1365          $path       = $this->get_stylesheet_directory();
1366          $domainpath = $this->get( 'DomainPath' );
1367          if ( $domainpath ) {
1368              $path .= $domainpath;
1369          } else {
1370              $path .= '/languages';
1371          }
1372  
1373          $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1374          return $this->textdomain_loaded;
1375      }
1376  
1377      /**
1378       * Whether the theme is allowed (multisite only).
1379       *
1380       * @since 3.4.0
1381       *
1382       * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
1383       *  settings, or 'both'. Defaults to 'both'.
1384       * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1385       * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1386       */
1387  	public function is_allowed( $check = 'both', $blog_id = null ) {
1388          if ( ! is_multisite() ) {
1389              return true;
1390          }
1391  
1392          if ( 'both' == $check || 'network' == $check ) {
1393              $allowed = self::get_allowed_on_network();
1394              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1395                  return true;
1396              }
1397          }
1398  
1399          if ( 'both' == $check || 'site' == $check ) {
1400              $allowed = self::get_allowed_on_site( $blog_id );
1401              if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) {
1402                  return true;
1403              }
1404          }
1405  
1406          return false;
1407      }
1408  
1409      /**
1410       * Determines the latest WordPress default theme that is installed.
1411       *
1412       * This hits the filesystem.
1413       *
1414       * @since  4.4.0
1415       *
1416       * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1417       */
1418  	public static function get_core_default_theme() {
1419          foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1420              $theme = wp_get_theme( $slug );
1421              if ( $theme->exists() ) {
1422                  return $theme;
1423              }
1424          }
1425          return false;
1426      }
1427  
1428      /**
1429       * Returns array of stylesheet names of themes allowed on the site or network.
1430       *
1431       * @since 3.4.0
1432       *
1433       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1434       * @return string[] Array of stylesheet names.
1435       */
1436  	public static function get_allowed( $blog_id = null ) {
1437          /**
1438           * Filters the array of themes allowed on the network.
1439           *
1440           * Site is provided as context so that a list of network allowed themes can
1441           * be filtered further.
1442           *
1443           * @since 4.5.0
1444           *
1445           * @param string[] $allowed_themes An array of theme stylesheet names.
1446           * @param int      $blog_id        ID of the site.
1447           */
1448          $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1449          return $network + self::get_allowed_on_site( $blog_id );
1450      }
1451  
1452      /**
1453       * Returns array of stylesheet names of themes allowed on the network.
1454       *
1455       * @since 3.4.0
1456       *
1457       * @staticvar array $allowed_themes
1458       *
1459       * @return string[] Array of stylesheet names.
1460       */
1461  	public static function get_allowed_on_network() {
1462          static $allowed_themes;
1463          if ( ! isset( $allowed_themes ) ) {
1464              $allowed_themes = (array) get_site_option( 'allowedthemes' );
1465          }
1466  
1467          /**
1468           * Filters the array of themes allowed on the network.
1469           *
1470           * @since MU (3.0.0)
1471           *
1472           * @param string[] $allowed_themes An array of theme stylesheet names.
1473           */
1474          $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1475  
1476          return $allowed_themes;
1477      }
1478  
1479      /**
1480       * Returns array of stylesheet names of themes allowed on the site.
1481       *
1482       * @since 3.4.0
1483       *
1484       * @staticvar array $allowed_themes
1485       *
1486       * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1487       * @return string[] Array of stylesheet names.
1488       */
1489  	public static function get_allowed_on_site( $blog_id = null ) {
1490          static $allowed_themes = array();
1491  
1492          if ( ! $blog_id || ! is_multisite() ) {
1493              $blog_id = get_current_blog_id();
1494          }
1495  
1496          if ( isset( $allowed_themes[ $blog_id ] ) ) {
1497              /**
1498               * Filters the array of themes allowed on the site.
1499               *
1500               * @since 4.5.0
1501               *
1502               * @param string[] $allowed_themes An array of theme stylesheet names.
1503               * @param int      $blog_id        ID of the site. Defaults to current site.
1504               */
1505              return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1506          }
1507  
1508          $current = $blog_id == get_current_blog_id();
1509  
1510          if ( $current ) {
1511              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1512          } else {
1513              switch_to_blog( $blog_id );
1514              $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1515              restore_current_blog();
1516          }
1517  
1518          // This is all super old MU back compat joy.
1519          // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1520          if ( false === $allowed_themes[ $blog_id ] ) {
1521              if ( $current ) {
1522                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1523              } else {
1524                  switch_to_blog( $blog_id );
1525                  $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1526                  restore_current_blog();
1527              }
1528  
1529              if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1530                  $allowed_themes[ $blog_id ] = array();
1531              } else {
1532                  $converted = array();
1533                  $themes    = wp_get_themes();
1534                  foreach ( $themes as $stylesheet => $theme_data ) {
1535                      if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) {
1536                          $converted[ $stylesheet ] = true;
1537                      }
1538                  }
1539                  $allowed_themes[ $blog_id ] = $converted;
1540              }
1541              // Set the option so we never have to go through this pain again.
1542              if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1543                  if ( $current ) {
1544                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1545                      delete_option( 'allowed_themes' );
1546                  } else {
1547                      switch_to_blog( $blog_id );
1548                      update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1549                      delete_option( 'allowed_themes' );
1550                      restore_current_blog();
1551                  }
1552              }
1553          }
1554  
1555          /** This filter is documented in wp-includes/class-wp-theme.php */
1556          return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1557      }
1558  
1559      /**
1560       * Enables a theme for all sites on the current network.
1561       *
1562       * @since 4.6.0
1563       *
1564       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
1565       */
1566  	public static function network_enable_theme( $stylesheets ) {
1567          if ( ! is_multisite() ) {
1568              return;
1569          }
1570  
1571          if ( ! is_array( $stylesheets ) ) {
1572              $stylesheets = array( $stylesheets );
1573          }
1574  
1575          $allowed_themes = get_site_option( 'allowedthemes' );
1576          foreach ( $stylesheets as $stylesheet ) {
1577              $allowed_themes[ $stylesheet ] = true;
1578          }
1579  
1580          update_site_option( 'allowedthemes', $allowed_themes );
1581      }
1582  
1583      /**
1584       * Disables a theme for all sites on the current network.
1585       *
1586       * @since 4.6.0
1587       *
1588       * @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
1589       */
1590  	public static function network_disable_theme( $stylesheets ) {
1591          if ( ! is_multisite() ) {
1592              return;
1593          }
1594  
1595          if ( ! is_array( $stylesheets ) ) {
1596              $stylesheets = array( $stylesheets );
1597          }
1598  
1599          $allowed_themes = get_site_option( 'allowedthemes' );
1600          foreach ( $stylesheets as $stylesheet ) {
1601              if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1602                  unset( $allowed_themes[ $stylesheet ] );
1603              }
1604          }
1605  
1606          update_site_option( 'allowedthemes', $allowed_themes );
1607      }
1608  
1609      /**
1610       * Sorts themes by name.
1611       *
1612       * @since 3.4.0
1613       *
1614       * @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
1615       */
1616  	public static function sort_by_name( &$themes ) {
1617          if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
1618              uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1619          } else {
1620              foreach ( $themes as $key => $theme ) {
1621                  $theme->translate_header( 'Name', $theme->headers['Name'] );
1622              }
1623              uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1624          }
1625      }
1626  
1627      /**
1628       * Callback function for usort() to naturally sort themes by name.
1629       *
1630       * Accesses the Name header directly from the class for maximum speed.
1631       * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1632       *
1633       * @since 3.4.0
1634       *
1635       * @param string $a First name.
1636       * @param string $b Second name.
1637       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1638       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1639       */
1640  	private static function _name_sort( $a, $b ) {
1641          return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1642      }
1643  
1644      /**
1645       * Callback function for usort() to naturally sort themes by translated name.
1646       *
1647       * @since 3.4.0
1648       *
1649       * @param string $a First name.
1650       * @param string $b Second name.
1651       * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1652       *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1653       */
1654  	private static function _name_sort_i18n( $a, $b ) {
1655          return strnatcasecmp( $a->name_translated, $b->name_translated );
1656      }
1657  }


Generated: Fri Nov 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1