Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-wp-theme.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1103
		}
1104
1105
		/**
1106
		 * Filters list of page templates for a theme.
1107
		 *
1108
		 * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1109
		 *
1110
		 * @since 3.9.0
1111
		 * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1112
		 * @since 4.7.0 Added the `$post_type` parameter.
1113
		 *
1114
		 * @param array        $post_templates Array of page templates. Keys are filenames,
1115
		 *                                     values are translated names.
1116
		 * @param WP_Theme     $this           The theme object.
1117
		 * @param WP_Post|null $post           The post being edited, provided for context, or null.
1118
		 * @param string       $post_type      Post type to get the templates for.
1119
		 */
1120
		return (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
1121
	}
1122
1123
	/**
1124
	 * Scans a directory for files of a certain extension.
1125
	 *
1126
	 * @since 3.4.0
1127
	 *
1128
	 * @static
1129
	 * @access private
1130
	 *
1131
	 * @param string            $path          Absolute path to search.
1132
	 * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1133
	 *                                         or null for all extensions. Default null.
1134
	 * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1135
	 *                                         -1 (infinite depth). Default 0.
1136
	 * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1137
	 *                                         returned path for the found files, particularly when this function
1138
	 *                                         recurses to lower depths. Default empty.
1139
	 * @return array|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1140
	 *                     with `$relative_path`, with the values being absolute paths. False otherwise.
1141
	 */
1142
	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1143
		if ( ! is_dir( $path ) )
1144
			return false;
1145
1146
		if ( $extensions ) {
1147
			$extensions = (array) $extensions;
1148
			$_extensions = implode( '|', $extensions );
1149
		}
1150
1151
		$relative_path = trailingslashit( $relative_path );
1152
		if ( '/' == $relative_path )
1153
			$relative_path = '';
1154
1155
		$results = scandir( $path );
1156
		$files = array();
1157
1158
		/**
1159
		 * Filters the array of excluded directories and files while scanning theme folder.
1160
		 *
1161
 		 * @since 4.7.4
1162
		 *
1163
		 * @param array $exclusions Array of excluded directories and files.
1164
		 */
1165
		$exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules' ) );
1166
1167
		foreach ( $results as $result ) {
1168
			if ( '.' == $result[0] || in_array( $result, $exclusions, true ) ) {
1169
				continue;
1170
			}
1171
			if ( is_dir( $path . '/' . $result ) ) {
1172
				if ( ! $depth )
1173
					continue;
1174
				$found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
1175
				$files = array_merge_recursive( $files, $found );
1176
			} elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
1177
				$files[ $relative_path . $result ] = $path . '/' . $result;
1178
			}
1179
		}
1180
1181
		return $files;
1182
	}
1183
1184
	/**
1185
	 * Loads the theme's textdomain.
1186
	 *
1187
	 * Translation files are not inherited from the parent theme. Todo: if this fails for the
1188
	 * child theme, it should probably try to load the parent theme's translations.
1189
	 *
1190
	 * @since 3.4.0
1191
	 * @access public
1192
	 *
1193
	 * @return bool True if the textdomain was successfully loaded or has already been loaded.
1194
	 * 	False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1195
	 */
1196
	public function load_textdomain() {
1197
		if ( isset( $this->textdomain_loaded ) )
1198
			return $this->textdomain_loaded;
1199
1200
		$textdomain = $this->get('TextDomain');
1201
		if ( ! $textdomain ) {
1202
			$this->textdomain_loaded = false;
1203
			return false;
1204
		}
1205
1206
		if ( is_textdomain_loaded( $textdomain ) ) {
1207
			$this->textdomain_loaded = true;
1208
			return true;
1209
		}
1210
1211
		$path = $this->get_stylesheet_directory();
1212
		if ( $domainpath = $this->get('DomainPath') )
1213
			$path .= $domainpath;
1214
		else
1215
			$path .= '/languages';
1216
1217
		$this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1218
		return $this->textdomain_loaded;
1219
	}
1220
1221
	/**
1222
	 * Whether the theme is allowed (multisite only).
1223
	 *
1224
	 * @since 3.4.0
1225
	 * @access public
1226
	 *
1227
	 * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
1228
	 * 	settings, or 'both'. Defaults to 'both'.
1229
	 * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1230
	 * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1231
	 */
1232
	public function is_allowed( $check = 'both', $blog_id = null ) {
1233
		if ( ! is_multisite() )
1234
			return true;
1235
1236 View Code Duplication
		if ( 'both' == $check || 'network' == $check ) {
1237
			$allowed = self::get_allowed_on_network();
1238
			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1239
				return true;
1240
		}
1241
1242 View Code Duplication
		if ( 'both' == $check || 'site' == $check ) {
1243
			$allowed = self::get_allowed_on_site( $blog_id );
1244
			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1245
				return true;
1246
		}
1247
1248
		return false;
1249
	}
1250
1251
	/**
1252
	 * Determines the latest WordPress default theme that is installed.
1253
	 *
1254
	 * This hits the filesystem.
1255
	 *
1256
	 * @since  4.4.0
1257
	 *
1258
	 * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1259
	 */
1260
	public static function get_core_default_theme() {
1261
		foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1262
			$theme = wp_get_theme( $slug );
1263
			if ( $theme->exists() ) {
1264
				return $theme;
1265
			}
1266
		}
1267
		return false;
1268
	}
1269
1270
	/**
1271
	 * Returns array of stylesheet names of themes allowed on the site or network.
1272
	 *
1273
	 * @since 3.4.0
1274
	 *
1275
	 * @static
1276
	 * @access public
1277
	 *
1278
	 * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1279
	 * @return array Array of stylesheet names.
1280
	 */
1281
	public static function get_allowed( $blog_id = null ) {
1282
		/**
1283
		 * Filters the array of themes allowed on the network.
1284
		 *
1285
		 * Site is provided as context so that a list of network allowed themes can
1286
		 * be filtered further.
1287
		 *
1288
		 * @since 4.5.0
1289
		 *
1290
		 * @param array $allowed_themes An array of theme stylesheet names.
1291
		 * @param int   $blog_id        ID of the site.
1292
		 */
1293
		$network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1294
		return $network + self::get_allowed_on_site( $blog_id );
1295
	}
1296
1297
	/**
1298
	 * Returns array of stylesheet names of themes allowed on the network.
1299
	 *
1300
	 * @since 3.4.0
1301
	 *
1302
	 * @static
1303
	 * @access public
1304
	 *
1305
	 * @staticvar array $allowed_themes
1306
	 *
1307
	 * @return array Array of stylesheet names.
1308
	 */
1309
	public static function get_allowed_on_network() {
1310
		static $allowed_themes;
1311
		if ( ! isset( $allowed_themes ) ) {
1312
			$allowed_themes = (array) get_site_option( 'allowedthemes' );
1313
		}
1314
1315
		/**
1316
		 * Filters the array of themes allowed on the network.
1317
		 *
1318
		 * @since MU
1319
		 *
1320
		 * @param array $allowed_themes An array of theme stylesheet names.
1321
		 */
1322
		$allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1323
1324
		return $allowed_themes;
1325
	}
1326
1327
	/**
1328
	 * Returns array of stylesheet names of themes allowed on the site.
1329
	 *
1330
	 * @since 3.4.0
1331
	 *
1332
	 * @static
1333
	 * @access public
1334
	 *
1335
	 * @staticvar array $allowed_themes
1336
	 *
1337
	 * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1338
	 * @return array Array of stylesheet names.
1339
	 */
1340
	public static function get_allowed_on_site( $blog_id = null ) {
1341
		static $allowed_themes = array();
1342
1343
		if ( ! $blog_id || ! is_multisite() )
1344
			$blog_id = get_current_blog_id();
1345
1346
		if ( isset( $allowed_themes[ $blog_id ] ) ) {
1347
			/**
1348
			 * Filters the array of themes allowed on the site.
1349
			 *
1350
			 * @since 4.5.0
1351
			 *
1352
			 * @param array $allowed_themes An array of theme stylesheet names.
1353
			 * @param int   $blog_id        ID of the site. Defaults to current site.
1354
			 */
1355
			return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1356
		}
1357
1358
		$current = $blog_id == get_current_blog_id();
1359
1360 View Code Duplication
		if ( $current ) {
1361
			$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1362
		} else {
1363
			switch_to_blog( $blog_id );
1364
			$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1365
			restore_current_blog();
1366
		}
1367
1368
		// This is all super old MU back compat joy.
1369
		// 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1370
		if ( false === $allowed_themes[ $blog_id ] ) {
1371 View Code Duplication
			if ( $current ) {
1372
				$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1373
			} else {
1374
				switch_to_blog( $blog_id );
1375
				$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1376
				restore_current_blog();
1377
			}
1378
1379
			if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1380
				$allowed_themes[ $blog_id ] = array();
1381
			} else {
1382
				$converted = array();
1383
				$themes = wp_get_themes();
1384 View Code Duplication
				foreach ( $themes as $stylesheet => $theme_data ) {
1385
					if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) )
1386
						$converted[ $stylesheet ] = true;
1387
				}
1388
				$allowed_themes[ $blog_id ] = $converted;
1389
			}
1390
			// Set the option so we never have to go through this pain again.
1391
			if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1392
				if ( $current ) {
1393
					update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1394
					delete_option( 'allowed_themes' );
1395
				} else {
1396
					switch_to_blog( $blog_id );
1397
					update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1398
					delete_option( 'allowed_themes' );
1399
					restore_current_blog();
1400
				}
1401
			}
1402
		}
1403
1404
		/** This filter is documented in wp-includes/class-wp-theme.php */
1405
		return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1406
	}
1407
1408
	/**
1409
	 * Enables a theme for all sites on the current network.
1410
	 *
1411
	 * @since 4.6.0
1412
	 * @access public
1413
	 * @static
1414
	 *
1415
	 * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1416
	 */
1417
	public static function network_enable_theme( $stylesheets ) {
1418
		if ( ! is_multisite() ) {
1419
			return;
1420
		}
1421
1422
		if ( ! is_array( $stylesheets ) ) {
1423
			$stylesheets = array( $stylesheets );
1424
		}
1425
1426
		$allowed_themes = get_site_option( 'allowedthemes' );
1427
		foreach ( $stylesheets as $stylesheet ) {
1428
			$allowed_themes[ $stylesheet ] = true;
1429
		}
1430
1431
		update_site_option( 'allowedthemes', $allowed_themes );
1432
	}
1433
1434
	/**
1435
	 * Disables a theme for all sites on the current network.
1436
	 *
1437
	 * @since 4.6.0
1438
	 * @access public
1439
	 * @static
1440
	 *
1441
	 * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1442
	 */
1443
	public static function network_disable_theme( $stylesheets ) {
1444
		if ( ! is_multisite() ) {
1445
			return;
1446
		}
1447
1448
		if ( ! is_array( $stylesheets ) ) {
1449
			$stylesheets = array( $stylesheets );
1450
		}
1451
1452
		$allowed_themes = get_site_option( 'allowedthemes' );
1453
		foreach ( $stylesheets as $stylesheet ) {
1454
			if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1455
				unset( $allowed_themes[ $stylesheet ] );
1456
			}
1457
		}
1458
1459
		update_site_option( 'allowedthemes', $allowed_themes );
1460
	}
1461
1462
	/**
1463
	 * Sorts themes by name.
1464
	 *
1465
	 * @since 3.4.0
1466
	 *
1467
	 * @static
1468
	 * @access public
1469
	 *
1470
	 * @param array $themes Array of themes to sort, passed by reference.
1471
	 */
1472
	public static function sort_by_name( &$themes ) {
1473
		if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
1474
			uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1475
		} else {
1476
			uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1477
		}
1478
	}
1479
1480
	/**
1481
	 * Callback function for usort() to naturally sort themes by name.
1482
	 *
1483
	 * Accesses the Name header directly from the class for maximum speed.
1484
	 * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1485
	 *
1486
	 * @since 3.4.0
1487
	 *
1488
	 * @static
1489
	 * @access private
1490
	 *
1491
	 * @param string $a First name.
1492
	 * @param string $b Second name.
1493
	 * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1494
	 *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1495
	 */
1496
	private static function _name_sort( $a, $b ) {
1497
		return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1498
	}
1499
1500
	/**
1501
	 * Name sort (with translation).
1502
	 *
1503
	 * @since 3.4.0
1504
	 *
1505
	 * @static
1506
	 * @access private
1507
	 *
1508
	 * @param string $a First name.
1509
	 * @param string $b Second name.
1510
	 * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1511
	 *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1512
	 */
1513
	private static function _name_sort_i18n( $a, $b ) {
1514
		// Don't mark up; Do translate.
1515
		return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
1516
	}
1517
}
1518