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


Generated: Sun Sep 15 01:00:03 2019 Cross-referenced by PHPXref 0.7.1