[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Sun Jan 16 01:00:03 2022 Cross-referenced by PHPXref 0.7.1