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