]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-theme.php
WordPress 4.6.2
[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         );
61
62         /**
63          * Renamed theme tags.
64          *
65          * @static
66          * @access private
67          * @var array
68          */
69         private static $tag_map = array(
70                 'fixed-width'    => 'fixed-layout',
71                 'flexible-width' => 'fluid-layout',
72         );
73
74         /**
75          * Absolute path to the theme root, usually wp-content/themes
76          *
77          * @access private
78          * @var string
79          */
80         private $theme_root;
81
82         /**
83          * Header data from the theme's style.css file.
84          *
85          * @access private
86          * @var array
87          */
88         private $headers = array();
89
90         /**
91          * Header data from the theme's style.css file after being sanitized.
92          *
93          * @access private
94          * @var array
95          */
96         private $headers_sanitized;
97
98         /**
99          * Header name from the theme's style.css after being translated.
100          *
101          * Cached due to sorting functions running over the translated name.
102          *
103          * @access private
104          * @var string
105          */
106         private $name_translated;
107
108         /**
109          * Errors encountered when initializing the theme.
110          *
111          * @access private
112          * @var WP_Error
113          */
114         private $errors;
115
116         /**
117          * The directory name of the theme's files, inside the theme root.
118          *
119          * In the case of a child theme, this is directory name of the child theme.
120          * Otherwise, 'stylesheet' is the same as 'template'.
121          *
122          * @access private
123          * @var string
124          */
125         private $stylesheet;
126
127         /**
128          * The directory name of the theme's files, inside the theme root.
129          *
130          * In the case of a child theme, this is the directory name of the parent theme.
131          * Otherwise, 'template' is the same as 'stylesheet'.
132          *
133          * @access private
134          * @var string
135          */
136         private $template;
137
138         /**
139          * A reference to the parent theme, in the case of a child theme.
140          *
141          * @access private
142          * @var WP_Theme
143          */
144         private $parent;
145
146         /**
147          * URL to the theme root, usually an absolute URL to wp-content/themes
148          *
149          * @access private
150          * var string
151          */
152         private $theme_root_uri;
153
154         /**
155          * Flag for whether the theme's textdomain is loaded.
156          *
157          * @access private
158          * @var bool
159          */
160         private $textdomain_loaded;
161
162         /**
163          * Stores an md5 hash of the theme root, to function as the cache key.
164          *
165          * @access private
166          * @var string
167          */
168         private $cache_hash;
169
170         /**
171          * Flag for whether the themes cache bucket should be persistently cached.
172          *
173          * Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter.
174          *
175          * @static
176          * @access private
177          * @var bool
178          */
179         private static $persistently_cache;
180
181         /**
182          * Expiration time for the themes cache bucket.
183          *
184          * By default the bucket is not cached, so this value is useless.
185          *
186          * @static
187          * @access private
188          * @var bool
189          */
190         private static $cache_expiration = 1800;
191
192         /**
193          * Constructor for WP_Theme.
194          *
195          * @global array $wp_theme_directories
196          *
197          * @param string $theme_dir Directory of the theme within the theme_root.
198          * @param string $theme_root Theme root.
199          * @param WP_Error|void $_child If this theme is a parent theme, the child may be passed for validation purposes.
200          */
201         public function __construct( $theme_dir, $theme_root, $_child = null ) {
202                 global $wp_theme_directories;
203
204                 // Initialize caching on first run.
205                 if ( ! isset( self::$persistently_cache ) ) {
206                         /** This action is documented in wp-includes/theme.php */
207                         self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
208                         if ( self::$persistently_cache ) {
209                                 wp_cache_add_global_groups( 'themes' );
210                                 if ( is_int( self::$persistently_cache ) )
211                                         self::$cache_expiration = self::$persistently_cache;
212                         } else {
213                                 wp_cache_add_non_persistent_groups( 'themes' );
214                         }
215                 }
216
217                 $this->theme_root = $theme_root;
218                 $this->stylesheet = $theme_dir;
219
220                 // Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
221                 if ( ! in_array( $theme_root, (array) $wp_theme_directories ) && in_array( dirname( $theme_root ), (array) $wp_theme_directories ) ) {
222                         $this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
223                         $this->theme_root = dirname( $theme_root );
224                 }
225
226                 $this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
227                 $theme_file = $this->stylesheet . '/style.css';
228
229                 $cache = $this->cache_get( 'theme' );
230
231                 if ( is_array( $cache ) ) {
232                         foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
233                                 if ( isset( $cache[ $key ] ) )
234                                         $this->$key = $cache[ $key ];
235                         }
236                         if ( $this->errors )
237                                 return;
238                         if ( isset( $cache['theme_root_template'] ) )
239                                 $theme_root_template = $cache['theme_root_template'];
240                 } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
241                         $this->headers['Name'] = $this->stylesheet;
242                         if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) )
243                                 $this->errors = new WP_Error( 'theme_not_found', sprintf( __( 'The theme directory "%s" does not exist.' ), esc_html( $this->stylesheet ) ) );
244                         else
245                                 $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
246                         $this->template = $this->stylesheet;
247                         $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
248                         if ( ! file_exists( $this->theme_root ) ) // Don't cache this one.
249                                 $this->errors->add( 'theme_root_missing', __( 'ERROR: The themes directory is either empty or doesn&#8217;t exist. Please check your installation.' ) );
250                         return;
251                 } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
252                         $this->headers['Name'] = $this->stylesheet;
253                         $this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
254                         $this->template = $this->stylesheet;
255                         $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
256                         return;
257                 } else {
258                         $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
259                         // Default themes always trump their pretenders.
260                         // Properly identify default themes that are inside a directory within wp-content/themes.
261                         if ( $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes ) ) {
262                                 if ( basename( $this->stylesheet ) != $default_theme_slug )
263                                         $this->headers['Name'] .= '/' . $this->stylesheet;
264                         }
265                 }
266
267                 // (If template is set from cache [and there are no errors], we know it's good.)
268                 if ( ! $this->template && ! ( $this->template = $this->headers['Template'] ) ) {
269                         $this->template = $this->stylesheet;
270                         if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) {
271                                 $error_message = sprintf(
272                                         /* translators: 1: index.php, 2: Codex URL, 3: style.css */
273                                         __( '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.' ),
274                                         '<code>index.php</code>',
275                                         __( 'https://codex.wordpress.org/Child_Themes' ),
276                                         '<code>style.css</code>'
277                                 );
278                                 $this->errors = new WP_Error( 'theme_no_index', $error_message );
279                                 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
280                                 return;
281                         }
282                 }
283
284                 // If we got our data from cache, we can assume that 'template' is pointing to the right place.
285                 if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) {
286                         // If we're in a directory of themes inside /themes, look for the parent nearby.
287                         // wp-content/themes/directory-of-themes/*
288                         $parent_dir = dirname( $this->stylesheet );
289                         if ( '.' != $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) {
290                                 $this->template = $parent_dir . '/' . $this->template;
291                         } elseif ( ( $directories = search_theme_directories() ) && isset( $directories[ $this->template ] ) ) {
292                                 // Look for the template in the search_theme_directories() results, in case it is in another theme root.
293                                 // We don't look into directories of themes, just the theme root.
294                                 $theme_root_template = $directories[ $this->template ]['theme_root'];
295                         } else {
296                                 // Parent theme is missing.
297                                 $this->errors = new WP_Error( 'theme_no_parent', sprintf( __( 'The parent theme is missing. Please install the "%s" parent theme.' ), esc_html( $this->template ) ) );
298                                 $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
299                                 $this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
300                                 return;
301                         }
302                 }
303
304                 // Set the parent, if we're a child theme.
305                 if ( $this->template != $this->stylesheet ) {
306                         // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
307                         if ( $_child instanceof WP_Theme && $_child->template == $this->stylesheet ) {
308                                 $_child->parent = null;
309                                 $_child->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $_child->template ) ) );
310                                 $_child->cache_add( 'theme', array( 'headers' => $_child->headers, 'errors' => $_child->errors, 'stylesheet' => $_child->stylesheet, 'template' => $_child->template ) );
311                                 // The two themes actually reference each other with the Template header.
312                                 if ( $_child->stylesheet == $this->template ) {
313                                         $this->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $this->template ) ) );
314                                         $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
315                                 }
316                                 return;
317                         }
318                         // Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
319                         $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
320                 }
321
322                 // We're good. If we didn't retrieve from cache, set it.
323                 if ( ! is_array( $cache ) ) {
324                         $cache = array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template );
325                         // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
326                         if ( isset( $theme_root_template ) )
327                                 $cache['theme_root_template'] = $theme_root_template;
328                         $this->cache_add( 'theme', $cache );
329                 }
330         }
331
332         /**
333          * When converting the object to a string, the theme name is returned.
334          *
335          * @return string Theme name, ready for display (translated)
336          */
337         public function __toString() {
338                 return (string) $this->display('Name');
339         }
340
341         /**
342          * __isset() magic method for properties formerly returned by current_theme_info()
343          *
344          * @staticvar array $properties
345          *
346          * @param string $offset Property to check if set.
347          * @return bool Whether the given property is set.
348          */
349         public function __isset( $offset ) {
350                 static $properties = array(
351                         'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet',
352                         'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri',
353                 );
354
355                 return in_array( $offset, $properties );
356         }
357
358         /**
359          * __get() magic method for properties formerly returned by current_theme_info()
360          *
361          * @param string $offset Property to get.
362          * @return mixed Property value.
363          */
364         public function __get( $offset ) {
365                 switch ( $offset ) {
366                         case 'name' :
367                         case 'title' :
368                                 return $this->get('Name');
369                         case 'version' :
370                                 return $this->get('Version');
371                         case 'parent_theme' :
372                                 return $this->parent() ? $this->parent()->get('Name') : '';
373                         case 'template_dir' :
374                                 return $this->get_template_directory();
375                         case 'stylesheet_dir' :
376                                 return $this->get_stylesheet_directory();
377                         case 'template' :
378                                 return $this->get_template();
379                         case 'stylesheet' :
380                                 return $this->get_stylesheet();
381                         case 'screenshot' :
382                                 return $this->get_screenshot( 'relative' );
383                         // 'author' and 'description' did not previously return translated data.
384                         case 'description' :
385                                 return $this->display('Description');
386                         case 'author' :
387                                 return $this->display('Author');
388                         case 'tags' :
389                                 return $this->get( 'Tags' );
390                         case 'theme_root' :
391                                 return $this->get_theme_root();
392                         case 'theme_root_uri' :
393                                 return $this->get_theme_root_uri();
394                         // For cases where the array was converted to an object.
395                         default :
396                                 return $this->offsetGet( $offset );
397                 }
398         }
399
400         /**
401          * Method to implement ArrayAccess for keys formerly returned by get_themes()
402          *
403          * @param mixed $offset
404          * @param mixed $value
405          */
406         public function offsetSet( $offset, $value ) {}
407
408         /**
409          * Method to implement ArrayAccess for keys formerly returned by get_themes()
410          *
411          * @param mixed $offset
412          */
413         public function offsetUnset( $offset ) {}
414
415         /**
416          * Method to implement ArrayAccess for keys formerly returned by get_themes()
417          *
418          * @staticvar array $keys
419          *
420          * @param mixed $offset
421          * @return bool
422          */
423         public function offsetExists( $offset ) {
424                 static $keys = array(
425                         'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description',
426                         'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir',
427                         'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme',
428                 );
429
430                 return in_array( $offset, $keys );
431         }
432
433         /**
434          * Method to implement ArrayAccess for keys formerly returned by get_themes().
435          *
436          * Author, Author Name, Author URI, and Description did not previously return
437          * translated data. We are doing so now as it is safe to do. However, as
438          * Name and Title could have been used as the key for get_themes(), both remain
439          * untranslated for back compatibility. This means that ['Name'] is not ideal,
440          * and care should be taken to use `$theme::display( 'Name' )` to get a properly
441          * translated header.
442          *
443          * @param mixed $offset
444          * @return mixed
445          */
446         public function offsetGet( $offset ) {
447                 switch ( $offset ) {
448                         case 'Name' :
449                         case 'Title' :
450                                 /*
451                                  * See note above about using translated data. get() is not ideal.
452                                  * It is only for backward compatibility. Use display().
453                                  */
454                                 return $this->get('Name');
455                         case 'Author' :
456                                 return $this->display( 'Author');
457                         case 'Author Name' :
458                                 return $this->display( 'Author', false);
459                         case 'Author URI' :
460                                 return $this->display('AuthorURI');
461                         case 'Description' :
462                                 return $this->display( 'Description');
463                         case 'Version' :
464                         case 'Status' :
465                                 return $this->get( $offset );
466                         case 'Template' :
467                                 return $this->get_template();
468                         case 'Stylesheet' :
469                                 return $this->get_stylesheet();
470                         case 'Template Files' :
471                                 return $this->get_files( 'php', 1, true );
472                         case 'Stylesheet Files' :
473                                 return $this->get_files( 'css', 0, false );
474                         case 'Template Dir' :
475                                 return $this->get_template_directory();
476                         case 'Stylesheet Dir' :
477                                 return $this->get_stylesheet_directory();
478                         case 'Screenshot' :
479                                 return $this->get_screenshot( 'relative' );
480                         case 'Tags' :
481                                 return $this->get('Tags');
482                         case 'Theme Root' :
483                                 return $this->get_theme_root();
484                         case 'Theme Root URI' :
485                                 return $this->get_theme_root_uri();
486                         case 'Parent Theme' :
487                                 return $this->parent() ? $this->parent()->get('Name') : '';
488                         default :
489                                 return null;
490                 }
491         }
492
493         /**
494          * Returns errors property.
495          *
496          * @since 3.4.0
497          * @access public
498          *
499          * @return WP_Error|false WP_Error if there are errors, or false.
500          */
501         public function errors() {
502                 return is_wp_error( $this->errors ) ? $this->errors : false;
503         }
504
505         /**
506          * Whether the theme exists.
507          *
508          * A theme with errors exists. A theme with the error of 'theme_not_found',
509          * meaning that the theme's directory was not found, does not exist.
510          *
511          * @since 3.4.0
512          * @access public
513          *
514          * @return bool Whether the theme exists.
515          */
516         public function exists() {
517                 return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes() ) );
518         }
519
520         /**
521          * Returns reference to the parent theme.
522          *
523          * @since 3.4.0
524          * @access public
525          *
526          * @return WP_Theme|false Parent theme, or false if the current theme is not a child theme.
527          */
528         public function parent() {
529                 return isset( $this->parent ) ? $this->parent : false;
530         }
531
532         /**
533          * Adds theme data to cache.
534          *
535          * Cache entries keyed by the theme and the type of data.
536          *
537          * @since 3.4.0
538          * @access private
539          *
540          * @param string $key Type of data to store (theme, screenshot, headers, page_templates)
541          * @param string $data Data to store
542          * @return bool Return value from wp_cache_add()
543          */
544         private function cache_add( $key, $data ) {
545                 return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration );
546         }
547
548         /**
549          * Gets theme data from cache.
550          *
551          * Cache entries are keyed by the theme and the type of data.
552          *
553          * @since 3.4.0
554          * @access private
555          *
556          * @param string $key Type of data to retrieve (theme, screenshot, headers, page_templates)
557          * @return mixed Retrieved data
558          */
559         private function cache_get( $key ) {
560                 return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' );
561         }
562
563         /**
564          * Clears the cache for the theme.
565          *
566          * @since 3.4.0
567          * @access public
568          */
569         public function cache_delete() {
570                 foreach ( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key )
571                         wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
572                 $this->template = $this->textdomain_loaded = $this->theme_root_uri = $this->parent = $this->errors = $this->headers_sanitized = $this->name_translated = null;
573                 $this->headers = array();
574                 $this->__construct( $this->stylesheet, $this->theme_root );
575         }
576
577         /**
578          * Get a raw, unformatted theme header.
579          *
580          * The header is sanitized, but is not translated, and is not marked up for display.
581          * To get a theme header for display, use the display() method.
582          *
583          * Use the get_template() method, not the 'Template' header, for finding the template.
584          * The 'Template' header is only good for what was written in the style.css, while
585          * get_template() takes into account where WordPress actually located the theme and
586          * whether it is actually valid.
587          *
588          * @since 3.4.0
589          * @access public
590          *
591          * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
592          * @return string|false String on success, false on failure.
593          */
594         public function get( $header ) {
595                 if ( ! isset( $this->headers[ $header ] ) )
596                         return false;
597
598                 if ( ! isset( $this->headers_sanitized ) ) {
599                         $this->headers_sanitized = $this->cache_get( 'headers' );
600                         if ( ! is_array( $this->headers_sanitized ) )
601                                 $this->headers_sanitized = array();
602                 }
603
604                 if ( isset( $this->headers_sanitized[ $header ] ) )
605                         return $this->headers_sanitized[ $header ];
606
607                 // If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets.
608                 if ( self::$persistently_cache ) {
609                         foreach ( array_keys( $this->headers ) as $_header )
610                                 $this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] );
611                         $this->cache_add( 'headers', $this->headers_sanitized );
612                 } else {
613                         $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] );
614                 }
615
616                 return $this->headers_sanitized[ $header ];
617         }
618
619         /**
620          * Gets a theme header, formatted and translated for display.
621          *
622          * @since 3.4.0
623          * @access public
624          *
625          * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
626          * @param bool $markup Optional. Whether to mark up the header. Defaults to true.
627          * @param bool $translate Optional. Whether to translate the header. Defaults to true.
628          * @return string|false Processed header, false on failure.
629          */
630         public function display( $header, $markup = true, $translate = true ) {
631                 $value = $this->get( $header );
632                 if ( false === $value ) {
633                         return false;
634                 }
635
636                 if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) )
637                         $translate = false;
638
639                 if ( $translate )
640                         $value = $this->translate_header( $header, $value );
641
642                 if ( $markup )
643                         $value = $this->markup_header( $header, $value, $translate );
644
645                 return $value;
646         }
647
648         /**
649          * Sanitize a theme header.
650          *
651          * @since 3.4.0
652          * @access private
653          *
654          * @staticvar array $header_tags
655          * @staticvar array $header_tags_with_a
656          *
657          * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
658          * @param string $value Value to sanitize.
659          * @return mixed
660          */
661         private function sanitize_header( $header, $value ) {
662                 switch ( $header ) {
663                         case 'Status' :
664                                 if ( ! $value ) {
665                                         $value = 'publish';
666                                         break;
667                                 }
668                                 // Fall through otherwise.
669                         case 'Name' :
670                                 static $header_tags = array(
671                                         'abbr'    => array( 'title' => true ),
672                                         'acronym' => array( 'title' => true ),
673                                         'code'    => true,
674                                         'em'      => true,
675                                         'strong'  => true,
676                                 );
677                                 $value = wp_kses( $value, $header_tags );
678                                 break;
679                         case 'Author' :
680                                 // There shouldn't be anchor tags in Author, but some themes like to be challenging.
681                         case 'Description' :
682                                 static $header_tags_with_a = array(
683                                         'a'       => array( 'href' => true, 'title' => true ),
684                                         'abbr'    => array( 'title' => true ),
685                                         'acronym' => array( 'title' => true ),
686                                         'code'    => true,
687                                         'em'      => true,
688                                         'strong'  => true,
689                                 );
690                                 $value = wp_kses( $value, $header_tags_with_a );
691                                 break;
692                         case 'ThemeURI' :
693                         case 'AuthorURI' :
694                                 $value = esc_url_raw( $value );
695                                 break;
696                         case 'Tags' :
697                                 $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
698                                 break;
699                         case 'Version' :
700                                 $value = strip_tags( $value );
701                                 break;
702                 }
703
704                 return $value;
705         }
706
707         /**
708          * Mark up a theme header.
709          *
710      * @since 3.4.0
711          * @access private
712          *
713          * @staticvar string $comma
714          *
715          * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
716          * @param string $value Value to mark up.
717          * @param string $translate Whether the header has been translated.
718          * @return string Value, marked up.
719          */
720         private function markup_header( $header, $value, $translate ) {
721                 switch ( $header ) {
722                         case 'Name' :
723                                 if ( empty( $value ) ) {
724                                         $value = esc_html( $this->get_stylesheet() );
725                                 }
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 page templates.
1010          *
1011          * @since 3.4.0
1012          * @access public
1013          *
1014          * @param WP_Post|null $post Optional. The post being edited, provided for context.
1015          * @return array Array of page templates, keyed by filename, with the value of the translated header name.
1016          */
1017         public function get_page_templates( $post = null ) {
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                 $page_templates = $this->cache_get( 'page_templates' );
1023
1024                 if ( ! is_array( $page_templates ) ) {
1025                         $page_templates = array();
1026
1027                         $files = (array) $this->get_files( 'php', 1 );
1028
1029                         foreach ( $files as $file => $full_path ) {
1030                                 if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) )
1031                                         continue;
1032                                 $page_templates[ $file ] = _cleanup_header_comment( $header[1] );
1033                         }
1034
1035                         $this->cache_add( 'page_templates', $page_templates );
1036                 }
1037
1038                 if ( $this->load_textdomain() ) {
1039                         foreach ( $page_templates as &$page_template ) {
1040                                 $page_template = $this->translate_header( 'Template Name', $page_template );
1041                         }
1042                 }
1043
1044                 if ( $this->parent() )
1045                         $page_templates += $this->parent()->get_page_templates( $post );
1046
1047                 /**
1048                  * Filters list of page templates for a theme.
1049                  *
1050                  * @since 3.9.0
1051                  * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1052                  *
1053                  * @param array        $page_templates Array of page templates. Keys are filenames,
1054                  *                                     values are translated names.
1055                  * @param WP_Theme     $this           The theme object.
1056                  * @param WP_Post|null $post           The post being edited, provided for context, or null.
1057                  */
1058                 return (array) apply_filters( 'theme_page_templates', $page_templates, $this, $post );
1059         }
1060
1061         /**
1062          * Scans a directory for files of a certain extension.
1063          *
1064          * @since 3.4.0
1065          *
1066          * @static
1067          * @access private
1068          *
1069          * @param string            $path          Absolute path to search.
1070          * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1071          *                                         or null for all extensions. Default null.
1072          * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1073          *                                         -1 (infinite depth). Default 0.
1074          * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1075          *                                         returned path for the found files, particularly when this function
1076          *                                         recurses to lower depths. Default empty.
1077          * @return array|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1078          *                     with `$relative_path`, with the values being absolute paths. False otherwise.
1079          */
1080         private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1081                 if ( ! is_dir( $path ) )
1082                         return false;
1083
1084                 if ( $extensions ) {
1085                         $extensions = (array) $extensions;
1086                         $_extensions = implode( '|', $extensions );
1087                 }
1088
1089                 $relative_path = trailingslashit( $relative_path );
1090                 if ( '/' == $relative_path )
1091                         $relative_path = '';
1092
1093                 $results = scandir( $path );
1094                 $files = array();
1095
1096                 foreach ( $results as $result ) {
1097                         if ( '.' == $result[0] )
1098                                 continue;
1099                         if ( is_dir( $path . '/' . $result ) ) {
1100                                 if ( ! $depth || 'CVS' == $result )
1101                                         continue;
1102                                 $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
1103                                 $files = array_merge_recursive( $files, $found );
1104                         } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1105                                 $files[ $relative_path . $result ] = $path . '/' . $result;
1106                         }
1107                 }
1108
1109                 return $files;
1110         }
1111
1112         /**
1113          * Loads the theme's textdomain.
1114          *
1115          * Translation files are not inherited from the parent theme. Todo: if this fails for the
1116          * child theme, it should probably try to load the parent theme's translations.
1117          *
1118          * @since 3.4.0
1119          * @access public
1120          *
1121          * @return bool True if the textdomain was successfully loaded or has already been loaded.
1122          *      False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1123          */
1124         public function load_textdomain() {
1125                 if ( isset( $this->textdomain_loaded ) )
1126                         return $this->textdomain_loaded;
1127
1128                 $textdomain = $this->get('TextDomain');
1129                 if ( ! $textdomain ) {
1130                         $this->textdomain_loaded = false;
1131                         return false;
1132                 }
1133
1134                 if ( is_textdomain_loaded( $textdomain ) ) {
1135                         $this->textdomain_loaded = true;
1136                         return true;
1137                 }
1138
1139                 $path = $this->get_stylesheet_directory();
1140                 if ( $domainpath = $this->get('DomainPath') )
1141                         $path .= $domainpath;
1142                 else
1143                         $path .= '/languages';
1144
1145                 $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1146                 return $this->textdomain_loaded;
1147         }
1148
1149         /**
1150          * Whether the theme is allowed (multisite only).
1151          *
1152          * @since 3.4.0
1153          * @access public
1154          *
1155          * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
1156          *      settings, or 'both'. Defaults to 'both'.
1157          * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1158          * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1159          */
1160         public function is_allowed( $check = 'both', $blog_id = null ) {
1161                 if ( ! is_multisite() )
1162                         return true;
1163
1164                 if ( 'both' == $check || 'network' == $check ) {
1165                         $allowed = self::get_allowed_on_network();
1166                         if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1167                                 return true;
1168                 }
1169
1170                 if ( 'both' == $check || 'site' == $check ) {
1171                         $allowed = self::get_allowed_on_site( $blog_id );
1172                         if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1173                                 return true;
1174                 }
1175
1176                 return false;
1177         }
1178
1179         /**
1180          * Determines the latest WordPress default theme that is installed.
1181          *
1182          * This hits the filesystem.
1183          *
1184          * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1185          */
1186         public static function get_core_default_theme() {
1187                 foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1188                         $theme = wp_get_theme( $slug );
1189                         if ( $theme->exists() ) {
1190                                 return $theme;
1191                         }
1192                 }
1193                 return false;
1194         }
1195
1196         /**
1197          * Returns array of stylesheet names of themes allowed on the site or network.
1198          *
1199          * @since 3.4.0
1200          *
1201          * @static
1202          * @access public
1203          *
1204          * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1205          * @return array Array of stylesheet names.
1206          */
1207         public static function get_allowed( $blog_id = null ) {
1208                 /**
1209                  * Filters the array of themes allowed on the network.
1210                  *
1211                  * Site is provided as context so that a list of network allowed themes can
1212                  * be filtered further.
1213                  *
1214                  * @since 4.5.0
1215                  *
1216                  * @param array $allowed_themes An array of theme stylesheet names.
1217                  * @param int   $blog_id        ID of the site.
1218                  */
1219                 $network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1220                 return $network + self::get_allowed_on_site( $blog_id );
1221         }
1222
1223         /**
1224          * Returns array of stylesheet names of themes allowed on the network.
1225          *
1226          * @since 3.4.0
1227          *
1228          * @static
1229          * @access public
1230          *
1231          * @staticvar array $allowed_themes
1232          *
1233          * @return array Array of stylesheet names.
1234          */
1235         public static function get_allowed_on_network() {
1236                 static $allowed_themes;
1237                 if ( ! isset( $allowed_themes ) ) {
1238                         $allowed_themes = (array) get_site_option( 'allowedthemes' );
1239                 }
1240
1241                 /**
1242                  * Filters the array of themes allowed on the network.
1243                  *
1244                  * @since MU
1245                  *
1246                  * @param array $allowed_themes An array of theme stylesheet names.
1247                  */
1248                 $allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1249
1250                 return $allowed_themes;
1251         }
1252
1253         /**
1254          * Returns array of stylesheet names of themes allowed on the site.
1255          *
1256          * @since 3.4.0
1257          *
1258          * @static
1259          * @access public
1260          *
1261          * @staticvar array $allowed_themes
1262          *
1263          * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1264          * @return array Array of stylesheet names.
1265          */
1266         public static function get_allowed_on_site( $blog_id = null ) {
1267                 static $allowed_themes = array();
1268
1269                 if ( ! $blog_id || ! is_multisite() )
1270                         $blog_id = get_current_blog_id();
1271
1272                 if ( isset( $allowed_themes[ $blog_id ] ) ) {
1273                         /**
1274                          * Filters the array of themes allowed on the site.
1275                          *
1276                          * @since 4.5.0
1277                          *
1278                          * @param array $allowed_themes An array of theme stylesheet names.
1279                          * @param int   $blog_id        ID of the site. Defaults to current site.
1280                          */
1281                         return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1282                 }
1283
1284                 $current = $blog_id == get_current_blog_id();
1285
1286                 if ( $current ) {
1287                         $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1288                 } else {
1289                         switch_to_blog( $blog_id );
1290                         $allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1291                         restore_current_blog();
1292                 }
1293
1294                 // This is all super old MU back compat joy.
1295                 // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1296                 if ( false === $allowed_themes[ $blog_id ] ) {
1297                         if ( $current ) {
1298                                 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1299                         } else {
1300                                 switch_to_blog( $blog_id );
1301                                 $allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1302                                 restore_current_blog();
1303                         }
1304
1305                         if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1306                                 $allowed_themes[ $blog_id ] = array();
1307                         } else {
1308                                 $converted = array();
1309                                 $themes = wp_get_themes();
1310                                 foreach ( $themes as $stylesheet => $theme_data ) {
1311                                         if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) )
1312                                                 $converted[ $stylesheet ] = true;
1313                                 }
1314                                 $allowed_themes[ $blog_id ] = $converted;
1315                         }
1316                         // Set the option so we never have to go through this pain again.
1317                         if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1318                                 if ( $current ) {
1319                                         update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1320                                         delete_option( 'allowed_themes' );
1321                                 } else {
1322                                         switch_to_blog( $blog_id );
1323                                         update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1324                                         delete_option( 'allowed_themes' );
1325                                         restore_current_blog();
1326                                 }
1327                         }
1328                 }
1329
1330                 /** This filter is documented in wp-includes/class-wp-theme.php */
1331                 return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1332         }
1333
1334         /**
1335          * Enables a theme for all sites on the current network.
1336          *
1337          * @since 4.6.0
1338          * @access public
1339          * @static
1340          *
1341          * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1342          */
1343         public static function network_enable_theme( $stylesheets ) {
1344                 if ( ! is_multisite() ) {
1345                         return;
1346                 }
1347
1348                 if ( ! is_array( $stylesheets ) ) {
1349                         $stylesheets = array( $stylesheets );
1350                 }
1351
1352                 $allowed_themes = get_site_option( 'allowedthemes' );
1353                 foreach ( $stylesheets as $stylesheet ) {
1354                         $allowed_themes[ $stylesheet ] = true;
1355                 }
1356
1357                 update_site_option( 'allowedthemes', $allowed_themes );
1358         }
1359
1360         /**
1361          * Disables a theme for all sites on the current network.
1362          *
1363          * @since 4.6.0
1364          * @access public
1365          * @static
1366          *
1367          * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1368          */
1369         public static function network_disable_theme( $stylesheets ) {
1370                 if ( ! is_multisite() ) {
1371                         return;
1372                 }
1373
1374                 if ( ! is_array( $stylesheets ) ) {
1375                         $stylesheets = array( $stylesheets );
1376                 }
1377
1378                 $allowed_themes = get_site_option( 'allowedthemes' );
1379                 foreach ( $stylesheets as $stylesheet ) {
1380                         if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1381                                 unset( $allowed_themes[ $stylesheet ] );
1382                         }
1383                 }
1384
1385                 update_site_option( 'allowedthemes', $allowed_themes );
1386         }
1387
1388         /**
1389          * Sorts themes by name.
1390          *
1391          * @since 3.4.0
1392          *
1393          * @static
1394          * @access public
1395          *
1396          * @param array $themes Array of themes to sort, passed by reference.
1397          */
1398         public static function sort_by_name( &$themes ) {
1399                 if ( 0 === strpos( get_locale(), 'en_' ) ) {
1400                         uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1401                 } else {
1402                         uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1403                 }
1404         }
1405
1406         /**
1407          * Callback function for usort() to naturally sort themes by name.
1408          *
1409          * Accesses the Name header directly from the class for maximum speed.
1410          * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1411          *
1412          * @since 3.4.0
1413          *
1414          * @static
1415          * @access private
1416          *
1417          * @param string $a First name.
1418          * @param string $b Second name.
1419          * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1420          *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1421          */
1422         private static function _name_sort( $a, $b ) {
1423                 return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1424         }
1425
1426         /**
1427          * Name sort (with translation).
1428          *
1429          * @since 3.4.0
1430          *
1431          * @static
1432          * @access private
1433          *
1434          * @param string $a First name.
1435          * @param string $b Second name.
1436          * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1437          *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1438          */
1439         private static function _name_sort_i18n( $a, $b ) {
1440                 // Don't mark up; Do translate.
1441                 return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
1442         }
1443 }