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