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