Issues (2010)

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.

wp-includes/class-wp-theme.php (23 issues)

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
	);
61
62
	/**
63
	 * Renamed theme tags.
64
	 *
65
	 * @static
66
	 * @access private
67
	 * @var array
68
	 */
69
	private static $tag_map = array(
70
		'fixed-width'    => 'fixed-layout',
71
		'flexible-width' => 'fluid-layout',
72
	);
73
74
	/**
75
	 * Absolute path to the theme root, usually wp-content/themes
76
	 *
77
	 * @access private
78
	 * @var string
79
	 */
80
	private $theme_root;
81
82
	/**
83
	 * Header data from the theme's style.css file.
84
	 *
85
	 * @access private
86
	 * @var array
87
	 */
88
	private $headers = array();
89
90
	/**
91
	 * Header data from the theme's style.css file after being sanitized.
92
	 *
93
	 * @access private
94
	 * @var array
95
	 */
96
	private $headers_sanitized;
97
98
	/**
99
	 * Header name from the theme's style.css after being translated.
100
	 *
101
	 * Cached due to sorting functions running over the translated name.
102
	 *
103
	 * @access private
104
	 * @var string
105
	 */
106
	private $name_translated;
107
108
	/**
109
	 * Errors encountered when initializing the theme.
110
	 *
111
	 * @access private
112
	 * @var WP_Error
113
	 */
114
	private $errors;
115
116
	/**
117
	 * The directory name of the theme's files, inside the theme root.
118
	 *
119
	 * In the case of a child theme, this is directory name of the child theme.
120
	 * Otherwise, 'stylesheet' is the same as 'template'.
121
	 *
122
	 * @access private
123
	 * @var string
124
	 */
125
	private $stylesheet;
126
127
	/**
128
	 * The directory name of the theme's files, inside the theme root.
129
	 *
130
	 * In the case of a child theme, this is the directory name of the parent theme.
131
	 * Otherwise, 'template' is the same as 'stylesheet'.
132
	 *
133
	 * @access private
134
	 * @var string
135
	 */
136
	private $template;
137
138
	/**
139
	 * A reference to the parent theme, in the case of a child theme.
140
	 *
141
	 * @access private
142
	 * @var WP_Theme
143
	 */
144
	private $parent;
145
146
	/**
147
	 * URL to the theme root, usually an absolute URL to wp-content/themes
148
	 *
149
	 * @access private
150
	 * var string
151
	 */
152
	private $theme_root_uri;
153
154
	/**
155
	 * Flag for whether the theme's textdomain is loaded.
156
	 *
157
	 * @access private
158
	 * @var bool
159
	 */
160
	private $textdomain_loaded;
161
162
	/**
163
	 * Stores an md5 hash of the theme root, to function as the cache key.
164
	 *
165
	 * @access private
166
	 * @var string
167
	 */
168
	private $cache_hash;
169
170
	/**
171
	 * Flag for whether the themes cache bucket should be persistently cached.
172
	 *
173
	 * Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter.
174
	 *
175
	 * @static
176
	 * @access private
177
	 * @var bool
178
	 */
179
	private static $persistently_cache;
180
181
	/**
182
	 * Expiration time for the themes cache bucket.
183
	 *
184
	 * By default the bucket is not cached, so this value is useless.
185
	 *
186
	 * @static
187
	 * @access private
188
	 * @var bool
189
	 */
190
	private static $cache_expiration = 1800;
191
192
	/**
193
	 * Constructor for WP_Theme.
194
	 *
195
	 * @global array $wp_theme_directories
196
	 *
197
	 * @param string $theme_dir Directory of the theme within the theme_root.
198
	 * @param string $theme_root Theme root.
199
	 * @param WP_Error|void $_child If this theme is a parent theme, the child may be passed for validation purposes.
200
	 */
201
	public function __construct( $theme_dir, $theme_root, $_child = null ) {
202
		global $wp_theme_directories;
203
204
		// Initialize caching on first run.
205
		if ( ! isset( self::$persistently_cache ) ) {
206
			/** This action is documented in wp-includes/theme.php */
207
			self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
208
			if ( self::$persistently_cache ) {
209
				wp_cache_add_global_groups( 'themes' );
210
				if ( is_int( self::$persistently_cache ) )
211
					self::$cache_expiration = self::$persistently_cache;
0 ignored issues
show
Documentation Bug introduced by
The property $cache_expiration was declared of type boolean, but self::$persistently_cache is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
212
			} else {
213
				wp_cache_add_non_persistent_groups( 'themes' );
214
			}
215
		}
216
217
		$this->theme_root = $theme_root;
218
		$this->stylesheet = $theme_dir;
219
220
		// Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
221
		if ( ! in_array( $theme_root, (array) $wp_theme_directories ) && in_array( dirname( $theme_root ), (array) $wp_theme_directories ) ) {
222
			$this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
223
			$this->theme_root = dirname( $theme_root );
224
		}
225
226
		$this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
227
		$theme_file = $this->stylesheet . '/style.css';
228
229
		$cache = $this->cache_get( 'theme' );
230
231
		if ( is_array( $cache ) ) {
232
			foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
233
				if ( isset( $cache[ $key ] ) )
234
					$this->$key = $cache[ $key ];
235
			}
236
			if ( $this->errors )
237
				return;
238
			if ( isset( $cache['theme_root_template'] ) )
239
				$theme_root_template = $cache['theme_root_template'];
240
		} elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
241
			$this->headers['Name'] = $this->stylesheet;
242
			if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) )
243
				$this->errors = new WP_Error( 'theme_not_found', sprintf( __( 'The theme directory "%s" does not exist.' ), esc_html( $this->stylesheet ) ) );
244
			else
245
				$this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
246
			$this->template = $this->stylesheet;
247
			$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
0 ignored issues
show
array('headers' => $this...te' => $this->template) is of type array<string,array<strin...","template":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
			if ( ! file_exists( $this->theme_root ) ) // Don't cache this one.
249
				$this->errors->add( 'theme_root_missing', __( 'ERROR: The themes directory is either empty or doesn&#8217;t exist. Please check your installation.' ) );
250
			return;
251
		} elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
252
			$this->headers['Name'] = $this->stylesheet;
253
			$this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
254
			$this->template = $this->stylesheet;
255
			$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
0 ignored issues
show
array('headers' => $this...te' => $this->template) is of type array<string,array<strin...","template":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
			return;
257
		} else {
258
			$this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
259
			// Default themes always trump their pretenders.
260
			// Properly identify default themes that are inside a directory within wp-content/themes.
261
			if ( $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes ) ) {
262
				if ( basename( $this->stylesheet ) != $default_theme_slug )
263
					$this->headers['Name'] .= '/' . $this->stylesheet;
264
			}
265
		}
266
267
		// (If template is set from cache [and there are no errors], we know it's good.)
268
		if ( ! $this->template && ! ( $this->template = $this->headers['Template'] ) ) {
269
			$this->template = $this->stylesheet;
270
			if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) {
271
				$error_message = sprintf(
272
					/* translators: 1: index.php, 2: Codex URL, 3: style.css */
273
					__( 'Template is missing. Standalone themes need to have a %1$s template file. <a href="%2$s">Child themes</a> need to have a Template header in the %3$s stylesheet.' ),
274
					'<code>index.php</code>',
275
					__( 'https://codex.wordpress.org/Child_Themes' ),
276
					'<code>style.css</code>'
277
				);
278
				$this->errors = new WP_Error( 'theme_no_index', $error_message );
279
				$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
0 ignored issues
show
array('headers' => $this...te' => $this->template) is of type array<string,array|objec...","template":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
280
				return;
281
			}
282
		}
283
284
		// If we got our data from cache, we can assume that 'template' is pointing to the right place.
285
		if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) {
286
			// If we're in a directory of themes inside /themes, look for the parent nearby.
287
			// wp-content/themes/directory-of-themes/*
288
			$parent_dir = dirname( $this->stylesheet );
289
			if ( '.' != $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) {
290
				$this->template = $parent_dir . '/' . $this->template;
291
			} elseif ( ( $directories = search_theme_directories() ) && isset( $directories[ $this->template ] ) ) {
292
				// Look for the template in the search_theme_directories() results, in case it is in another theme root.
293
				// We don't look into directories of themes, just the theme root.
294
				$theme_root_template = $directories[ $this->template ]['theme_root'];
295
			} else {
296
				// Parent theme is missing.
297
				$this->errors = new WP_Error( 'theme_no_parent', sprintf( __( 'The parent theme is missing. Please install the "%s" parent theme.' ), esc_html( $this->template ) ) );
298
				$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
0 ignored issues
show
array('headers' => $this...te' => $this->template) is of type array<string,?,{"headers...tring","template":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
299
				$this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
0 ignored issues
show
$this is of type this<WP_Theme>, but the function expects a object<WP_Error>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
300
				return;
301
			}
302
		}
303
304
		// Set the parent, if we're a child theme.
305
		if ( $this->template != $this->stylesheet ) {
306
			// If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
307
			if ( $_child instanceof WP_Theme && $_child->template == $this->stylesheet ) {
308
				$_child->parent = null;
309
				$_child->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $_child->template ) ) );
310
				$_child->cache_add( 'theme', array( 'headers' => $_child->headers, 'errors' => $_child->errors, 'stylesheet' => $_child->stylesheet, 'template' => $_child->template ) );
311
				// The two themes actually reference each other with the Template header.
312
				if ( $_child->stylesheet == $this->template ) {
313
					$this->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), esc_html( $this->template ) ) );
314
					$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
0 ignored issues
show
array('headers' => $this...te' => $this->template) is of type array<string,?,{"headers...tring","template":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
315
				}
316
				return;
317
			}
318
			// Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
319
			$this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
0 ignored issues
show
$this is of type this<WP_Theme>, but the function expects a object<WP_Error>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
320
		}
321
322
		// We're good. If we didn't retrieve from cache, set it.
323
		if ( ! is_array( $cache ) ) {
324
			$cache = array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template );
325
			// If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
326
			if ( isset( $theme_root_template ) )
327
				$cache['theme_root_template'] = $theme_root_template;
328
			$this->cache_add( 'theme', $cache );
0 ignored issues
show
$cache is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
329
		}
330
	}
331
332
	/**
333
	 * When converting the object to a string, the theme name is returned.
334
	 *
335
	 * @return string Theme name, ready for display (translated)
336
	 */
337
	public function __toString() {
338
		return (string) $this->display('Name');
339
	}
340
341
	/**
342
	 * __isset() magic method for properties formerly returned by current_theme_info()
343
	 *
344
	 * @staticvar array $properties
345
	 *
346
	 * @param string $offset Property to check if set.
347
	 * @return bool Whether the given property is set.
348
	 */
349
	public function __isset( $offset ) {
350
		static $properties = array(
351
			'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet',
352
			'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri',
353
		);
354
355
		return in_array( $offset, $properties );
356
	}
357
358
	/**
359
	 * __get() magic method for properties formerly returned by current_theme_info()
360
	 *
361
	 * @param string $offset Property to get.
362
	 * @return mixed Property value.
363
	 */
364
	public function __get( $offset ) {
365
		switch ( $offset ) {
366
			case 'name' :
367
			case 'title' :
368
				return $this->get('Name');
369
			case 'version' :
370
				return $this->get('Version');
371
			case 'parent_theme' :
372
				return $this->parent() ? $this->parent()->get('Name') : '';
373
			case 'template_dir' :
374
				return $this->get_template_directory();
375
			case 'stylesheet_dir' :
376
				return $this->get_stylesheet_directory();
377
			case 'template' :
378
				return $this->get_template();
379
			case 'stylesheet' :
380
				return $this->get_stylesheet();
381
			case 'screenshot' :
382
				return $this->get_screenshot( 'relative' );
383
			// 'author' and 'description' did not previously return translated data.
384
			case 'description' :
385
				return $this->display('Description');
386
			case 'author' :
387
				return $this->display('Author');
388
			case 'tags' :
389
				return $this->get( 'Tags' );
390
			case 'theme_root' :
391
				return $this->get_theme_root();
392
			case 'theme_root_uri' :
393
				return $this->get_theme_root_uri();
394
			// For cases where the array was converted to an object.
395
			default :
396
				return $this->offsetGet( $offset );
397
		}
398
	}
399
400
	/**
401
	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
402
	 *
403
	 * @param mixed $offset
404
	 * @param mixed $value
405
	 */
406
	public function offsetSet( $offset, $value ) {}
407
408
	/**
409
	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
410
	 *
411
	 * @param mixed $offset
412
	 */
413
	public function offsetUnset( $offset ) {}
414
415
	/**
416
	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
417
	 *
418
	 * @staticvar array $keys
419
	 *
420
	 * @param mixed $offset
421
	 * @return bool
422
	 */
423
	public function offsetExists( $offset ) {
424
		static $keys = array(
425
			'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description',
426
			'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir',
427
			'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme',
428
		);
429
430
		return in_array( $offset, $keys );
431
	}
432
433
	/**
434
	 * Method to implement ArrayAccess for keys formerly returned by get_themes().
435
	 *
436
	 * Author, Author Name, Author URI, and Description did not previously return
437
	 * translated data. We are doing so now as it is safe to do. However, as
438
	 * Name and Title could have been used as the key for get_themes(), both remain
439
	 * untranslated for back compatibility. This means that ['Name'] is not ideal,
440
	 * and care should be taken to use `$theme::display( 'Name' )` to get a properly
441
	 * translated header.
442
	 *
443
	 * @param mixed $offset
444
	 * @return mixed
445
	 */
446
	public function offsetGet( $offset ) {
447
		switch ( $offset ) {
448
			case 'Name' :
449
			case 'Title' :
450
				/*
451
				 * See note above about using translated data. get() is not ideal.
452
				 * It is only for backward compatibility. Use display().
453
				 */
454
				return $this->get('Name');
455
			case 'Author' :
456
				return $this->display( 'Author');
457
			case 'Author Name' :
458
				return $this->display( 'Author', false);
459
			case 'Author URI' :
460
				return $this->display('AuthorURI');
461
			case 'Description' :
462
				return $this->display( 'Description');
463
			case 'Version' :
464
			case 'Status' :
465
				return $this->get( $offset );
466
			case 'Template' :
467
				return $this->get_template();
468
			case 'Stylesheet' :
469
				return $this->get_stylesheet();
470
			case 'Template Files' :
471
				return $this->get_files( 'php', 1, true );
472
			case 'Stylesheet Files' :
473
				return $this->get_files( 'css', 0, false );
474
			case 'Template Dir' :
475
				return $this->get_template_directory();
476
			case 'Stylesheet Dir' :
477
				return $this->get_stylesheet_directory();
478
			case 'Screenshot' :
479
				return $this->get_screenshot( 'relative' );
480
			case 'Tags' :
481
				return $this->get('Tags');
482
			case 'Theme Root' :
483
				return $this->get_theme_root();
484
			case 'Theme Root URI' :
485
				return $this->get_theme_root_uri();
486
			case 'Parent Theme' :
487
				return $this->parent() ? $this->parent()->get('Name') : '';
488
			default :
489
				return null;
490
		}
491
	}
492
493
	/**
494
	 * Returns errors property.
495
	 *
496
	 * @since 3.4.0
497
	 * @access public
498
	 *
499
	 * @return WP_Error|false WP_Error if there are errors, or false.
500
	 */
501
	public function errors() {
502
		return is_wp_error( $this->errors ) ? $this->errors : false;
503
	}
504
505
	/**
506
	 * Whether the theme exists.
507
	 *
508
	 * A theme with errors exists. A theme with the error of 'theme_not_found',
509
	 * meaning that the theme's directory was not found, does not exist.
510
	 *
511
	 * @since 3.4.0
512
	 * @access public
513
	 *
514
	 * @return bool Whether the theme exists.
515
	 */
516
	public function exists() {
517
		return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes() ) );
518
	}
519
520
	/**
521
	 * Returns reference to the parent theme.
522
	 *
523
	 * @since 3.4.0
524
	 * @access public
525
	 *
526
	 * @return WP_Theme|false Parent theme, or false if the current theme is not a child theme.
527
	 */
528
	public function parent() {
529
		return isset( $this->parent ) ? $this->parent : false;
530
	}
531
532
	/**
533
	 * Adds theme data to cache.
534
	 *
535
	 * Cache entries keyed by the theme and the type of data.
536
	 *
537
	 * @since 3.4.0
538
	 * @access private
539
	 *
540
	 * @param string $key Type of data to store (theme, screenshot, headers, page_templates)
541
	 * @param string $data Data to store
542
	 * @return bool Return value from wp_cache_add()
543
	 */
544
	private function cache_add( $key, $data ) {
545
		return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration );
0 ignored issues
show
self::$cache_expiration is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
546
	}
547
548
	/**
549
	 * Gets theme data from cache.
550
	 *
551
	 * Cache entries are keyed by the theme and the type of data.
552
	 *
553
	 * @since 3.4.0
554
	 * @access private
555
	 *
556
	 * @param string $key Type of data to retrieve (theme, screenshot, headers, page_templates)
557
	 * @return mixed Retrieved data
558
	 */
559
	private function cache_get( $key ) {
560
		return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' );
561
	}
562
563
	/**
564
	 * Clears the cache for the theme.
565
	 *
566
	 * @since 3.4.0
567
	 * @access public
568
	 */
569
	public function cache_delete() {
570
		foreach ( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key )
571
			wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
572
		$this->template = $this->textdomain_loaded = $this->theme_root_uri = $this->parent = $this->errors = $this->headers_sanitized = $this->name_translated = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->name_translated = null of type null is incompatible with the declared type array of property $headers_sanitized.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
573
		$this->headers = array();
574
		$this->__construct( $this->stylesheet, $this->theme_root );
575
	}
576
577
	/**
578
	 * Get a raw, unformatted theme header.
579
	 *
580
	 * The header is sanitized, but is not translated, and is not marked up for display.
581
	 * To get a theme header for display, use the display() method.
582
	 *
583
	 * Use the get_template() method, not the 'Template' header, for finding the template.
584
	 * The 'Template' header is only good for what was written in the style.css, while
585
	 * get_template() takes into account where WordPress actually located the theme and
586
	 * whether it is actually valid.
587
	 *
588
	 * @since 3.4.0
589
	 * @access public
590
	 *
591
	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
592
	 * @return string|false String on success, false on failure.
593
	 */
594
	public function get( $header ) {
595
		if ( ! isset( $this->headers[ $header ] ) )
596
			return false;
597
598
		if ( ! isset( $this->headers_sanitized ) ) {
599
			$this->headers_sanitized = $this->cache_get( 'headers' );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->cache_get('headers') of type * is incompatible with the declared type array of property $headers_sanitized.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
600
			if ( ! is_array( $this->headers_sanitized ) )
601
				$this->headers_sanitized = array();
602
		}
603
604
		if ( isset( $this->headers_sanitized[ $header ] ) )
605
			return $this->headers_sanitized[ $header ];
606
607
		// If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets.
608
		if ( self::$persistently_cache ) {
609
			foreach ( array_keys( $this->headers ) as $_header )
610
				$this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] );
611
			$this->cache_add( 'headers', $this->headers_sanitized );
0 ignored issues
show
$this->headers_sanitized is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
612
		} else {
613
			$this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] );
614
		}
615
616
		return $this->headers_sanitized[ $header ];
617
	}
618
619
	/**
620
	 * Gets a theme header, formatted and translated for display.
621
	 *
622
	 * @since 3.4.0
623
	 * @access public
624
	 *
625
	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
626
	 * @param bool $markup Optional. Whether to mark up the header. Defaults to true.
627
	 * @param bool $translate Optional. Whether to translate the header. Defaults to true.
628
	 * @return string|false Processed header, false on failure.
629
	 */
630
	public function display( $header, $markup = true, $translate = true ) {
631
		$value = $this->get( $header );
632
		if ( false === $value ) {
633
			return false;
634
		}
635
636
		if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) )
637
			$translate = false;
638
639
		if ( $translate )
640
			$value = $this->translate_header( $header, $value );
641
642
		if ( $markup )
643
			$value = $this->markup_header( $header, $value, $translate );
0 ignored issues
show
$translate is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
644
645
		return $value;
646
	}
647
648
	/**
649
	 * Sanitize a theme header.
650
	 *
651
	 * @since 3.4.0
652
	 * @access private
653
	 *
654
	 * @staticvar array $header_tags
655
	 * @staticvar array $header_tags_with_a
656
	 *
657
	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
658
	 * @param string $value Value to sanitize.
659
	 * @return mixed
660
	 */
661
	private function sanitize_header( $header, $value ) {
662
		switch ( $header ) {
663
			case 'Status' :
664
				if ( ! $value ) {
665
					$value = 'publish';
666
					break;
667
				}
668
				// Fall through otherwise.
669
			case 'Name' :
670
				static $header_tags = array(
671
					'abbr'    => array( 'title' => true ),
672
					'acronym' => array( 'title' => true ),
673
					'code'    => true,
674
					'em'      => true,
675
					'strong'  => true,
676
				);
677
				$value = wp_kses( $value, $header_tags );
678
				break;
679
			case 'Author' :
680
				// There shouldn't be anchor tags in Author, but some themes like to be challenging.
681
			case 'Description' :
682
				static $header_tags_with_a = array(
683
					'a'       => array( 'href' => true, 'title' => true ),
684
					'abbr'    => array( 'title' => true ),
685
					'acronym' => array( 'title' => true ),
686
					'code'    => true,
687
					'em'      => true,
688
					'strong'  => true,
689
				);
690
				$value = wp_kses( $value, $header_tags_with_a );
691
				break;
692
			case 'ThemeURI' :
693
			case 'AuthorURI' :
694
				$value = esc_url_raw( $value );
695
				break;
696
			case 'Tags' :
697
				$value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
698
				break;
699
			case 'Version' :
700
				$value = strip_tags( $value );
701
				break;
702
		}
703
704
		return $value;
705
	}
706
707
	/**
708
	 * Mark up a theme header.
709
	 *
710
     * @since 3.4.0
711
	 * @access private
712
	 *
713
	 * @staticvar string $comma
714
	 *
715
	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
716
	 * @param string $value Value to mark up.
717
	 * @param string $translate Whether the header has been translated.
718
	 * @return string Value, marked up.
719
	 */
720
	private function markup_header( $header, $value, $translate ) {
721
		switch ( $header ) {
722
			case 'Name' :
723
				if ( empty( $value ) )
724
					$value = $this->get_stylesheet();
725
				break;
726
			case 'Description' :
727
				$value = wptexturize( $value );
728
				break;
729
			case 'Author' :
730
				if ( $this->get('AuthorURI') ) {
731
					$value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value );
0 ignored issues
show
$translate is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
732
				} elseif ( ! $value ) {
733
					$value = __( 'Anonymous' );
734
				}
735
				break;
736
			case 'Tags' :
737
				static $comma = null;
738
				if ( ! isset( $comma ) ) {
739
					/* translators: used between list items, there is a space after the comma */
740
					$comma = __( ', ' );
741
				}
742
				$value = implode( $comma, $value );
743
				break;
744
			case 'ThemeURI' :
745
			case 'AuthorURI' :
746
				$value = esc_url( $value );
747
				break;
748
		}
749
750
		return $value;
751
	}
752
753
	/**
754
	 * Translate a theme header.
755
	 *
756
	 * @since 3.4.0
757
	 * @access private
758
	 *
759
	 * @staticvar array $tags_list
760
	 *
761
	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
762
	 * @param string $value Value to translate.
763
	 * @return string Translated value.
764
	 */
765
	private function translate_header( $header, $value ) {
766
		switch ( $header ) {
767
			case 'Name' :
768
				// Cached for sorting reasons.
769
				if ( isset( $this->name_translated ) )
770
					return $this->name_translated;
771
				$this->name_translated = translate( $value, $this->get('TextDomain' ) );
0 ignored issues
show
It seems like $this->get('TextDomain') targeting WP_Theme::get() can also be of type false; however, translate() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
772
				return $this->name_translated;
773
			case 'Tags' :
774
				if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) {
775
					return $value;
776
				}
777
778
				static $tags_list;
779
				if ( ! isset( $tags_list ) ) {
780
					$tags_list = array(
781
						// As of 4.6, deprecated tags which are only used to provide translation for older themes.
782
						'black' => __( 'Black' ), 'blue' => __( 'Blue' ), 'brown'  => __( 'Brown' ),
783
						'gray' => __( 'Gray' ), 'green'  => __( 'Green' ), 'orange' => __( 'Orange' ),
784
						'pink' => __( 'Pink' ), 'purple' => __( 'Purple' ), 'red' => __( 'Red' ),
785
						'silver' => __( 'Silver' ), 'tan' => __( 'Tan' ), 'white' => __( 'White' ),
786
						'yellow' => __( 'Yellow' ), 'dark' => __( 'Dark' ), 'light' => __( 'Light' ),
787
						'fixed-layout' => __( 'Fixed Layout' ), 'fluid-layout' => __( 'Fluid Layout' ),
788
						'responsive-layout' => __( 'Responsive Layout' ), 'blavatar' => __( 'Blavatar' ),
789
						'photoblogging' => __( 'Photoblogging' ), 'seasonal' => __( 'Seasonal' ),
790
					);
791
792
					$feature_list = get_theme_feature_list( false ); // No API
793
					foreach ( $feature_list as $tags ) {
794
						$tags_list += $tags;
795
					}
796
				}
797
798
				foreach ( $value as &$tag ) {
0 ignored issues
show
The expression $value of type string is not traversable.
Loading history...
799
					if ( isset( $tags_list[ $tag ] ) ) {
800
						$tag = $tags_list[ $tag ];
801
					} elseif ( isset( self::$tag_map[ $tag ] ) ) {
802
						$tag = $tags_list[ self::$tag_map[ $tag ] ];
803
					}
804
				}
805
806
				return $value;
807
808
			default :
809
				$value = translate( $value, $this->get('TextDomain') );
0 ignored issues
show
It seems like $this->get('TextDomain') targeting WP_Theme::get() can also be of type false; however, translate() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
810
		}
811
		return $value;
812
	}
813
814
	/**
815
	 * The directory name of the theme's "stylesheet" files, inside the theme root.
816
	 *
817
	 * In the case of a child theme, this is directory name of the child theme.
818
	 * Otherwise, get_stylesheet() is the same as get_template().
819
	 *
820
	 * @since 3.4.0
821
	 * @access public
822
	 *
823
	 * @return string Stylesheet
824
	 */
825
	public function get_stylesheet() {
826
		return $this->stylesheet;
827
	}
828
829
	/**
830
	 * The directory name of the theme's "template" files, inside the theme root.
831
	 *
832
	 * In the case of a child theme, this is the directory name of the parent theme.
833
	 * Otherwise, the get_template() is the same as get_stylesheet().
834
	 *
835
	 * @since 3.4.0
836
	 * @access public
837
	 *
838
	 * @return string Template
839
	 */
840
	public function get_template() {
841
		return $this->template;
842
	}
843
844
	/**
845
	 * Returns the absolute path to the directory of a theme's "stylesheet" files.
846
	 *
847
	 * In the case of a child theme, this is the absolute path to the directory
848
	 * of the child theme's files.
849
	 *
850
	 * @since 3.4.0
851
	 * @access public
852
	 *
853
	 * @return string Absolute path of the stylesheet directory.
854
	 */
855
	public function get_stylesheet_directory() {
856
		if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes() ) )
857
			return '';
858
859
		return $this->theme_root . '/' . $this->stylesheet;
860
	}
861
862
	/**
863
	 * Returns the absolute path to the directory of a theme's "template" files.
864
	 *
865
	 * In the case of a child theme, this is the absolute path to the directory
866
	 * of the parent theme's files.
867
	 *
868
	 * @since 3.4.0
869
	 * @access public
870
	 *
871
	 * @return string Absolute path of the template directory.
872
	 */
873
	public function get_template_directory() {
874
		if ( $this->parent() )
875
			$theme_root = $this->parent()->theme_root;
876
		else
877
			$theme_root = $this->theme_root;
878
879
		return $theme_root . '/' . $this->template;
880
	}
881
882
	/**
883
	 * Returns the URL to the directory of a theme's "stylesheet" files.
884
	 *
885
	 * In the case of a child theme, this is the URL to the directory of the
886
	 * child theme's files.
887
	 *
888
	 * @since 3.4.0
889
	 * @access public
890
	 *
891
	 * @return string URL to the stylesheet directory.
892
	 */
893
	public function get_stylesheet_directory_uri() {
894
		return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
895
	}
896
897
	/**
898
	 * Returns the URL to the directory of a theme's "template" files.
899
	 *
900
	 * In the case of a child theme, this is the URL to the directory of the
901
	 * parent theme's files.
902
	 *
903
	 * @since 3.4.0
904
	 * @access public
905
	 *
906
	 * @return string URL to the template directory.
907
	 */
908
	public function get_template_directory_uri() {
909
		if ( $this->parent() )
910
			$theme_root_uri = $this->parent()->get_theme_root_uri();
911
		else
912
			$theme_root_uri = $this->get_theme_root_uri();
913
914
		return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
915
	}
916
917
	/**
918
	 * The absolute path to the directory of the theme root.
919
	 *
920
	 * This is typically the absolute path to wp-content/themes.
921
	 *
922
	 * @since 3.4.0
923
	 * @access public
924
	 *
925
	 * @return string Theme root.
926
	 */
927
	public function get_theme_root() {
928
		return $this->theme_root;
929
	}
930
931
	/**
932
	 * Returns the URL to the directory of the theme root.
933
	 *
934
	 * This is typically the absolute URL to wp-content/themes. This forms the basis
935
	 * for all other URLs returned by WP_Theme, so we pass it to the public function
936
	 * get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter.
937
	 *
938
	 * @since 3.4.0
939
	 * @access public
940
	 *
941
	 * @return string Theme root URI.
942
	 */
943
	public function get_theme_root_uri() {
944
		if ( ! isset( $this->theme_root_uri ) )
945
			$this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
946
		return $this->theme_root_uri;
947
	}
948
949
	/**
950
	 * Returns the main screenshot file for the theme.
951
	 *
952
	 * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
953
	 *
954
	 * Screenshots for a theme must be in the stylesheet directory. (In the case of child
955
	 * themes, parent theme screenshots are not inherited.)
956
	 *
957
	 * @since 3.4.0
958
	 * @access public
959
	 *
960
	 * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
961
	 * @return string|false Screenshot file. False if the theme does not have a screenshot.
962
	 */
963
	public function get_screenshot( $uri = 'uri' ) {
964
		$screenshot = $this->cache_get( 'screenshot' );
965
		if ( $screenshot ) {
966
			if ( 'relative' == $uri )
967
				return $screenshot;
968
			return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
969
		} elseif ( 0 === $screenshot ) {
970
			return false;
971
		}
972
973
		foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) {
974
			if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
975
				$this->cache_add( 'screenshot', 'screenshot.' . $ext );
976
				if ( 'relative' == $uri )
977
					return 'screenshot.' . $ext;
978
				return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
979
			}
980
		}
981
982
		$this->cache_add( 'screenshot', 0 );
983
		return false;
984
	}
985
986
	/**
987
	 * Return files in the theme's directory.
988
	 *
989
	 * @since 3.4.0
990
	 * @access public
991
	 *
992
	 * @param mixed $type Optional. Array of extensions to return. Defaults to all files (null).
993
	 * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite.
994
	 * @param bool $search_parent Optional. Whether to return parent files. Defaults to false.
995
	 * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values
996
	 * 	             being absolute paths.
997
	 */
998
	public function get_files( $type = null, $depth = 0, $search_parent = false ) {
999
		$files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
1000
1001
		if ( $search_parent && $this->parent() )
1002
			$files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
1003
1004
		return $files;
1005
	}
1006
1007
	/**
1008
	 * Returns the theme's page templates.
1009
	 *
1010
	 * @since 3.4.0
1011
	 * @access public
1012
	 *
1013
	 * @param WP_Post|null $post Optional. The post being edited, provided for context.
1014
	 * @return array Array of page templates, keyed by filename, with the value of the translated header name.
1015
	 */
1016
	public function get_page_templates( $post = null ) {
1017
		// If you screw up your current theme and we invalidate your parent, most things still work. Let it slide.
1018
		if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) )
1019
			return array();
1020
1021
		$page_templates = $this->cache_get( 'page_templates' );
1022
1023
		if ( ! is_array( $page_templates ) ) {
1024
			$page_templates = array();
1025
1026
			$files = (array) $this->get_files( 'php', 1 );
1027
1028
			foreach ( $files as $file => $full_path ) {
1029
				if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) )
1030
					continue;
1031
				$page_templates[ $file ] = _cleanup_header_comment( $header[1] );
1032
			}
1033
1034
			$this->cache_add( 'page_templates', $page_templates );
0 ignored issues
show
$page_templates is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1035
		}
1036
1037
		if ( $this->load_textdomain() ) {
1038
			foreach ( $page_templates as &$page_template ) {
1039
				$page_template = $this->translate_header( 'Template Name', $page_template );
1040
			}
1041
		}
1042
1043
		if ( $this->parent() )
1044
			$page_templates += $this->parent()->get_page_templates( $post );
1045
1046
		/**
1047
		 * Filters list of page templates for a theme.
1048
		 *
1049
		 * @since 3.9.0
1050
		 * @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
1051
		 *
1052
		 * @param array        $page_templates Array of page templates. Keys are filenames,
1053
		 *                                     values are translated names.
1054
		 * @param WP_Theme     $this           The theme object.
1055
		 * @param WP_Post|null $post           The post being edited, provided for context, or null.
1056
		 */
1057
		return (array) apply_filters( 'theme_page_templates', $page_templates, $this, $post );
1058
	}
1059
1060
	/**
1061
	 * Scans a directory for files of a certain extension.
1062
	 *
1063
	 * @since 3.4.0
1064
	 *
1065
	 * @static
1066
	 * @access private
1067
	 *
1068
	 * @param string            $path          Absolute path to search.
1069
	 * @param array|string|null $extensions    Optional. Array of extensions to find, string of a single extension,
1070
	 *                                         or null for all extensions. Default null.
1071
	 * @param int               $depth         Optional. How many levels deep to search for files. Accepts 0, 1+, or
1072
	 *                                         -1 (infinite depth). Default 0.
1073
	 * @param string            $relative_path Optional. The basename of the absolute path. Used to control the
1074
	 *                                         returned path for the found files, particularly when this function
1075
	 *                                         recurses to lower depths. Default empty.
1076
	 * @return array|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
1077
	 *                     with `$relative_path`, with the values being absolute paths. False otherwise.
1078
	 */
1079
	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
1080
		if ( ! is_dir( $path ) )
1081
			return false;
1082
1083
		if ( $extensions ) {
1084
			$extensions = (array) $extensions;
1085
			$_extensions = implode( '|', $extensions );
1086
		}
1087
1088
		$relative_path = trailingslashit( $relative_path );
1089
		if ( '/' == $relative_path )
1090
			$relative_path = '';
1091
1092
		$results = scandir( $path );
1093
		$files = array();
1094
1095
		foreach ( $results as $result ) {
1096
			if ( '.' == $result[0] )
1097
				continue;
1098
			if ( is_dir( $path . '/' . $result ) ) {
1099
				if ( ! $depth || 'CVS' == $result )
1100
					continue;
1101
				$found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
1102
				$files = array_merge_recursive( $files, $found );
1103
			} elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
0 ignored issues
show
The variable $_extensions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1104
				$files[ $relative_path . $result ] = $path . '/' . $result;
1105
			}
1106
		}
1107
1108
		return $files;
1109
	}
1110
1111
	/**
1112
	 * Loads the theme's textdomain.
1113
	 *
1114
	 * Translation files are not inherited from the parent theme. Todo: if this fails for the
1115
	 * child theme, it should probably try to load the parent theme's translations.
1116
	 *
1117
	 * @since 3.4.0
1118
	 * @access public
1119
	 *
1120
	 * @return bool True if the textdomain was successfully loaded or has already been loaded.
1121
	 * 	False if no textdomain was specified in the file headers, or if the domain could not be loaded.
1122
	 */
1123
	public function load_textdomain() {
1124
		if ( isset( $this->textdomain_loaded ) )
1125
			return $this->textdomain_loaded;
1126
1127
		$textdomain = $this->get('TextDomain');
1128
		if ( ! $textdomain ) {
1129
			$this->textdomain_loaded = false;
1130
			return false;
1131
		}
1132
1133
		if ( is_textdomain_loaded( $textdomain ) ) {
1134
			$this->textdomain_loaded = true;
1135
			return true;
1136
		}
1137
1138
		$path = $this->get_stylesheet_directory();
1139
		if ( $domainpath = $this->get('DomainPath') )
1140
			$path .= $domainpath;
1141
		else
1142
			$path .= '/languages';
1143
1144
		$this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
1145
		return $this->textdomain_loaded;
1146
	}
1147
1148
	/**
1149
	 * Whether the theme is allowed (multisite only).
1150
	 *
1151
	 * @since 3.4.0
1152
	 * @access public
1153
	 *
1154
	 * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
1155
	 * 	settings, or 'both'. Defaults to 'both'.
1156
	 * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current site.
1157
	 * @return bool Whether the theme is allowed for the network. Returns true in single-site.
1158
	 */
1159
	public function is_allowed( $check = 'both', $blog_id = null ) {
1160
		if ( ! is_multisite() )
1161
			return true;
1162
1163 View Code Duplication
		if ( 'both' == $check || 'network' == $check ) {
1164
			$allowed = self::get_allowed_on_network();
1165
			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1166
				return true;
1167
		}
1168
1169 View Code Duplication
		if ( 'both' == $check || 'site' == $check ) {
1170
			$allowed = self::get_allowed_on_site( $blog_id );
1171
			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
1172
				return true;
1173
		}
1174
1175
		return false;
1176
	}
1177
1178
	/**
1179
	 * Determines the latest WordPress default theme that is installed.
1180
	 *
1181
	 * This hits the filesystem.
1182
	 *
1183
	 * @return WP_Theme|false Object, or false if no theme is installed, which would be bad.
1184
	 */
1185
	public static function get_core_default_theme() {
1186
		foreach ( array_reverse( self::$default_themes ) as $slug => $name ) {
1187
			$theme = wp_get_theme( $slug );
1188
			if ( $theme->exists() ) {
1189
				return $theme;
1190
			}
1191
		}
1192
		return false;
1193
	}
1194
1195
	/**
1196
	 * Returns array of stylesheet names of themes allowed on the site or network.
1197
	 *
1198
	 * @since 3.4.0
1199
	 *
1200
	 * @static
1201
	 * @access public
1202
	 *
1203
	 * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1204
	 * @return array Array of stylesheet names.
1205
	 */
1206
	public static function get_allowed( $blog_id = null ) {
1207
		/**
1208
		 * Filters the array of themes allowed on the network.
1209
		 *
1210
		 * Site is provided as context so that a list of network allowed themes can
1211
		 * be filtered further.
1212
		 *
1213
		 * @since 4.5.0
1214
		 *
1215
		 * @param array $allowed_themes An array of theme stylesheet names.
1216
		 * @param int   $blog_id        ID of the site.
1217
		 */
1218
		$network = (array) apply_filters( 'network_allowed_themes', self::get_allowed_on_network(), $blog_id );
1219
		return $network + self::get_allowed_on_site( $blog_id );
1220
	}
1221
1222
	/**
1223
	 * Returns array of stylesheet names of themes allowed on the network.
1224
	 *
1225
	 * @since 3.4.0
1226
	 *
1227
	 * @static
1228
	 * @access public
1229
	 *
1230
	 * @staticvar array $allowed_themes
1231
	 *
1232
	 * @return array Array of stylesheet names.
1233
	 */
1234
	public static function get_allowed_on_network() {
1235
		static $allowed_themes;
1236
		if ( ! isset( $allowed_themes ) ) {
1237
			$allowed_themes = (array) get_site_option( 'allowedthemes' );
1238
		}
1239
1240
		/**
1241
		 * Filters the array of themes allowed on the network.
1242
		 *
1243
		 * @since MU
1244
		 *
1245
		 * @param array $allowed_themes An array of theme stylesheet names.
1246
		 */
1247
		$allowed_themes = apply_filters( 'allowed_themes', $allowed_themes );
1248
1249
		return $allowed_themes;
1250
	}
1251
1252
	/**
1253
	 * Returns array of stylesheet names of themes allowed on the site.
1254
	 *
1255
	 * @since 3.4.0
1256
	 *
1257
	 * @static
1258
	 * @access public
1259
	 *
1260
	 * @staticvar array $allowed_themes
1261
	 *
1262
	 * @param int $blog_id Optional. ID of the site. Defaults to the current site.
1263
	 * @return array Array of stylesheet names.
1264
	 */
1265
	public static function get_allowed_on_site( $blog_id = null ) {
1266
		static $allowed_themes = array();
1267
1268
		if ( ! $blog_id || ! is_multisite() )
0 ignored issues
show
Bug Best Practice introduced by
The expression $blog_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1269
			$blog_id = get_current_blog_id();
1270
1271
		if ( isset( $allowed_themes[ $blog_id ] ) ) {
1272
			/**
1273
			 * Filters the array of themes allowed on the site.
1274
			 *
1275
			 * @since 4.5.0
1276
			 *
1277
			 * @param array $allowed_themes An array of theme stylesheet names.
1278
			 * @param int   $blog_id        ID of the site. Defaults to current site.
1279
			 */
1280
			return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1281
		}
1282
1283
		$current = $blog_id == get_current_blog_id();
1284
1285 View Code Duplication
		if ( $current ) {
1286
			$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1287
		} else {
1288
			switch_to_blog( $blog_id );
1289
			$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
1290
			restore_current_blog();
1291
		}
1292
1293
		// This is all super old MU back compat joy.
1294
		// 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
1295
		if ( false === $allowed_themes[ $blog_id ] ) {
1296 View Code Duplication
			if ( $current ) {
1297
				$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1298
			} else {
1299
				switch_to_blog( $blog_id );
1300
				$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
1301
				restore_current_blog();
1302
			}
1303
1304
			if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
1305
				$allowed_themes[ $blog_id ] = array();
1306
			} else {
1307
				$converted = array();
1308
				$themes = wp_get_themes();
1309 View Code Duplication
				foreach ( $themes as $stylesheet => $theme_data ) {
1310
					if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) )
1311
						$converted[ $stylesheet ] = true;
1312
				}
1313
				$allowed_themes[ $blog_id ] = $converted;
1314
			}
1315
			// Set the option so we never have to go through this pain again.
1316
			if ( is_admin() && $allowed_themes[ $blog_id ] ) {
1317
				if ( $current ) {
1318
					update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1319
					delete_option( 'allowed_themes' );
1320
				} else {
1321
					switch_to_blog( $blog_id );
1322
					update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
1323
					delete_option( 'allowed_themes' );
1324
					restore_current_blog();
1325
				}
1326
			}
1327
		}
1328
1329
		/** This filter is documented in wp-includes/class-wp-theme.php */
1330
		return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
1331
	}
1332
1333
	/**
1334
	 * Enables a theme for all sites on the current network.
1335
	 *
1336
	 * @since 4.6.0
1337
	 * @access public
1338
	 * @static
1339
	 *
1340
	 * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1341
	 */
1342
	public static function network_enable_theme( $stylesheets ) {
1343
		if ( ! is_multisite() ) {
1344
			return;
1345
		}
1346
1347
		if ( ! is_array( $stylesheets ) ) {
1348
			$stylesheets = array( $stylesheets );
1349
		}
1350
1351
		$allowed_themes = get_site_option( 'allowedthemes' );
1352
		foreach ( $stylesheets as $stylesheet ) {
1353
			$allowed_themes[ $stylesheet ] = true;
1354
		}
1355
1356
		update_site_option( 'allowedthemes', $allowed_themes );
1357
	}
1358
1359
	/**
1360
	 * Disables a theme for all sites on the current network.
1361
	 *
1362
	 * @since 4.6.0
1363
	 * @access public
1364
	 * @static
1365
	 *
1366
	 * @param string|array $stylesheets Stylesheet name or array of stylesheet names.
1367
	 */
1368
	public static function network_disable_theme( $stylesheets ) {
1369
		if ( ! is_multisite() ) {
1370
			return;
1371
		}
1372
1373
		if ( ! is_array( $stylesheets ) ) {
1374
			$stylesheets = array( $stylesheets );
1375
		}
1376
1377
		$allowed_themes = get_site_option( 'allowedthemes' );
1378
		foreach ( $stylesheets as $stylesheet ) {
1379
			if ( isset( $allowed_themes[ $stylesheet ] ) ) {
1380
				unset( $allowed_themes[ $stylesheet ] );
1381
			}
1382
		}
1383
1384
		update_site_option( 'allowedthemes', $allowed_themes );
1385
	}
1386
1387
	/**
1388
	 * Sorts themes by name.
1389
	 *
1390
	 * @since 3.4.0
1391
	 *
1392
	 * @static
1393
	 * @access public
1394
	 *
1395
	 * @param array $themes Array of themes to sort, passed by reference.
1396
	 */
1397
	public static function sort_by_name( &$themes ) {
1398
		if ( 0 === strpos( get_locale(), 'en_' ) ) {
1399
			uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
1400
		} else {
1401
			uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
1402
		}
1403
	}
1404
1405
	/**
1406
	 * Callback function for usort() to naturally sort themes by name.
1407
	 *
1408
	 * Accesses the Name header directly from the class for maximum speed.
1409
	 * Would choke on HTML but we don't care enough to slow it down with strip_tags().
1410
	 *
1411
	 * @since 3.4.0
1412
	 *
1413
	 * @static
1414
	 * @access private
1415
	 *
1416
	 * @param string $a First name.
1417
	 * @param string $b Second name.
1418
	 * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1419
	 *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1420
	 */
1421
	private static function _name_sort( $a, $b ) {
1422
		return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
1423
	}
1424
1425
	/**
1426
	 * Name sort (with translation).
1427
	 *
1428
	 * @since 3.4.0
1429
	 *
1430
	 * @static
1431
	 * @access private
1432
	 *
1433
	 * @param string $a First name.
1434
	 * @param string $b Second name.
1435
	 * @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
1436
	 *             Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
1437
	 */
1438
	private static function _name_sort_i18n( $a, $b ) {
1439
		// Don't mark up; Do translate.
1440
		return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
0 ignored issues
show
The method display cannot be called on $a (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
The method display cannot be called on $b (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1441
	}
1442
}
1443