[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

/wp-includes/ -> class-wp-theme.php (source)

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


Generated: Tue Aug 20 08:20:01 2019 Cross-referenced by PHPXref 0.7