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