[ Index ] |
WordPress Cross Reference |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WP_Theme Class 4 * 5 * @package WordPress 6 * @subpackage Theme 7 */ 8 9 final class WP_Theme implements ArrayAccess { 10 11 /** 12 * Headers for style.css files. 13 * 14 * @static 15 * @access private 16 * @var array 17 */ 18 private static $file_headers = array( 19 'Name' => 'Theme Name', 20 'ThemeURI' => 'Theme URI', 21 'Description' => 'Description', 22 'Author' => 'Author', 23 'AuthorURI' => 'Author URI', 24 'Version' => 'Version', 25 'Template' => 'Template', 26 'Status' => 'Status', 27 'Tags' => 'Tags', 28 'TextDomain' => 'Text Domain', 29 'DomainPath' => 'Domain Path', 30 ); 31 32 /** 33 * Default themes. 34 * 35 * @static 36 * @access private 37 * @var array 38 */ 39 private static $default_themes = array( 40 'classic' => 'WordPress Classic', 41 'default' => 'WordPress Default', 42 'twentyten' => 'Twenty Ten', 43 'twentyeleven' => 'Twenty Eleven', 44 'twentytwelve' => 'Twenty Twelve', 45 'twentythirteen' => 'Twenty Thirteen', 46 'twentyfourteen' => 'Twenty Fourteen', 47 ); 48 49 /** 50 * Renamed theme tags. 51 */ 52 private static $tag_map = array( 53 'fixed-width' => 'fixed-layout', 54 'flexible-width' => 'fluid-layout', 55 ); 56 57 /** 58 * Absolute path to the theme root, usually wp-content/themes 59 * 60 * @access private 61 * @var string 62 */ 63 private $theme_root; 64 65 /** 66 * Header data from the theme's style.css file. 67 * 68 * @access private 69 * @var array 70 */ 71 private $headers = array(); 72 73 /** 74 * Header data from the theme's style.css file after being sanitized. 75 * 76 * @access private 77 * @var array 78 */ 79 private $headers_sanitized; 80 81 /** 82 * Header name from the theme's style.css after being translated. 83 * 84 * Cached due to sorting functions running over the translated name. 85 */ 86 private $name_translated; 87 88 /** 89 * Errors encountered when initializing the theme. 90 * 91 * @access private 92 * @var WP_Error 93 */ 94 private $errors; 95 96 /** 97 * The directory name of the theme's files, inside the theme root. 98 * 99 * In the case of a child theme, this is directory name of the child theme. 100 * Otherwise, 'stylesheet' is the same as 'template'. 101 * 102 * @access private 103 * @var string 104 */ 105 private $stylesheet; 106 107 /** 108 * The directory name of the theme's files, inside the theme root. 109 * 110 * In the case of a child theme, this is the directory name of the parent theme. 111 * Otherwise, 'template' is the same as 'stylesheet'. 112 * 113 * @access private 114 * @var string 115 */ 116 private $template; 117 118 /** 119 * A reference to the parent theme, in the case of a child theme. 120 * 121 * @access private 122 * @var WP_Theme 123 */ 124 private $parent; 125 126 /** 127 * URL to the theme root, usually an absolute URL to wp-content/themes 128 * 129 * @access private 130 * var string 131 */ 132 private $theme_root_uri; 133 134 /** 135 * Flag for whether the theme's textdomain is loaded. 136 * 137 * @access private 138 * @var bool 139 */ 140 private $textdomain_loaded; 141 142 /** 143 * Stores an md5 hash of the theme root, to function as the cache key. 144 * 145 * @access private 146 * @var string 147 */ 148 private $cache_hash; 149 150 /** 151 * Flag for whether the themes cache bucket should be persistently cached. 152 * 153 * Default is false. Can be set with the wp_cache_themes_persistently filter. 154 * 155 * @access private 156 * @var bool 157 */ 158 private static $persistently_cache; 159 160 /** 161 * Expiration time for the themes cache bucket. 162 * 163 * By default the bucket is not cached, so this value is useless. 164 * 165 * @access private 166 * @var bool 167 */ 168 private static $cache_expiration = 1800; 169 170 /** 171 * Constructor for WP_Theme. 172 * 173 * @param string $theme_dir Directory of the theme within the theme_root. 174 * @param string $theme_root Theme root. 175 * @param WP_Error|null $_child If this theme is a parent theme, the child may be passed for validation purposes. 176 */ 177 public function __construct( $theme_dir, $theme_root, $_child = null ) { 178 global $wp_theme_directories; 179 180 // Initialize caching on first run. 181 if ( ! isset( self::$persistently_cache ) ) { 182 self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' ); 183 if ( self::$persistently_cache ) { 184 wp_cache_add_global_groups( 'themes' ); 185 if ( is_int( self::$persistently_cache ) ) 186 self::$cache_expiration = self::$persistently_cache; 187 } else { 188 wp_cache_add_non_persistent_groups( 'themes' ); 189 } 190 } 191 192 $this->theme_root = $theme_root; 193 $this->stylesheet = $theme_dir; 194 195 // Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead. 196 if ( ! in_array( $theme_root, (array) $wp_theme_directories ) && in_array( dirname( $theme_root ), (array) $wp_theme_directories ) ) { 197 $this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet; 198 $this->theme_root = dirname( $theme_root ); 199 } 200 201 $this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet ); 202 $theme_file = $this->stylesheet . '/style.css'; 203 204 $cache = $this->cache_get( 'theme' ); 205 206 if ( is_array( $cache ) ) { 207 foreach ( array( 'errors', 'headers', 'template' ) as $key ) { 208 if ( isset( $cache[ $key ] ) ) 209 $this->$key = $cache[ $key ]; 210 } 211 if ( $this->errors ) 212 return; 213 if ( isset( $cache['theme_root_template'] ) ) 214 $theme_root_template = $cache['theme_root_template']; 215 } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) { 216 $this->headers['Name'] = $this->stylesheet; 217 if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) ) 218 $this->errors = new WP_Error( 'theme_not_found', sprintf( __( 'The theme directory "%s" does not exist.' ), $this->stylesheet ) ); 219 else 220 $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) ); 221 $this->template = $this->stylesheet; 222 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); 223 if ( ! file_exists( $this->theme_root ) ) // Don't cache this one. 224 $this->errors->add( 'theme_root_missing', __( 'ERROR: The themes directory is either empty or doesn’t exist. Please check your installation.' ) ); 225 return; 226 } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) { 227 $this->headers['Name'] = $this->stylesheet; 228 $this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) ); 229 $this->template = $this->stylesheet; 230 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); 231 return; 232 } else { 233 $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' ); 234 // Default themes always trump their pretenders. 235 // Properly identify default themes that are inside a directory within wp-content/themes. 236 if ( $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes ) ) { 237 if ( basename( $this->stylesheet ) != $default_theme_slug ) 238 $this->headers['Name'] .= '/' . $this->stylesheet; 239 } 240 } 241 242 // (If template is set from cache [and there are no errors], we know it's good.) 243 if ( ! $this->template && ! ( $this->template = $this->headers['Template'] ) ) { 244 $this->template = $this->stylesheet; 245 if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) { 246 $this->errors = new WP_Error( 'theme_no_index', __( 'Template is missing.' ) ); 247 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); 248 return; 249 } 250 } 251 252 // If we got our data from cache, we can assume that 'template' is pointing to the right place. 253 if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) { 254 // If we're in a directory of themes inside /themes, look for the parent nearby. 255 // wp-content/themes/directory-of-themes/* 256 $parent_dir = dirname( $this->stylesheet ); 257 if ( '.' != $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) { 258 $this->template = $parent_dir . '/' . $this->template; 259 } elseif ( ( $directories = search_theme_directories() ) && isset( $directories[ $this->template ] ) ) { 260 // Look for the template in the search_theme_directories() results, in case it is in another theme root. 261 // We don't look into directories of themes, just the theme root. 262 $theme_root_template = $directories[ $this->template ]['theme_root']; 263 } else { 264 // Parent theme is missing. 265 $this->errors = new WP_Error( 'theme_no_parent', sprintf( __( 'The parent theme is missing. Please install the "%s" parent theme.' ), $this->template ) ); 266 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); 267 $this->parent = new WP_Theme( $this->template, $this->theme_root, $this ); 268 return; 269 } 270 } 271 272 // Set the parent, if we're a child theme. 273 if ( $this->template != $this->stylesheet ) { 274 // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out. 275 if ( is_a( $_child, 'WP_Theme' ) && $_child->template == $this->stylesheet ) { 276 $_child->parent = null; 277 $_child->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $_child->template ) ); 278 $_child->cache_add( 'theme', array( 'headers' => $_child->headers, 'errors' => $_child->errors, 'stylesheet' => $_child->stylesheet, 'template' => $_child->template ) ); 279 // The two themes actually reference each other with the Template header. 280 if ( $_child->stylesheet == $this->template ) { 281 $this->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $this->template ) ); 282 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); 283 } 284 return; 285 } 286 // Set the parent. Pass the current instance so we can do the crazy checks above and assess errors. 287 $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this ); 288 } 289 290 // We're good. If we didn't retrieve from cache, set it. 291 if ( ! is_array( $cache ) ) { 292 $cache = array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ); 293 // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above. 294 if ( isset( $theme_root_template ) ) 295 $cache['theme_root_template'] = $theme_root_template; 296 $this->cache_add( 'theme', $cache ); 297 } 298 } 299 300 /** 301 * When converting the object to a string, the theme name is returned. 302 * 303 * @return string Theme name, ready for display (translated) 304 */ 305 public function __toString() { 306 return (string) $this->display('Name'); 307 } 308 309 /** 310 * __isset() magic method for properties formerly returned by current_theme_info() 311 */ 312 public function __isset( $offset ) { 313 static $properties = array( 314 'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet', 315 'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri', 316 ); 317 318 return in_array( $offset, $properties ); 319 } 320 321 /** 322 * __get() magic method for properties formerly returned by current_theme_info() 323 */ 324 public function __get( $offset ) { 325 switch ( $offset ) { 326 case 'name' : 327 case 'title' : 328 return $this->get('Name'); 329 case 'version' : 330 return $this->get('Version'); 331 case 'parent_theme' : 332 return $this->parent() ? $this->parent()->get('Name') : ''; 333 case 'template_dir' : 334 return $this->get_template_directory(); 335 case 'stylesheet_dir' : 336 return $this->get_stylesheet_directory(); 337 case 'template' : 338 return $this->get_template(); 339 case 'stylesheet' : 340 return $this->get_stylesheet(); 341 case 'screenshot' : 342 return $this->get_screenshot( 'relative' ); 343 // 'author' and 'description' did not previously return translated data. 344 case 'description' : 345 return $this->display('Description'); 346 case 'author' : 347 return $this->display('Author'); 348 case 'tags' : 349 return $this->get( 'Tags' ); 350 case 'theme_root' : 351 return $this->get_theme_root(); 352 case 'theme_root_uri' : 353 return $this->get_theme_root_uri(); 354 // For cases where the array was converted to an object. 355 default : 356 return $this->offsetGet( $offset ); 357 } 358 } 359 360 /** 361 * Method to implement ArrayAccess for keys formerly returned by get_themes() 362 */ 363 public function offsetSet( $offset, $value ) {} 364 365 /** 366 * Method to implement ArrayAccess for keys formerly returned by get_themes() 367 */ 368 public function offsetUnset( $offset ) {} 369 370 /** 371 * Method to implement ArrayAccess for keys formerly returned by get_themes() 372 */ 373 public function offsetExists( $offset ) { 374 static $keys = array( 375 'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description', 376 'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir', 377 'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme', 378 ); 379 380 return in_array( $offset, $keys ); 381 } 382 383 /** 384 * Method to implement ArrayAccess for keys formerly returned by get_themes(). 385 * 386 * Author, Author Name, Author URI, and Description did not previously return 387 * translated data. We are doing so now as it is safe to do. However, as 388 * Name and Title could have been used as the key for get_themes(), both remain 389 * untranslated for back compatibility. This means that ['Name'] is not ideal, 390 * and care should be taken to use $theme->display('Name') to get a properly 391 * translated header. 392 */ 393 public function offsetGet( $offset ) { 394 switch ( $offset ) { 395 case 'Name' : 396 case 'Title' : 397 // See note above about using translated data. get() is not ideal. 398 // It is only for backwards compatibility. Use display(). 399 return $this->get('Name'); 400 case 'Author' : 401 return $this->display( 'Author'); 402 case 'Author Name' : 403 return $this->display( 'Author', false); 404 case 'Author URI' : 405 return $this->display('AuthorURI'); 406 case 'Description' : 407 return $this->display( 'Description'); 408 case 'Version' : 409 case 'Status' : 410 return $this->get( $offset ); 411 case 'Template' : 412 return $this->get_template(); 413 case 'Stylesheet' : 414 return $this->get_stylesheet(); 415 case 'Template Files' : 416 return $this->get_files( 'php', 1, true ); 417 case 'Stylesheet Files' : 418 return $this->get_files( 'css', 0, false ); 419 case 'Template Dir' : 420 return $this->get_template_directory(); 421 case 'Stylesheet Dir' : 422 return $this->get_stylesheet_directory(); 423 case 'Screenshot' : 424 return $this->get_screenshot( 'relative' ); 425 case 'Tags' : 426 return $this->get('Tags'); 427 case 'Theme Root' : 428 return $this->get_theme_root(); 429 case 'Theme Root URI' : 430 return $this->get_theme_root_uri(); 431 case 'Parent Theme' : 432 return $this->parent() ? $this->parent()->get('Name') : ''; 433 default : 434 return null; 435 } 436 } 437 438 /** 439 * Returns errors property. 440 * 441 * @since 3.4.0 442 * @access public 443 * 444 * @return WP_Error|bool WP_Error if there are errors, or false. 445 */ 446 public function errors() { 447 return is_wp_error( $this->errors ) ? $this->errors : false; 448 } 449 450 /** 451 * Whether the theme exists. 452 * 453 * A theme with errors exists. A theme with the error of 'theme_not_found', 454 * meaning that the theme's directory was not found, does not exist. 455 * 456 * @since 3.4.0 457 * @access public 458 * 459 * @return bool Whether the theme exists. 460 */ 461 public function exists() { 462 return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes() ) ); 463 } 464 465 /** 466 * Returns reference to the parent theme. 467 * 468 * @since 3.4.0 469 * @access public 470 * 471 * @return WP_Theme|bool Parent theme, or false if the current theme is not a child theme. 472 */ 473 public function parent() { 474 return isset( $this->parent ) ? $this->parent : false; 475 } 476 477 /** 478 * Adds theme data to cache. 479 * 480 * Cache entries keyed by the theme and the type of data. 481 * 482 * @access private 483 * @since 3.4.0 484 * 485 * @param string $key Type of data to store (theme, screenshot, headers, page_templates) 486 * @param string $data Data to store 487 * @return bool Return value from wp_cache_add() 488 */ 489 private function cache_add( $key, $data ) { 490 return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration ); 491 } 492 493 /** 494 * Gets theme data from cache. 495 * 496 * Cache entries are keyed by the theme and the type of data. 497 * 498 * @access private 499 * @since 3.4.0 500 * 501 * @param string $key Type of data to retrieve (theme, screenshot, headers, page_templates) 502 * @return mixed Retrieved data 503 */ 504 private function cache_get( $key ) { 505 return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' ); 506 } 507 508 /** 509 * Clears the cache for the theme. 510 * 511 * @access public 512 * @since 3.4.0 513 */ 514 public function cache_delete() { 515 foreach ( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key ) 516 wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' ); 517 $this->template = $this->textdomain_loaded = $this->theme_root_uri = $this->parent = $this->errors = $this->headers_sanitized = $this->name_translated = null; 518 $this->headers = array(); 519 $this->__construct( $this->stylesheet, $this->theme_root ); 520 } 521 522 /** 523 * Get a raw, unformatted theme header. 524 * 525 * The header is sanitized, but is not translated, and is not marked up for display. 526 * To get a theme header for display, use the display() method. 527 * 528 * Use the get_template() method, not the 'Template' header, for finding the template. 529 * The 'Template' header is only good for what was written in the style.css, while 530 * get_template() takes into account where WordPress actually located the theme and 531 * whether it is actually valid. 532 * 533 * @access public 534 * @since 3.4.0 535 * 536 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. 537 * @return string String on success, false on failure. 538 */ 539 public function get( $header ) { 540 if ( ! isset( $this->headers[ $header ] ) ) 541 return false; 542 543 if ( ! isset( $this->headers_sanitized ) ) { 544 $this->headers_sanitized = $this->cache_get( 'headers' ); 545 if ( ! is_array( $this->headers_sanitized ) ) 546 $this->headers_sanitized = array(); 547 } 548 549 if ( isset( $this->headers_sanitized[ $header ] ) ) 550 return $this->headers_sanitized[ $header ]; 551 552 // If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets. 553 if ( self::$persistently_cache ) { 554 foreach ( array_keys( $this->headers ) as $_header ) 555 $this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] ); 556 $this->cache_add( 'headers', $this->headers_sanitized ); 557 } else { 558 $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] ); 559 } 560 561 return $this->headers_sanitized[ $header ]; 562 } 563 564 /** 565 * Gets a theme header, formatted and translated for display. 566 * 567 * @access public 568 * @since 3.4.0 569 * 570 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. 571 * @param bool $markup Optional. Whether to mark up the header. Defaults to true. 572 * @param bool $translate Optional. Whether to translate the header. Defaults to true. 573 * @return string Processed header, false on failure. 574 */ 575 public function display( $header, $markup = true, $translate = true ) { 576 $value = $this->get( $header ); 577 578 if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) 579 $translate = false; 580 581 if ( $translate ) 582 $value = $this->translate_header( $header, $value ); 583 584 if ( $markup ) 585 $value = $this->markup_header( $header, $value, $translate ); 586 587 return $value; 588 } 589 590 /** 591 * Sanitize a theme header. 592 * 593 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. 594 * @param string $value Value to sanitize. 595 */ 596 private function sanitize_header( $header, $value ) { 597 switch ( $header ) { 598 case 'Status' : 599 if ( ! $value ) { 600 $value = 'publish'; 601 break; 602 } 603 // Fall through otherwise. 604 case 'Name' : 605 static $header_tags = array( 606 'abbr' => array( 'title' => true ), 607 'acronym' => array( 'title' => true ), 608 'code' => true, 609 'em' => true, 610 'strong' => true, 611 ); 612 $value = wp_kses( $value, $header_tags ); 613 break; 614 case 'Author' : 615 // There shouldn't be anchor tags in Author, but some themes like to be challenging. 616 case 'Description' : 617 static $header_tags_with_a = array( 618 'a' => array( 'href' => true, 'title' => true ), 619 'abbr' => array( 'title' => true ), 620 'acronym' => array( 'title' => true ), 621 'code' => true, 622 'em' => true, 623 'strong' => true, 624 ); 625 $value = wp_kses( $value, $header_tags_with_a ); 626 break; 627 case 'ThemeURI' : 628 case 'AuthorURI' : 629 $value = esc_url_raw( $value ); 630 break; 631 case 'Tags' : 632 $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) ); 633 break; 634 } 635 636 return $value; 637 } 638 639 /** 640 * Mark up a theme header. 641 * 642 * @access private 643 * @since 3.4.0 644 * 645 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. 646 * @param string $value Value to mark up. 647 * @param string $translate Whether the header has been translated. 648 * @return string Value, marked up. 649 */ 650 private function markup_header( $header, $value, $translate ) { 651 switch ( $header ) { 652 case 'Name' : 653 if ( empty( $value ) ) 654 $value = $this->get_stylesheet(); 655 break; 656 case 'Description' : 657 $value = wptexturize( $value ); 658 break; 659 case 'Author' : 660 if ( $this->get('AuthorURI') ) { 661 static $attr = null; 662 if ( ! isset( $attr ) ) 663 $attr = esc_attr__( 'Visit author homepage' ); 664 $value = sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', $this->display( 'AuthorURI', true, $translate ), $attr, $value ); 665 } elseif ( ! $value ) { 666 $value = __( 'Anonymous' ); 667 } 668 break; 669 case 'Tags' : 670 static $comma = null; 671 if ( ! isset( $comma ) ) { 672 /* translators: used between list items, there is a space after the comma */ 673 $comma = __( ', ' ); 674 } 675 $value = implode( $comma, $value ); 676 break; 677 case 'ThemeURI' : 678 case 'AuthorURI' : 679 $value = esc_url( $value ); 680 break; 681 } 682 683 return $value; 684 } 685 686 /** 687 * Translate a theme header. 688 * 689 * @access private 690 * @since 3.4.0 691 * 692 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags. 693 * @param string $value Value to translate. 694 * @return string Translated value. 695 */ 696 private function translate_header( $header, $value ) { 697 switch ( $header ) { 698 case 'Name' : 699 // Cached for sorting reasons. 700 if ( isset( $this->name_translated ) ) 701 return $this->name_translated; 702 $this->name_translated = translate( $value, $this->get('TextDomain' ) ); 703 return $this->name_translated; 704 case 'Tags' : 705 if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) 706 return $value; 707 708 static $tags_list; 709 if ( ! isset( $tags_list ) ) { 710 $tags_list = array(); 711 $feature_list = get_theme_feature_list( false ); // No API 712 foreach ( $feature_list as $tags ) 713 $tags_list += $tags; 714 } 715 716 foreach ( $value as &$tag ) { 717 if ( isset( $tags_list[ $tag ] ) ) { 718 $tag = $tags_list[ $tag ]; 719 } elseif ( isset( self::$tag_map[ $tag ] ) ) { 720 $tag = $tags_list[ self::$tag_map[ $tag ] ]; 721 } 722 } 723 724 return $value; 725 break; 726 default : 727 $value = translate( $value, $this->get('TextDomain') ); 728 } 729 return $value; 730 } 731 732 /** 733 * The directory name of the theme's "stylesheet" files, inside the theme root. 734 * 735 * In the case of a child theme, this is directory name of the child theme. 736 * Otherwise, get_stylesheet() is the same as get_template(). 737 * 738 * @since 3.4.0 739 * @access public 740 * 741 * @return string Stylesheet 742 */ 743 public function get_stylesheet() { 744 return $this->stylesheet; 745 } 746 747 /** 748 * The directory name of the theme's "template" files, inside the theme root. 749 * 750 * In the case of a child theme, this is the directory name of the parent theme. 751 * Otherwise, the get_template() is the same as get_stylesheet(). 752 * 753 * @since 3.4.0 754 * @access public 755 * 756 * @return string Template 757 */ 758 public function get_template() { 759 return $this->template; 760 } 761 762 /** 763 * Returns the absolute path to the directory of a theme's "stylesheet" files. 764 * 765 * In the case of a child theme, this is the absolute path to the directory 766 * of the child theme's files. 767 * 768 * @since 3.4.0 769 * @access public 770 * 771 * @return string Absolute path of the stylesheet directory. 772 */ 773 public function get_stylesheet_directory() { 774 if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes() ) ) 775 return ''; 776 777 return $this->theme_root . '/' . $this->stylesheet; 778 } 779 780 /** 781 * Returns the absolute path to the directory of a theme's "template" files. 782 * 783 * In the case of a child theme, this is the absolute path to the directory 784 * of the parent theme's files. 785 * 786 * @since 3.4.0 787 * @access public 788 * 789 * @return string Absolute path of the template directory. 790 */ 791 public function get_template_directory() { 792 if ( $this->parent() ) 793 $theme_root = $this->parent()->theme_root; 794 else 795 $theme_root = $this->theme_root; 796 797 return $theme_root . '/' . $this->template; 798 } 799 800 /** 801 * Returns the URL to the directory of a theme's "stylesheet" files. 802 * 803 * In the case of a child theme, this is the URL to the directory of the 804 * child theme's files. 805 * 806 * @since 3.4.0 807 * @access public 808 * 809 * @return string URL to the stylesheet directory. 810 */ 811 public function get_stylesheet_directory_uri() { 812 return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) ); 813 } 814 815 /** 816 * Returns the URL to the directory of a theme's "template" files. 817 * 818 * In the case of a child theme, this is the URL to the directory of the 819 * parent theme's files. 820 * 821 * @since 3.4.0 822 * @access public 823 * 824 * @return string URL to the template directory. 825 */ 826 public function get_template_directory_uri() { 827 if ( $this->parent() ) 828 $theme_root_uri = $this->parent()->get_theme_root_uri(); 829 else 830 $theme_root_uri = $this->get_theme_root_uri(); 831 832 return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) ); 833 } 834 835 /** 836 * The absolute path to the directory of the theme root. 837 * 838 * This is typically the absolute path to wp-content/themes. 839 * 840 * @since 3.4.0 841 * @access public 842 * 843 * @return string Theme root. 844 */ 845 public function get_theme_root() { 846 return $this->theme_root; 847 } 848 849 /** 850 * Returns the URL to the directory of the theme root. 851 * 852 * This is typically the absolute URL to wp-content/themes. This forms the basis 853 * for all other URLs returned by WP_Theme, so we pass it to the public function 854 * get_theme_root_uri() and allow it to run the theme_root_uri filter. 855 * 856 * @uses get_theme_root_uri() 857 * 858 * @since 3.4.0 859 * @access public 860 * 861 * @return string Theme root URI. 862 */ 863 public function get_theme_root_uri() { 864 if ( ! isset( $this->theme_root_uri ) ) 865 $this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root ); 866 return $this->theme_root_uri; 867 } 868 869 /** 870 * Returns the main screenshot file for the theme. 871 * 872 * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed. 873 * 874 * Screenshots for a theme must be in the stylesheet directory. (In the case of child 875 * themes, parent theme screenshots are not inherited.) 876 * 877 * @since 3.4.0 878 * @access public 879 * 880 * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI. 881 * @return mixed Screenshot file. False if the theme does not have a screenshot. 882 */ 883 public function get_screenshot( $uri = 'uri' ) { 884 $screenshot = $this->cache_get( 'screenshot' ); 885 if ( $screenshot ) { 886 if ( 'relative' == $uri ) 887 return $screenshot; 888 return $this->get_stylesheet_directory_uri() . '/' . $screenshot; 889 } elseif ( 0 === $screenshot ) { 890 return false; 891 } 892 893 foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) { 894 if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { 895 $this->cache_add( 'screenshot', 'screenshot.' . $ext ); 896 if ( 'relative' == $uri ) 897 return 'screenshot.' . $ext; 898 return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext; 899 } 900 } 901 902 $this->cache_add( 'screenshot', 0 ); 903 return false; 904 } 905 906 /** 907 * Return files in the theme's directory. 908 * 909 * @since 3.4.0 910 * @access public 911 * 912 * @param mixed $type Optional. Array of extensions to return. Defaults to all files (null). 913 * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite. 914 * @param bool $search_parent Optional. Whether to return parent files. Defaults to false. 915 * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values 916 * being absolute paths. 917 */ 918 public function get_files( $type = null, $depth = 0, $search_parent = false ) { 919 $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth ); 920 921 if ( $search_parent && $this->parent() ) 922 $files += (array) self::scandir( $this->get_template_directory(), $type, $depth ); 923 924 return $files; 925 } 926 927 /** 928 * Returns the theme's page templates. 929 * 930 * @since 3.4.0 931 * @access public 932 * 933 * @return array Array of page templates, keyed by filename, with the value of the translated header name. 934 */ 935 public function get_page_templates() { 936 // If you screw up your current theme and we invalidate your parent, most things still work. Let it slide. 937 if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) 938 return array(); 939 940 $page_templates = $this->cache_get( 'page_templates' ); 941 942 if ( ! is_array( $page_templates ) ) { 943 $page_templates = array(); 944 945 $files = (array) $this->get_files( 'php', 1 ); 946 947 foreach ( $files as $file => $full_path ) { 948 if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) 949 continue; 950 $page_templates[ $file ] = _cleanup_header_comment( $header[1] ); 951 } 952 953 $this->cache_add( 'page_templates', $page_templates ); 954 } 955 956 if ( $this->load_textdomain() ) { 957 foreach ( $page_templates as &$page_template ) { 958 $page_template = $this->translate_header( 'Template Name', $page_template ); 959 } 960 } 961 962 if ( $this->parent() ) 963 $page_templates += $this->parent()->get_page_templates(); 964 965 return $page_templates; 966 } 967 968 /** 969 * Scans a directory for files of a certain extension. 970 * 971 * @since 3.4.0 972 * @access private 973 * 974 * @param string $path Absolute path to search. 975 * @param mixed Array of extensions to find, string of a single extension, or null for all extensions. 976 * @param int $depth How deep to search for files. Optional, defaults to a flat scan (0 depth). -1 depth is infinite. 977 * @param string $relative_path The basename of the absolute path. Used to control the returned path 978 * for the found files, particularly when this function recurses to lower depths. 979 */ 980 private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) { 981 if ( ! is_dir( $path ) ) 982 return false; 983 984 if ( $extensions ) { 985 $extensions = (array) $extensions; 986 $_extensions = implode( '|', $extensions ); 987 } 988 989 $relative_path = trailingslashit( $relative_path ); 990 if ( '/' == $relative_path ) 991 $relative_path = ''; 992 993 $results = scandir( $path ); 994 $files = array(); 995 996 foreach ( $results as $result ) { 997 if ( '.' == $result[0] ) 998 continue; 999 if ( is_dir( $path . '/' . $result ) ) { 1000 if ( ! $depth || 'CVS' == $result ) 1001 continue; 1002 $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result ); 1003 $files = array_merge_recursive( $files, $found ); 1004 } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) { 1005 $files[ $relative_path . $result ] = $path . '/' . $result; 1006 } 1007 } 1008 1009 return $files; 1010 } 1011 1012 /** 1013 * Loads the theme's textdomain. 1014 * 1015 * Translation files are not inherited from the parent theme. Todo: if this fails for the 1016 * child theme, it should probably try to load the parent theme's translations. 1017 * 1018 * @since 3.4.0 1019 * @access public 1020 * 1021 * @return True if the textdomain was successfully loaded or has already been loaded. False if 1022 * no textdomain was specified in the file headers, or if the domain could not be loaded. 1023 */ 1024 public function load_textdomain() { 1025 if ( isset( $this->textdomain_loaded ) ) 1026 return $this->textdomain_loaded; 1027 1028 $textdomain = $this->get('TextDomain'); 1029 if ( ! $textdomain ) { 1030 $this->textdomain_loaded = false; 1031 return false; 1032 } 1033 1034 if ( is_textdomain_loaded( $textdomain ) ) { 1035 $this->textdomain_loaded = true; 1036 return true; 1037 } 1038 1039 $path = $this->get_stylesheet_directory(); 1040 if ( $domainpath = $this->get('DomainPath') ) 1041 $path .= $domainpath; 1042 else 1043 $path .= '/languages'; 1044 1045 $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path ); 1046 return $this->textdomain_loaded; 1047 } 1048 1049 /** 1050 * Whether the theme is allowed (multisite only). 1051 * 1052 * @since 3.4.0 1053 * @access public 1054 * 1055 * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site' 1056 * settings, or 'both'. Defaults to 'both'. 1057 * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current blog. 1058 * @return bool Whether the theme is allowed for the network. Returns true in single-site. 1059 */ 1060 public function is_allowed( $check = 'both', $blog_id = null ) { 1061 if ( ! is_multisite() ) 1062 return true; 1063 1064 if ( 'both' == $check || 'network' == $check ) { 1065 $allowed = self::get_allowed_on_network(); 1066 if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) 1067 return true; 1068 } 1069 1070 if ( 'both' == $check || 'site' == $check ) { 1071 $allowed = self::get_allowed_on_site( $blog_id ); 1072 if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) 1073 return true; 1074 } 1075 1076 return false; 1077 } 1078 1079 /** 1080 * Returns array of stylesheet names of themes allowed on the site or network. 1081 * 1082 * @since 3.4.0 1083 * @access public 1084 * 1085 * @param int $blog_id Optional. Defaults to current blog. 1086 * @return array Array of stylesheet names. 1087 */ 1088 public static function get_allowed( $blog_id = null ) { 1089 $network = (array) apply_filters( 'allowed_themes', self::get_allowed_on_network() ); 1090 return $network + self::get_allowed_on_site( $blog_id ); 1091 } 1092 1093 /** 1094 * Returns array of stylesheet names of themes allowed on the network. 1095 * 1096 * @since 3.4.0 1097 * @access public 1098 * 1099 * @return array Array of stylesheet names. 1100 */ 1101 public static function get_allowed_on_network() { 1102 static $allowed_themes; 1103 if ( ! isset( $allowed_themes ) ) 1104 $allowed_themes = (array) get_site_option( 'allowedthemes' ); 1105 return $allowed_themes; 1106 } 1107 1108 /** 1109 * Returns array of stylesheet names of themes allowed on the site. 1110 * 1111 * @since 3.4.0 1112 * @access public 1113 * 1114 * @param int $blog_id Optional. Defaults to current blog. 1115 * @return array Array of stylesheet names. 1116 */ 1117 public static function get_allowed_on_site( $blog_id = null ) { 1118 static $allowed_themes = array(); 1119 1120 if ( ! $blog_id || ! is_multisite() ) 1121 $blog_id = get_current_blog_id(); 1122 1123 if ( isset( $allowed_themes[ $blog_id ] ) ) 1124 return $allowed_themes[ $blog_id ]; 1125 1126 $current = $blog_id == get_current_blog_id(); 1127 1128 if ( $current ) { 1129 $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' ); 1130 } else { 1131 switch_to_blog( $blog_id ); 1132 $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' ); 1133 restore_current_blog(); 1134 } 1135 1136 // This is all super old MU back compat joy. 1137 // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name. 1138 if ( false === $allowed_themes[ $blog_id ] ) { 1139 if ( $current ) { 1140 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' ); 1141 } else { 1142 switch_to_blog( $blog_id ); 1143 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' ); 1144 restore_current_blog(); 1145 } 1146 1147 if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) { 1148 $allowed_themes[ $blog_id ] = array(); 1149 } else { 1150 $converted = array(); 1151 $themes = wp_get_themes(); 1152 foreach ( $themes as $stylesheet => $theme_data ) { 1153 if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) ) 1154 $converted[ $stylesheet ] = true; 1155 } 1156 $allowed_themes[ $blog_id ] = $converted; 1157 } 1158 // Set the option so we never have to go through this pain again. 1159 if ( is_admin() && $allowed_themes[ $blog_id ] ) { 1160 if ( $current ) { 1161 update_option( 'allowedthemes', $allowed_themes[ $blog_id ] ); 1162 delete_option( 'allowed_themes' ); 1163 } else { 1164 switch_to_blog( $blog_id ); 1165 update_option( 'allowedthemes', $allowed_themes[ $blog_id ] ); 1166 delete_option( 'allowed_themes' ); 1167 restore_current_blog(); 1168 } 1169 } 1170 } 1171 1172 return (array) $allowed_themes[ $blog_id ]; 1173 } 1174 1175 /** 1176 * Sort themes by name. 1177 * 1178 * @since 3.4.0 1179 * @access public 1180 */ 1181 public static function sort_by_name( &$themes ) { 1182 if ( 0 === strpos( get_locale(), 'en_' ) ) { 1183 uasort( $themes, array( 'WP_Theme', '_name_sort' ) ); 1184 } else { 1185 uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) ); 1186 } 1187 } 1188 1189 /** 1190 * Callback function for usort() to naturally sort themes by name. 1191 * 1192 * Accesses the Name header directly from the class for maximum speed. 1193 * Would choke on HTML but we don't care enough to slow it down with strip_tags(). 1194 * 1195 * @since 3.4.0 1196 * @access private 1197 */ 1198 private static function _name_sort( $a, $b ) { 1199 return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] ); 1200 } 1201 1202 /** 1203 * Name sort (with translation). 1204 * 1205 * @since 3.4.0 1206 * @access private 1207 */ 1208 private static function _name_sort_i18n( $a, $b ) { 1209 // Don't mark up; Do translate. 1210 return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) ); 1211 } 1212 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Mar 25 01:41:18 2014 | WordPress honlapkészítés: online1.hu |