~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/class-wp-theme.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

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