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