[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Fri Sep 30 08:20:01 2022 Cross-referenced by PHPXref