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/theme.php (25 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
 * Theme, template, and stylesheet functions.
4
 *
5
 * @package WordPress
6
 * @subpackage Theme
7
 */
8
9
/**
10
 * Returns an array of WP_Theme objects based on the arguments.
11
 *
12
 * Despite advances over get_themes(), this function is quite expensive, and grows
13
 * linearly with additional themes. Stick to wp_get_theme() if possible.
14
 *
15
 * @since 3.4.0
16
 *
17
 * @global array $wp_theme_directories
18
 * @staticvar array $_themes
19
 *
20
 * @param array $args The search arguments. Optional.
21
 * - errors      mixed  True to return themes with errors, false to return themes without errors, null
22
 *                      to return all themes. Defaults to false.
23
 * - allowed     mixed  (Multisite) True to return only allowed themes for a site. False to return only
24
 *                      disallowed themes for a site. 'site' to return only site-allowed themes. 'network'
25
 *                      to return only network-allowed themes. Null to return all themes. Defaults to null.
26
 * - blog_id     int    (Multisite) The blog ID used to calculate which themes are allowed. Defaults to 0,
27
 *                      synonymous for the current blog.
28
 * @return array Array of WP_Theme objects.
29
 */
30
function wp_get_themes( $args = array() ) {
31
	global $wp_theme_directories;
32
33
	$defaults = array( 'errors' => false, 'allowed' => null, 'blog_id' => 0 );
34
	$args = wp_parse_args( $args, $defaults );
35
36
	$theme_directories = search_theme_directories();
37
38
	if ( count( $wp_theme_directories ) > 1 ) {
39
		// Make sure the current theme wins out, in case search_theme_directories() picks the wrong
40
		// one in the case of a conflict. (Normally, last registered theme root wins.)
41
		$current_theme = get_stylesheet();
42
		if ( isset( $theme_directories[ $current_theme ] ) ) {
43
			$root_of_current_theme = get_raw_theme_root( $current_theme );
44
			if ( ! in_array( $root_of_current_theme, $wp_theme_directories ) )
45
				$root_of_current_theme = WP_CONTENT_DIR . $root_of_current_theme;
46
			$theme_directories[ $current_theme ]['theme_root'] = $root_of_current_theme;
47
		}
48
	}
49
50
	if ( empty( $theme_directories ) )
51
		return array();
52
53
	if ( is_multisite() && null !== $args['allowed'] ) {
54
		$allowed = $args['allowed'];
55
		if ( 'network' === $allowed )
56
			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_network() );
57
		elseif ( 'site' === $allowed )
58
			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_site( $args['blog_id'] ) );
59
		elseif ( $allowed )
60
			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
61
		else
62
			$theme_directories = array_diff_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
63
	}
64
65
	$themes = array();
66
	static $_themes = array();
67
68
	foreach ( $theme_directories as $theme => $theme_root ) {
69
		if ( isset( $_themes[ $theme_root['theme_root'] . '/' . $theme ] ) )
70
			$themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ];
71
		else
72
			$themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
73
	}
74
75
	if ( null !== $args['errors'] ) {
76
		foreach ( $themes as $theme => $wp_theme ) {
77
			if ( $wp_theme->errors() != $args['errors'] )
78
				unset( $themes[ $theme ] );
79
		}
80
	}
81
82
	return $themes;
83
}
84
85
/**
86
 * Gets a WP_Theme object for a theme.
87
 *
88
 * @since 3.4.0
89
 *
90
 * @global array $wp_theme_directories
91
 *
92
 * @param string $stylesheet Directory name for the theme. Optional. Defaults to current theme.
93
 * @param string $theme_root Absolute path of the theme root to look in. Optional. If not specified, get_raw_theme_root()
94
 * 	                         is used to calculate the theme root for the $stylesheet provided (or current theme).
95
 * @return WP_Theme Theme object. Be sure to check the object's exists() method if you need to confirm the theme's existence.
96
 */
97
function wp_get_theme( $stylesheet = null, $theme_root = null ) {
98
	global $wp_theme_directories;
99
100
	if ( empty( $stylesheet ) )
101
		$stylesheet = get_stylesheet();
102
103 View Code Duplication
	if ( empty( $theme_root ) ) {
104
		$theme_root = get_raw_theme_root( $stylesheet );
105
		if ( false === $theme_root )
106
			$theme_root = WP_CONTENT_DIR . '/themes';
107
		elseif ( ! in_array( $theme_root, (array) $wp_theme_directories ) )
108
			$theme_root = WP_CONTENT_DIR . $theme_root;
109
	}
110
111
	return new WP_Theme( $stylesheet, $theme_root );
112
}
113
114
/**
115
 * Clears the cache held by get_theme_roots() and WP_Theme.
116
 *
117
 * @since 3.5.0
118
 * @param bool $clear_update_cache Whether to clear the Theme updates cache
119
 */
120
function wp_clean_themes_cache( $clear_update_cache = true ) {
121
	if ( $clear_update_cache )
122
		delete_site_transient( 'update_themes' );
123
	search_theme_directories( true );
124
	foreach ( wp_get_themes( array( 'errors' => null ) ) as $theme )
125
		$theme->cache_delete();
126
}
127
128
/**
129
 * Whether a child theme is in use.
130
 *
131
 * @since 3.0.0
132
 *
133
 * @return bool true if a child theme is in use, false otherwise.
134
 **/
135
function is_child_theme() {
136
	return ( TEMPLATEPATH !== STYLESHEETPATH );
137
}
138
139
/**
140
 * Retrieve name of the current stylesheet.
141
 *
142
 * The theme name that the administrator has currently set the front end theme
143
 * as.
144
 *
145
 * For all intents and purposes, the template name and the stylesheet name are
146
 * going to be the same for most cases.
147
 *
148
 * @since 1.5.0
149
 *
150
 * @return string Stylesheet name.
151
 */
152
function get_stylesheet() {
153
	/**
154
	 * Filters the name of current stylesheet.
155
	 *
156
	 * @since 1.5.0
157
	 *
158
	 * @param string $stylesheet Name of the current stylesheet.
159
	 */
160
	return apply_filters( 'stylesheet', get_option( 'stylesheet' ) );
161
}
162
163
/**
164
 * Retrieve stylesheet directory path for current theme.
165
 *
166
 * @since 1.5.0
167
 *
168
 * @return string Path to current theme directory.
169
 */
170
function get_stylesheet_directory() {
171
	$stylesheet = get_stylesheet();
172
	$theme_root = get_theme_root( $stylesheet );
173
	$stylesheet_dir = "$theme_root/$stylesheet";
174
175
	/**
176
	 * Filters the stylesheet directory path for current theme.
177
	 *
178
	 * @since 1.5.0
179
	 *
180
	 * @param string $stylesheet_dir Absolute path to the current theme.
181
	 * @param string $stylesheet     Directory name of the current theme.
182
	 * @param string $theme_root     Absolute path to themes directory.
183
	 */
184
	return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
185
}
186
187
/**
188
 * Retrieve stylesheet directory URI.
189
 *
190
 * @since 1.5.0
191
 *
192
 * @return string
193
 */
194 View Code Duplication
function get_stylesheet_directory_uri() {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
195
	$stylesheet = str_replace( '%2F', '/', rawurlencode( get_stylesheet() ) );
196
	$theme_root_uri = get_theme_root_uri( $stylesheet );
197
	$stylesheet_dir_uri = "$theme_root_uri/$stylesheet";
198
199
	/**
200
	 * Filters the stylesheet directory URI.
201
	 *
202
	 * @since 1.5.0
203
	 *
204
	 * @param string $stylesheet_dir_uri Stylesheet directory URI.
205
	 * @param string $stylesheet         Name of the activated theme's directory.
206
	 * @param string $theme_root_uri     Themes root URI.
207
	 */
208
	return apply_filters( 'stylesheet_directory_uri', $stylesheet_dir_uri, $stylesheet, $theme_root_uri );
209
}
210
211
/**
212
 * Retrieves the URI of current theme stylesheet.
213
 *
214
 * The stylesheet file name is 'style.css' which is appended to the stylesheet directory URI path.
215
 * See get_stylesheet_directory_uri().
216
 *
217
 * @since 1.5.0
218
 *
219
 * @return string
220
 */
221
function get_stylesheet_uri() {
222
	$stylesheet_dir_uri = get_stylesheet_directory_uri();
223
	$stylesheet_uri = $stylesheet_dir_uri . '/style.css';
224
	/**
225
	 * Filters the URI of the current theme stylesheet.
226
	 *
227
	 * @since 1.5.0
228
	 *
229
	 * @param string $stylesheet_uri     Stylesheet URI for the current theme/child theme.
230
	 * @param string $stylesheet_dir_uri Stylesheet directory URI for the current theme/child theme.
231
	 */
232
	return apply_filters( 'stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
233
}
234
235
/**
236
 * Retrieves the localized stylesheet URI.
237
 *
238
 * The stylesheet directory for the localized stylesheet files are located, by
239
 * default, in the base theme directory. The name of the locale file will be the
240
 * locale followed by '.css'. If that does not exist, then the text direction
241
 * stylesheet will be checked for existence, for example 'ltr.css'.
242
 *
243
 * The theme may change the location of the stylesheet directory by either using
244
 * the {@see 'stylesheet_directory_uri'} or {@see 'locale_stylesheet_uri'} filters.
245
 *
246
 * If you want to change the location of the stylesheet files for the entire
247
 * WordPress workflow, then change the former. If you just have the locale in a
248
 * separate folder, then change the latter.
249
 *
250
 * @since 2.1.0
251
 *
252
 * @global WP_Locale $wp_locale
253
 *
254
 * @return string
255
 */
256
function get_locale_stylesheet_uri() {
257
	global $wp_locale;
258
	$stylesheet_dir_uri = get_stylesheet_directory_uri();
259
	$dir = get_stylesheet_directory();
260
	$locale = get_locale();
261
	if ( file_exists("$dir/$locale.css") )
262
		$stylesheet_uri = "$stylesheet_dir_uri/$locale.css";
263
	elseif ( !empty($wp_locale->text_direction) && file_exists("$dir/{$wp_locale->text_direction}.css") )
264
		$stylesheet_uri = "$stylesheet_dir_uri/{$wp_locale->text_direction}.css";
265
	else
266
		$stylesheet_uri = '';
267
	/**
268
	 * Filters the localized stylesheet URI.
269
	 *
270
	 * @since 2.1.0
271
	 *
272
	 * @param string $stylesheet_uri     Localized stylesheet URI.
273
	 * @param string $stylesheet_dir_uri Stylesheet directory URI.
274
	 */
275
	return apply_filters( 'locale_stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
276
}
277
278
/**
279
 * Retrieve name of the current theme.
280
 *
281
 * @since 1.5.0
282
 *
283
 * @return string Template name.
284
 */
285
function get_template() {
286
	/**
287
	 * Filters the name of the current theme.
288
	 *
289
	 * @since 1.5.0
290
	 *
291
	 * @param string $template Current theme's directory name.
292
	 */
293
	return apply_filters( 'template', get_option( 'template' ) );
294
}
295
296
/**
297
 * Retrieve current theme directory.
298
 *
299
 * @since 1.5.0
300
 *
301
 * @return string Template directory path.
302
 */
303
function get_template_directory() {
304
	$template = get_template();
305
	$theme_root = get_theme_root( $template );
306
	$template_dir = "$theme_root/$template";
307
308
	/**
309
	 * Filters the current theme directory path.
310
	 *
311
	 * @since 1.5.0
312
	 *
313
	 * @param string $template_dir The URI of the current theme directory.
314
	 * @param string $template     Directory name of the current theme.
315
	 * @param string $theme_root   Absolute path to the themes directory.
316
	 */
317
	return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
318
}
319
320
/**
321
 * Retrieve theme directory URI.
322
 *
323
 * @since 1.5.0
324
 *
325
 * @return string Template directory URI.
326
 */
327 View Code Duplication
function get_template_directory_uri() {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
	$template = str_replace( '%2F', '/', rawurlencode( get_template() ) );
329
	$theme_root_uri = get_theme_root_uri( $template );
330
	$template_dir_uri = "$theme_root_uri/$template";
331
332
	/**
333
	 * Filters the current theme directory URI.
334
	 *
335
	 * @since 1.5.0
336
	 *
337
	 * @param string $template_dir_uri The URI of the current theme directory.
338
	 * @param string $template         Directory name of the current theme.
339
	 * @param string $theme_root_uri   The themes root URI.
340
	 */
341
	return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
342
}
343
344
/**
345
 * Retrieve theme roots.
346
 *
347
 * @since 2.9.0
348
 *
349
 * @global array $wp_theme_directories
350
 *
351
 * @return array|string An array of theme roots keyed by template/stylesheet or a single theme root if all themes have the same root.
352
 */
353
function get_theme_roots() {
354
	global $wp_theme_directories;
355
356
	if ( count($wp_theme_directories) <= 1 )
357
		return '/themes';
358
359
	$theme_roots = get_site_transient( 'theme_roots' );
360
	if ( false === $theme_roots ) {
361
		search_theme_directories( true ); // Regenerate the transient.
362
		$theme_roots = get_site_transient( 'theme_roots' );
363
	}
364
	return $theme_roots;
365
}
366
367
/**
368
 * Register a directory that contains themes.
369
 *
370
 * @since 2.9.0
371
 *
372
 * @global array $wp_theme_directories
373
 *
374
 * @param string $directory Either the full filesystem path to a theme folder or a folder within WP_CONTENT_DIR
375
 * @return bool
376
 */
377
function register_theme_directory( $directory ) {
378
	global $wp_theme_directories;
379
380
	if ( ! file_exists( $directory ) ) {
381
		// Try prepending as the theme directory could be relative to the content directory
382
		$directory = WP_CONTENT_DIR . '/' . $directory;
383
		// If this directory does not exist, return and do not register
384
		if ( ! file_exists( $directory ) ) {
385
			return false;
386
		}
387
	}
388
389
	if ( ! is_array( $wp_theme_directories ) ) {
390
		$wp_theme_directories = array();
391
	}
392
393
	$untrailed = untrailingslashit( $directory );
394
	if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories ) ) {
395
		$wp_theme_directories[] = $untrailed;
396
	}
397
398
	return true;
399
}
400
401
/**
402
 * Search all registered theme directories for complete and valid themes.
403
 *
404
 * @since 2.9.0
405
 *
406
 * @global array $wp_theme_directories
407
 * @staticvar array $found_themes
408
 *
409
 * @param bool $force Optional. Whether to force a new directory scan. Defaults to false.
410
 * @return array|false Valid themes found
411
 */
412
function search_theme_directories( $force = false ) {
413
	global $wp_theme_directories;
414
	static $found_themes = null;
415
416
	if ( empty( $wp_theme_directories ) )
417
		return false;
418
419
	if ( ! $force && isset( $found_themes ) )
420
		return $found_themes;
421
422
	$found_themes = array();
423
424
	$wp_theme_directories = (array) $wp_theme_directories;
425
	$relative_theme_roots = array();
426
427
	// Set up maybe-relative, maybe-absolute array of theme directories.
428
	// We always want to return absolute, but we need to cache relative
429
	// to use in get_theme_root().
430
	foreach ( $wp_theme_directories as $theme_root ) {
431
		if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) )
432
			$relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
433
		else
434
			$relative_theme_roots[ $theme_root ] = $theme_root;
435
	}
436
437
	/**
438
	 * Filters whether to get the cache of the registered theme directories.
439
	 *
440
	 * @since 3.4.0
441
	 *
442
	 * @param bool   $cache_expiration Whether to get the cache of the theme directories. Default false.
443
	 * @param string $cache_directory  Directory to be searched for the cache.
444
	 */
445
	if ( $cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' ) ) {
446
		$cached_roots = get_site_transient( 'theme_roots' );
447
		if ( is_array( $cached_roots ) ) {
448
			foreach ( $cached_roots as $theme_dir => $theme_root ) {
449
				// A cached theme root is no longer around, so skip it.
450
				if ( ! isset( $relative_theme_roots[ $theme_root ] ) )
451
					continue;
452
				$found_themes[ $theme_dir ] = array(
453
					'theme_file' => $theme_dir . '/style.css',
454
					'theme_root' => $relative_theme_roots[ $theme_root ], // Convert relative to absolute.
455
				);
456
			}
457
			return $found_themes;
458
		}
459
		if ( ! is_int( $cache_expiration ) )
460
			$cache_expiration = 1800; // half hour
461
	} else {
462
		$cache_expiration = 1800; // half hour
463
	}
464
465
	/* Loop the registered theme directories and extract all themes */
466
	foreach ( $wp_theme_directories as $theme_root ) {
467
468
		// Start with directories in the root of the current theme directory.
469
		$dirs = @ scandir( $theme_root );
470
		if ( ! $dirs ) {
471
			trigger_error( "$theme_root is not readable", E_USER_NOTICE );
472
			continue;
473
		}
474
		foreach ( $dirs as $dir ) {
475 View Code Duplication
			if ( ! is_dir( $theme_root . '/' . $dir ) || $dir[0] == '.' || $dir == 'CVS' )
476
				continue;
477
			if ( file_exists( $theme_root . '/' . $dir . '/style.css' ) ) {
478
				// wp-content/themes/a-single-theme
479
				// wp-content/themes is $theme_root, a-single-theme is $dir
480
				$found_themes[ $dir ] = array(
481
					'theme_file' => $dir . '/style.css',
482
					'theme_root' => $theme_root,
483
				);
484
			} else {
485
				$found_theme = false;
486
				// wp-content/themes/a-folder-of-themes/*
487
				// wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs
488
				$sub_dirs = @ scandir( $theme_root . '/' . $dir );
489
				if ( ! $sub_dirs ) {
490
					trigger_error( "$theme_root/$dir is not readable", E_USER_NOTICE );
491
					continue;
492
				}
493
				foreach ( $sub_dirs as $sub_dir ) {
494 View Code Duplication
					if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || $dir[0] == '.' || $dir == 'CVS' )
495
						continue;
496
					if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) )
497
						continue;
498
					$found_themes[ $dir . '/' . $sub_dir ] = array(
499
						'theme_file' => $dir . '/' . $sub_dir . '/style.css',
500
						'theme_root' => $theme_root,
501
					);
502
					$found_theme = true;
503
				}
504
				// Never mind the above, it's just a theme missing a style.css.
505
				// Return it; WP_Theme will catch the error.
506
				if ( ! $found_theme )
507
					$found_themes[ $dir ] = array(
508
						'theme_file' => $dir . '/style.css',
509
						'theme_root' => $theme_root,
510
					);
511
			}
512
		}
513
	}
514
515
	asort( $found_themes );
516
517
	$theme_roots = array();
518
	$relative_theme_roots = array_flip( $relative_theme_roots );
519
520
	foreach ( $found_themes as $theme_dir => $theme_data ) {
521
		$theme_roots[ $theme_dir ] = $relative_theme_roots[ $theme_data['theme_root'] ]; // Convert absolute to relative.
522
	}
523
524
	if ( $theme_roots != get_site_transient( 'theme_roots' ) )
525
		set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
526
527
	return $found_themes;
528
}
529
530
/**
531
 * Retrieve path to themes directory.
532
 *
533
 * Does not have trailing slash.
534
 *
535
 * @since 1.5.0
536
 *
537
 * @global array $wp_theme_directories
538
 *
539
 * @param string $stylesheet_or_template The stylesheet or template name of the theme
540
 * @return string Theme path.
541
 */
542
function get_theme_root( $stylesheet_or_template = false ) {
543
	global $wp_theme_directories;
544
545 View Code Duplication
	if ( $stylesheet_or_template && $theme_root = get_raw_theme_root( $stylesheet_or_template ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stylesheet_or_template of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
546
		// Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
547
		// This gives relative theme roots the benefit of the doubt when things go haywire.
548
		if ( ! in_array( $theme_root, (array) $wp_theme_directories ) )
549
			$theme_root = WP_CONTENT_DIR . $theme_root;
550
	} else {
551
		$theme_root = WP_CONTENT_DIR . '/themes';
552
	}
553
554
	/**
555
	 * Filters the absolute path to the themes directory.
556
	 *
557
	 * @since 1.5.0
558
	 *
559
	 * @param string $theme_root Absolute path to themes directory.
560
	 */
561
	return apply_filters( 'theme_root', $theme_root );
562
}
563
564
/**
565
 * Retrieve URI for themes directory.
566
 *
567
 * Does not have trailing slash.
568
 *
569
 * @since 1.5.0
570
 *
571
 * @global array $wp_theme_directories
572
 *
573
 * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
574
 * 	                                     Default is to leverage the main theme root.
575
 * @param string $theme_root             Optional. The theme root for which calculations will be based, preventing
576
 * 	                                     the need for a get_raw_theme_root() call.
577
 * @return string Themes URI.
578
 */
579
function get_theme_root_uri( $stylesheet_or_template = false, $theme_root = false ) {
580
	global $wp_theme_directories;
581
582
	if ( $stylesheet_or_template && ! $theme_root )
0 ignored issues
show
Bug Best Practice introduced by
The expression $stylesheet_or_template of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $theme_root of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
583
		$theme_root = get_raw_theme_root( $stylesheet_or_template );
584
585
	if ( $stylesheet_or_template && $theme_root ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stylesheet_or_template of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $theme_root of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
586
		if ( in_array( $theme_root, (array) $wp_theme_directories ) ) {
587
			// Absolute path. Make an educated guess. YMMV -- but note the filter below.
588
			if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) )
589
				$theme_root_uri = content_url( str_replace( WP_CONTENT_DIR, '', $theme_root ) );
590
			elseif ( 0 === strpos( $theme_root, ABSPATH ) )
591
				$theme_root_uri = site_url( str_replace( ABSPATH, '', $theme_root ) );
592
			elseif ( 0 === strpos( $theme_root, WP_PLUGIN_DIR ) || 0 === strpos( $theme_root, WPMU_PLUGIN_DIR ) )
593
				$theme_root_uri = plugins_url( basename( $theme_root ), $theme_root );
594
			else
595
				$theme_root_uri = $theme_root;
596
		} else {
597
			$theme_root_uri = content_url( $theme_root );
598
		}
599
	} else {
600
		$theme_root_uri = content_url( 'themes' );
601
	}
602
603
	/**
604
	 * Filters the URI for themes directory.
605
	 *
606
	 * @since 1.5.0
607
	 *
608
	 * @param string $theme_root_uri         The URI for themes directory.
609
	 * @param string $siteurl                WordPress web address which is set in General Options.
610
	 * @param string $stylesheet_or_template Stylesheet or template name of the theme.
611
	 */
612
	return apply_filters( 'theme_root_uri', $theme_root_uri, get_option( 'siteurl' ), $stylesheet_or_template );
613
}
614
615
/**
616
 * Get the raw theme root relative to the content directory with no filters applied.
617
 *
618
 * @since 3.1.0
619
 *
620
 * @global array $wp_theme_directories
621
 *
622
 * @param string $stylesheet_or_template The stylesheet or template name of the theme
623
 * @param bool   $skip_cache             Optional. Whether to skip the cache.
624
 *                                       Defaults to false, meaning the cache is used.
625
 * @return string Theme root
626
 */
627
function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
628
	global $wp_theme_directories;
629
630
	if ( count($wp_theme_directories) <= 1 )
631
		return '/themes';
632
633
	$theme_root = false;
634
635
	// If requesting the root for the current theme, consult options to avoid calling get_theme_roots()
636
	if ( ! $skip_cache ) {
637
		if ( get_option('stylesheet') == $stylesheet_or_template )
638
			$theme_root = get_option('stylesheet_root');
639
		elseif ( get_option('template') == $stylesheet_or_template )
640
			$theme_root = get_option('template_root');
641
	}
642
643
	if ( empty($theme_root) ) {
644
		$theme_roots = get_theme_roots();
645
		if ( !empty($theme_roots[$stylesheet_or_template]) )
646
			$theme_root = $theme_roots[$stylesheet_or_template];
647
	}
648
649
	return $theme_root;
650
}
651
652
/**
653
 * Display localized stylesheet link element.
654
 *
655
 * @since 2.1.0
656
 */
657
function locale_stylesheet() {
658
	$stylesheet = get_locale_stylesheet_uri();
659
	if ( empty($stylesheet) )
660
		return;
661
	echo '<link rel="stylesheet" href="' . $stylesheet . '" type="text/css" media="screen" />';
662
}
663
664
/**
665
 * Switches the theme.
666
 *
667
 * Accepts one argument: $stylesheet of the theme. It also accepts an additional function signature
668
 * of two arguments: $template then $stylesheet. This is for backward compatibility.
669
 *
670
 * @since 2.5.0
671
 *
672
 * @global array                $wp_theme_directories
673
 * @global WP_Customize_Manager $wp_customize
674
 * @global array                $sidebars_widgets
675
 *
676
 * @param string $stylesheet Stylesheet name
677
 */
678
function switch_theme( $stylesheet ) {
679
	global $wp_theme_directories, $wp_customize, $sidebars_widgets;
680
681
	$_sidebars_widgets = null;
682
	if ( 'wp_ajax_customize_save' === current_action() ) {
683
		$_sidebars_widgets = $wp_customize->post_value( $wp_customize->get_setting( 'old_sidebars_widgets_data' ) );
684
	} elseif ( is_array( $sidebars_widgets ) ) {
685
		$_sidebars_widgets = $sidebars_widgets;
686
	}
687
688
	if ( is_array( $_sidebars_widgets ) ) {
689
		set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $_sidebars_widgets ) );
690
	}
691
692
	$nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
693
694
	if ( func_num_args() > 1 ) {
695
		$stylesheet = func_get_arg( 1 );
696
	}
697
698
	$old_theme = wp_get_theme();
699
	$new_theme = wp_get_theme( $stylesheet );
700
	$template  = $new_theme->get_template();
701
702
	update_option( 'template', $template );
703
	update_option( 'stylesheet', $stylesheet );
704
705
	if ( count( $wp_theme_directories ) > 1 ) {
706
		update_option( 'template_root', get_raw_theme_root( $template, true ) );
707
		update_option( 'stylesheet_root', get_raw_theme_root( $stylesheet, true ) );
708
	} else {
709
		delete_option( 'template_root' );
710
		delete_option( 'stylesheet_root' );
711
	}
712
713
	$new_name  = $new_theme->get('Name');
714
715
	update_option( 'current_theme', $new_name );
716
717
	// Migrate from the old mods_{name} option to theme_mods_{slug}.
718
	if ( is_admin() && false === get_option( 'theme_mods_' . $stylesheet ) ) {
719
		$default_theme_mods = (array) get_option( 'mods_' . $new_name );
720
		if ( ! empty( $nav_menu_locations ) && empty( $default_theme_mods['nav_menu_locations'] ) ) {
721
			$default_theme_mods['nav_menu_locations'] = $nav_menu_locations;
722
		}
723
		add_option( "theme_mods_$stylesheet", $default_theme_mods );
724
	} else {
725
		/*
726
		 * Since retrieve_widgets() is called when initializing a theme in the Customizer,
727
		 * we need to remove the theme mods to avoid overwriting changes made via
728
		 * the Customizer when accessing wp-admin/widgets.php.
729
		 */
730
		if ( 'wp_ajax_customize_save' === current_action() ) {
731
			remove_theme_mod( 'sidebars_widgets' );
732
		}
733
734
		if ( ! empty( $nav_menu_locations ) ) {
735
			$nav_mods = get_theme_mod( 'nav_menu_locations' );
736
			if ( empty( $nav_mods ) ) {
737
				set_theme_mod( 'nav_menu_locations', $nav_menu_locations );
738
			}
739
		}
740
	}
741
742
	update_option( 'theme_switched', $old_theme->get_stylesheet() );
743
744
	/**
745
	 * Fires after the theme is switched.
746
	 *
747
	 * @since 1.5.0
748
	 * @since 4.5.0 Introduced the `$old_theme` parameter.
749
	 *
750
	 * @param string   $new_name  Name of the new theme.
751
	 * @param WP_Theme $new_theme WP_Theme instance of the new theme.
752
	 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
753
	 */
754
	do_action( 'switch_theme', $new_name, $new_theme, $old_theme );
755
}
756
757
/**
758
 * Checks that current theme files 'index.php' and 'style.css' exists.
759
 *
760
 * Does not initially check the default theme, which is the fallback and should always exist.
761
 * But if it doesn't exist, it'll fall back to the latest core default theme that does exist.
762
 * Will switch theme to the fallback theme if current theme does not validate.
763
 *
764
 * You can use the {@see 'validate_current_theme'} filter to return false to
765
 * disable this functionality.
766
 *
767
 * @since 1.5.0
768
 * @see WP_DEFAULT_THEME
769
 *
770
 * @return bool
771
 */
772
function validate_current_theme() {
773
	/**
774
	 * Filters whether to validate the current theme.
775
	 *
776
	 * @since 2.7.0
777
	 *
778
	 * @param bool $validate Whether to validate the current theme. Default true.
779
	 */
780
	if ( wp_installing() || ! apply_filters( 'validate_current_theme', true ) )
781
		return true;
782
783
	if ( ! file_exists( get_template_directory() . '/index.php' ) ) {
784
		// Invalid.
785
	} elseif ( ! file_exists( get_template_directory() . '/style.css' ) ) {
786
		// Invalid.
787
	} elseif ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
788
		// Invalid.
789
	} else {
790
		// Valid.
791
		return true;
792
	}
793
794
	$default = wp_get_theme( WP_DEFAULT_THEME );
795
	if ( $default->exists() ) {
796
		switch_theme( WP_DEFAULT_THEME );
797
		return false;
798
	}
799
800
	/**
801
	 * If we're in an invalid state but WP_DEFAULT_THEME doesn't exist,
802
	 * switch to the latest core default theme that's installed.
803
	 * If it turns out that this latest core default theme is our current
804
	 * theme, then there's nothing we can do about that, so we have to bail,
805
	 * rather than going into an infinite loop. (This is why there are
806
	 * checks against WP_DEFAULT_THEME above, also.) We also can't do anything
807
	 * if it turns out there is no default theme installed. (That's `false`.)
808
	 */
809
	$default = WP_Theme::get_core_default_theme();
810
	if ( false === $default || get_stylesheet() == $default->get_stylesheet() ) {
811
		return true;
812
	}
813
814
	switch_theme( $default->get_stylesheet() );
815
	return false;
816
}
817
818
/**
819
 * Retrieve all theme modifications.
820
 *
821
 * @since 3.1.0
822
 *
823
 * @return array|void Theme modifications.
824
 */
825
function get_theme_mods() {
826
	$theme_slug = get_option( 'stylesheet' );
827
	$mods = get_option( "theme_mods_$theme_slug" );
828
	if ( false === $mods ) {
829
		$theme_name = get_option( 'current_theme' );
830
		if ( false === $theme_name )
831
			$theme_name = wp_get_theme()->get('Name');
832
		$mods = get_option( "mods_$theme_name" ); // Deprecated location.
833
		if ( is_admin() && false !== $mods ) {
834
			update_option( "theme_mods_$theme_slug", $mods );
835
			delete_option( "mods_$theme_name" );
836
		}
837
	}
838
	return $mods;
839
}
840
841
/**
842
 * Retrieve theme modification value for the current theme.
843
 *
844
 * If the modification name does not exist, then the $default will be passed
845
 * through {@link https://secure.php.net/sprintf sprintf()} PHP function with the first
846
 * string the template directory URI and the second string the stylesheet
847
 * directory URI.
848
 *
849
 * @since 2.1.0
850
 *
851
 * @param string      $name    Theme modification name.
852
 * @param bool|string $default
853
 * @return string
854
 */
855
function get_theme_mod( $name, $default = false ) {
856
	$mods = get_theme_mods();
857
858
	if ( isset( $mods[$name] ) ) {
859
		/**
860
		 * Filters the theme modification, or 'theme_mod', value.
861
		 *
862
		 * The dynamic portion of the hook name, `$name`, refers to
863
		 * the key name of the modification array. For example,
864
		 * 'header_textcolor', 'header_image', and so on depending
865
		 * on the theme options.
866
		 *
867
		 * @since 2.2.0
868
		 *
869
		 * @param string $current_mod The value of the current theme modification.
870
		 */
871
		return apply_filters( "theme_mod_{$name}", $mods[$name] );
872
	}
873
874
	if ( is_string( $default ) )
875
		$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
876
877
	/** This filter is documented in wp-includes/theme.php */
878
	return apply_filters( "theme_mod_{$name}", $default );
879
}
880
881
/**
882
 * Update theme modification value for the current theme.
883
 *
884
 * @since 2.1.0
885
 *
886
 * @param string $name  Theme modification name.
887
 * @param mixed  $value Theme modification value.
888
 */
889
function set_theme_mod( $name, $value ) {
890
	$mods = get_theme_mods();
891
	$old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;
892
893
	/**
894
	 * Filters the theme mod value on save.
895
	 *
896
	 * The dynamic portion of the hook name, `$name`, refers to the key name of
897
	 * the modification array. For example, 'header_textcolor', 'header_image',
898
	 * and so on depending on the theme options.
899
	 *
900
	 * @since 3.9.0
901
	 *
902
	 * @param string $value     The new value of the theme mod.
903
	 * @param string $old_value The current value of the theme mod.
904
	 */
905
	$mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );
906
907
	$theme = get_option( 'stylesheet' );
908
	update_option( "theme_mods_$theme", $mods );
909
}
910
911
/**
912
 * Remove theme modification name from current theme list.
913
 *
914
 * If removing the name also removes all elements, then the entire option will
915
 * be removed.
916
 *
917
 * @since 2.1.0
918
 *
919
 * @param string $name Theme modification name.
920
 */
921
function remove_theme_mod( $name ) {
922
	$mods = get_theme_mods();
923
924
	if ( ! isset( $mods[ $name ] ) )
925
		return;
926
927
	unset( $mods[ $name ] );
928
929
	if ( empty( $mods ) ) {
930
		remove_theme_mods();
931
		return;
932
	}
933
	$theme = get_option( 'stylesheet' );
934
	update_option( "theme_mods_$theme", $mods );
935
}
936
937
/**
938
 * Remove theme modifications option for current theme.
939
 *
940
 * @since 2.1.0
941
 */
942
function remove_theme_mods() {
943
	delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
944
945
	// Old style.
946
	$theme_name = get_option( 'current_theme' );
947
	if ( false === $theme_name )
948
		$theme_name = wp_get_theme()->get('Name');
949
	delete_option( 'mods_' . $theme_name );
950
}
951
952
/**
953
 * Retrieves the custom header text color in 3- or 6-digit hexadecimal form.
954
 *
955
 * @since 2.1.0
956
 *
957
 * @return string Header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
958
 */
959
function get_header_textcolor() {
960
	return get_theme_mod('header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
961
}
962
963
/**
964
 * Displays the custom header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
965
 *
966
 * @since 2.1.0
967
 */
968
function header_textcolor() {
969
	echo get_header_textcolor();
970
}
971
972
/**
973
 * Whether to display the header text.
974
 *
975
 * @since 3.4.0
976
 *
977
 * @return bool
978
 */
979
function display_header_text() {
980
	if ( ! current_theme_supports( 'custom-header', 'header-text' ) )
981
		return false;
982
983
	$text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
984
	return 'blank' !== $text_color;
985
}
986
987
/**
988
 * Check whether a header image is set or not.
989
 *
990
 * @since 4.2.0
991
 *
992
 * @see get_header_image()
993
 *
994
 * @return bool Whether a header image is set or not.
995
 */
996
function has_header_image() {
997
	return (bool) get_header_image();
998
}
999
1000
/**
1001
 * Retrieve header image for custom header.
1002
 *
1003
 * @since 2.1.0
1004
 *
1005
 * @return string|false
1006
 */
1007
function get_header_image() {
1008
	$url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1009
1010
	if ( 'remove-header' == $url )
1011
		return false;
1012
1013
	if ( is_random_header_image() )
1014
		$url = get_random_header_image();
1015
1016
	return esc_url_raw( set_url_scheme( $url ) );
1017
}
1018
1019
/**
1020
 * Create image tag markup for a custom header image.
1021
 *
1022
 * @since 4.4.0
1023
 *
1024
 * @param array $attr Optional. Additional attributes for the image tag. Can be used
1025
 *                              to override the default attributes. Default empty.
1026
 * @return string HTML image element markup or empty string on failure.
1027
 */
1028
function get_header_image_tag( $attr = array() ) {
1029
	$header = get_custom_header();
1030
	$header->url = get_header_image();
1031
1032
	if ( ! $header->url ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $header->url of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1033
		return '';
1034
	}
1035
1036
	$width = absint( $header->width );
1037
	$height = absint( $header->height );
1038
1039
	$attr = wp_parse_args(
1040
		$attr,
1041
		array(
1042
			'src' => $header->url,
1043
			'width' => $width,
1044
			'height' => $height,
1045
			'alt' => get_bloginfo( 'name' ),
1046
		)
1047
	);
1048
1049
	// Generate 'srcset' and 'sizes' if not already present.
1050
	if ( empty( $attr['srcset'] ) && ! empty( $header->attachment_id ) ) {
1051
		$image_meta = get_post_meta( $header->attachment_id, '_wp_attachment_metadata', true );
1052
		$size_array = array( $width, $height );
1053
1054
		if ( is_array( $image_meta ) ) {
1055
			$srcset = wp_calculate_image_srcset( $size_array, $header->url, $image_meta, $header->attachment_id );
1056
			$sizes = ! empty( $attr['sizes'] ) ? $attr['sizes'] : wp_calculate_image_sizes( $size_array, $header->url, $image_meta, $header->attachment_id );
1057
1058
			if ( $srcset && $sizes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $srcset of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1059
				$attr['srcset'] = $srcset;
1060
				$attr['sizes'] = $sizes;
1061
			}
1062
		}
1063
	}
1064
1065
	$attr = array_map( 'esc_attr', $attr );
1066
	$html = '<img';
1067
1068
	foreach ( $attr as $name => $value ) {
1069
		$html .= ' ' . $name . '="' . $value . '"';
1070
	}
1071
1072
	$html .= ' />';
1073
1074
	/**
1075
	 * Filters the markup of header images.
1076
	 *
1077
	 * @since 4.4.0
1078
	 *
1079
	 * @param string $html   The HTML image tag markup being filtered.
1080
	 * @param object $header The custom header object returned by 'get_custom_header()'.
1081
	 * @param array  $attr   Array of the attributes for the image tag.
1082
	 */
1083
	return apply_filters( 'get_header_image_tag', $html, $header, $attr );
1084
}
1085
1086
/**
1087
 * Display the image markup for a custom header image.
1088
 *
1089
 * @since 4.4.0
1090
 *
1091
 * @param array $attr Optional. Attributes for the image markup. Default empty.
1092
 */
1093
function the_header_image_tag( $attr = array() ) {
1094
	echo get_header_image_tag( $attr );
1095
}
1096
1097
/**
1098
 * Get random header image data from registered images in theme.
1099
 *
1100
 * @since 3.4.0
1101
 *
1102
 * @access private
1103
 *
1104
 * @global array  $_wp_default_headers
1105
 * @staticvar object $_wp_random_header
1106
 *
1107
 * @return object
1108
 */
1109
function _get_random_header_data() {
1110
	static $_wp_random_header = null;
1111
1112
	if ( empty( $_wp_random_header ) ) {
1113
		global $_wp_default_headers;
1114
		$header_image_mod = get_theme_mod( 'header_image', '' );
1115
		$headers = array();
1116
1117
		if ( 'random-uploaded-image' == $header_image_mod )
1118
			$headers = get_uploaded_header_images();
1119
		elseif ( ! empty( $_wp_default_headers ) ) {
1120
			if ( 'random-default-image' == $header_image_mod ) {
1121
				$headers = $_wp_default_headers;
1122
			} else {
1123
				if ( current_theme_supports( 'custom-header', 'random-default' ) )
1124
					$headers = $_wp_default_headers;
1125
			}
1126
		}
1127
1128
		if ( empty( $headers ) )
1129
			return new stdClass;
1130
1131
		$_wp_random_header = (object) $headers[ array_rand( $headers ) ];
1132
1133
		$_wp_random_header->url =  sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1134
		$_wp_random_header->thumbnail_url =  sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1135
	}
1136
	return $_wp_random_header;
1137
}
1138
1139
/**
1140
 * Get random header image url from registered images in theme.
1141
 *
1142
 * @since 3.2.0
1143
 *
1144
 * @return string Path to header image
1145
 */
1146
function get_random_header_image() {
1147
	$random_image = _get_random_header_data();
1148
	if ( empty( $random_image->url ) )
1149
		return '';
1150
	return $random_image->url;
1151
}
1152
1153
/**
1154
 * Check if random header image is in use.
1155
 *
1156
 * Always true if user expressly chooses the option in Appearance > Header.
1157
 * Also true if theme has multiple header images registered, no specific header image
1158
 * is chosen, and theme turns on random headers with add_theme_support().
1159
 *
1160
 * @since 3.2.0
1161
 *
1162
 * @param string $type The random pool to use. any|default|uploaded
1163
 * @return bool
1164
 */
1165
function is_random_header_image( $type = 'any' ) {
1166
	$header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1167
1168
	if ( 'any' == $type ) {
1169
		if ( 'random-default-image' == $header_image_mod || 'random-uploaded-image' == $header_image_mod || ( '' != get_random_header_image() && empty( $header_image_mod ) ) )
1170
			return true;
1171
	} else {
1172
		if ( "random-$type-image" == $header_image_mod )
1173
			return true;
1174
		elseif ( 'default' == $type && empty( $header_image_mod ) && '' != get_random_header_image() )
1175
			return true;
1176
	}
1177
1178
	return false;
1179
}
1180
1181
/**
1182
 * Display header image URL.
1183
 *
1184
 * @since 2.1.0
1185
 */
1186
function header_image() {
1187
	$image = get_header_image();
1188
	if ( $image ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $image of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1189
		echo esc_url( $image );
1190
	}
1191
}
1192
1193
/**
1194
 * Get the header images uploaded for the current theme.
1195
 *
1196
 * @since 3.2.0
1197
 *
1198
 * @return array
1199
 */
1200
function get_uploaded_header_images() {
1201
	$header_images = array();
1202
1203
	// @todo caching
1204
	$headers = get_posts( array( 'post_type' => 'attachment', 'meta_key' => '_wp_attachment_is_custom_header', 'meta_value' => get_option('stylesheet'), 'orderby' => 'none', 'nopaging' => true ) );
1205
1206
	if ( empty( $headers ) )
1207
		return array();
1208
1209
	foreach ( (array) $headers as $header ) {
1210
		$url = esc_url_raw( wp_get_attachment_url( $header->ID ) );
0 ignored issues
show
It seems like wp_get_attachment_url($header->ID) targeting wp_get_attachment_url() can also be of type false; however, esc_url_raw() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
1211
		$header_data = wp_get_attachment_metadata( $header->ID );
1212
		$header_index = $header->ID;
1213
1214
		$header_images[$header_index] = array();
1215
		$header_images[$header_index]['attachment_id'] = $header->ID;
1216
		$header_images[$header_index]['url'] =  $url;
1217
		$header_images[$header_index]['thumbnail_url'] = $url;
1218
		$header_images[$header_index]['alt_text'] = get_post_meta( $header->ID, '_wp_attachment_image_alt', true );
1219
1220
		if ( isset( $header_data['width'] ) )
1221
			$header_images[$header_index]['width'] = $header_data['width'];
1222
		if ( isset( $header_data['height'] ) )
1223
			$header_images[$header_index]['height'] = $header_data['height'];
1224
	}
1225
1226
	return $header_images;
1227
}
1228
1229
/**
1230
 * Get the header image data.
1231
 *
1232
 * @since 3.4.0
1233
 *
1234
 * @global array $_wp_default_headers
1235
 *
1236
 * @return object
1237
 */
1238
function get_custom_header() {
1239
	global $_wp_default_headers;
1240
1241
	if ( is_random_header_image() ) {
1242
		$data = _get_random_header_data();
1243
	} else {
1244
		$data = get_theme_mod( 'header_image_data' );
1245
		if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
1246
			$directory_args = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
1247
			$data = array();
1248
			$data['url'] = $data['thumbnail_url'] = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
1249
			if ( ! empty( $_wp_default_headers ) ) {
1250
				foreach ( (array) $_wp_default_headers as $default_header ) {
1251
					$url = vsprintf( $default_header['url'], $directory_args );
1252
					if ( $data['url'] == $url ) {
1253
						$data = $default_header;
1254
						$data['url'] = $url;
1255
						$data['thumbnail_url'] = vsprintf( $data['thumbnail_url'], $directory_args );
1256
						break;
1257
					}
1258
				}
1259
			}
1260
		}
1261
	}
1262
1263
	$default = array(
1264
		'url'           => '',
1265
		'thumbnail_url' => '',
1266
		'width'         => get_theme_support( 'custom-header', 'width' ),
1267
		'height'        => get_theme_support( 'custom-header', 'height' ),
1268
		'video'         => get_theme_support( 'custom-header', 'video' ),
1269
	);
1270
	return (object) wp_parse_args( $data, $default );
1271
}
1272
1273
/**
1274
 * Register a selection of default headers to be displayed by the custom header admin UI.
1275
 *
1276
 * @since 3.0.0
1277
 *
1278
 * @global array $_wp_default_headers
1279
 *
1280
 * @param array $headers Array of headers keyed by a string id. The ids point to arrays containing 'url', 'thumbnail_url', and 'description' keys.
1281
 */
1282
function register_default_headers( $headers ) {
1283
	global $_wp_default_headers;
1284
1285
	$_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1286
}
1287
1288
/**
1289
 * Unregister default headers.
1290
 *
1291
 * This function must be called after register_default_headers() has already added the
1292
 * header you want to remove.
1293
 *
1294
 * @see register_default_headers()
1295
 * @since 3.0.0
1296
 *
1297
 * @global array $_wp_default_headers
1298
 *
1299
 * @param string|array $header The header string id (key of array) to remove, or an array thereof.
1300
 * @return bool|void A single header returns true on success, false on failure.
1301
 *                   There is currently no return value for multiple headers.
1302
 */
1303
function unregister_default_headers( $header ) {
1304
	global $_wp_default_headers;
1305
	if ( is_array( $header ) ) {
1306
		array_map( 'unregister_default_headers', $header );
1307
	} elseif ( isset( $_wp_default_headers[ $header ] ) ) {
1308
		unset( $_wp_default_headers[ $header ] );
1309
		return true;
1310
	} else {
1311
		return false;
1312
	}
1313
}
1314
1315
/**
1316
 * Check whether a header video is set or not.
1317
 *
1318
 * @since 4.7.0
1319
 *
1320
 * @see get_header_video_url()
1321
 *
1322
 * @return bool Whether a header video is set or not.
1323
 */
1324
function has_header_video() {
1325
	return (bool) get_header_video_url();
1326
}
1327
1328
/**
1329
 * Retrieve header video URL for custom header.
1330
 *
1331
 * Uses a local video if present, or falls back to an external video.
1332
 *
1333
 * @since 4.7.0
1334
 *
1335
 * @return string|false Header video URL or false if there is no video.
1336
 */
1337
function get_header_video_url() {
1338
	$id = absint( get_theme_mod( 'header_video' ) );
1339
	$url = esc_url( get_theme_mod( 'external_header_video' ) );
1340
1341
	if ( $id ) {
1342
		// Get the file URL from the attachment ID.
1343
		$url = wp_get_attachment_url( $id );
1344
	}
1345
1346
	/**
1347
	 * Filters the header video URL.
1348
	 *
1349
	 * @since 4.7.3
1350
	 *
1351
	 * @param string $url Header video URL, if available.
1352
	 */
1353
	$url = apply_filters( 'get_header_video_url', $url );
1354
1355
	if ( ! $id && ! $url ) {
1356
		return false;
1357
	}
1358
1359
	return esc_url_raw( set_url_scheme( $url ) );
1360
}
1361
1362
/**
1363
 * Display header video URL.
1364
 *
1365
 * @since 4.7.0
1366
 */
1367
function the_header_video_url() {
1368
	$video = get_header_video_url();
1369
	if ( $video ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $video of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1370
		echo esc_url( $video );
1371
	}
1372
}
1373
1374
/**
1375
 * Retrieve header video settings.
1376
 *
1377
 * @since 4.7.0
1378
 *
1379
 * @return array
1380
 */
1381
function get_header_video_settings() {
1382
	$header     = get_custom_header();
1383
	$video_url  = get_header_video_url();
1384
	$video_type = wp_check_filetype( $video_url, wp_get_mime_types() );
0 ignored issues
show
It seems like $video_url defined by get_header_video_url() on line 1383 can also be of type false; however, wp_check_filetype() 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...
1385
1386
	$settings = array(
1387
		'mimeType'  => '',
1388
		'posterUrl' => get_header_image(),
1389
		'videoUrl'  => $video_url,
1390
		'width'     => absint( $header->width ),
1391
		'height'    => absint( $header->height ),
1392
		'minWidth'  => 900,
1393
		'minHeight' => 500,
1394
		'l10n'      => array(
1395
			'pause'      => __( 'Pause' ),
1396
			'play'       => __( 'Play' ),
1397
			'pauseSpeak' => __( 'Video is paused.'),
1398
			'playSpeak'  => __( 'Video is playing.'),
1399
		),
1400
	);
1401
1402
	if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {
1403
		$settings['mimeType'] = 'video/x-youtube';
1404
	} elseif ( ! empty( $video_type['type'] ) ) {
1405
		$settings['mimeType'] = $video_type['type'];
1406
	}
1407
1408
	return apply_filters( 'header_video_settings', $settings );
1409
}
1410
1411
/**
1412
 * Check whether a custom header is set or not.
1413
 *
1414
 * @since 4.7.0
1415
 *
1416
 * @return bool True if a custom header is set. False if not.
1417
 */
1418
function has_custom_header() {
1419
	if ( has_header_image() || ( has_header_video() && is_header_video_active() ) ) {
0 ignored issues
show
This if statement, and the following return statement can be replaced with return has_header_image(..._header_video_active();.
Loading history...
1420
		return true;
1421
	}
1422
1423
	return false;
1424
}
1425
1426
/**
1427
 * Checks whether the custom header video is eligible to show on the current page.
1428
 *
1429
 * @since 4.7.0
1430
 *
1431
 * @return bool True if the custom header video should be shown. False if not.
1432
 */
1433
function is_header_video_active() {
1434
	if ( ! get_theme_support( 'custom-header', 'video' ) ) {
1435
		return false;
1436
	}
1437
1438
	$video_active_cb = get_theme_support( 'custom-header', 'video-active-callback' );
1439
1440
	if ( empty( $video_active_cb ) || ! is_callable( $video_active_cb ) ) {
1441
		$show_video = true;
1442
	} else {
1443
		$show_video = call_user_func( $video_active_cb );
1444
	}
1445
1446
	/**
1447
	 * Modify whether the custom header video is eligible to show on the current page.
1448
	 *
1449
	 * @since 4.7.0
1450
	 *
1451
	 * @param bool $show_video Whether the custom header video should be shown. Returns the value
1452
	 *                         of the theme setting for the `custom-header`'s `video-active-callback`.
1453
	 *                         If no callback is set, the default value is that of `is_front_page()`.
1454
	 */
1455
	return apply_filters( 'is_header_video_active', $show_video );
1456
}
1457
1458
/**
1459
 * Retrieve the markup for a custom header.
1460
 *
1461
 * The container div will always be returned in the Customizer preview.
1462
 *
1463
 * @since 4.7.0
1464
 *
1465
 * @return string The markup for a custom header on success.
1466
 */
1467
function get_custom_header_markup() {
1468
	if ( ! has_custom_header() && ! is_customize_preview() ) {
1469
		return '';
1470
	}
1471
1472
	return sprintf(
1473
		'<div id="wp-custom-header" class="wp-custom-header">%s</div>',
1474
		get_header_image_tag()
1475
	);
1476
}
1477
1478
/**
1479
 * Print the markup for a custom header.
1480
 *
1481
 * A container div will always be printed in the Customizer preview.
1482
 *
1483
 * @since 4.7.0
1484
 */
1485
function the_custom_header_markup() {
1486
	$custom_header = get_custom_header_markup();
1487
	if ( empty( $custom_header ) ) {
1488
		return;
1489
	}
1490
1491
	echo $custom_header;
1492
1493
	if ( is_header_video_active() && ( has_header_video() || is_customize_preview() ) ) {
1494
		wp_enqueue_script( 'wp-custom-header' );
1495
		wp_localize_script( 'wp-custom-header', '_wpCustomHeaderSettings', get_header_video_settings() );
1496
	}
1497
}
1498
1499
/**
1500
 * Retrieve background image for custom background.
1501
 *
1502
 * @since 3.0.0
1503
 *
1504
 * @return string
1505
 */
1506
function get_background_image() {
1507
	return get_theme_mod('background_image', get_theme_support( 'custom-background', 'default-image' ) );
1508
}
1509
1510
/**
1511
 * Display background image path.
1512
 *
1513
 * @since 3.0.0
1514
 */
1515
function background_image() {
1516
	echo get_background_image();
1517
}
1518
1519
/**
1520
 * Retrieve value for custom background color.
1521
 *
1522
 * @since 3.0.0
1523
 *
1524
 * @return string
1525
 */
1526
function get_background_color() {
1527
	return get_theme_mod('background_color', get_theme_support( 'custom-background', 'default-color' ) );
1528
}
1529
1530
/**
1531
 * Display background color value.
1532
 *
1533
 * @since 3.0.0
1534
 */
1535
function background_color() {
1536
	echo get_background_color();
1537
}
1538
1539
/**
1540
 * Default custom background callback.
1541
 *
1542
 * @since 3.0.0
1543
 * @access protected
1544
 */
1545
function _custom_background_cb() {
1546
	// $background is the saved custom image, or the default image.
1547
	$background = set_url_scheme( get_background_image() );
1548
1549
	// $color is the saved custom color.
1550
	// A default has to be specified in style.css. It will not be printed here.
1551
	$color = get_background_color();
1552
1553
	if ( $color === get_theme_support( 'custom-background', 'default-color' ) ) {
1554
		$color = false;
1555
	}
1556
1557
	if ( ! $background && ! $color ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $color of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1558
		if ( is_customize_preview() ) {
1559
			echo '<style type="text/css" id="custom-background-css"></style>';
1560
		}
1561
		return;
1562
	}
1563
1564
	$style = $color ? "background-color: #$color;" : '';
1565
1566
	if ( $background ) {
1567
		$image = ' background-image: url("' . esc_url_raw( $background ) . '");';
1568
1569
		// Background Position.
1570
		$position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) );
1571
		$position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) );
1572
1573
		if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) {
1574
			$position_x = 'left';
1575
		}
1576
1577
		if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) {
1578
			$position_y = 'top';
1579
		}
1580
1581
		$position = " background-position: $position_x $position_y;";
1582
1583
		// Background Size.
1584
		$size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) );
1585
1586
		if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) {
1587
			$size = 'auto';
1588
		}
1589
1590
		$size = " background-size: $size;";
1591
1592
		// Background Repeat.
1593
		$repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
1594
1595
		if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) {
1596
			$repeat = 'repeat';
1597
		}
1598
1599
		$repeat = " background-repeat: $repeat;";
1600
1601
		// Background Scroll.
1602
		$attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
1603
1604
		if ( 'fixed' !== $attachment ) {
1605
			$attachment = 'scroll';
1606
		}
1607
1608
		$attachment = " background-attachment: $attachment;";
1609
1610
		$style .= $image . $position . $size . $repeat . $attachment;
1611
	}
1612
?>
1613
<style type="text/css" id="custom-background-css">
1614
body.custom-background { <?php echo trim( $style ); ?> }
1615
</style>
1616
<?php
1617
}
1618
1619
/**
1620
 * Render the Custom CSS style element.
1621
 *
1622
 * @since 4.7.0
1623
 * @access public
1624
 */
1625
function wp_custom_css_cb() {
1626
	$styles = wp_get_custom_css();
1627
	if ( $styles || is_customize_preview() ) : ?>
1628
		<style type="text/css" id="wp-custom-css">
1629
			<?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
1630
		</style>
1631
	<?php endif;
1632
}
1633
1634
/**
1635
 * Fetch the `custom_css` post for a given theme.
1636
 *
1637
 * @since 4.7.0
1638
 * @access public
1639
 *
1640
 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
1641
 * @return WP_Post|null The custom_css post or null if none exists.
0 ignored issues
show
Should the return type not be WP_Post|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1642
 */
1643
function wp_get_custom_css_post( $stylesheet = '' ) {
1644
	if ( empty( $stylesheet ) ) {
1645
		$stylesheet = get_stylesheet();
1646
	}
1647
1648
	$custom_css_query_vars = array(
1649
		'post_type'              => 'custom_css',
1650
		'post_status'            => get_post_stati(),
1651
		'name'                   => sanitize_title( $stylesheet ),
1652
		'posts_per_page'         => 1,
1653
		'no_found_rows'          => true,
1654
		'cache_results'          => true,
1655
		'update_post_meta_cache' => false,
1656
		'update_post_term_cache' => false,
1657
		'lazy_load_term_meta'    => false,
1658
	);
1659
1660
	$post = null;
1661
	if ( get_stylesheet() === $stylesheet ) {
1662
		$post_id = get_theme_mod( 'custom_css_post_id' );
1663
1664
		if ( $post_id > 0 && get_post( $post_id ) ) {
1665
			$post = get_post( $post_id );
1666
		}
1667
1668
		// `-1` indicates no post exists; no query necessary.
1669
		if ( ! $post && -1 !== $post_id ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $post_id (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
1670
			$query = new WP_Query( $custom_css_query_vars );
1671
			$post = $query->post;
1672
			/*
1673
			 * Cache the lookup. See wp_update_custom_css_post().
1674
			 * @todo This should get cleared if a custom_css post is added/removed.
1675
			 */
1676
			set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
1677
		}
1678
	} else {
1679
		$query = new WP_Query( $custom_css_query_vars );
1680
		$post = $query->post;
1681
	}
1682
1683
	return $post;
1684
}
1685
1686
/**
1687
 * Fetch the saved Custom CSS content for rendering.
1688
 *
1689
 * @since 4.7.0
1690
 * @access public
1691
 *
1692
 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
1693
 * @return string The Custom CSS Post content.
1694
 */
1695
function wp_get_custom_css( $stylesheet = '' ) {
1696
	$css = '';
1697
1698
	if ( empty( $stylesheet ) ) {
1699
		$stylesheet = get_stylesheet();
1700
	}
1701
1702
	$post = wp_get_custom_css_post( $stylesheet );
1703
	if ( $post ) {
1704
		$css = $post->post_content;
1705
	}
1706
1707
	/**
1708
	 * Filters the Custom CSS Output into the <head>.
1709
	 *
1710
	 * @since 4.7.0
1711
	 *
1712
	 * @param string $css        CSS pulled in from the Custom CSS CPT.
1713
	 * @param string $stylesheet The theme stylesheet name.
1714
	 */
1715
	$css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
1716
1717
	return $css;
1718
}
1719
1720
/**
1721
 * Update the `custom_css` post for a given theme.
1722
 *
1723
 * Inserts a `custom_css` post when one doesn't yet exist.
1724
 *
1725
 * @since 4.7.0
1726
 * @access public
1727
 *
1728
 * @param string $css CSS, stored in `post_content`.
1729
 * @param array  $args {
1730
 *     Args.
1731
 *
1732
 *     @type string $preprocessed Pre-processed CSS, stored in `post_content_filtered`. Normally empty string. Optional.
1733
 *     @type string $stylesheet   Stylesheet (child theme) to update. Optional, defaults to current theme/stylesheet.
1734
 * }
1735
 * @return WP_Post|WP_Error Post on success, error on failure.
0 ignored issues
show
Should the return type not be WP_Error|integer|WP_Post|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1736
 */
1737
function wp_update_custom_css_post( $css, $args = array() ) {
1738
	$args = wp_parse_args( $args, array(
1739
		'preprocessed' => '',
1740
		'stylesheet' => get_stylesheet(),
1741
	) );
1742
1743
	$data = array(
1744
		'css' => $css,
1745
		'preprocessed' => $args['preprocessed'],
1746
	);
1747
1748
	/**
1749
	 * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args for a `custom_css` post being updated.
1750
	 *
1751
	 * This filter can be used by plugin that offer CSS pre-processors, to store the original
1752
	 * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`.
1753
	 * When used in this way, the `post_content_filtered` should be supplied as the setting value
1754
	 * instead of `post_content` via a the `customize_value_custom_css` filter, for example:
1755
	 *
1756
	 * <code>
1757
	 * add_filter( 'customize_value_custom_css', function( $value, $setting ) {
1758
	 *     $post = wp_get_custom_css_post( $setting->stylesheet );
1759
	 *     if ( $post && ! empty( $post->post_content_filtered ) ) {
1760
	 *         $css = $post->post_content_filtered;
1761
	 *     }
1762
	 *     return $css;
1763
	 * }, 10, 2 );
1764
	 * </code>
1765
	 *
1766
	 * @since 4.7.0
1767
	 * @param array $data {
1768
	 *     Custom CSS data.
1769
	 *
1770
	 *     @type string $css          CSS stored in `post_content`.
1771
	 *     @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`. Normally empty string.
1772
	 * }
1773
	 * @param array $args {
1774
	 *     The args passed into `wp_update_custom_css_post()` merged with defaults.
1775
	 *
1776
	 *     @type string $css          The original CSS passed in to be updated.
1777
	 *     @type string $preprocessed The original preprocessed CSS passed in to be updated.
1778
	 *     @type string $stylesheet   The stylesheet (theme) being updated.
1779
	 * }
1780
	 */
1781
	$data = apply_filters( 'update_custom_css_data', $data, array_merge( $args, compact( 'css' ) ) );
1782
1783
	$post_data = array(
1784
		'post_title' => $args['stylesheet'],
1785
		'post_name' => sanitize_title( $args['stylesheet'] ),
1786
		'post_type' => 'custom_css',
1787
		'post_status' => 'publish',
1788
		'post_content' => $data['css'],
1789
		'post_content_filtered' => $data['preprocessed'],
1790
	);
1791
1792
	// Update post if it already exists, otherwise create a new one.
1793
	$post = wp_get_custom_css_post( $args['stylesheet'] );
1794
	if ( $post ) {
1795
		$post_data['ID'] = $post->ID;
1796
		$r = wp_update_post( wp_slash( $post_data ), true );
0 ignored issues
show
It seems like wp_slash($post_data) targeting wp_slash() can also be of type string; however, wp_update_post() does only seem to accept array|object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1797
	} else {
1798
		$r = wp_insert_post( wp_slash( $post_data ), true );
0 ignored issues
show
It seems like wp_slash($post_data) targeting wp_slash() can also be of type string; however, wp_insert_post() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1799
1800
		if ( ! is_wp_error( $r ) ) {
1801
			if ( get_stylesheet() === $args['stylesheet'] ) {
1802
				set_theme_mod( 'custom_css_post_id', $r );
1803
			}
1804
1805
			// Trigger creation of a revision. This should be removed once #30854 is resolved.
1806
			if ( 0 === count( wp_get_post_revisions( $r ) ) ) {
1807
				wp_save_post_revision( $r );
1808
			}
1809
		}
1810
	}
1811
1812
	if ( is_wp_error( $r ) ) {
1813
		return $r;
1814
	}
1815
	return get_post( $r );
0 ignored issues
show
It seems like $r can also be of type object<WP_Error>; however, get_post() does only seem to accept integer|object<WP_Post>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1816
}
1817
1818
/**
1819
 * Add callback for custom TinyMCE editor stylesheets.
1820
 *
1821
 * The parameter $stylesheet is the name of the stylesheet, relative to
1822
 * the theme root. It also accepts an array of stylesheets.
1823
 * It is optional and defaults to 'editor-style.css'.
1824
 *
1825
 * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
1826
 * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
1827
 * If an array of stylesheets is passed to add_editor_style(),
1828
 * RTL is only added for the first stylesheet.
1829
 *
1830
 * Since version 3.4 the TinyMCE body has .rtl CSS class.
1831
 * It is a better option to use that class and add any RTL styles to the main stylesheet.
1832
 *
1833
 * @since 3.0.0
1834
 *
1835
 * @global array $editor_styles
1836
 *
1837
 * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1838
 * 	                               Defaults to 'editor-style.css'
1839
 */
1840
function add_editor_style( $stylesheet = 'editor-style.css' ) {
1841
	add_theme_support( 'editor-style' );
1842
1843
	if ( ! is_admin() )
1844
		return;
1845
1846
	global $editor_styles;
1847
	$editor_styles = (array) $editor_styles;
1848
	$stylesheet    = (array) $stylesheet;
1849
	if ( is_rtl() ) {
1850
		$rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1851
		$stylesheet[] = $rtl_stylesheet;
1852
	}
1853
1854
	$editor_styles = array_merge( $editor_styles, $stylesheet );
1855
}
1856
1857
/**
1858
 * Removes all visual editor stylesheets.
1859
 *
1860
 * @since 3.1.0
1861
 *
1862
 * @global array $editor_styles
1863
 *
1864
 * @return bool True on success, false if there were no stylesheets to remove.
1865
 */
1866
function remove_editor_styles() {
1867
	if ( ! current_theme_supports( 'editor-style' ) )
1868
		return false;
1869
	_remove_theme_support( 'editor-style' );
1870
	if ( is_admin() )
1871
		$GLOBALS['editor_styles'] = array();
1872
	return true;
1873
}
1874
1875
/**
1876
 * Retrieve any registered editor stylesheets
1877
 *
1878
 * @since 4.0.0
1879
 *
1880
 * @global array $editor_styles Registered editor stylesheets
1881
 *
1882
 * @return array If registered, a list of editor stylesheet URLs.
1883
 */
1884
function get_editor_stylesheets() {
1885
	$stylesheets = array();
1886
	// load editor_style.css if the current theme supports it
1887
	if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
1888
		$editor_styles = $GLOBALS['editor_styles'];
1889
1890
		$editor_styles = array_unique( array_filter( $editor_styles ) );
1891
		$style_uri = get_stylesheet_directory_uri();
1892
		$style_dir = get_stylesheet_directory();
1893
1894
		// Support externally referenced styles (like, say, fonts).
1895
		foreach ( $editor_styles as $key => $file ) {
1896
			if ( preg_match( '~^(https?:)?//~', $file ) ) {
1897
				$stylesheets[] = esc_url_raw( $file );
1898
				unset( $editor_styles[ $key ] );
1899
			}
1900
		}
1901
1902
		// Look in a parent theme first, that way child theme CSS overrides.
1903
		if ( is_child_theme() ) {
1904
			$template_uri = get_template_directory_uri();
1905
			$template_dir = get_template_directory();
1906
1907
			foreach ( $editor_styles as $key => $file ) {
1908
				if ( $file && file_exists( "$template_dir/$file" ) ) {
1909
					$stylesheets[] = "$template_uri/$file";
1910
				}
1911
			}
1912
		}
1913
1914
		foreach ( $editor_styles as $file ) {
1915
			if ( $file && file_exists( "$style_dir/$file" ) ) {
1916
				$stylesheets[] = "$style_uri/$file";
1917
			}
1918
		}
1919
	}
1920
1921
	/**
1922
	 * Filters the array of stylesheets applied to the editor.
1923
	 *
1924
	 * @since 4.3.0
1925
	 *
1926
	 * @param array $stylesheets Array of stylesheets to be applied to the editor.
1927
	 */
1928
	return apply_filters( 'editor_stylesheets', $stylesheets );
1929
}
1930
1931
/**
1932
 * Expand a theme's starter content configuration using core-provided data.
1933
 *
1934
 * @since 4.7.0
1935
 *
1936
 * @return array Array of starter content.
1937
 */
1938
function get_theme_starter_content() {
1939
	$theme_support = get_theme_support( 'starter-content' );
1940
	if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
1941
		$config = $theme_support[0];
1942
	} else {
1943
		$config = array();
1944
	}
1945
1946
	$core_content = array(
1947
		'widgets' => array(
1948
			'text_business_info' => array( 'text', array(
1949
				'title' => _x( 'Find Us', 'Theme starter content' ),
1950
				'text' => join( '', array(
1951
					'<p><strong>' . _x( 'Address', 'Theme starter content' ) . '</strong><br />',
1952
					_x( '123 Main Street', 'Theme starter content' ) . '<br />' . _x( 'New York, NY 10001', 'Theme starter content' ) . '</p>',
1953
					'<p><strong>' . _x( 'Hours', 'Theme starter content' ) . '</strong><br />',
1954
					_x( 'Monday&mdash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . '<br />' . _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ) . '</p>'
1955
				) ),
1956
			) ),
1957
			'text_about' => array( 'text', array(
1958
				'title' => _x( 'About This Site', 'Theme starter content' ),
1959
				'text' => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
1960
			) ),
1961
			'archives' => array( 'archives', array(
1962
				'title' => _x( 'Archives', 'Theme starter content' ),
1963
			) ),
1964
			'calendar' => array( 'calendar', array(
1965
				'title' => _x( 'Calendar', 'Theme starter content' ),
1966
			) ),
1967
			'categories' => array( 'categories', array(
1968
				'title' => _x( 'Categories', 'Theme starter content' ),
1969
			) ),
1970
			'meta' => array( 'meta', array(
1971
				'title' => _x( 'Meta', 'Theme starter content' ),
1972
			) ),
1973
			'recent-comments' => array( 'recent-comments', array(
1974
				'title' => _x( 'Recent Comments', 'Theme starter content' ),
1975
			) ),
1976
			'recent-posts' => array( 'recent-posts', array(
1977
				'title' => _x( 'Recent Posts', 'Theme starter content' ),
1978
			) ),
1979
			'search' => array( 'search', array(
1980
				'title' => _x( 'Search', 'Theme starter content' ),
1981
			) ),
1982
		),
1983
		'nav_menus' => array(
1984
			'link_home' => array(
1985
				'type' => 'custom',
1986
				'title' => _x( 'Home', 'Theme starter content' ),
1987
				'url' => home_url( '/' ),
1988
			),
1989
			'page_home' => array( // Deprecated in favor of link_home.
1990
				'type' => 'post_type',
1991
				'object' => 'page',
1992
				'object_id' => '{{home}}',
1993
			),
1994
			'page_about' => array(
1995
				'type' => 'post_type',
1996
				'object' => 'page',
1997
				'object_id' => '{{about}}',
1998
			),
1999
			'page_blog' => array(
2000
				'type' => 'post_type',
2001
				'object' => 'page',
2002
				'object_id' => '{{blog}}',
2003
			),
2004
			'page_news' => array(
2005
				'type' => 'post_type',
2006
				'object' => 'page',
2007
				'object_id' => '{{news}}',
2008
			),
2009
			'page_contact' => array(
2010
				'type' => 'post_type',
2011
				'object' => 'page',
2012
				'object_id' => '{{contact}}',
2013
			),
2014
2015
			'link_email' => array(
2016
				'title' => _x( 'Email', 'Theme starter content' ),
2017
				'url' => 'mailto:[email protected]',
2018
			),
2019
			'link_facebook' => array(
2020
				'title' => _x( 'Facebook', 'Theme starter content' ),
2021
				'url' => 'https://www.facebook.com/wordpress',
2022
			),
2023
			'link_foursquare' => array(
2024
				'title' => _x( 'Foursquare', 'Theme starter content' ),
2025
				'url' => 'https://foursquare.com/',
2026
			),
2027
			'link_github' => array(
2028
				'title' => _x( 'GitHub', 'Theme starter content' ),
2029
				'url' => 'https://github.com/wordpress/',
2030
			),
2031
			'link_instagram' => array(
2032
				'title' => _x( 'Instagram', 'Theme starter content' ),
2033
				'url' => 'https://www.instagram.com/explore/tags/wordcamp/',
2034
			),
2035
			'link_linkedin' => array(
2036
				'title' => _x( 'LinkedIn', 'Theme starter content' ),
2037
				'url' => 'https://www.linkedin.com/company/1089783',
2038
			),
2039
			'link_pinterest' => array(
2040
				'title' => _x( 'Pinterest', 'Theme starter content' ),
2041
				'url' => 'https://www.pinterest.com/',
2042
			),
2043
			'link_twitter' => array(
2044
				'title' => _x( 'Twitter', 'Theme starter content' ),
2045
				'url' => 'https://twitter.com/wordpress',
2046
			),
2047
			'link_yelp' => array(
2048
				'title' => _x( 'Yelp', 'Theme starter content' ),
2049
				'url' => 'https://www.yelp.com',
2050
			),
2051
			'link_youtube' => array(
2052
				'title' => _x( 'YouTube', 'Theme starter content' ),
2053
				'url' => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
2054
			),
2055
		),
2056
		'posts' => array(
2057
			'home' => array(
2058
				'post_type' => 'page',
2059
				'post_title' => _x( 'Home', 'Theme starter content' ),
2060
				'post_content' => _x( 'Welcome to your site! This is your homepage, which is what most visitors will see when they come to your site for the first time.', 'Theme starter content' ),
2061
			),
2062
			'about' => array(
2063
				'post_type' => 'page',
2064
				'post_title' => _x( 'About', 'Theme starter content' ),
2065
				'post_content' => _x( 'You might be an artist who would like to introduce yourself and your work here or maybe you&rsquo;re a business with a mission to describe.', 'Theme starter content' ),
2066
			),
2067
			'contact' => array(
2068
				'post_type' => 'page',
2069
				'post_title' => _x( 'Contact', 'Theme starter content' ),
2070
				'post_content' => _x( 'This is a page with some basic contact information, such as an address and phone number. You might also try a plugin to add a contact form.', 'Theme starter content' ),
2071
			),
2072
			'blog' => array(
2073
				'post_type' => 'page',
2074
				'post_title' => _x( 'Blog', 'Theme starter content' ),
2075
			),
2076
			'news' => array(
2077
				'post_type' => 'page',
2078
				'post_title' => _x( 'News', 'Theme starter content' ),
2079
			),
2080
2081
			'homepage-section' => array(
2082
				'post_type' => 'page',
2083
				'post_title' => _x( 'A homepage section', 'Theme starter content' ),
2084
				'post_content' => _x( 'This is an example of a homepage section. Homepage sections can be any page other than the homepage itself, including the page that shows your latest blog posts.', 'Theme starter content' ),
2085
			),
2086
		),
2087
	);
2088
2089
	$content = array();
2090
2091
	foreach ( $config as $type => $args ) {
2092
		switch( $type ) {
2093
			// Use options and theme_mods as-is.
2094
			case 'options' :
2095
			case 'theme_mods' :
2096
				$content[ $type ] = $config[ $type ];
2097
				break;
2098
2099
			// Widgets are grouped into sidebars.
2100
			case 'widgets' :
2101
				foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2102
					foreach ( $widgets as $id => $widget ) {
2103
						if ( is_array( $widget ) ) {
2104
2105
							// Item extends core content.
2106
							if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2107
								$widget = array(
2108
									$core_content[ $type ][ $id ][0],
2109
									array_merge( $core_content[ $type ][ $id ][1], $widget ),
2110
								);
2111
							}
2112
2113
							$content[ $type ][ $sidebar_id ][] = $widget;
2114 View Code Duplication
						} elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
2115
							$content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
2116
						}
2117
					}
2118
				}
2119
				break;
2120
2121
			// And nav menu items are grouped into nav menus.
2122
			case 'nav_menus' :
2123
				foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2124
2125
					// Ensure nav menus get a name.
2126
					if ( empty( $nav_menu['name'] ) ) {
2127
						$nav_menu['name'] = $nav_menu_location;
2128
					}
2129
2130
					$content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2131
2132
					foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2133
						if ( is_array( $nav_menu_item ) ) {
2134
2135
							// Item extends core content.
2136 View Code Duplication
							if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2137
								$nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
2138
							}
2139
2140
							$content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
2141 View Code Duplication
						} elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
2142
							$content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
2143
						}
2144
					}
2145
				}
2146
				break;
2147
2148
			// Attachments are posts but have special treatment.
2149
			case 'attachments' :
2150
				foreach ( $config[ $type ] as $id => $item ) {
2151
					if ( ! empty( $item['file'] ) ) {
2152
						$content[ $type ][ $id ] = $item;
2153
					}
2154
				}
2155
				break;
2156
2157
			// All that's left now are posts (besides attachments). Not a default case for the sake of clarity and future work.
2158
			case 'posts' :
2159
				foreach ( $config[ $type ] as $id => $item ) {
2160
					if ( is_array( $item ) ) {
2161
2162
						// Item extends core content.
2163 View Code Duplication
						if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2164
							$item = array_merge( $core_content[ $type ][ $id ], $item );
2165
						}
2166
2167
						// Enforce a subset of fields.
2168
						$content[ $type ][ $id ] = wp_array_slice_assoc(
2169
							$item,
2170
							array(
2171
								'post_type',
2172
								'post_title',
2173
								'post_excerpt',
2174
								'post_name',
2175
								'post_content',
2176
								'menu_order',
2177
								'comment_status',
2178
								'thumbnail',
2179
								'template',
2180
							)
2181
						);
2182
					} elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2183
						$content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2184
					}
2185
				}
2186
				break;
2187
		}
2188
	}
2189
2190
	/**
2191
	 * Filters the expanded array of starter content.
2192
	 *
2193
	 * @since 4.7.0
2194
	 *
2195
	 * @param array $content Array of starter content.
2196
	 * @param array $config  Array of theme-specific starter content configuration.
2197
	 */
2198
	return apply_filters( 'get_theme_starter_content', $content, $config );
2199
}
2200
2201
/**
2202
 * Registers theme support for a given feature.
2203
 *
2204
 * Must be called in the theme's functions.php file to work.
2205
 * If attached to a hook, it must be {@see 'after_setup_theme'}.
2206
 * The {@see 'init'} hook may be too late for some features.
2207
 *
2208
 * @since 2.9.0
2209
 * @since 3.6.0 The `html5` feature was added
2210
 * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'
2211
 * @since 4.1.0 The `title-tag` feature was added
2212
 * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added
2213
 * @since 4.7.0 The `starter-content` feature was added
2214
 *
2215
 * @global array $_wp_theme_features
2216
 *
2217
 * @param string $feature  The feature being added. Likely core values include 'post-formats',
2218
 *                         'post-thumbnails', 'html5', 'custom-logo', 'custom-header-uploads',
2219
 *                         'custom-header', 'custom-background', 'title-tag', 'starter-content', etc.
2220
 * @param mixed  $args,... Optional extra arguments to pass along with certain features.
0 ignored issues
show
There is no parameter named $args,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2221
 * @return void|bool False on failure, void otherwise.
2222
 */
2223
function add_theme_support( $feature ) {
2224
	global $_wp_theme_features;
2225
2226
	if ( func_num_args() == 1 )
2227
		$args = true;
2228
	else
2229
		$args = array_slice( func_get_args(), 1 );
2230
2231
	switch ( $feature ) {
2232
		case 'post-thumbnails':
2233
			// All post types are already supported.
2234
			if ( true === get_theme_support( 'post-thumbnails' ) ) {
2235
				return;
2236
			}
2237
2238
			/*
2239
			 * Merge post types with any that already declared their support
2240
			 * for post thumbnails.
2241
			 */
2242
			if ( is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
2243
				$args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
2244
			}
2245
2246
			break;
2247
2248
		case 'post-formats' :
2249
			if ( is_array( $args[0] ) ) {
2250
				$post_formats = get_post_format_slugs();
2251
				unset( $post_formats['standard'] );
2252
2253
				$args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
2254
			}
2255
			break;
2256
2257
		case 'html5' :
2258
			// You can't just pass 'html5', you need to pass an array of types.
2259
			if ( empty( $args[0] ) ) {
2260
				// Build an array of types for back-compat.
2261
				$args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
2262
			} elseif ( ! is_array( $args[0] ) ) {
2263
				_doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
2264
				return false;
2265
			}
2266
2267
			// Calling 'html5' again merges, rather than overwrites.
2268
			if ( isset( $_wp_theme_features['html5'] ) )
2269
				$args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
2270
			break;
2271
2272
		case 'custom-logo':
2273
			if ( ! is_array( $args ) ) {
2274
				$args = array( 0 => array() );
2275
			}
2276
			$defaults = array(
2277
				'width'       => null,
2278
				'height'      => null,
2279
				'flex-width'  => false,
2280
				'flex-height' => false,
2281
				'header-text' => '',
2282
			);
2283
			$args[0] = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
2284
2285
			// Allow full flexibility if no size is specified.
2286
			if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
2287
				$args[0]['flex-width']  = true;
2288
				$args[0]['flex-height'] = true;
2289
			}
2290
			break;
2291
2292
		case 'custom-header-uploads' :
2293
			return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2294
2295
		case 'custom-header' :
2296
			if ( ! is_array( $args ) )
2297
				$args = array( 0 => array() );
2298
2299
			$defaults = array(
2300
				'default-image' => '',
2301
				'random-default' => false,
2302
				'width' => 0,
2303
				'height' => 0,
2304
				'flex-height' => false,
2305
				'flex-width' => false,
2306
				'default-text-color' => '',
2307
				'header-text' => true,
2308
				'uploads' => true,
2309
				'wp-head-callback' => '',
2310
				'admin-head-callback' => '',
2311
				'admin-preview-callback' => '',
2312
				'video' => false,
2313
				'video-active-callback' => 'is_front_page',
2314
			);
2315
2316
			$jit = isset( $args[0]['__jit'] );
2317
			unset( $args[0]['__jit'] );
2318
2319
			// Merge in data from previous add_theme_support() calls.
2320
			// The first value registered wins. (A child theme is set up first.)
2321
			if ( isset( $_wp_theme_features['custom-header'] ) )
2322
				$args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
2323
2324
			// Load in the defaults at the end, as we need to insure first one wins.
2325
			// This will cause all constants to be defined, as each arg will then be set to the default.
2326
			if ( $jit )
2327
				$args[0] = wp_parse_args( $args[0], $defaults );
2328
2329
			// If a constant was defined, use that value. Otherwise, define the constant to ensure
2330
			// the constant is always accurate (and is not defined later,  overriding our value).
2331
			// As stated above, the first value wins.
2332
			// Once we get to wp_loaded (just-in-time), define any constants we haven't already.
2333
			// Constants are lame. Don't reference them. This is just for backward compatibility.
2334
2335 View Code Duplication
			if ( defined( 'NO_HEADER_TEXT' ) )
2336
				$args[0]['header-text'] = ! NO_HEADER_TEXT;
2337
			elseif ( isset( $args[0]['header-text'] ) )
2338
				define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
2339
2340 View Code Duplication
			if ( defined( 'HEADER_IMAGE_WIDTH' ) )
2341
				$args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
2342
			elseif ( isset( $args[0]['width'] ) )
2343
				define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
2344
2345 View Code Duplication
			if ( defined( 'HEADER_IMAGE_HEIGHT' ) )
2346
				$args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
2347
			elseif ( isset( $args[0]['height'] ) )
2348
				define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
2349
2350 View Code Duplication
			if ( defined( 'HEADER_TEXTCOLOR' ) )
2351
				$args[0]['default-text-color'] = HEADER_TEXTCOLOR;
2352
			elseif ( isset( $args[0]['default-text-color'] ) )
2353
				define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
2354
2355 View Code Duplication
			if ( defined( 'HEADER_IMAGE' ) )
2356
				$args[0]['default-image'] = HEADER_IMAGE;
2357
			elseif ( isset( $args[0]['default-image'] ) )
2358
				define( 'HEADER_IMAGE', $args[0]['default-image'] );
2359
2360
			if ( $jit && ! empty( $args[0]['default-image'] ) )
2361
				$args[0]['random-default'] = false;
2362
2363
			// If headers are supported, and we still don't have a defined width or height,
2364
			// we have implicit flex sizes.
2365
			if ( $jit ) {
2366
				if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) )
2367
					$args[0]['flex-width'] = true;
2368
				if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) )
2369
					$args[0]['flex-height'] = true;
2370
			}
2371
2372
			break;
2373
2374
		case 'custom-background' :
2375
			if ( ! is_array( $args ) )
2376
				$args = array( 0 => array() );
2377
2378
			$defaults = array(
2379
				'default-image'          => '',
2380
				'default-preset'         => 'default',
2381
				'default-position-x'     => 'left',
2382
				'default-position-y'     => 'top',
2383
				'default-size'           => 'auto',
2384
				'default-repeat'         => 'repeat',
2385
				'default-attachment'     => 'scroll',
2386
				'default-color'          => '',
2387
				'wp-head-callback'       => '_custom_background_cb',
2388
				'admin-head-callback'    => '',
2389
				'admin-preview-callback' => '',
2390
			);
2391
2392
			$jit = isset( $args[0]['__jit'] );
2393
			unset( $args[0]['__jit'] );
2394
2395
			// Merge in data from previous add_theme_support() calls. The first value registered wins.
2396
			if ( isset( $_wp_theme_features['custom-background'] ) )
2397
				$args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
2398
2399
			if ( $jit )
2400
				$args[0] = wp_parse_args( $args[0], $defaults );
2401
2402 View Code Duplication
			if ( defined( 'BACKGROUND_COLOR' ) )
2403
				$args[0]['default-color'] = BACKGROUND_COLOR;
2404
			elseif ( isset( $args[0]['default-color'] ) || $jit )
2405
				define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
2406
2407 View Code Duplication
			if ( defined( 'BACKGROUND_IMAGE' ) )
2408
				$args[0]['default-image'] = BACKGROUND_IMAGE;
2409
			elseif ( isset( $args[0]['default-image'] ) || $jit )
2410
				define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
2411
2412
			break;
2413
2414
		// Ensure that 'title-tag' is accessible in the admin.
2415
		case 'title-tag' :
2416
			// Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
2417
			if ( did_action( 'wp_loaded' ) ) {
2418
				/* translators: 1: Theme support 2: hook name */
2419
				_doing_it_wrong( "add_theme_support( 'title-tag' )", sprintf( __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
2420
					'<code>title-tag</code>', '<code>wp_loaded</code>' ), '4.1.0' );
2421
2422
				return false;
2423
			}
2424
	}
2425
2426
	$_wp_theme_features[ $feature ] = $args;
2427
}
2428
2429
/**
2430
 * Registers the internal custom header and background routines.
2431
 *
2432
 * @since 3.4.0
2433
 * @access private
2434
 *
2435
 * @global Custom_Image_Header $custom_image_header
2436
 * @global Custom_Background   $custom_background
2437
 */
2438
function _custom_header_background_just_in_time() {
2439
	global $custom_image_header, $custom_background;
2440
2441
	if ( current_theme_supports( 'custom-header' ) ) {
2442
		// In case any constants were defined after an add_custom_image_header() call, re-run.
2443
		add_theme_support( 'custom-header', array( '__jit' => true ) );
2444
2445
		$args = get_theme_support( 'custom-header' );
2446
		if ( $args[0]['wp-head-callback'] )
2447
			add_action( 'wp_head', $args[0]['wp-head-callback'] );
2448
2449 View Code Duplication
		if ( is_admin() ) {
2450
			require_once( ABSPATH . 'wp-admin/custom-header.php' );
2451
			$custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2452
		}
2453
	}
2454
2455
	if ( current_theme_supports( 'custom-background' ) ) {
2456
		// In case any constants were defined after an add_custom_background() call, re-run.
2457
		add_theme_support( 'custom-background', array( '__jit' => true ) );
2458
2459
		$args = get_theme_support( 'custom-background' );
2460
		add_action( 'wp_head', $args[0]['wp-head-callback'] );
2461
2462 View Code Duplication
		if ( is_admin() ) {
2463
			require_once( ABSPATH . 'wp-admin/custom-background.php' );
2464
			$custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2465
		}
2466
	}
2467
}
2468
2469
/**
2470
 * Adds CSS to hide header text for custom logo, based on Customizer setting.
2471
 *
2472
 * @since 4.5.0
2473
 * @access private
2474
 */
2475
function _custom_logo_header_styles() {
2476
	if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
2477
		$classes = (array) get_theme_support( 'custom-logo', 'header-text' );
2478
		$classes = array_map( 'sanitize_html_class', $classes );
2479
		$classes = '.' . implode( ', .', $classes );
2480
2481
		?>
2482
		<!-- Custom Logo: hide header text -->
2483
		<style id="custom-logo-css" type="text/css">
2484
			<?php echo $classes; ?> {
2485
				position: absolute;
2486
				clip: rect(1px, 1px, 1px, 1px);
2487
			}
2488
		</style>
2489
	<?php
2490
	}
2491
}
2492
2493
/**
2494
 * Gets the theme support arguments passed when registering that support
2495
 *
2496
 * @since 3.1.0
2497
 *
2498
 * @global array $_wp_theme_features
2499
 *
2500
 * @param string $feature the feature to check
2501
 * @return mixed The array of extra arguments or the value for the registered feature.
2502
 */
2503
function get_theme_support( $feature ) {
2504
	global $_wp_theme_features;
2505
	if ( ! isset( $_wp_theme_features[ $feature ] ) )
2506
		return false;
2507
2508
	if ( func_num_args() <= 1 )
2509
		return $_wp_theme_features[ $feature ];
2510
2511
	$args = array_slice( func_get_args(), 1 );
2512
	switch ( $feature ) {
2513
		case 'custom-logo' :
2514
		case 'custom-header' :
2515 View Code Duplication
		case 'custom-background' :
2516
			if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
2517
				return $_wp_theme_features[ $feature ][0][ $args[0] ];
2518
			return false;
2519
2520
		default :
2521
			return $_wp_theme_features[ $feature ];
2522
	}
2523
}
2524
2525
/**
2526
 * Allows a theme to de-register its support of a certain feature
2527
 *
2528
 * Should be called in the theme's functions.php file. Generally would
2529
 * be used for child themes to override support from the parent theme.
2530
 *
2531
 * @since 3.0.0
2532
 * @see add_theme_support()
2533
 * @param string $feature the feature being added
2534
 * @return bool|void Whether feature was removed.
2535
 */
2536
function remove_theme_support( $feature ) {
2537
	// Blacklist: for internal registrations not used directly by themes.
2538
	if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) )
2539
		return false;
2540
2541
	return _remove_theme_support( $feature );
2542
}
2543
2544
/**
2545
 * Do not use. Removes theme support internally, ignorant of the blacklist.
2546
 *
2547
 * @access private
2548
 * @since 3.1.0
2549
 *
2550
 * @global array               $_wp_theme_features
2551
 * @global Custom_Image_Header $custom_image_header
2552
 * @global Custom_Background   $custom_background
2553
 *
2554
 * @param string $feature
2555
 */
2556
function _remove_theme_support( $feature ) {
2557
	global $_wp_theme_features;
2558
2559
	switch ( $feature ) {
2560
		case 'custom-header-uploads' :
2561
			if ( ! isset( $_wp_theme_features['custom-header'] ) )
2562
				return false;
2563
			add_theme_support( 'custom-header', array( 'uploads' => false ) );
2564
			return; // Do not continue - custom-header-uploads no longer exists.
2565
	}
2566
2567
	if ( ! isset( $_wp_theme_features[ $feature ] ) )
2568
		return false;
2569
2570
	switch ( $feature ) {
2571
		case 'custom-header' :
2572
			if ( ! did_action( 'wp_loaded' ) )
2573
				break;
2574
			$support = get_theme_support( 'custom-header' );
2575
			if ( isset( $support[0]['wp-head-callback'] ) ) {
2576
				remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2577
			}
2578
			if ( isset( $GLOBALS['custom_image_header'] ) ) {
2579
				remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
2580
				unset( $GLOBALS['custom_image_header'] );
2581
			}
2582
			break;
2583
2584
		case 'custom-background' :
2585
			if ( ! did_action( 'wp_loaded' ) )
2586
				break;
2587
			$support = get_theme_support( 'custom-background' );
2588
			remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2589
			remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
2590
			unset( $GLOBALS['custom_background'] );
2591
			break;
2592
	}
2593
2594
	unset( $_wp_theme_features[ $feature ] );
2595
	return true;
2596
}
2597
2598
/**
2599
 * Checks a theme's support for a given feature
2600
 *
2601
 * @since 2.9.0
2602
 *
2603
 * @global array $_wp_theme_features
2604
 *
2605
 * @param string $feature the feature being checked
2606
 * @return bool
2607
 */
2608
function current_theme_supports( $feature ) {
2609
	global $_wp_theme_features;
2610
2611
	if ( 'custom-header-uploads' == $feature )
2612
		return current_theme_supports( 'custom-header', 'uploads' );
2613
2614
	if ( !isset( $_wp_theme_features[$feature] ) )
2615
		return false;
2616
2617
	// If no args passed then no extra checks need be performed
2618
	if ( func_num_args() <= 1 )
2619
		return true;
2620
2621
	$args = array_slice( func_get_args(), 1 );
2622
2623
	switch ( $feature ) {
2624
		case 'post-thumbnails':
2625
			// post-thumbnails can be registered for only certain content/post types by passing
2626
			// an array of types to add_theme_support(). If no array was passed, then
2627
			// any type is accepted
2628
			if ( true === $_wp_theme_features[$feature] )  // Registered for all types
2629
				return true;
2630
			$content_type = $args[0];
2631
			return in_array( $content_type, $_wp_theme_features[$feature][0] );
2632
2633
		case 'html5':
2634
		case 'post-formats':
2635
			// specific post formats can be registered by passing an array of types to
2636
			// add_theme_support()
2637
2638
			// Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
2639
2640
			$type = $args[0];
2641
			return in_array( $type, $_wp_theme_features[$feature][0] );
2642
2643
		case 'custom-logo':
2644
		case 'custom-header':
2645 View Code Duplication
		case 'custom-background':
2646
			// Specific capabilities can be registered by passing an array to add_theme_support().
2647
			return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
2648
	}
2649
2650
	/**
2651
	 * Filters whether the current theme supports a specific feature.
2652
	 *
2653
	 * The dynamic portion of the hook name, `$feature`, refers to the specific theme
2654
	 * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
2655
	 * 'custom-header', 'menus', 'automatic-feed-links', 'html5',
2656
	 * 'starter-content', and 'customize-selective-refresh-widgets'.
2657
	 *
2658
	 * @since 3.4.0
2659
	 *
2660
	 * @param bool   true     Whether the current theme supports the given feature. Default true.
2661
	 * @param array  $args    Array of arguments for the feature.
2662
	 * @param string $feature The theme feature.
2663
	 */
2664
	return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[$feature] );
2665
}
2666
2667
/**
2668
 * Checks a theme's support for a given feature before loading the functions which implement it.
2669
 *
2670
 * @since 2.9.0
2671
 *
2672
 * @param string $feature The feature being checked.
2673
 * @param string $include Path to the file.
2674
 * @return bool True if the current theme supports the supplied feature, false otherwise.
2675
 */
2676
function require_if_theme_supports( $feature, $include ) {
2677
	if ( current_theme_supports( $feature ) ) {
2678
		require ( $include );
2679
		return true;
2680
	}
2681
	return false;
2682
}
2683
2684
/**
2685
 * Checks an attachment being deleted to see if it's a header or background image.
2686
 *
2687
 * If true it removes the theme modification which would be pointing at the deleted
2688
 * attachment.
2689
 *
2690
 * @access private
2691
 * @since 3.0.0
2692
 * @since 4.3.0 Also removes `header_image_data`.
2693
 * @since 4.5.0 Also removes custom logo theme mods.
2694
 *
2695
 * @param int $id The attachment id.
2696
 */
2697
function _delete_attachment_theme_mod( $id ) {
2698
	$attachment_image = wp_get_attachment_url( $id );
2699
	$header_image     = get_header_image();
2700
	$background_image = get_background_image();
2701
	$custom_logo_id   = get_theme_mod( 'custom_logo' );
2702
2703
	if ( $custom_logo_id && $custom_logo_id == $id ) {
2704
		remove_theme_mod( 'custom_logo' );
2705
		remove_theme_mod( 'header_text' );
2706
	}
2707
2708
	if ( $header_image && $header_image == $attachment_image ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $header_image of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2709
		remove_theme_mod( 'header_image' );
2710
		remove_theme_mod( 'header_image_data' );
2711
	}
2712
2713
	if ( $background_image && $background_image == $attachment_image ) {
2714
		remove_theme_mod( 'background_image' );
2715
	}
2716
}
2717
2718
/**
2719
 * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
2720
 *
2721
 * See {@see 'after_switch_theme'}.
2722
 *
2723
 * @since 3.3.0
2724
 */
2725
function check_theme_switched() {
2726
	if ( $stylesheet = get_option( 'theme_switched' ) ) {
2727
		$old_theme = wp_get_theme( $stylesheet );
2728
2729
		// Prevent retrieve_widgets() from running since Customizer already called it up front
2730
		if ( get_option( 'theme_switched_via_customizer' ) ) {
2731
			remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
2732
			update_option( 'theme_switched_via_customizer', false );
2733
		}
2734
2735
		if ( $old_theme->exists() ) {
2736
			/**
2737
			 * Fires on the first WP load after a theme switch if the old theme still exists.
2738
			 *
2739
			 * This action fires multiple times and the parameters differs
2740
			 * according to the context, if the old theme exists or not.
2741
			 * If the old theme is missing, the parameter will be the slug
2742
			 * of the old theme.
2743
			 *
2744
			 * @since 3.3.0
2745
			 *
2746
			 * @param string   $old_name  Old theme name.
2747
			 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
2748
			 */
2749
			do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
2750
		} else {
2751
			/** This action is documented in wp-includes/theme.php */
2752
			do_action( 'after_switch_theme', $stylesheet );
2753
		}
2754
		flush_rewrite_rules();
2755
2756
		update_option( 'theme_switched', false );
2757
	}
2758
}
2759
2760
/**
2761
 * Includes and instantiates the WP_Customize_Manager class.
2762
 *
2763
 * Loads the Customizer at plugins_loaded when accessing the customize.php admin
2764
 * page or when any request includes a wp_customize=on param or a customize_changeset
2765
 * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
2766
 * WordPress is loading, especially in the Customizer preview
2767
 * or when making Customizer Ajax requests for widgets or menus.
2768
 *
2769
 * @since 3.4.0
2770
 *
2771
 * @global WP_Customize_Manager $wp_customize
2772
 */
2773
function _wp_customize_include() {
2774
2775
	$is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
2776
	$should_include = (
2777
		$is_customize_admin_page
2778
		||
2779
		( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
2780
		||
2781
		( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
2782
	);
2783
2784
	if ( ! $should_include ) {
2785
		return;
2786
	}
2787
2788
	/*
2789
	 * Note that wp_unslash() is not being used on the input vars because it is
2790
	 * called before wp_magic_quotes() gets called. Besides this fact, none of
2791
	 * the values should contain any characters needing slashes anyway.
2792
	 */
2793
	$keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel' );
2794
	$input_vars = array_merge(
2795
		wp_array_slice_assoc( $_GET, $keys ),
2796
		wp_array_slice_assoc( $_POST, $keys )
2797
	);
2798
2799
	$theme = null;
2800
	$changeset_uuid = null;
2801
	$messenger_channel = null;
2802
2803
	if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
2804
		$changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
2805
	} elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
2806
		$changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
2807
	}
2808
2809
	// Note that theme will be sanitized via WP_Theme.
2810
	if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
2811
		$theme = $input_vars['theme'];
2812
	} elseif ( isset( $input_vars['customize_theme'] ) ) {
2813
		$theme = $input_vars['customize_theme'];
2814
	}
2815
	if ( isset( $input_vars['customize_messenger_channel'] ) ) {
2816
		$messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
2817
	}
2818
2819
	require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2820
	$GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel' ) );
2821
}
2822
2823
/**
2824
 * Publish a snapshot's changes.
2825
 *
2826
 * @param string  $new_status     New post status.
2827
 * @param string  $old_status     Old post status.
2828
 * @param WP_Post $changeset_post Changeset post object.
2829
 */
2830
function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
2831
	global $wp_customize, $wpdb;
2832
2833
	$is_publishing_changeset = (
2834
		'customize_changeset' === $changeset_post->post_type
2835
		&&
2836
		'publish' === $new_status
2837
		&&
2838
		'publish' !== $old_status
2839
	);
2840
	if ( ! $is_publishing_changeset ) {
2841
		return;
2842
	}
2843
2844
	if ( empty( $wp_customize ) ) {
2845
		require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2846
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) );
2847
	}
2848
2849
	if ( ! did_action( 'customize_register' ) ) {
2850
		/*
2851
		 * When running from CLI or Cron, the customize_register action will need
2852
		 * to be triggered in order for core, themes, and plugins to register their
2853
		 * settings. Normally core will add_action( 'customize_register' ) at
2854
		 * priority 10 to register the core settings, and if any themes/plugins
2855
		 * also add_action( 'customize_register' ) at the same priority, they
2856
		 * will have a $wp_customize with those settings registered since they
2857
		 * call add_action() afterward, normally. However, when manually doing
2858
		 * the customize_register action after the setup_theme, then the order
2859
		 * will be reversed for two actions added at priority 10, resulting in
2860
		 * the core settings no longer being available as expected to themes/plugins.
2861
		 * So the following manually calls the method that registers the core
2862
		 * settings up front before doing the action.
2863
		 */
2864
		remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
2865
		$wp_customize->register_controls();
2866
2867
		/** This filter is documented in /wp-includes/class-wp-customize-manager.php */
2868
		do_action( 'customize_register', $wp_customize );
2869
	}
2870
	$wp_customize->_publish_changeset_values( $changeset_post->ID ) ;
2871
2872
	/*
2873
	 * Trash the changeset post if revisions are not enabled. Unpublished
2874
	 * changesets by default get garbage collected due to the auto-draft status.
2875
	 * When a changeset post is published, however, it would no longer get cleaned
2876
	 * out. Ths is a problem when the changeset posts are never displayed anywhere,
2877
	 * since they would just be endlessly piling up. So here we use the revisions
2878
	 * feature to indicate whether or not a published changeset should get trashed
2879
	 * and thus garbage collected.
2880
	 */
2881
	if ( ! wp_revisions_enabled( $changeset_post ) ) {
2882
		$post = $changeset_post;
2883
		$post_id = $changeset_post->ID;
2884
2885
		/*
2886
		 * The following re-formulates the logic from wp_trash_post() as done in
2887
		 * wp_publish_post(). The reason for bypassing wp_trash_post() is that it
2888
		 * will mutate the the post_content and the post_name when they should be
2889
		 * untouched.
2890
		 */
2891
		if ( ! EMPTY_TRASH_DAYS ) {
2892
			wp_delete_post( $post_id, true );
2893
		} else {
2894
			/** This action is documented in wp-includes/post.php */
2895
			do_action( 'wp_trash_post', $post_id );
2896
2897
			add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
2898
			add_post_meta( $post_id, '_wp_trash_meta_time', time() );
2899
2900
			$old_status = $post->post_status;
2901
			$new_status = 'trash';
2902
			$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
2903
			clean_post_cache( $post->ID );
2904
2905
			$post->post_status = $new_status;
2906
			wp_transition_post_status( $new_status, $old_status, $post );
2907
2908
			/** This action is documented in wp-includes/post.php */
2909
			do_action( 'edit_post', $post->ID, $post );
2910
2911
			/** This action is documented in wp-includes/post.php */
2912
			do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
2913
2914
			/** This action is documented in wp-includes/post.php */
2915
			do_action( 'save_post', $post->ID, $post, true );
2916
2917
			/** This action is documented in wp-includes/post.php */
2918
			do_action( 'wp_insert_post', $post->ID, $post, true );
2919
2920
			/** This action is documented in wp-includes/post.php */
2921
			do_action( 'trashed_post', $post_id );
2922
		}
2923
	}
2924
}
2925
2926
/**
2927
 * Filters changeset post data upon insert to ensure post_name is intact.
2928
 *
2929
 * This is needed to prevent the post_name from being dropped when the post is
2930
 * transitioned into pending status by a contributor.
2931
 *
2932
 * @since 4.7.0
2933
 * @see wp_insert_post()
2934
 *
2935
 * @param array $post_data          An array of slashed post data.
2936
 * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
2937
 * @returns array Filtered data.
2938
 */
2939
function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
2940
	if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
2941
2942
		// Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
2943
		if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
2944
			$post_data['post_name'] = $supplied_post_data['post_name'];
2945
		}
2946
	}
2947
	return $post_data;
2948
}
2949
2950
/**
2951
 * Adds settings for the customize-loader script.
2952
 *
2953
 * @since 3.4.0
2954
 */
2955
function _wp_customize_loader_settings() {
2956
	$admin_origin = parse_url( admin_url() );
2957
	$home_origin  = parse_url( home_url() );
2958
	$cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
2959
2960
	$browser = array(
2961
		'mobile' => wp_is_mobile(),
2962
		'ios'    => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
2963
	);
2964
2965
	$settings = array(
2966
		'url'           => esc_url( admin_url( 'customize.php' ) ),
2967
		'isCrossDomain' => $cross_domain,
2968
		'browser'       => $browser,
2969
		'l10n'          => array(
2970
			'saveAlert'       => __( 'The changes you made will be lost if you navigate away from this page.' ),
2971
			'mainIframeTitle' => __( 'Customizer' ),
2972
		),
2973
	);
2974
2975
	$script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
2976
2977
	$wp_scripts = wp_scripts();
2978
	$data = $wp_scripts->get_data( 'customize-loader', 'data' );
2979
	if ( $data )
2980
		$script = "$data\n$script";
2981
2982
	$wp_scripts->add_data( 'customize-loader', 'data', $script );
2983
}
2984
2985
/**
2986
 * Returns a URL to load the Customizer.
2987
 *
2988
 * @since 3.4.0
2989
 *
2990
 * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
2991
 * 	                         The theme's stylesheet will be urlencoded if necessary.
2992
 * @return string
2993
 */
2994
function wp_customize_url( $stylesheet = null ) {
2995
	$url = admin_url( 'customize.php' );
2996
	if ( $stylesheet )
0 ignored issues
show
Bug Best Practice introduced by
The expression $stylesheet of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2997
		$url .= '?theme=' . urlencode( $stylesheet );
2998
	return esc_url( $url );
2999
}
3000
3001
/**
3002
 * Prints a script to check whether or not the Customizer is supported,
3003
 * and apply either the no-customize-support or customize-support class
3004
 * to the body.
3005
 *
3006
 * This function MUST be called inside the body tag.
3007
 *
3008
 * Ideally, call this function immediately after the body tag is opened.
3009
 * This prevents a flash of unstyled content.
3010
 *
3011
 * It is also recommended that you add the "no-customize-support" class
3012
 * to the body tag by default.
3013
 *
3014
 * @since 3.4.0
3015
 * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
3016
 */
3017
function wp_customize_support_script() {
3018
	$admin_origin = parse_url( admin_url() );
3019
	$home_origin  = parse_url( home_url() );
3020
	$cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
3021
3022
	?>
3023
	<!--[if lte IE 8]>
3024
		<script type="text/javascript">
3025
			document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
3026
		</script>
3027
	<![endif]-->
3028
	<!--[if gte IE 9]><!-->
3029
		<script type="text/javascript">
3030
			(function() {
3031
				var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3032
3033
		<?php	if ( $cross_domain ) : ?>
3034
				request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
3035
		<?php	else : ?>
3036
				request = true;
3037
		<?php	endif; ?>
3038
3039
				b[c] = b[c].replace( rcs, ' ' );
3040
				// The customizer requires postMessage and CORS (if the site is cross domain)
3041
				b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
3042
			}());
3043
		</script>
3044
	<!--<![endif]-->
3045
	<?php
3046
}
3047
3048
/**
3049
 * Whether the site is being previewed in the Customizer.
3050
 *
3051
 * @since 4.0.0
3052
 *
3053
 * @global WP_Customize_Manager $wp_customize Customizer instance.
3054
 *
3055
 * @return bool True if the site is being previewed in the Customizer, false otherwise.
3056
 */
3057
function is_customize_preview() {
0 ignored issues
show
The function is_customize_preview() has been defined more than once; this definition is ignored, only the first definition in src/wp-content/themes/twentyfourteen/functions.php (L556-560) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3058
	global $wp_customize;
3059
3060
	return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
3061
}
3062
3063
/**
3064
 * Make sure that auto-draft posts get their post_date bumped to prevent premature garbage-collection.
3065
 *
3066
 * When a changeset is updated but remains an auto-draft, ensure the post_date
3067
 * for the auto-draft posts remains the same so that it will be
3068
 * garbage-collected at the same time by `wp_delete_auto_drafts()`. Otherwise,
3069
 * if the changeset is updated to be a draft then update the posts
3070
 * to have a far-future post_date so that they will never be garbage collected
3071
 * unless the changeset post itself is deleted.
3072
 *
3073
 * @since 4.8.0
3074
 * @access private
3075
 * @see wp_delete_auto_drafts()
3076
 *
3077
 * @param string   $new_status Transition to this post status.
3078
 * @param string   $old_status Previous post status.
3079
 * @param \WP_Post $post       Post data.
3080
 * @global wpdb $wpdb
3081
 */
3082
function _wp_keep_alive_customize_changeset_dependent_auto_drafts( $new_status, $old_status, $post ) {
3083
	global $wpdb;
3084
	unset( $old_status );
3085
3086
	// Short-circuit if not a changeset or if the changeset was published.
3087
	if ( 'customize_changeset' !== $post->post_type || 'publish' === $new_status ) {
3088
		return;
3089
	}
3090
3091
	if ( 'auto-draft' === $new_status ) {
3092
		/*
3093
		 * Keep the post date for the post matching the changeset
3094
		 * so that it will not be garbage-collected before the changeset.
3095
		 */
3096
		$new_post_date = $post->post_date;
3097
	} else {
3098
		/*
3099
		 * Since the changeset no longer has an auto-draft (and it is not published)
3100
		 * it is now a persistent changeset, a long-lived draft, and so any
3101
		 * associated auto-draft posts should have their dates
3102
		 * pushed out very far into the future to prevent them from ever
3103
		 * being garbage-collected.
3104
		 */
3105
		$new_post_date = gmdate( 'Y-m-d H:i:d', strtotime( '+100 years' ) );
3106
	}
3107
3108
	$data = json_decode( $post->post_content, true );
3109
	if ( empty( $data['nav_menus_created_posts']['value'] ) ) {
3110
		return;
3111
	}
3112
	foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
3113
		if ( empty( $post_id ) || 'auto-draft' !== get_post_status( $post_id ) ) {
3114
			continue;
3115
		}
3116
		$wpdb->update(
3117
			$wpdb->posts,
3118
			array( 'post_date' => $new_post_date ), // Note wp_delete_auto_drafts() only looks at this this date.
3119
			array( 'ID' => $post_id )
3120
		);
3121
		clean_post_cache( $post_id );
3122
	}
3123
}
3124