]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-theme.php
WordPress 4.7-scripts
[autoinstalls/wordpress.git] / wp-includes / class-wp-theme.php
1 <?php
2 /**
3  * WP_Theme Class
4  *
5  * @package WordPress
6  * @subpackage Theme
7  * @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 = $this->get_stylesheet();
726                                 break;
727                         case 'Description' :
728                                 $value = wptexturize( $value );
729                                 break;
730                         case 'Author' :
731                                 if ( $this->get('AuthorURI') ) {
732                                         $value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value );
733                                 } elseif ( ! $value ) {
734                                         $value = __( 'Anonymous' );
735                                 }
736                                 break;
737                         case 'Tags' :
738                                 static $comma = null;
739                                 if ( ! isset( $comma ) ) {
740                                         /* translators: used between list items, there is a space after the comma */
741                                         $comma = __( ', ' );
742                                 }
743                                 $value = implode( $comma, $value );
744                                 break;
745                         case 'ThemeURI' :
746                         case 'AuthorURI' :
747                                 $value = esc_url( $value );
748                                 break;
749                 }
750
751                 return $value;
752         }
753
754         /**
755          * Translate a theme header.
756          *
757          * @since 3.4.0
758          * @access private
759          *
760          * @staticvar array $tags_list
761          *
762          * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
763          * @param string $value Value to translate.
764          * @return string Translated value.
765          */
766         private function translate_header( $header, $value ) {
767                 switch ( $header ) {
768                         case 'Name' :
769                                 // Cached for sorting reasons.
770                                 if ( isset( $this->name_translated ) )
771                                         return $this->name_translated;
772                                 $this->name_translated = translate( $value, $this->get('TextDomain' ) );
773                                 return $this->name_translated;
774                         case 'Tags' :
775                                 if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) {
776                                         return $value;
777                                 }
778
779                                 static $tags_list;
780                                 if ( ! isset( $tags_list ) ) {
781                                         $tags_list = array(
782                                                 // As of 4.6, deprecated tags which are only used to provide translation for older themes.
783                                                 'black' => __( 'Black' ), 'blue' => __( 'Blue' ), 'brown'  => __( 'Brown' ),
784                                                 'gray' => __( 'Gray' ), 'green'  => __( 'Green' ), 'orange' => __( 'Orange' ),
785                                                 'pink' => __( 'Pink' ), 'purple' => __( 'Purple' ), 'red' => __( 'Red' ),
786                                                 'silver' => __( 'Silver' ), 'tan' => __( 'Tan' ), 'white' => __( 'White' ),
787                                                 'yellow' => __( 'Yellow' ), 'dark' => __( 'Dark' ), 'light' => __( 'Light' ),
788                                                 'fixed-layout' => __( 'Fixed Layout' ), 'fluid-layout' => __( 'Fluid Layout' ),
789                                                 'responsive-layout' => __( 'Responsive Layout' ), 'blavatar' => __( 'Blavatar' ),
790                                                 'photoblogging' => __( 'Photoblogging' ), 'seasonal' => __( 'Seasonal' ),
791                                         );
792
793                                         $feature_list = get_theme_feature_list( false ); // No API
794                                         foreach ( $feature_list as $tags ) {
795                                                 $tags_list += $tags;
796                                         }
797                                 }
798
799                                 foreach ( $value as &$tag ) {
800                                         if ( isset( $tags_list[ $tag ] ) ) {
801                                                 $tag = $tags_list[ $tag ];
802                                         } elseif ( isset( self::$tag_map[ $tag ] ) ) {
803                                                 $tag = $tags_list[ self::$tag_map[ $tag ] ];
804                                         }
805                                 }
806
807                                 return $value;
808
809                         default :
810                                 $value = translate( $value, $this->get('TextDomain') );
811                 }
812                 return $value;
813         }
814
815         /**
816          * The directory name of the theme's "stylesheet" files, inside the theme root.
817          *
818          * In the case of a child theme, this is directory name of the child theme.
819          * Otherwise, get_stylesheet() is the same as get_template().
820          *
821          * @since 3.4.0
822          * @access public
823          *
824          * @return string Stylesheet
825          */
826         public function get_stylesheet() {
827                 return $this->stylesheet;
828         }
829
830         /**
831          * The directory name of the theme's "template" files, inside the theme root.
832          *
833          * In the case of a child theme, this is the directory name of the parent theme.
834          * Otherwise, the get_template() is the same as get_stylesheet().
835          *
836          * @since 3.4.0
837          * @access public
838          *
839          * @return string Template
840          */
841         public function get_template() {
842                 return $this->template;
843         }
844
845         /**
846          * Returns the absolute path to the directory of a theme's "stylesheet" files.
847          *
848          * In the case of a child theme, this is the absolute path to the directory
849          * of the child theme's files.
850          *
851          * @since 3.4.0
852          * @access public
853          *
854          * @return string Absolute path of the stylesheet directory.
855          */
856         public function get_stylesheet_directory() {
857                 if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes() ) )
858                         return '';
859
860                 return $this->theme_root . '/' . $this->stylesheet;
861         }
862
863         /**
864          * Returns the absolute path to the directory of a theme's "template" files.
865          *
866          * In the case of a child theme, this is the absolute path to the directory
867          * of the parent theme's files.
868          *
869          * @since 3.4.0
870          * @access public
871          *
872          * @return string Absolute path of the template directory.
873          */
874         public function get_template_directory() {
875                 if ( $this->parent() )
876                         $theme_root = $this->parent()->theme_root;
877                 else
878                         $theme_root = $this->theme_root;
879
880                 return $theme_root . '/' . $this->template;
881         }
882
883         /**
884          * Returns the URL to the directory of a theme's "stylesheet" files.
885          *
886          * In the case of a child theme, this is the URL to the directory of the
887          * child theme's files.
888          *
889          * @since 3.4.0
890          * @access public
891          *
892          * @return string URL to the stylesheet directory.
893          */
894         public function get_stylesheet_directory_uri() {
895                 return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
896         }
897
898         /**
899          * Returns the URL to the directory of a theme's "template" files.
900          *
901          * In the case of a child theme, this is the URL to the directory of the
902          * parent theme's files.
903          *
904          * @since 3.4.0
905          * @access public
906          *
907          * @return string URL to the template directory.
908          */
909         public function get_template_directory_uri() {
910                 if ( $this->parent() )
911                         $theme_root_uri = $this->parent()->get_theme_root_uri();
912                 else
913                         $theme_root_uri = $this->get_theme_root_uri();
914
915                 return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
916         }
917
918         /**
919          * The absolute path to the directory of the theme root.
920          *
921          * This is typically the absolute path to wp-content/themes.
922          *
923          * @since 3.4.0
924          * @access public
925          *
926          * @return string Theme root.
927          */
928         public function get_theme_root() {
929                 return $this->theme_root;
930         }
931
932         /**
933          * Returns the URL to the directory of the theme root.
934          *
935          * This is typically the absolute URL to wp-content/themes. This forms the basis
936          * for all other URLs returned by WP_Theme, so we pass it to the public function
937          * get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter.
938          *
939          * @since 3.4.0
940          * @access public
941          *
942          * @return string Theme root URI.
943          */
944         public function get_theme_root_uri() {
945                 if ( ! isset( $this->theme_root_uri ) )
946                         $this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
947                 return $this->theme_root_uri;
948         }
949
950         /**
951          * Returns the main screenshot file for the theme.
952          *
953          * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
954          *
955          * Screenshots for a theme must be in the stylesheet directory. (In the case of child
956          * themes, parent theme screenshots are not inherited.)
957          *
958          * @since 3.4.0
959          * @access public
960          *
961          * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
962          * @return string|false Screenshot file. False if the theme does not have a screenshot.
963          */
964         public function get_screenshot( $uri = 'uri' ) {
965                 $screenshot = $this->cache_get( 'screenshot' );
966                 if ( $screenshot ) {
967                         if ( 'relative' == $uri )
968                                 return $screenshot;
969                         return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
970                 } elseif ( 0 === $screenshot ) {
971                         return false;
972                 }
973
974                 foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) {
975                         if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
976                                 $this->cache_add( 'screenshot', 'screenshot.' . $ext );
977                                 if ( 'relative' == $uri )
978                                         return 'screenshot.' . $ext;
979                                 return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
980                         }
981                 }
982
983                 $this->cache_add( 'screenshot', 0 );
984                 return false;
985         }
986
987         /**
988          * Return files in the theme's directory.
989          *
990          * @since 3.4.0
991          * @access public
992          *
993          * @param mixed $type Optional. Array of extensions to return. Defaults to all files (null).
994          * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite.
995          * @param bool $search_parent Optional. Whether to return parent files. Defaults to false.
996          * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values
997          *                   being absolute paths.
998          */
999         public function get_files( $type = null, $depth = 0, $search_parent = false ) {
1000                 $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
1001
1002                 if ( $search_parent && $this->parent() )
1003                         $files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
1004
1005                 return $files;
1006         }
1007
1008         /**
1009          * Returns the theme's post templates.
1010          *
1011          * @since 4.7.0
1012          * @access public
1013          *
1014          * @return array Array of page templates, keyed by filename and post type,
1015          *               with the value of the translated header name.
1016          */
1017         public function get_post_templates() {
1018                 // If you screw up your current theme and we invalidate your parent, most things still work. Let it slide.
1019                 if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) {
1020                         return array();
1021                 }
1022
1023                 $post_templates = $this->cache_get( 'post_templates' );
1024
1025                 if ( ! is_array( $post_templates ) ) {
1026                         $post_templates = array();
1027
1028                         $files = (array) $this->get_files( 'php', 1 );
1029
1030                         foreach ( $files as $file => $full_path ) {
1031                                 if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) {
1032                                         continue;
1033                                 }
1034
1035                                 $types = array( 'page' );
1036                                 if ( preg_match( '|Template Post Type:(.*)$|mi', file_get_contents( $full_path ), $type ) ) {
1037                                         $types = explode( ',', _cleanup_header_comment( $type[1] ) );
1038                                 }
1039
1040                                 foreach ( $types as $type ) {
1041                                         $type = sanitize_key( $type );
1042                                         if ( ! isset( $post_templates[ $type ] ) ) {
1043                                                 $post_templates[ $type ] = array();
1044                                         }
1045
1046                                         $post_templates[ $type ][ $file ] = _cleanup_header_comment( $header[1] );
1047                                 }
1048                         }
1049
1050                         $this->cache_add( 'post_templates', $post_templates );
1051                 }
1052
1053                 if ( $this->load_textdomain() ) {
1054                         foreach ( $post_templates as &$post_type ) {
1055                                 foreach ( $post_type as &$post_template ) {
1056                                         $post_template = $this->translate_header( 'Template Name', $post_template );
1057                                 }
1058                         }
1059                 }
1060
1061                 return $post_templates;
1062         }
1063
1064         /**
1065          * Returns the theme's post templates for a given post type.
1066          *
1067          * @since 3.4.0
1068          * @since 4.7.0 Added the `$post_type` parameter.
1069          * @access public
1070          *
1071          * @param WP_Post|null $post      Optional. The post being edited, provided for context.
1072          * @param string       $post_type Optional. Post type to get the templates for. Default 'page'.
1073          *                                If a post is provided, its post type is used.
1074          * @return array Array of page templates, keyed by filename, with the value of the translated header name.
1075          */
1076         public function get_page_templates( $post = null, $post_type = 'page' ) {
1077                 if ( $post ) {
1078                         $post_type = get_post_type( $post );
1079                 }
1080
1081                 $post_templates = $this->get_post_templates();
1082                 $post_templates = isset( $post_templates[ $post_type ] ) ? $post_templates[ $post_type ] : array();
1083
1084                 if ( $this->parent() ) {
1085                         $post_templates += $this->parent()->get_page_templates( $post, $post_type );
1086                 }
1087
1088                 /**
1089                  * Filters list of page templates for a theme.
1090                  *
1091                  * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1092                  *
1093                  * @since 3.9.0
1094                  * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1095                  * @since 4.7.0 Added the `$post_type` parameter.
1096                  *
1097                  * @param array        $post_templates Array of page templates. Keys are filenames,
1098                  *                                     values are translated names.
1099                  * @param WP_Theme     $this           The theme object.
1100                  * @param WP_Post|null $post           The post being edited, provided for context, or null.
1101                  * @param string       $post_type      Post type to get the templates for.
1102                  */
1103                 return (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1104         }
1105
1106         /**
1107          * Scans a directory for files of a certain extension.
1108          *
1109          * @since 3.4.0
1110          *
1111          * @static
1112          * @access private
1113          *
1114          * @param string            $path          Absolute path to search.
1115          * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1116          *                                         or null for all extensions. Default null.
1117          * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1118          *                                         -1 (infinite depth). Default 0.
1119          * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1120          *                                         returned path for the found files, particularly when this function
1121          *                                         recurses to lower depths. Default empty.
1122          * @return array|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1123          *                     with `$relative_path`, with the values being absolute paths. False otherwise.
1124          */
1125         private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1126                 if ( ! is_dir( $path ) )
1127                         return false;
1128
1129                 if ( $extensions ) {
1130                         $extensions = (array) $extensions;
1131                         $_extensions = implode( '|', $extensions );
1132                 }
1133
1134                 $relative_path = trailingslashit( $relative_path );
1135                 if ( '/' == $relative_path )
1136                         $relative_path = '';
1137
1138                 $results = scandir( $path );
1139                 $files = array();
1140
1141                 foreach ( $results as $result ) {
1142                         if ( '.' == $result[0] )
1143                                 continue;
1144                         if ( is_dir( $path . '/' . $result ) ) {
1145                                 if ( ! $depth || 'CVS' == $result )
1146                                         continue;
1147                                 $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
1148                                 $files = array_merge_recursive( $files, $found );
1149                         } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1150                                 $files[ $relative_path . $result ] = $path . '/' . $result;
1151                         }
1152                 }
1153
1154                 return $files;
1155         }
1156
1157         /**
1158          * Loads the theme's textdomain.
1159          *
1160          * Translation files are not inherited from the parent theme. Todo: if this fails for the
1161          * child theme, it should probably try to load the parent theme's translations.
1162          *
1163          * @since 3.4.0
1164          * @access public
1165          *
1166          * @return bool True if the textdomain was successfully loaded or has already been loaded.
1167          *      False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1168          */
1169         public function load_textdomain() {
1170                 if ( isset( $this->textdomain_loaded ) )
1171                         return $this->textdomain_loaded;
1172
1173                 $textdomain = $this->get('TextDomain');
1174                 if ( ! $textdomain ) {
1175                         $this->textdomain_loaded = false;
1176                         return false;
1177                 }
1178
1179                 if ( is_textdomain_loaded( $textdomain ) ) {
1180                         $this->textdomain_loaded = true;
1181                         return true;
1182                 }
1183
1184                 $path = $this->get_stylesheet_directory();
1185                 if ( $domainpath = $this->get('DomainPath') )
1186                         $path .= $domainpath;
1187                 else
1188                         $path .= '/languages';
1189
1190                 $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1191                 return $this->textdomain_loaded;
1192         }
1193
1194         /**
1195          * Whether the theme is allowed (multisite only).
1196          *
1197          * @since 3.4.0
1198          * @access public
1199          *
1200          * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
1201          *      settings, or 'both'. Defaults to 'both'.
1202          * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1203          * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1204          */
1205         public function is_allowed( $check = 'both', $blog_id = null ) {
1206                 if ( ! is_multisite() )
1207                         return true;
1208
1209                 if ( 'both' == $check || 'network' == $check ) {
1210                         $allowed = self::get_allowed_on_network();
1211                         if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1212                                 return true;
1213                 }
1214
1215                 if ( 'both' == $check || 'site' == $check ) {
1216                         $allowed = self::get_allowed_on_site( $blog_id );
1217                         if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1218                                 return true;
1219                 }
1220
1221                 return false;
1222         }
1223
1224         /**
1225          * Determines the latest WordPress default theme that is installed.
1226          *
1227          * This hits the filesystem.
1228          *
1229          * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1230          */
1231         public static function get_core_default_theme() {
1232                 foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1233                         $theme = wp_get_theme( $slug );
1234                         if ( $theme->exists() ) {
1235                                 return $theme;
1236                         }
1237                 }
1238                 return false;
1239         }
1240
1241         /**
1242          * Returns array of stylesheet names of themes allowed on the site or network.
1243          *
1244          * @since 3.4.0
1245          *
1246          * @static
1247          * @access public
1248          *
1249          * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1250          * @return array Array of stylesheet names.
1251          */
1252         public static function get_allowed( $blog_id = null ) {
1253                 /**
1254                  * Filters the array of themes allowed on the network.
1255                  *
1256                  * Site is provided as context so that a list of network allowed themes can
1257                  * be filtered further.
1258                  *
1259                  * @since 4.5.0
1260                  *
1261                  * @param array $allowed_themes An array of theme stylesheet names.
1262                  * @param int   $blog_id        ID of the site.
1263                  */
1264                 $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1265                 return $network + self::get_allowed_on_site( $blog_id );
1266         }
1267
1268         /**
1269          * Returns array of stylesheet names of themes allowed on the network.
1270          *
1271          * @since 3.4.0
1272          *
1273          * @static
1274          * @access public
1275          *
1276          * @staticvar array $allowed_themes
1277          *
1278          * @return array Array of stylesheet names.
1279          */
1280         public static function get_allowed_on_network() {
1281                 static $allowed_themes;
1282                 if ( ! isset( $allowed_themes ) ) {
1283                         $allowed_themes = (array) get_site_option( 'allowedthemes' );
1284                 }
1285
1286                 /**
1287                  * Filters the array of themes allowed on the network.
1288                  *
1289                  * @since MU
1290                  *
1291                  * @param array $allowed_themes An array of theme stylesheet names.
1292                  */
1293                 $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1294
1295                 return $allowed_themes;
1296         }
1297
1298         /**
1299          * Returns array of stylesheet names of themes allowed on the site.
1300          *
1301          * @since 3.4.0
1302          *
1303          * @static
1304          * @access public
1305          *
1306          * @staticvar array $allowed_themes
1307          *
1308          * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1309          * @return array Array of stylesheet names.
1310          */
1311         public static function get_allowed_on_site( $blog_id = null ) {
1312                 static $allowed_themes = array();
1313
1314                 if ( ! $blog_id || ! is_multisite() )
1315                         $blog_id = get_current_blog_id();
1316
1317                 if ( isset( $allowed_themes[ $blog_id ] ) ) {
1318                         /**
1319                          * Filters the array of themes allowed on the site.
1320                          *
1321                          * @since 4.5.0
1322                          *
1323                          * @param array $allowed_themes An array of theme stylesheet names.
1324                          * @param int   $blog_id        ID of the site. Defaults to current site.
1325                          */
1326                         return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1327                 }
1328
1329                 $current = $blog_id == get_current_blog_id();
1330
1331                 if ( $current ) {
1332                         $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1333                 } else {
1334                         switch_to_blog( $blog_id );
1335                         $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1336                         restore_current_blog();
1337                 }
1338
1339                 // This is all super old MU back compat joy.
1340                 // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1341                 if ( false === $allowed_themes[ $blog_id ] ) {
1342                         if ( $current ) {
1343                                 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1344                         } else {
1345                                 switch_to_blog( $blog_id );
1346                                 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1347                                 restore_current_blog();
1348                         }
1349
1350                         if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1351                                 $allowed_themes[ $blog_id ] = array();
1352                         } else {
1353                                 $converted = array();
1354                                 $themes = wp_get_themes();
1355                                 foreach ( $themes as $stylesheet => $theme_data ) {
1356                                         if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) )
1357                                                 $converted[ $stylesheet ] = true;
1358                                 }
1359                                 $allowed_themes[ $blog_id ] = $converted;
1360                         }
1361                         // Set the option so we never have to go through this pain again.
1362                         if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1363                                 if ( $current ) {
1364                                         update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1365                                         delete_option( 'allowed_themes' );
1366                                 } else {
1367                                         switch_to_blog( $blog_id );
1368                                         update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1369                                         delete_option( 'allowed_themes' );
1370                                         restore_current_blog();
1371                                 }
1372                         }
1373                 }
1374
1375                 /** This filter is documented in wp-includes/class-wp-theme.php */
1376                 return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1377         }
1378
1379         /**
1380          * Enables a theme for all sites on the current network.
1381          *
1382          * @since 4.6.0
1383          * @access public
1384          * @static
1385          *
1386          * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1387          */
1388         public static function network_enable_theme( $stylesheets ) {
1389                 if ( ! is_multisite() ) {
1390                         return;
1391                 }
1392
1393                 if ( ! is_array( $stylesheets ) ) {
1394                         $stylesheets = array( $stylesheets );
1395                 }
1396
1397                 $allowed_themes = get_site_option( 'allowedthemes' );
1398                 foreach ( $stylesheets as $stylesheet ) {
1399                         $allowed_themes[ $stylesheet ] = true;
1400                 }
1401
1402                 update_site_option( 'allowedthemes', $allowed_themes );
1403         }
1404
1405         /**
1406          * Disables a theme for all sites on the current network.
1407          *
1408          * @since 4.6.0
1409          * @access public
1410          * @static
1411          *
1412          * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1413          */
1414         public static function network_disable_theme( $stylesheets ) {
1415                 if ( ! is_multisite() ) {
1416                         return;
1417                 }
1418
1419                 if ( ! is_array( $stylesheets ) ) {
1420                         $stylesheets = array( $stylesheets );
1421                 }
1422
1423                 $allowed_themes = get_site_option( 'allowedthemes' );
1424                 foreach ( $stylesheets as $stylesheet ) {
1425                         if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1426                                 unset( $allowed_themes[ $stylesheet ] );
1427                         }
1428                 }
1429
1430                 update_site_option( 'allowedthemes', $allowed_themes );
1431         }
1432
1433         /**
1434          * Sorts themes by name.
1435          *
1436          * @since 3.4.0
1437          *
1438          * @static
1439          * @access public
1440          *
1441          * @param array $themes Array of themes to sort, passed by reference.
1442          */
1443         public static function sort_by_name( &$themes ) {
1444                 if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
1445                         uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1446                 } else {
1447                         uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1448                 }
1449         }
1450
1451         /**
1452          * Callback function for usort() to naturally sort themes by name.
1453          *
1454          * Accesses the Name header directly from the class for maximum speed.
1455          * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1456          *
1457          * @since 3.4.0
1458          *
1459          * @static
1460          * @access private
1461          *
1462          * @param string $a First name.
1463          * @param string $b Second name.
1464          * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1465          *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1466          */
1467         private static function _name_sort( $a, $b ) {
1468                 return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1469         }
1470
1471         /**
1472          * Name sort (with translation).
1473          *
1474          * @since 3.4.0
1475          *
1476          * @static
1477          * @access private
1478          *
1479          * @param string $a First name.
1480          * @param string $b Second name.
1481          * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1482          *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1483          */
1484         private static function _name_sort_i18n( $a, $b ) {
1485                 // Don't mark up; Do translate.
1486                 return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
1487         }
1488 }