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