[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

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

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


Generated: Sat Nov 23 20:47:33 2019 Cross-referenced by PHPXref 0.7