ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

theme.php (125388B)


      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  *
     19  * @param array $args {
     20  *     Optional. The search arguments.
     21  *
     22  *     @type mixed $errors  True to return themes with errors, false to return
     23  *                          themes without errors, null to return all themes.
     24  *                          Default false.
     25  *     @type mixed $allowed (Multisite) True to return only allowed themes for a site.
     26  *                          False to return only disallowed themes for a site.
     27  *                          'site' to return only site-allowed themes.
     28  *                          'network' to return only network-allowed themes.
     29  *                          Null to return all themes. Default null.
     30  *     @type int   $blog_id (Multisite) The blog ID used to calculate which themes
     31  *                          are allowed. Default 0, synonymous for the current blog.
     32  * }
     33  * @return WP_Theme[] Array of WP_Theme objects.
     34  */
     35 function wp_get_themes( $args = array() ) {
     36 	global $wp_theme_directories;
     37 
     38 	$defaults = array(
     39 		'errors'  => false,
     40 		'allowed' => null,
     41 		'blog_id' => 0,
     42 	);
     43 	$args     = wp_parse_args( $args, $defaults );
     44 
     45 	$theme_directories = search_theme_directories();
     46 
     47 	if ( is_array( $wp_theme_directories ) && count( $wp_theme_directories ) > 1 ) {
     48 		// Make sure the current theme wins out, in case search_theme_directories() picks the wrong
     49 		// one in the case of a conflict. (Normally, last registered theme root wins.)
     50 		$current_theme = get_stylesheet();
     51 		if ( isset( $theme_directories[ $current_theme ] ) ) {
     52 			$root_of_current_theme = get_raw_theme_root( $current_theme );
     53 			if ( ! in_array( $root_of_current_theme, $wp_theme_directories, true ) ) {
     54 				$root_of_current_theme = WP_CONTENT_DIR . $root_of_current_theme;
     55 			}
     56 			$theme_directories[ $current_theme ]['theme_root'] = $root_of_current_theme;
     57 		}
     58 	}
     59 
     60 	if ( empty( $theme_directories ) ) {
     61 		return array();
     62 	}
     63 
     64 	if ( is_multisite() && null !== $args['allowed'] ) {
     65 		$allowed = $args['allowed'];
     66 		if ( 'network' === $allowed ) {
     67 			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_network() );
     68 		} elseif ( 'site' === $allowed ) {
     69 			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_site( $args['blog_id'] ) );
     70 		} elseif ( $allowed ) {
     71 			$theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
     72 		} else {
     73 			$theme_directories = array_diff_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
     74 		}
     75 	}
     76 
     77 	$themes         = array();
     78 	static $_themes = array();
     79 
     80 	foreach ( $theme_directories as $theme => $theme_root ) {
     81 		if ( isset( $_themes[ $theme_root['theme_root'] . '/' . $theme ] ) ) {
     82 			$themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ];
     83 		} else {
     84 			$themes[ $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
     85 
     86 			$_themes[ $theme_root['theme_root'] . '/' . $theme ] = $themes[ $theme ];
     87 		}
     88 	}
     89 
     90 	if ( null !== $args['errors'] ) {
     91 		foreach ( $themes as $theme => $wp_theme ) {
     92 			if ( $wp_theme->errors() != $args['errors'] ) {
     93 				unset( $themes[ $theme ] );
     94 			}
     95 		}
     96 	}
     97 
     98 	return $themes;
     99 }
    100 
    101 /**
    102  * Gets a WP_Theme object for a theme.
    103  *
    104  * @since 3.4.0
    105  *
    106  * @global array $wp_theme_directories
    107  *
    108  * @param string $stylesheet Optional. Directory name for the theme. Defaults to current theme.
    109  * @param string $theme_root Optional. Absolute path of the theme root to look in.
    110  *                           If not specified, get_raw_theme_root() is used to calculate
    111  *                           the theme root for the $stylesheet provided (or current theme).
    112  * @return WP_Theme Theme object. Be sure to check the object's exists() method
    113  *                  if you need to confirm the theme's existence.
    114  */
    115 function wp_get_theme( $stylesheet = '', $theme_root = '' ) {
    116 	global $wp_theme_directories;
    117 
    118 	if ( empty( $stylesheet ) ) {
    119 		$stylesheet = get_stylesheet();
    120 	}
    121 
    122 	if ( empty( $theme_root ) ) {
    123 		$theme_root = get_raw_theme_root( $stylesheet );
    124 		if ( false === $theme_root ) {
    125 			$theme_root = WP_CONTENT_DIR . '/themes';
    126 		} elseif ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
    127 			$theme_root = WP_CONTENT_DIR . $theme_root;
    128 		}
    129 	}
    130 
    131 	return new WP_Theme( $stylesheet, $theme_root );
    132 }
    133 
    134 /**
    135  * Clears the cache held by get_theme_roots() and WP_Theme.
    136  *
    137  * @since 3.5.0
    138  * @param bool $clear_update_cache Whether to clear the theme updates cache.
    139  */
    140 function wp_clean_themes_cache( $clear_update_cache = true ) {
    141 	if ( $clear_update_cache ) {
    142 		delete_site_transient( 'update_themes' );
    143 	}
    144 	search_theme_directories( true );
    145 	foreach ( wp_get_themes( array( 'errors' => null ) ) as $theme ) {
    146 		$theme->cache_delete();
    147 	}
    148 }
    149 
    150 /**
    151  * Whether a child theme is in use.
    152  *
    153  * @since 3.0.0
    154  *
    155  * @return bool True if a child theme is in use, false otherwise.
    156  */
    157 function is_child_theme() {
    158 	return ( TEMPLATEPATH !== STYLESHEETPATH );
    159 }
    160 
    161 /**
    162  * Retrieves name of the current stylesheet.
    163  *
    164  * The theme name that is currently set as the front end theme.
    165  *
    166  * For all intents and purposes, the template name and the stylesheet name
    167  * are going to be the same for most cases.
    168  *
    169  * @since 1.5.0
    170  *
    171  * @return string Stylesheet name.
    172  */
    173 function get_stylesheet() {
    174 	/**
    175 	 * Filters the name of current stylesheet.
    176 	 *
    177 	 * @since 1.5.0
    178 	 *
    179 	 * @param string $stylesheet Name of the current stylesheet.
    180 	 */
    181 	return apply_filters( 'stylesheet', get_option( 'stylesheet' ) );
    182 }
    183 
    184 /**
    185  * Retrieves stylesheet directory path for current theme.
    186  *
    187  * @since 1.5.0
    188  *
    189  * @return string Path to current theme's stylesheet directory.
    190  */
    191 function get_stylesheet_directory() {
    192 	$stylesheet     = get_stylesheet();
    193 	$theme_root     = get_theme_root( $stylesheet );
    194 	$stylesheet_dir = "$theme_root/$stylesheet";
    195 
    196 	/**
    197 	 * Filters the stylesheet directory path for current theme.
    198 	 *
    199 	 * @since 1.5.0
    200 	 *
    201 	 * @param string $stylesheet_dir Absolute path to the current theme.
    202 	 * @param string $stylesheet     Directory name of the current theme.
    203 	 * @param string $theme_root     Absolute path to themes directory.
    204 	 */
    205 	return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
    206 }
    207 
    208 /**
    209  * Retrieves stylesheet directory URI for current theme.
    210  *
    211  * @since 1.5.0
    212  *
    213  * @return string URI to current theme's stylesheet directory.
    214  */
    215 function get_stylesheet_directory_uri() {
    216 	$stylesheet         = str_replace( '%2F', '/', rawurlencode( get_stylesheet() ) );
    217 	$theme_root_uri     = get_theme_root_uri( $stylesheet );
    218 	$stylesheet_dir_uri = "$theme_root_uri/$stylesheet";
    219 
    220 	/**
    221 	 * Filters the stylesheet directory URI.
    222 	 *
    223 	 * @since 1.5.0
    224 	 *
    225 	 * @param string $stylesheet_dir_uri Stylesheet directory URI.
    226 	 * @param string $stylesheet         Name of the activated theme's directory.
    227 	 * @param string $theme_root_uri     Themes root URI.
    228 	 */
    229 	return apply_filters( 'stylesheet_directory_uri', $stylesheet_dir_uri, $stylesheet, $theme_root_uri );
    230 }
    231 
    232 /**
    233  * Retrieves stylesheet URI for current theme.
    234  *
    235  * The stylesheet file name is 'style.css' which is appended to the stylesheet directory URI path.
    236  * See get_stylesheet_directory_uri().
    237  *
    238  * @since 1.5.0
    239  *
    240  * @return string URI to current theme's stylesheet.
    241  */
    242 function get_stylesheet_uri() {
    243 	$stylesheet_dir_uri = get_stylesheet_directory_uri();
    244 	$stylesheet_uri     = $stylesheet_dir_uri . '/style.css';
    245 	/**
    246 	 * Filters the URI of the current theme stylesheet.
    247 	 *
    248 	 * @since 1.5.0
    249 	 *
    250 	 * @param string $stylesheet_uri     Stylesheet URI for the current theme/child theme.
    251 	 * @param string $stylesheet_dir_uri Stylesheet directory URI for the current theme/child theme.
    252 	 */
    253 	return apply_filters( 'stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
    254 }
    255 
    256 /**
    257  * Retrieves the localized stylesheet URI.
    258  *
    259  * The stylesheet directory for the localized stylesheet files are located, by
    260  * default, in the base theme directory. The name of the locale file will be the
    261  * locale followed by '.css'. If that does not exist, then the text direction
    262  * stylesheet will be checked for existence, for example 'ltr.css'.
    263  *
    264  * The theme may change the location of the stylesheet directory by either using
    265  * the {@see 'stylesheet_directory_uri'} or {@see 'locale_stylesheet_uri'} filters.
    266  *
    267  * If you want to change the location of the stylesheet files for the entire
    268  * WordPress workflow, then change the former. If you just have the locale in a
    269  * separate folder, then change the latter.
    270  *
    271  * @since 2.1.0
    272  *
    273  * @global WP_Locale $wp_locale WordPress date and time locale object.
    274  *
    275  * @return string URI to current theme's localized stylesheet.
    276  */
    277 function get_locale_stylesheet_uri() {
    278 	global $wp_locale;
    279 	$stylesheet_dir_uri = get_stylesheet_directory_uri();
    280 	$dir                = get_stylesheet_directory();
    281 	$locale             = get_locale();
    282 	if ( file_exists( "$dir/$locale.css" ) ) {
    283 		$stylesheet_uri = "$stylesheet_dir_uri/$locale.css";
    284 	} elseif ( ! empty( $wp_locale->text_direction ) && file_exists( "$dir/{$wp_locale->text_direction}.css" ) ) {
    285 		$stylesheet_uri = "$stylesheet_dir_uri/{$wp_locale->text_direction}.css";
    286 	} else {
    287 		$stylesheet_uri = '';
    288 	}
    289 	/**
    290 	 * Filters the localized stylesheet URI.
    291 	 *
    292 	 * @since 2.1.0
    293 	 *
    294 	 * @param string $stylesheet_uri     Localized stylesheet URI.
    295 	 * @param string $stylesheet_dir_uri Stylesheet directory URI.
    296 	 */
    297 	return apply_filters( 'locale_stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
    298 }
    299 
    300 /**
    301  * Retrieves name of the current theme.
    302  *
    303  * @since 1.5.0
    304  *
    305  * @return string Template name.
    306  */
    307 function get_template() {
    308 	/**
    309 	 * Filters the name of the current theme.
    310 	 *
    311 	 * @since 1.5.0
    312 	 *
    313 	 * @param string $template Current theme's directory name.
    314 	 */
    315 	return apply_filters( 'template', get_option( 'template' ) );
    316 }
    317 
    318 /**
    319  * Retrieves template directory path for current theme.
    320  *
    321  * @since 1.5.0
    322  *
    323  * @return string Path to current theme's template directory.
    324  */
    325 function get_template_directory() {
    326 	$template     = get_template();
    327 	$theme_root   = get_theme_root( $template );
    328 	$template_dir = "$theme_root/$template";
    329 
    330 	/**
    331 	 * Filters the current theme directory path.
    332 	 *
    333 	 * @since 1.5.0
    334 	 *
    335 	 * @param string $template_dir The path of the current theme directory.
    336 	 * @param string $template     Directory name of the current theme.
    337 	 * @param string $theme_root   Absolute path to the themes directory.
    338 	 */
    339 	return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
    340 }
    341 
    342 /**
    343  * Retrieves template directory URI for current theme.
    344  *
    345  * @since 1.5.0
    346  *
    347  * @return string URI to current theme's template directory.
    348  */
    349 function get_template_directory_uri() {
    350 	$template         = str_replace( '%2F', '/', rawurlencode( get_template() ) );
    351 	$theme_root_uri   = get_theme_root_uri( $template );
    352 	$template_dir_uri = "$theme_root_uri/$template";
    353 
    354 	/**
    355 	 * Filters the current theme directory URI.
    356 	 *
    357 	 * @since 1.5.0
    358 	 *
    359 	 * @param string $template_dir_uri The URI of the current theme directory.
    360 	 * @param string $template         Directory name of the current theme.
    361 	 * @param string $theme_root_uri   The themes root URI.
    362 	 */
    363 	return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
    364 }
    365 
    366 /**
    367  * Retrieves theme roots.
    368  *
    369  * @since 2.9.0
    370  *
    371  * @global array $wp_theme_directories
    372  *
    373  * @return array|string An array of theme roots keyed by template/stylesheet
    374  *                      or a single theme root if all themes have the same root.
    375  */
    376 function get_theme_roots() {
    377 	global $wp_theme_directories;
    378 
    379 	if ( ! is_array( $wp_theme_directories ) || count( $wp_theme_directories ) <= 1 ) {
    380 		return '/themes';
    381 	}
    382 
    383 	$theme_roots = get_site_transient( 'theme_roots' );
    384 	if ( false === $theme_roots ) {
    385 		search_theme_directories( true ); // Regenerate the transient.
    386 		$theme_roots = get_site_transient( 'theme_roots' );
    387 	}
    388 	return $theme_roots;
    389 }
    390 
    391 /**
    392  * Registers a directory that contains themes.
    393  *
    394  * @since 2.9.0
    395  *
    396  * @global array $wp_theme_directories
    397  *
    398  * @param string $directory Either the full filesystem path to a theme folder
    399  *                          or a folder within WP_CONTENT_DIR.
    400  * @return bool True if successfully registered a directory that contains themes,
    401  *              false if the directory does not exist.
    402  */
    403 function register_theme_directory( $directory ) {
    404 	global $wp_theme_directories;
    405 
    406 	if ( ! file_exists( $directory ) ) {
    407 		// Try prepending as the theme directory could be relative to the content directory.
    408 		$directory = WP_CONTENT_DIR . '/' . $directory;
    409 		// If this directory does not exist, return and do not register.
    410 		if ( ! file_exists( $directory ) ) {
    411 			return false;
    412 		}
    413 	}
    414 
    415 	if ( ! is_array( $wp_theme_directories ) ) {
    416 		$wp_theme_directories = array();
    417 	}
    418 
    419 	$untrailed = untrailingslashit( $directory );
    420 	if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories, true ) ) {
    421 		$wp_theme_directories[] = $untrailed;
    422 	}
    423 
    424 	return true;
    425 }
    426 
    427 /**
    428  * Searches all registered theme directories for complete and valid themes.
    429  *
    430  * @since 2.9.0
    431  *
    432  * @global array $wp_theme_directories
    433  *
    434  * @param bool $force Optional. Whether to force a new directory scan. Default false.
    435  * @return array|false Valid themes found on success, false on failure.
    436  */
    437 function search_theme_directories( $force = false ) {
    438 	global $wp_theme_directories;
    439 	static $found_themes = null;
    440 
    441 	if ( empty( $wp_theme_directories ) ) {
    442 		return false;
    443 	}
    444 
    445 	if ( ! $force && isset( $found_themes ) ) {
    446 		return $found_themes;
    447 	}
    448 
    449 	$found_themes = array();
    450 
    451 	$wp_theme_directories = (array) $wp_theme_directories;
    452 	$relative_theme_roots = array();
    453 
    454 	/*
    455 	 * Set up maybe-relative, maybe-absolute array of theme directories.
    456 	 * We always want to return absolute, but we need to cache relative
    457 	 * to use in get_theme_root().
    458 	 */
    459 	foreach ( $wp_theme_directories as $theme_root ) {
    460 		if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) ) {
    461 			$relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
    462 		} else {
    463 			$relative_theme_roots[ $theme_root ] = $theme_root;
    464 		}
    465 	}
    466 
    467 	/**
    468 	 * Filters whether to get the cache of the registered theme directories.
    469 	 *
    470 	 * @since 3.4.0
    471 	 *
    472 	 * @param bool   $cache_expiration Whether to get the cache of the theme directories. Default false.
    473 	 * @param string $context          The class or function name calling the filter.
    474 	 */
    475 	$cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' );
    476 
    477 	if ( $cache_expiration ) {
    478 		$cached_roots = get_site_transient( 'theme_roots' );
    479 		if ( is_array( $cached_roots ) ) {
    480 			foreach ( $cached_roots as $theme_dir => $theme_root ) {
    481 				// A cached theme root is no longer around, so skip it.
    482 				if ( ! isset( $relative_theme_roots[ $theme_root ] ) ) {
    483 					continue;
    484 				}
    485 				$found_themes[ $theme_dir ] = array(
    486 					'theme_file' => $theme_dir . '/style.css',
    487 					'theme_root' => $relative_theme_roots[ $theme_root ], // Convert relative to absolute.
    488 				);
    489 			}
    490 			return $found_themes;
    491 		}
    492 		if ( ! is_int( $cache_expiration ) ) {
    493 			$cache_expiration = 30 * MINUTE_IN_SECONDS;
    494 		}
    495 	} else {
    496 		$cache_expiration = 30 * MINUTE_IN_SECONDS;
    497 	}
    498 
    499 	/* Loop the registered theme directories and extract all themes */
    500 	foreach ( $wp_theme_directories as $theme_root ) {
    501 
    502 		// Start with directories in the root of the current theme directory.
    503 		$dirs = @ scandir( $theme_root );
    504 		if ( ! $dirs ) {
    505 			trigger_error( "$theme_root is not readable", E_USER_NOTICE );
    506 			continue;
    507 		}
    508 		foreach ( $dirs as $dir ) {
    509 			if ( ! is_dir( $theme_root . '/' . $dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
    510 				continue;
    511 			}
    512 			if ( file_exists( $theme_root . '/' . $dir . '/style.css' ) ) {
    513 				// wp-content/themes/a-single-theme
    514 				// wp-content/themes is $theme_root, a-single-theme is $dir.
    515 				$found_themes[ $dir ] = array(
    516 					'theme_file' => $dir . '/style.css',
    517 					'theme_root' => $theme_root,
    518 				);
    519 			} else {
    520 				$found_theme = false;
    521 				// wp-content/themes/a-folder-of-themes/*
    522 				// wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs.
    523 				$sub_dirs = @ scandir( $theme_root . '/' . $dir );
    524 				if ( ! $sub_dirs ) {
    525 					trigger_error( "$theme_root/$dir is not readable", E_USER_NOTICE );
    526 					continue;
    527 				}
    528 				foreach ( $sub_dirs as $sub_dir ) {
    529 					if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
    530 						continue;
    531 					}
    532 					if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) ) {
    533 						continue;
    534 					}
    535 					$found_themes[ $dir . '/' . $sub_dir ] = array(
    536 						'theme_file' => $dir . '/' . $sub_dir . '/style.css',
    537 						'theme_root' => $theme_root,
    538 					);
    539 					$found_theme                           = true;
    540 				}
    541 				// Never mind the above, it's just a theme missing a style.css.
    542 				// Return it; WP_Theme will catch the error.
    543 				if ( ! $found_theme ) {
    544 					$found_themes[ $dir ] = array(
    545 						'theme_file' => $dir . '/style.css',
    546 						'theme_root' => $theme_root,
    547 					);
    548 				}
    549 			}
    550 		}
    551 	}
    552 
    553 	asort( $found_themes );
    554 
    555 	$theme_roots          = array();
    556 	$relative_theme_roots = array_flip( $relative_theme_roots );
    557 
    558 	foreach ( $found_themes as $theme_dir => $theme_data ) {
    559 		$theme_roots[ $theme_dir ] = $relative_theme_roots[ $theme_data['theme_root'] ]; // Convert absolute to relative.
    560 	}
    561 
    562 	if ( get_site_transient( 'theme_roots' ) != $theme_roots ) {
    563 		set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
    564 	}
    565 
    566 	return $found_themes;
    567 }
    568 
    569 /**
    570  * Retrieves path to themes directory.
    571  *
    572  * Does not have trailing slash.
    573  *
    574  * @since 1.5.0
    575  *
    576  * @global array $wp_theme_directories
    577  *
    578  * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
    579  *                                       Default is to leverage the main theme root.
    580  * @return string Themes directory path.
    581  */
    582 function get_theme_root( $stylesheet_or_template = '' ) {
    583 	global $wp_theme_directories;
    584 
    585 	$theme_root = '';
    586 
    587 	if ( $stylesheet_or_template ) {
    588 		$theme_root = get_raw_theme_root( $stylesheet_or_template );
    589 		if ( $theme_root ) {
    590 			// Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
    591 			// This gives relative theme roots the benefit of the doubt when things go haywire.
    592 			if ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
    593 				$theme_root = WP_CONTENT_DIR . $theme_root;
    594 			}
    595 		}
    596 	}
    597 
    598 	if ( ! $theme_root ) {
    599 		$theme_root = WP_CONTENT_DIR . '/themes';
    600 	}
    601 
    602 	/**
    603 	 * Filters the absolute path to the themes directory.
    604 	 *
    605 	 * @since 1.5.0
    606 	 *
    607 	 * @param string $theme_root Absolute path to themes directory.
    608 	 */
    609 	return apply_filters( 'theme_root', $theme_root );
    610 }
    611 
    612 /**
    613  * Retrieves URI for themes directory.
    614  *
    615  * Does not have trailing slash.
    616  *
    617  * @since 1.5.0
    618  *
    619  * @global array $wp_theme_directories
    620  *
    621  * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
    622  *                                       Default is to leverage the main theme root.
    623  * @param string $theme_root             Optional. The theme root for which calculations will be based,
    624  *                                       preventing the need for a get_raw_theme_root() call. Default empty.
    625  * @return string Themes directory URI.
    626  */
    627 function get_theme_root_uri( $stylesheet_or_template = '', $theme_root = '' ) {
    628 	global $wp_theme_directories;
    629 
    630 	if ( $stylesheet_or_template && ! $theme_root ) {
    631 		$theme_root = get_raw_theme_root( $stylesheet_or_template );
    632 	}
    633 
    634 	if ( $stylesheet_or_template && $theme_root ) {
    635 		if ( in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
    636 			// Absolute path. Make an educated guess. YMMV -- but note the filter below.
    637 			if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) ) {
    638 				$theme_root_uri = content_url( str_replace( WP_CONTENT_DIR, '', $theme_root ) );
    639 			} elseif ( 0 === strpos( $theme_root, ABSPATH ) ) {
    640 				$theme_root_uri = site_url( str_replace( ABSPATH, '', $theme_root ) );
    641 			} elseif ( 0 === strpos( $theme_root, WP_PLUGIN_DIR ) || 0 === strpos( $theme_root, WPMU_PLUGIN_DIR ) ) {
    642 				$theme_root_uri = plugins_url( basename( $theme_root ), $theme_root );
    643 			} else {
    644 				$theme_root_uri = $theme_root;
    645 			}
    646 		} else {
    647 			$theme_root_uri = content_url( $theme_root );
    648 		}
    649 	} else {
    650 		$theme_root_uri = content_url( 'themes' );
    651 	}
    652 
    653 	/**
    654 	 * Filters the URI for themes directory.
    655 	 *
    656 	 * @since 1.5.0
    657 	 *
    658 	 * @param string $theme_root_uri         The URI for themes directory.
    659 	 * @param string $siteurl                WordPress web address which is set in General Options.
    660 	 * @param string $stylesheet_or_template The stylesheet or template name of the theme.
    661 	 */
    662 	return apply_filters( 'theme_root_uri', $theme_root_uri, get_option( 'siteurl' ), $stylesheet_or_template );
    663 }
    664 
    665 /**
    666  * Gets the raw theme root relative to the content directory with no filters applied.
    667  *
    668  * @since 3.1.0
    669  *
    670  * @global array $wp_theme_directories
    671  *
    672  * @param string $stylesheet_or_template The stylesheet or template name of the theme.
    673  * @param bool   $skip_cache             Optional. Whether to skip the cache.
    674  *                                       Defaults to false, meaning the cache is used.
    675  * @return string Theme root.
    676  */
    677 function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
    678 	global $wp_theme_directories;
    679 
    680 	if ( ! is_array( $wp_theme_directories ) || count( $wp_theme_directories ) <= 1 ) {
    681 		return '/themes';
    682 	}
    683 
    684 	$theme_root = false;
    685 
    686 	// If requesting the root for the current theme, consult options to avoid calling get_theme_roots().
    687 	if ( ! $skip_cache ) {
    688 		if ( get_option( 'stylesheet' ) == $stylesheet_or_template ) {
    689 			$theme_root = get_option( 'stylesheet_root' );
    690 		} elseif ( get_option( 'template' ) == $stylesheet_or_template ) {
    691 			$theme_root = get_option( 'template_root' );
    692 		}
    693 	}
    694 
    695 	if ( empty( $theme_root ) ) {
    696 		$theme_roots = get_theme_roots();
    697 		if ( ! empty( $theme_roots[ $stylesheet_or_template ] ) ) {
    698 			$theme_root = $theme_roots[ $stylesheet_or_template ];
    699 		}
    700 	}
    701 
    702 	return $theme_root;
    703 }
    704 
    705 /**
    706  * Displays localized stylesheet link element.
    707  *
    708  * @since 2.1.0
    709  */
    710 function locale_stylesheet() {
    711 	$stylesheet = get_locale_stylesheet_uri();
    712 	if ( empty( $stylesheet ) ) {
    713 		return;
    714 	}
    715 
    716 	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
    717 
    718 	printf(
    719 		'<link rel="stylesheet" href="%s"%s media="screen" />',
    720 		$stylesheet,
    721 		$type_attr
    722 	);
    723 }
    724 
    725 /**
    726  * Switches the theme.
    727  *
    728  * Accepts one argument: $stylesheet of the theme. It also accepts an additional function signature
    729  * of two arguments: $template then $stylesheet. This is for backward compatibility.
    730  *
    731  * @since 2.5.0
    732  *
    733  * @global array                $wp_theme_directories
    734  * @global WP_Customize_Manager $wp_customize
    735  * @global array                $sidebars_widgets
    736  *
    737  * @param string $stylesheet Stylesheet name.
    738  */
    739 function switch_theme( $stylesheet ) {
    740 	global $wp_theme_directories, $wp_customize, $sidebars_widgets;
    741 
    742 	$requirements = validate_theme_requirements( $stylesheet );
    743 	if ( is_wp_error( $requirements ) ) {
    744 		wp_die( $requirements );
    745 	}
    746 
    747 	$_sidebars_widgets = null;
    748 	if ( 'wp_ajax_customize_save' === current_action() ) {
    749 		$old_sidebars_widgets_data_setting = $wp_customize->get_setting( 'old_sidebars_widgets_data' );
    750 		if ( $old_sidebars_widgets_data_setting ) {
    751 			$_sidebars_widgets = $wp_customize->post_value( $old_sidebars_widgets_data_setting );
    752 		}
    753 	} elseif ( is_array( $sidebars_widgets ) ) {
    754 		$_sidebars_widgets = $sidebars_widgets;
    755 	}
    756 
    757 	if ( is_array( $_sidebars_widgets ) ) {
    758 		set_theme_mod(
    759 			'sidebars_widgets',
    760 			array(
    761 				'time' => time(),
    762 				'data' => $_sidebars_widgets,
    763 			)
    764 		);
    765 	}
    766 
    767 	$nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
    768 	update_option( 'theme_switch_menu_locations', $nav_menu_locations );
    769 
    770 	if ( func_num_args() > 1 ) {
    771 		$stylesheet = func_get_arg( 1 );
    772 	}
    773 
    774 	$old_theme = wp_get_theme();
    775 	$new_theme = wp_get_theme( $stylesheet );
    776 	$template  = $new_theme->get_template();
    777 
    778 	if ( wp_is_recovery_mode() ) {
    779 		$paused_themes = wp_paused_themes();
    780 		$paused_themes->delete( $old_theme->get_stylesheet() );
    781 		$paused_themes->delete( $old_theme->get_template() );
    782 	}
    783 
    784 	update_option( 'template', $template );
    785 	update_option( 'stylesheet', $stylesheet );
    786 
    787 	if ( count( $wp_theme_directories ) > 1 ) {
    788 		update_option( 'template_root', get_raw_theme_root( $template, true ) );
    789 		update_option( 'stylesheet_root', get_raw_theme_root( $stylesheet, true ) );
    790 	} else {
    791 		delete_option( 'template_root' );
    792 		delete_option( 'stylesheet_root' );
    793 	}
    794 
    795 	$new_name = $new_theme->get( 'Name' );
    796 
    797 	update_option( 'current_theme', $new_name );
    798 
    799 	// Migrate from the old mods_{name} option to theme_mods_{slug}.
    800 	if ( is_admin() && false === get_option( 'theme_mods_' . $stylesheet ) ) {
    801 		$default_theme_mods = (array) get_option( 'mods_' . $new_name );
    802 		if ( ! empty( $nav_menu_locations ) && empty( $default_theme_mods['nav_menu_locations'] ) ) {
    803 			$default_theme_mods['nav_menu_locations'] = $nav_menu_locations;
    804 		}
    805 		add_option( "theme_mods_$stylesheet", $default_theme_mods );
    806 	} else {
    807 		/*
    808 		 * Since retrieve_widgets() is called when initializing a theme in the Customizer,
    809 		 * we need to remove the theme mods to avoid overwriting changes made via
    810 		 * the Customizer when accessing wp-admin/widgets.php.
    811 		 */
    812 		if ( 'wp_ajax_customize_save' === current_action() ) {
    813 			remove_theme_mod( 'sidebars_widgets' );
    814 		}
    815 	}
    816 
    817 	update_option( 'theme_switched', $old_theme->get_stylesheet() );
    818 
    819 	/**
    820 	 * Fires after the theme is switched.
    821 	 *
    822 	 * @since 1.5.0
    823 	 * @since 4.5.0 Introduced the `$old_theme` parameter.
    824 	 *
    825 	 * @param string   $new_name  Name of the new theme.
    826 	 * @param WP_Theme $new_theme WP_Theme instance of the new theme.
    827 	 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
    828 	 */
    829 	do_action( 'switch_theme', $new_name, $new_theme, $old_theme );
    830 }
    831 
    832 /**
    833  * Checks that the current theme has 'index.php' and 'style.css' files.
    834  *
    835  * Does not initially check the default theme, which is the fallback and should always exist.
    836  * But if it doesn't exist, it'll fall back to the latest core default theme that does exist.
    837  * Will switch theme to the fallback theme if current theme does not validate.
    838  *
    839  * You can use the {@see 'validate_current_theme'} filter to return false to disable
    840  * this functionality.
    841  *
    842  * @since 1.5.0
    843  *
    844  * @see WP_DEFAULT_THEME
    845  *
    846  * @return bool
    847  */
    848 function validate_current_theme() {
    849 	/**
    850 	 * Filters whether to validate the current theme.
    851 	 *
    852 	 * @since 2.7.0
    853 	 *
    854 	 * @param bool $validate Whether to validate the current theme. Default true.
    855 	 */
    856 	if ( wp_installing() || ! apply_filters( 'validate_current_theme', true ) ) {
    857 		return true;
    858 	}
    859 
    860 	if ( ! file_exists( get_template_directory() . '/index.php' ) ) {
    861 		// Invalid.
    862 	} elseif ( ! file_exists( get_template_directory() . '/style.css' ) ) {
    863 		// Invalid.
    864 	} elseif ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
    865 		// Invalid.
    866 	} else {
    867 		// Valid.
    868 		return true;
    869 	}
    870 
    871 	$default = wp_get_theme( WP_DEFAULT_THEME );
    872 	if ( $default->exists() ) {
    873 		switch_theme( WP_DEFAULT_THEME );
    874 		return false;
    875 	}
    876 
    877 	/**
    878 	 * If we're in an invalid state but WP_DEFAULT_THEME doesn't exist,
    879 	 * switch to the latest core default theme that's installed.
    880 	 *
    881 	 * If it turns out that this latest core default theme is our current
    882 	 * theme, then there's nothing we can do about that, so we have to bail,
    883 	 * rather than going into an infinite loop. (This is why there are
    884 	 * checks against WP_DEFAULT_THEME above, also.) We also can't do anything
    885 	 * if it turns out there is no default theme installed. (That's `false`.)
    886 	 */
    887 	$default = WP_Theme::get_core_default_theme();
    888 	if ( false === $default || get_stylesheet() == $default->get_stylesheet() ) {
    889 		return true;
    890 	}
    891 
    892 	switch_theme( $default->get_stylesheet() );
    893 	return false;
    894 }
    895 
    896 /**
    897  * Validates the theme requirements for WordPress version and PHP version.
    898  *
    899  * Uses the information from `Requires at least` and `Requires PHP` headers
    900  * defined in the theme's `style.css` file.
    901  *
    902  * @since 5.5.0
    903  * @since 5.8.0 Removed support for using `readme.txt` as a fallback.
    904  *
    905  * @param string $stylesheet Directory name for the theme.
    906  * @return true|WP_Error True if requirements are met, WP_Error on failure.
    907  */
    908 function validate_theme_requirements( $stylesheet ) {
    909 	$theme = wp_get_theme( $stylesheet );
    910 
    911 	// If the theme is a Full Site Editing theme, check for the presence of the Gutenberg plugin.
    912 	$theme_tags = $theme->get( 'Tags' );
    913 
    914 	if ( ! empty( $theme_tags ) && in_array( 'full-site-editing', $theme_tags, true ) && ! function_exists( 'gutenberg_is_fse_theme' ) ) {
    915 		return new WP_Error(
    916 			'theme_requires_gutenberg_plugin',
    917 			sprintf(
    918 					/* translators: %s: Theme name. */
    919 				_x( '<strong>Error:</strong> This theme (%s) uses Full Site Editing, which requires the Gutenberg plugin to be activated.', 'theme' ),
    920 				$theme->display( 'Name' )
    921 			)
    922 		);
    923 	}
    924 
    925 	$requirements = array(
    926 		'requires'     => ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '',
    927 		'requires_php' => ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '',
    928 	);
    929 
    930 	$compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
    931 	$compatible_php = is_php_version_compatible( $requirements['requires_php'] );
    932 
    933 	if ( ! $compatible_wp && ! $compatible_php ) {
    934 		return new WP_Error(
    935 			'theme_wp_php_incompatible',
    936 			sprintf(
    937 				/* translators: %s: Theme name. */
    938 				_x( '<strong>Error:</strong> Current WordPress and PHP versions do not meet minimum requirements for %s.', 'theme' ),
    939 				$theme->display( 'Name' )
    940 			)
    941 		);
    942 	} elseif ( ! $compatible_php ) {
    943 		return new WP_Error(
    944 			'theme_php_incompatible',
    945 			sprintf(
    946 				/* translators: %s: Theme name. */
    947 				_x( '<strong>Error:</strong> Current PHP version does not meet minimum requirements for %s.', 'theme' ),
    948 				$theme->display( 'Name' )
    949 			)
    950 		);
    951 	} elseif ( ! $compatible_wp ) {
    952 		return new WP_Error(
    953 			'theme_wp_incompatible',
    954 			sprintf(
    955 				/* translators: %s: Theme name. */
    956 				_x( '<strong>Error:</strong> Current WordPress version does not meet minimum requirements for %s.', 'theme' ),
    957 				$theme->display( 'Name' )
    958 			)
    959 		);
    960 	}
    961 
    962 	return true;
    963 }
    964 
    965 /**
    966  * Retrieves all theme modifications.
    967  *
    968  * @since 3.1.0
    969  *
    970  * @return array|void Theme modifications.
    971  */
    972 function get_theme_mods() {
    973 	$theme_slug = get_option( 'stylesheet' );
    974 	$mods       = get_option( "theme_mods_$theme_slug" );
    975 	if ( false === $mods ) {
    976 		$theme_name = get_option( 'current_theme' );
    977 		if ( false === $theme_name ) {
    978 			$theme_name = wp_get_theme()->get( 'Name' );
    979 		}
    980 		$mods = get_option( "mods_$theme_name" ); // Deprecated location.
    981 		if ( is_admin() && false !== $mods ) {
    982 			update_option( "theme_mods_$theme_slug", $mods );
    983 			delete_option( "mods_$theme_name" );
    984 		}
    985 	}
    986 	return $mods;
    987 }
    988 
    989 /**
    990  * Retrieves theme modification value for the current theme.
    991  *
    992  * If the modification name does not exist, then the $default will be passed
    993  * through {@link https://www.php.net/sprintf sprintf()} PHP function with
    994  * the template directory URI as the first string and the stylesheet directory URI
    995  * as the second string.
    996  *
    997  * @since 2.1.0
    998  *
    999  * @param string       $name    Theme modification name.
   1000  * @param string|false $default Optional. Theme modification default value. Default false.
   1001  * @return mixed Theme modification value.
   1002  */
   1003 function get_theme_mod( $name, $default = false ) {
   1004 	$mods = get_theme_mods();
   1005 
   1006 	if ( isset( $mods[ $name ] ) ) {
   1007 		/**
   1008 		 * Filters the theme modification, or 'theme_mod', value.
   1009 		 *
   1010 		 * The dynamic portion of the hook name, `$name`, refers to the key name
   1011 		 * of the modification array. For example, 'header_textcolor', 'header_image',
   1012 		 * and so on depending on the theme options.
   1013 		 *
   1014 		 * @since 2.2.0
   1015 		 *
   1016 		 * @param string $current_mod The value of the current theme modification.
   1017 		 */
   1018 		return apply_filters( "theme_mod_{$name}", $mods[ $name ] );
   1019 	}
   1020 
   1021 	if ( is_string( $default ) ) {
   1022 		// Only run the replacement if an sprintf() string format pattern was found.
   1023 		if ( preg_match( '#(?<!%)%(?:\d+\$?)?s#', $default ) ) {
   1024 			// Remove a single trailing percent sign.
   1025 			$default = preg_replace( '#(?<!%)%$#', '', $default );
   1026 			$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
   1027 		}
   1028 	}
   1029 
   1030 	/** This filter is documented in wp-includes/theme.php */
   1031 	return apply_filters( "theme_mod_{$name}", $default );
   1032 }
   1033 
   1034 /**
   1035  * Updates theme modification value for the current theme.
   1036  *
   1037  * @since 2.1.0
   1038  * @since 5.6.0 A return value was added.
   1039  *
   1040  * @param string $name  Theme modification name.
   1041  * @param mixed  $value Theme modification value.
   1042  * @return bool True if the value was updated, false otherwise.
   1043  */
   1044 function set_theme_mod( $name, $value ) {
   1045 	$mods      = get_theme_mods();
   1046 	$old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;
   1047 
   1048 	/**
   1049 	 * Filters the theme modification, or 'theme_mod', value on save.
   1050 	 *
   1051 	 * The dynamic portion of the hook name, `$name`, refers to the key name
   1052 	 * of the modification array. For example, 'header_textcolor', 'header_image',
   1053 	 * and so on depending on the theme options.
   1054 	 *
   1055 	 * @since 3.9.0
   1056 	 *
   1057 	 * @param string $value     The new value of the theme modification.
   1058 	 * @param string $old_value The current value of the theme modification.
   1059 	 */
   1060 	$mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );
   1061 
   1062 	$theme = get_option( 'stylesheet' );
   1063 
   1064 	return update_option( "theme_mods_$theme", $mods );
   1065 }
   1066 
   1067 /**
   1068  * Removes theme modification name from current theme list.
   1069  *
   1070  * If removing the name also removes all elements, then the entire option
   1071  * will be removed.
   1072  *
   1073  * @since 2.1.0
   1074  *
   1075  * @param string $name Theme modification name.
   1076  */
   1077 function remove_theme_mod( $name ) {
   1078 	$mods = get_theme_mods();
   1079 
   1080 	if ( ! isset( $mods[ $name ] ) ) {
   1081 		return;
   1082 	}
   1083 
   1084 	unset( $mods[ $name ] );
   1085 
   1086 	if ( empty( $mods ) ) {
   1087 		remove_theme_mods();
   1088 		return;
   1089 	}
   1090 
   1091 	$theme = get_option( 'stylesheet' );
   1092 
   1093 	update_option( "theme_mods_$theme", $mods );
   1094 }
   1095 
   1096 /**
   1097  * Removes theme modifications option for current theme.
   1098  *
   1099  * @since 2.1.0
   1100  */
   1101 function remove_theme_mods() {
   1102 	delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
   1103 
   1104 	// Old style.
   1105 	$theme_name = get_option( 'current_theme' );
   1106 	if ( false === $theme_name ) {
   1107 		$theme_name = wp_get_theme()->get( 'Name' );
   1108 	}
   1109 
   1110 	delete_option( 'mods_' . $theme_name );
   1111 }
   1112 
   1113 /**
   1114  * Retrieves the custom header text color in 3- or 6-digit hexadecimal form.
   1115  *
   1116  * @since 2.1.0
   1117  *
   1118  * @return string Header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
   1119  */
   1120 function get_header_textcolor() {
   1121 	return get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
   1122 }
   1123 
   1124 /**
   1125  * Displays the custom header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
   1126  *
   1127  * @since 2.1.0
   1128  */
   1129 function header_textcolor() {
   1130 	echo get_header_textcolor();
   1131 }
   1132 
   1133 /**
   1134  * Whether to display the header text.
   1135  *
   1136  * @since 3.4.0
   1137  *
   1138  * @return bool
   1139  */
   1140 function display_header_text() {
   1141 	if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
   1142 		return false;
   1143 	}
   1144 
   1145 	$text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
   1146 	return 'blank' !== $text_color;
   1147 }
   1148 
   1149 /**
   1150  * Checks whether a header image is set or not.
   1151  *
   1152  * @since 4.2.0
   1153  *
   1154  * @see get_header_image()
   1155  *
   1156  * @return bool Whether a header image is set or not.
   1157  */
   1158 function has_header_image() {
   1159 	return (bool) get_header_image();
   1160 }
   1161 
   1162 /**
   1163  * Retrieves header image for custom header.
   1164  *
   1165  * @since 2.1.0
   1166  *
   1167  * @return string|false
   1168  */
   1169 function get_header_image() {
   1170 	$url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
   1171 
   1172 	if ( 'remove-header' === $url ) {
   1173 		return false;
   1174 	}
   1175 
   1176 	if ( is_random_header_image() ) {
   1177 		$url = get_random_header_image();
   1178 	}
   1179 
   1180 	return esc_url_raw( set_url_scheme( $url ) );
   1181 }
   1182 
   1183 /**
   1184  * Creates image tag markup for a custom header image.
   1185  *
   1186  * @since 4.4.0
   1187  *
   1188  * @param array $attr Optional. Additional attributes for the image tag. Can be used
   1189  *                              to override the default attributes. Default empty.
   1190  * @return string HTML image element markup or empty string on failure.
   1191  */
   1192 function get_header_image_tag( $attr = array() ) {
   1193 	$header      = get_custom_header();
   1194 	$header->url = get_header_image();
   1195 
   1196 	if ( ! $header->url ) {
   1197 		return '';
   1198 	}
   1199 
   1200 	$width  = absint( $header->width );
   1201 	$height = absint( $header->height );
   1202 
   1203 	$attr = wp_parse_args(
   1204 		$attr,
   1205 		array(
   1206 			'src'    => $header->url,
   1207 			'width'  => $width,
   1208 			'height' => $height,
   1209 			'alt'    => get_bloginfo( 'name' ),
   1210 		)
   1211 	);
   1212 
   1213 	// Generate 'srcset' and 'sizes' if not already present.
   1214 	if ( empty( $attr['srcset'] ) && ! empty( $header->attachment_id ) ) {
   1215 		$image_meta = get_post_meta( $header->attachment_id, '_wp_attachment_metadata', true );
   1216 		$size_array = array( $width, $height );
   1217 
   1218 		if ( is_array( $image_meta ) ) {
   1219 			$srcset = wp_calculate_image_srcset( $size_array, $header->url, $image_meta, $header->attachment_id );
   1220 			$sizes  = ! empty( $attr['sizes'] ) ? $attr['sizes'] : wp_calculate_image_sizes( $size_array, $header->url, $image_meta, $header->attachment_id );
   1221 
   1222 			if ( $srcset && $sizes ) {
   1223 				$attr['srcset'] = $srcset;
   1224 				$attr['sizes']  = $sizes;
   1225 			}
   1226 		}
   1227 	}
   1228 
   1229 	$attr = array_map( 'esc_attr', $attr );
   1230 	$html = '<img';
   1231 
   1232 	foreach ( $attr as $name => $value ) {
   1233 		$html .= ' ' . $name . '="' . $value . '"';
   1234 	}
   1235 
   1236 	$html .= ' />';
   1237 
   1238 	/**
   1239 	 * Filters the markup of header images.
   1240 	 *
   1241 	 * @since 4.4.0
   1242 	 *
   1243 	 * @param string $html   The HTML image tag markup being filtered.
   1244 	 * @param object $header The custom header object returned by 'get_custom_header()'.
   1245 	 * @param array  $attr   Array of the attributes for the image tag.
   1246 	 */
   1247 	return apply_filters( 'get_header_image_tag', $html, $header, $attr );
   1248 }
   1249 
   1250 /**
   1251  * Displays the image markup for a custom header image.
   1252  *
   1253  * @since 4.4.0
   1254  *
   1255  * @param array $attr Optional. Attributes for the image markup. Default empty.
   1256  */
   1257 function the_header_image_tag( $attr = array() ) {
   1258 	echo get_header_image_tag( $attr );
   1259 }
   1260 
   1261 /**
   1262  * Gets random header image data from registered images in theme.
   1263  *
   1264  * @since 3.4.0
   1265  *
   1266  * @access private
   1267  *
   1268  * @global array $_wp_default_headers
   1269  *
   1270  * @return object
   1271  */
   1272 function _get_random_header_data() {
   1273 	static $_wp_random_header = null;
   1274 
   1275 	if ( empty( $_wp_random_header ) ) {
   1276 		global $_wp_default_headers;
   1277 		$header_image_mod = get_theme_mod( 'header_image', '' );
   1278 		$headers          = array();
   1279 
   1280 		if ( 'random-uploaded-image' === $header_image_mod ) {
   1281 			$headers = get_uploaded_header_images();
   1282 		} elseif ( ! empty( $_wp_default_headers ) ) {
   1283 			if ( 'random-default-image' === $header_image_mod ) {
   1284 				$headers = $_wp_default_headers;
   1285 			} else {
   1286 				if ( current_theme_supports( 'custom-header', 'random-default' ) ) {
   1287 					$headers = $_wp_default_headers;
   1288 				}
   1289 			}
   1290 		}
   1291 
   1292 		if ( empty( $headers ) ) {
   1293 			return new stdClass;
   1294 		}
   1295 
   1296 		$_wp_random_header = (object) $headers[ array_rand( $headers ) ];
   1297 
   1298 		$_wp_random_header->url           = sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
   1299 		$_wp_random_header->thumbnail_url = sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
   1300 	}
   1301 
   1302 	return $_wp_random_header;
   1303 }
   1304 
   1305 /**
   1306  * Gets random header image URL from registered images in theme.
   1307  *
   1308  * @since 3.2.0
   1309  *
   1310  * @return string Path to header image.
   1311  */
   1312 function get_random_header_image() {
   1313 	$random_image = _get_random_header_data();
   1314 
   1315 	if ( empty( $random_image->url ) ) {
   1316 		return '';
   1317 	}
   1318 
   1319 	return $random_image->url;
   1320 }
   1321 
   1322 /**
   1323  * Checks if random header image is in use.
   1324  *
   1325  * Always true if user expressly chooses the option in Appearance > Header.
   1326  * Also true if theme has multiple header images registered, no specific header image
   1327  * is chosen, and theme turns on random headers with add_theme_support().
   1328  *
   1329  * @since 3.2.0
   1330  *
   1331  * @param string $type The random pool to use. Possible values include 'any',
   1332  *                     'default', 'uploaded'. Default 'any'.
   1333  * @return bool
   1334  */
   1335 function is_random_header_image( $type = 'any' ) {
   1336 	$header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
   1337 
   1338 	if ( 'any' === $type ) {
   1339 		if ( 'random-default-image' === $header_image_mod
   1340 			|| 'random-uploaded-image' === $header_image_mod
   1341 			|| ( '' !== get_random_header_image() && empty( $header_image_mod ) )
   1342 		) {
   1343 			return true;
   1344 		}
   1345 	} else {
   1346 		if ( "random-$type-image" === $header_image_mod ) {
   1347 			return true;
   1348 		} elseif ( 'default' === $type && empty( $header_image_mod ) && '' !== get_random_header_image() ) {
   1349 			return true;
   1350 		}
   1351 	}
   1352 
   1353 	return false;
   1354 }
   1355 
   1356 /**
   1357  * Displays header image URL.
   1358  *
   1359  * @since 2.1.0
   1360  */
   1361 function header_image() {
   1362 	$image = get_header_image();
   1363 
   1364 	if ( $image ) {
   1365 		echo esc_url( $image );
   1366 	}
   1367 }
   1368 
   1369 /**
   1370  * Gets the header images uploaded for the current theme.
   1371  *
   1372  * @since 3.2.0
   1373  *
   1374  * @return array
   1375  */
   1376 function get_uploaded_header_images() {
   1377 	$header_images = array();
   1378 
   1379 	// @todo Caching.
   1380 	$headers = get_posts(
   1381 		array(
   1382 			'post_type'  => 'attachment',
   1383 			'meta_key'   => '_wp_attachment_is_custom_header',
   1384 			'meta_value' => get_option( 'stylesheet' ),
   1385 			'orderby'    => 'none',
   1386 			'nopaging'   => true,
   1387 		)
   1388 	);
   1389 
   1390 	if ( empty( $headers ) ) {
   1391 		return array();
   1392 	}
   1393 
   1394 	foreach ( (array) $headers as $header ) {
   1395 		$url          = esc_url_raw( wp_get_attachment_url( $header->ID ) );
   1396 		$header_data  = wp_get_attachment_metadata( $header->ID );
   1397 		$header_index = $header->ID;
   1398 
   1399 		$header_images[ $header_index ]                      = array();
   1400 		$header_images[ $header_index ]['attachment_id']     = $header->ID;
   1401 		$header_images[ $header_index ]['url']               = $url;
   1402 		$header_images[ $header_index ]['thumbnail_url']     = $url;
   1403 		$header_images[ $header_index ]['alt_text']          = get_post_meta( $header->ID, '_wp_attachment_image_alt', true );
   1404 		$header_images[ $header_index ]['attachment_parent'] = isset( $header_data['attachment_parent'] ) ? $header_data['attachment_parent'] : '';
   1405 
   1406 		if ( isset( $header_data['width'] ) ) {
   1407 			$header_images[ $header_index ]['width'] = $header_data['width'];
   1408 		}
   1409 		if ( isset( $header_data['height'] ) ) {
   1410 			$header_images[ $header_index ]['height'] = $header_data['height'];
   1411 		}
   1412 	}
   1413 
   1414 	return $header_images;
   1415 }
   1416 
   1417 /**
   1418  * Gets the header image data.
   1419  *
   1420  * @since 3.4.0
   1421  *
   1422  * @global array $_wp_default_headers
   1423  *
   1424  * @return object
   1425  */
   1426 function get_custom_header() {
   1427 	global $_wp_default_headers;
   1428 
   1429 	if ( is_random_header_image() ) {
   1430 		$data = _get_random_header_data();
   1431 	} else {
   1432 		$data = get_theme_mod( 'header_image_data' );
   1433 		if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
   1434 			$directory_args        = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
   1435 			$data                  = array();
   1436 			$data['url']           = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
   1437 			$data['thumbnail_url'] = $data['url'];
   1438 			if ( ! empty( $_wp_default_headers ) ) {
   1439 				foreach ( (array) $_wp_default_headers as $default_header ) {
   1440 					$url = vsprintf( $default_header['url'], $directory_args );
   1441 					if ( $data['url'] == $url ) {
   1442 						$data                  = $default_header;
   1443 						$data['url']           = $url;
   1444 						$data['thumbnail_url'] = vsprintf( $data['thumbnail_url'], $directory_args );
   1445 						break;
   1446 					}
   1447 				}
   1448 			}
   1449 		}
   1450 	}
   1451 
   1452 	$default = array(
   1453 		'url'           => '',
   1454 		'thumbnail_url' => '',
   1455 		'width'         => get_theme_support( 'custom-header', 'width' ),
   1456 		'height'        => get_theme_support( 'custom-header', 'height' ),
   1457 		'video'         => get_theme_support( 'custom-header', 'video' ),
   1458 	);
   1459 	return (object) wp_parse_args( $data, $default );
   1460 }
   1461 
   1462 /**
   1463  * Registers a selection of default headers to be displayed by the custom header admin UI.
   1464  *
   1465  * @since 3.0.0
   1466  *
   1467  * @global array $_wp_default_headers
   1468  *
   1469  * @param array $headers Array of headers keyed by a string ID. The IDs point to arrays
   1470  *                       containing 'url', 'thumbnail_url', and 'description' keys.
   1471  */
   1472 function register_default_headers( $headers ) {
   1473 	global $_wp_default_headers;
   1474 
   1475 	$_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
   1476 }
   1477 
   1478 /**
   1479  * Unregisters default headers.
   1480  *
   1481  * This function must be called after register_default_headers() has already added the
   1482  * header you want to remove.
   1483  *
   1484  * @see register_default_headers()
   1485  * @since 3.0.0
   1486  *
   1487  * @global array $_wp_default_headers
   1488  *
   1489  * @param string|array $header The header string id (key of array) to remove, or an array thereof.
   1490  * @return bool|void A single header returns true on success, false on failure.
   1491  *                   There is currently no return value for multiple headers.
   1492  */
   1493 function unregister_default_headers( $header ) {
   1494 	global $_wp_default_headers;
   1495 	if ( is_array( $header ) ) {
   1496 		array_map( 'unregister_default_headers', $header );
   1497 	} elseif ( isset( $_wp_default_headers[ $header ] ) ) {
   1498 		unset( $_wp_default_headers[ $header ] );
   1499 		return true;
   1500 	} else {
   1501 		return false;
   1502 	}
   1503 }
   1504 
   1505 /**
   1506  * Checks whether a header video is set or not.
   1507  *
   1508  * @since 4.7.0
   1509  *
   1510  * @see get_header_video_url()
   1511  *
   1512  * @return bool Whether a header video is set or not.
   1513  */
   1514 function has_header_video() {
   1515 	return (bool) get_header_video_url();
   1516 }
   1517 
   1518 /**
   1519  * Retrieves header video URL for custom header.
   1520  *
   1521  * Uses a local video if present, or falls back to an external video.
   1522  *
   1523  * @since 4.7.0
   1524  *
   1525  * @return string|false Header video URL or false if there is no video.
   1526  */
   1527 function get_header_video_url() {
   1528 	$id = absint( get_theme_mod( 'header_video' ) );
   1529 
   1530 	if ( $id ) {
   1531 		// Get the file URL from the attachment ID.
   1532 		$url = wp_get_attachment_url( $id );
   1533 	} else {
   1534 		$url = get_theme_mod( 'external_header_video' );
   1535 	}
   1536 
   1537 	/**
   1538 	 * Filters the header video URL.
   1539 	 *
   1540 	 * @since 4.7.3
   1541 	 *
   1542 	 * @param string $url Header video URL, if available.
   1543 	 */
   1544 	$url = apply_filters( 'get_header_video_url', $url );
   1545 
   1546 	if ( ! $id && ! $url ) {
   1547 		return false;
   1548 	}
   1549 
   1550 	return esc_url_raw( set_url_scheme( $url ) );
   1551 }
   1552 
   1553 /**
   1554  * Displays header video URL.
   1555  *
   1556  * @since 4.7.0
   1557  */
   1558 function the_header_video_url() {
   1559 	$video = get_header_video_url();
   1560 
   1561 	if ( $video ) {
   1562 		echo esc_url( $video );
   1563 	}
   1564 }
   1565 
   1566 /**
   1567  * Retrieves header video settings.
   1568  *
   1569  * @since 4.7.0
   1570  *
   1571  * @return array
   1572  */
   1573 function get_header_video_settings() {
   1574 	$header     = get_custom_header();
   1575 	$video_url  = get_header_video_url();
   1576 	$video_type = wp_check_filetype( $video_url, wp_get_mime_types() );
   1577 
   1578 	$settings = array(
   1579 		'mimeType'  => '',
   1580 		'posterUrl' => get_header_image(),
   1581 		'videoUrl'  => $video_url,
   1582 		'width'     => absint( $header->width ),
   1583 		'height'    => absint( $header->height ),
   1584 		'minWidth'  => 900,
   1585 		'minHeight' => 500,
   1586 		'l10n'      => array(
   1587 			'pause'      => __( 'Pause' ),
   1588 			'play'       => __( 'Play' ),
   1589 			'pauseSpeak' => __( 'Video is paused.' ),
   1590 			'playSpeak'  => __( 'Video is playing.' ),
   1591 		),
   1592 	);
   1593 
   1594 	if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {
   1595 		$settings['mimeType'] = 'video/x-youtube';
   1596 	} elseif ( ! empty( $video_type['type'] ) ) {
   1597 		$settings['mimeType'] = $video_type['type'];
   1598 	}
   1599 
   1600 	/**
   1601 	 * Filters header video settings.
   1602 	 *
   1603 	 * @since 4.7.0
   1604 	 *
   1605 	 * @param array $settings An array of header video settings.
   1606 	 */
   1607 	return apply_filters( 'header_video_settings', $settings );
   1608 }
   1609 
   1610 /**
   1611  * Checks whether a custom header is set or not.
   1612  *
   1613  * @since 4.7.0
   1614  *
   1615  * @return bool True if a custom header is set. False if not.
   1616  */
   1617 function has_custom_header() {
   1618 	if ( has_header_image() || ( has_header_video() && is_header_video_active() ) ) {
   1619 		return true;
   1620 	}
   1621 
   1622 	return false;
   1623 }
   1624 
   1625 /**
   1626  * Checks whether the custom header video is eligible to show on the current page.
   1627  *
   1628  * @since 4.7.0
   1629  *
   1630  * @return bool True if the custom header video should be shown. False if not.
   1631  */
   1632 function is_header_video_active() {
   1633 	if ( ! get_theme_support( 'custom-header', 'video' ) ) {
   1634 		return false;
   1635 	}
   1636 
   1637 	$video_active_cb = get_theme_support( 'custom-header', 'video-active-callback' );
   1638 
   1639 	if ( empty( $video_active_cb ) || ! is_callable( $video_active_cb ) ) {
   1640 		$show_video = true;
   1641 	} else {
   1642 		$show_video = call_user_func( $video_active_cb );
   1643 	}
   1644 
   1645 	/**
   1646 	 * Filters whether the custom header video is eligible to show on the current page.
   1647 	 *
   1648 	 * @since 4.7.0
   1649 	 *
   1650 	 * @param bool $show_video Whether the custom header video should be shown. Returns the value
   1651 	 *                         of the theme setting for the `custom-header`'s `video-active-callback`.
   1652 	 *                         If no callback is set, the default value is that of `is_front_page()`.
   1653 	 */
   1654 	return apply_filters( 'is_header_video_active', $show_video );
   1655 }
   1656 
   1657 /**
   1658  * Retrieves the markup for a custom header.
   1659  *
   1660  * The container div will always be returned in the Customizer preview.
   1661  *
   1662  * @since 4.7.0
   1663  *
   1664  * @return string The markup for a custom header on success.
   1665  */
   1666 function get_custom_header_markup() {
   1667 	if ( ! has_custom_header() && ! is_customize_preview() ) {
   1668 		return '';
   1669 	}
   1670 
   1671 	return sprintf(
   1672 		'<div id="wp-custom-header" class="wp-custom-header">%s</div>',
   1673 		get_header_image_tag()
   1674 	);
   1675 }
   1676 
   1677 /**
   1678  * Prints the markup for a custom header.
   1679  *
   1680  * A container div will always be printed in the Customizer preview.
   1681  *
   1682  * @since 4.7.0
   1683  */
   1684 function the_custom_header_markup() {
   1685 	$custom_header = get_custom_header_markup();
   1686 	if ( empty( $custom_header ) ) {
   1687 		return;
   1688 	}
   1689 
   1690 	echo $custom_header;
   1691 
   1692 	if ( is_header_video_active() && ( has_header_video() || is_customize_preview() ) ) {
   1693 		wp_enqueue_script( 'wp-custom-header' );
   1694 		wp_localize_script( 'wp-custom-header', '_wpCustomHeaderSettings', get_header_video_settings() );
   1695 	}
   1696 }
   1697 
   1698 /**
   1699  * Retrieves background image for custom background.
   1700  *
   1701  * @since 3.0.0
   1702  *
   1703  * @return string
   1704  */
   1705 function get_background_image() {
   1706 	return get_theme_mod( 'background_image', get_theme_support( 'custom-background', 'default-image' ) );
   1707 }
   1708 
   1709 /**
   1710  * Displays background image path.
   1711  *
   1712  * @since 3.0.0
   1713  */
   1714 function background_image() {
   1715 	echo get_background_image();
   1716 }
   1717 
   1718 /**
   1719  * Retrieves value for custom background color.
   1720  *
   1721  * @since 3.0.0
   1722  *
   1723  * @return string
   1724  */
   1725 function get_background_color() {
   1726 	return get_theme_mod( 'background_color', get_theme_support( 'custom-background', 'default-color' ) );
   1727 }
   1728 
   1729 /**
   1730  * Displays background color value.
   1731  *
   1732  * @since 3.0.0
   1733  */
   1734 function background_color() {
   1735 	echo get_background_color();
   1736 }
   1737 
   1738 /**
   1739  * Default custom background callback.
   1740  *
   1741  * @since 3.0.0
   1742  */
   1743 function _custom_background_cb() {
   1744 	// $background is the saved custom image, or the default image.
   1745 	$background = set_url_scheme( get_background_image() );
   1746 
   1747 	// $color is the saved custom color.
   1748 	// A default has to be specified in style.css. It will not be printed here.
   1749 	$color = get_background_color();
   1750 
   1751 	if ( get_theme_support( 'custom-background', 'default-color' ) === $color ) {
   1752 		$color = false;
   1753 	}
   1754 
   1755 	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
   1756 
   1757 	if ( ! $background && ! $color ) {
   1758 		if ( is_customize_preview() ) {
   1759 			printf( '<style%s id="custom-background-css"></style>', $type_attr );
   1760 		}
   1761 		return;
   1762 	}
   1763 
   1764 	$style = $color ? "background-color: #$color;" : '';
   1765 
   1766 	if ( $background ) {
   1767 		$image = ' background-image: url("' . esc_url_raw( $background ) . '");';
   1768 
   1769 		// Background Position.
   1770 		$position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) );
   1771 		$position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) );
   1772 
   1773 		if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) {
   1774 			$position_x = 'left';
   1775 		}
   1776 
   1777 		if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) {
   1778 			$position_y = 'top';
   1779 		}
   1780 
   1781 		$position = " background-position: $position_x $position_y;";
   1782 
   1783 		// Background Size.
   1784 		$size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) );
   1785 
   1786 		if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) {
   1787 			$size = 'auto';
   1788 		}
   1789 
   1790 		$size = " background-size: $size;";
   1791 
   1792 		// Background Repeat.
   1793 		$repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
   1794 
   1795 		if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) {
   1796 			$repeat = 'repeat';
   1797 		}
   1798 
   1799 		$repeat = " background-repeat: $repeat;";
   1800 
   1801 		// Background Scroll.
   1802 		$attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
   1803 
   1804 		if ( 'fixed' !== $attachment ) {
   1805 			$attachment = 'scroll';
   1806 		}
   1807 
   1808 		$attachment = " background-attachment: $attachment;";
   1809 
   1810 		$style .= $image . $position . $size . $repeat . $attachment;
   1811 	}
   1812 	?>
   1813 <style<?php echo $type_attr; ?> id="custom-background-css">
   1814 body.custom-background { <?php echo trim( $style ); ?> }
   1815 </style>
   1816 	<?php
   1817 }
   1818 
   1819 /**
   1820  * Renders the Custom CSS style element.
   1821  *
   1822  * @since 4.7.0
   1823  */
   1824 function wp_custom_css_cb() {
   1825 	$styles = wp_get_custom_css();
   1826 	if ( $styles || is_customize_preview() ) :
   1827 		$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
   1828 		?>
   1829 		<style<?php echo $type_attr; ?> id="wp-custom-css">
   1830 			<?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
   1831 		</style>
   1832 		<?php
   1833 	endif;
   1834 }
   1835 
   1836 /**
   1837  * Fetches the `custom_css` post for a given theme.
   1838  *
   1839  * @since 4.7.0
   1840  *
   1841  * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
   1842  * @return WP_Post|null The custom_css post or null if none exists.
   1843  */
   1844 function wp_get_custom_css_post( $stylesheet = '' ) {
   1845 	if ( empty( $stylesheet ) ) {
   1846 		$stylesheet = get_stylesheet();
   1847 	}
   1848 
   1849 	$custom_css_query_vars = array(
   1850 		'post_type'              => 'custom_css',
   1851 		'post_status'            => get_post_stati(),
   1852 		'name'                   => sanitize_title( $stylesheet ),
   1853 		'posts_per_page'         => 1,
   1854 		'no_found_rows'          => true,
   1855 		'cache_results'          => true,
   1856 		'update_post_meta_cache' => false,
   1857 		'update_post_term_cache' => false,
   1858 		'lazy_load_term_meta'    => false,
   1859 	);
   1860 
   1861 	$post = null;
   1862 	if ( get_stylesheet() === $stylesheet ) {
   1863 		$post_id = get_theme_mod( 'custom_css_post_id' );
   1864 
   1865 		if ( $post_id > 0 && get_post( $post_id ) ) {
   1866 			$post = get_post( $post_id );
   1867 		}
   1868 
   1869 		// `-1` indicates no post exists; no query necessary.
   1870 		if ( ! $post && -1 !== $post_id ) {
   1871 			$query = new WP_Query( $custom_css_query_vars );
   1872 			$post  = $query->post;
   1873 			/*
   1874 			 * Cache the lookup. See wp_update_custom_css_post().
   1875 			 * @todo This should get cleared if a custom_css post is added/removed.
   1876 			 */
   1877 			set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
   1878 		}
   1879 	} else {
   1880 		$query = new WP_Query( $custom_css_query_vars );
   1881 		$post  = $query->post;
   1882 	}
   1883 
   1884 	return $post;
   1885 }
   1886 
   1887 /**
   1888  * Fetches the saved Custom CSS content for rendering.
   1889  *
   1890  * @since 4.7.0
   1891  *
   1892  * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
   1893  * @return string The Custom CSS Post content.
   1894  */
   1895 function wp_get_custom_css( $stylesheet = '' ) {
   1896 	$css = '';
   1897 
   1898 	if ( empty( $stylesheet ) ) {
   1899 		$stylesheet = get_stylesheet();
   1900 	}
   1901 
   1902 	$post = wp_get_custom_css_post( $stylesheet );
   1903 	if ( $post ) {
   1904 		$css = $post->post_content;
   1905 	}
   1906 
   1907 	/**
   1908 	 * Filters the Custom CSS Output into the <head>.
   1909 	 *
   1910 	 * @since 4.7.0
   1911 	 *
   1912 	 * @param string $css        CSS pulled in from the Custom CSS CPT.
   1913 	 * @param string $stylesheet The theme stylesheet name.
   1914 	 */
   1915 	$css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
   1916 
   1917 	return $css;
   1918 }
   1919 
   1920 /**
   1921  * Updates the `custom_css` post for a given theme.
   1922  *
   1923  * Inserts a `custom_css` post when one doesn't yet exist.
   1924  *
   1925  * @since 4.7.0
   1926  *
   1927  * @param string $css CSS, stored in `post_content`.
   1928  * @param array  $args {
   1929  *     Args.
   1930  *
   1931  *     @type string $preprocessed Pre-processed CSS, stored in `post_content_filtered`. Normally empty string. Optional.
   1932  *     @type string $stylesheet   Stylesheet (child theme) to update. Optional, defaults to current theme/stylesheet.
   1933  * }
   1934  * @return WP_Post|WP_Error Post on success, error on failure.
   1935  */
   1936 function wp_update_custom_css_post( $css, $args = array() ) {
   1937 	$args = wp_parse_args(
   1938 		$args,
   1939 		array(
   1940 			'preprocessed' => '',
   1941 			'stylesheet'   => get_stylesheet(),
   1942 		)
   1943 	);
   1944 
   1945 	$data = array(
   1946 		'css'          => $css,
   1947 		'preprocessed' => $args['preprocessed'],
   1948 	);
   1949 
   1950 	/**
   1951 	 * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args for a `custom_css` post being updated.
   1952 	 *
   1953 	 * This filter can be used by plugin that offer CSS pre-processors, to store the original
   1954 	 * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`.
   1955 	 * When used in this way, the `post_content_filtered` should be supplied as the setting value
   1956 	 * instead of `post_content` via a the `customize_value_custom_css` filter, for example:
   1957 	 *
   1958 	 * <code>
   1959 	 * add_filter( 'customize_value_custom_css', function( $value, $setting ) {
   1960 	 *     $post = wp_get_custom_css_post( $setting->stylesheet );
   1961 	 *     if ( $post && ! empty( $post->post_content_filtered ) ) {
   1962 	 *         $css = $post->post_content_filtered;
   1963 	 *     }
   1964 	 *     return $css;
   1965 	 * }, 10, 2 );
   1966 	 * </code>
   1967 	 *
   1968 	 * @since 4.7.0
   1969 	 * @param array $data {
   1970 	 *     Custom CSS data.
   1971 	 *
   1972 	 *     @type string $css          CSS stored in `post_content`.
   1973 	 *     @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`. Normally empty string.
   1974 	 * }
   1975 	 * @param array $args {
   1976 	 *     The args passed into `wp_update_custom_css_post()` merged with defaults.
   1977 	 *
   1978 	 *     @type string $css          The original CSS passed in to be updated.
   1979 	 *     @type string $preprocessed The original preprocessed CSS passed in to be updated.
   1980 	 *     @type string $stylesheet   The stylesheet (theme) being updated.
   1981 	 * }
   1982 	 */
   1983 	$data = apply_filters( 'update_custom_css_data', $data, array_merge( $args, compact( 'css' ) ) );
   1984 
   1985 	$post_data = array(
   1986 		'post_title'            => $args['stylesheet'],
   1987 		'post_name'             => sanitize_title( $args['stylesheet'] ),
   1988 		'post_type'             => 'custom_css',
   1989 		'post_status'           => 'publish',
   1990 		'post_content'          => $data['css'],
   1991 		'post_content_filtered' => $data['preprocessed'],
   1992 	);
   1993 
   1994 	// Update post if it already exists, otherwise create a new one.
   1995 	$post = wp_get_custom_css_post( $args['stylesheet'] );
   1996 	if ( $post ) {
   1997 		$post_data['ID'] = $post->ID;
   1998 		$r               = wp_update_post( wp_slash( $post_data ), true );
   1999 	} else {
   2000 		$r = wp_insert_post( wp_slash( $post_data ), true );
   2001 
   2002 		if ( ! is_wp_error( $r ) ) {
   2003 			if ( get_stylesheet() === $args['stylesheet'] ) {
   2004 				set_theme_mod( 'custom_css_post_id', $r );
   2005 			}
   2006 
   2007 			// Trigger creation of a revision. This should be removed once #30854 is resolved.
   2008 			if ( 0 === count( wp_get_post_revisions( $r ) ) ) {
   2009 				wp_save_post_revision( $r );
   2010 			}
   2011 		}
   2012 	}
   2013 
   2014 	if ( is_wp_error( $r ) ) {
   2015 		return $r;
   2016 	}
   2017 	return get_post( $r );
   2018 }
   2019 
   2020 /**
   2021  * Adds callback for custom TinyMCE editor stylesheets.
   2022  *
   2023  * The parameter $stylesheet is the name of the stylesheet, relative to
   2024  * the theme root. It also accepts an array of stylesheets.
   2025  * It is optional and defaults to 'editor-style.css'.
   2026  *
   2027  * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
   2028  * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
   2029  * If an array of stylesheets is passed to add_editor_style(),
   2030  * RTL is only added for the first stylesheet.
   2031  *
   2032  * Since version 3.4 the TinyMCE body has .rtl CSS class.
   2033  * It is a better option to use that class and add any RTL styles to the main stylesheet.
   2034  *
   2035  * @since 3.0.0
   2036  *
   2037  * @global array $editor_styles
   2038  *
   2039  * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
   2040  *                                 Defaults to 'editor-style.css'
   2041  */
   2042 function add_editor_style( $stylesheet = 'editor-style.css' ) {
   2043 	global $editor_styles;
   2044 
   2045 	add_theme_support( 'editor-style' );
   2046 
   2047 	$editor_styles = (array) $editor_styles;
   2048 	$stylesheet    = (array) $stylesheet;
   2049 
   2050 	if ( is_rtl() ) {
   2051 		$rtl_stylesheet = str_replace( '.css', '-rtl.css', $stylesheet[0] );
   2052 		$stylesheet[]   = $rtl_stylesheet;
   2053 	}
   2054 
   2055 	$editor_styles = array_merge( $editor_styles, $stylesheet );
   2056 }
   2057 
   2058 /**
   2059  * Removes all visual editor stylesheets.
   2060  *
   2061  * @since 3.1.0
   2062  *
   2063  * @global array $editor_styles
   2064  *
   2065  * @return bool True on success, false if there were no stylesheets to remove.
   2066  */
   2067 function remove_editor_styles() {
   2068 	if ( ! current_theme_supports( 'editor-style' ) ) {
   2069 		return false;
   2070 	}
   2071 	_remove_theme_support( 'editor-style' );
   2072 	if ( is_admin() ) {
   2073 		$GLOBALS['editor_styles'] = array();
   2074 	}
   2075 	return true;
   2076 }
   2077 
   2078 /**
   2079  * Retrieves any registered editor stylesheet URLs.
   2080  *
   2081  * @since 4.0.0
   2082  *
   2083  * @global array $editor_styles Registered editor stylesheets
   2084  *
   2085  * @return string[] If registered, a list of editor stylesheet URLs.
   2086  */
   2087 function get_editor_stylesheets() {
   2088 	$stylesheets = array();
   2089 	// Load editor_style.css if the current theme supports it.
   2090 	if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
   2091 		$editor_styles = $GLOBALS['editor_styles'];
   2092 
   2093 		$editor_styles = array_unique( array_filter( $editor_styles ) );
   2094 		$style_uri     = get_stylesheet_directory_uri();
   2095 		$style_dir     = get_stylesheet_directory();
   2096 
   2097 		// Support externally referenced styles (like, say, fonts).
   2098 		foreach ( $editor_styles as $key => $file ) {
   2099 			if ( preg_match( '~^(https?:)?//~', $file ) ) {
   2100 				$stylesheets[] = esc_url_raw( $file );
   2101 				unset( $editor_styles[ $key ] );
   2102 			}
   2103 		}
   2104 
   2105 		// Look in a parent theme first, that way child theme CSS overrides.
   2106 		if ( is_child_theme() ) {
   2107 			$template_uri = get_template_directory_uri();
   2108 			$template_dir = get_template_directory();
   2109 
   2110 			foreach ( $editor_styles as $key => $file ) {
   2111 				if ( $file && file_exists( "$template_dir/$file" ) ) {
   2112 					$stylesheets[] = "$template_uri/$file";
   2113 				}
   2114 			}
   2115 		}
   2116 
   2117 		foreach ( $editor_styles as $file ) {
   2118 			if ( $file && file_exists( "$style_dir/$file" ) ) {
   2119 				$stylesheets[] = "$style_uri/$file";
   2120 			}
   2121 		}
   2122 	}
   2123 
   2124 	/**
   2125 	 * Filters the array of URLs of stylesheets applied to the editor.
   2126 	 *
   2127 	 * @since 4.3.0
   2128 	 *
   2129 	 * @param string[] $stylesheets Array of URLs of stylesheets to be applied to the editor.
   2130 	 */
   2131 	return apply_filters( 'editor_stylesheets', $stylesheets );
   2132 }
   2133 
   2134 /**
   2135  * Expands a theme's starter content configuration using core-provided data.
   2136  *
   2137  * @since 4.7.0
   2138  *
   2139  * @return array Array of starter content.
   2140  */
   2141 function get_theme_starter_content() {
   2142 	$theme_support = get_theme_support( 'starter-content' );
   2143 	if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
   2144 		$config = $theme_support[0];
   2145 	} else {
   2146 		$config = array();
   2147 	}
   2148 
   2149 	$core_content = array(
   2150 		'widgets'   => array(
   2151 			'text_business_info' => array(
   2152 				'text',
   2153 				array(
   2154 					'title'  => _x( 'Find Us', 'Theme starter content' ),
   2155 					'text'   => implode(
   2156 						'',
   2157 						array(
   2158 							'<strong>' . _x( 'Address', 'Theme starter content' ) . "</strong>\n",
   2159 							_x( '123 Main Street', 'Theme starter content' ) . "\n" . _x( 'New York, NY 10001', 'Theme starter content' ) . "\n\n",
   2160 							'<strong>' . _x( 'Hours', 'Theme starter content' ) . "</strong>\n",
   2161 							_x( 'Monday&ndash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . "\n" . _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ),
   2162 						)
   2163 					),
   2164 					'filter' => true,
   2165 					'visual' => true,
   2166 				),
   2167 			),
   2168 			'text_about'         => array(
   2169 				'text',
   2170 				array(
   2171 					'title'  => _x( 'About This Site', 'Theme starter content' ),
   2172 					'text'   => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
   2173 					'filter' => true,
   2174 					'visual' => true,
   2175 				),
   2176 			),
   2177 			'archives'           => array(
   2178 				'archives',
   2179 				array(
   2180 					'title' => _x( 'Archives', 'Theme starter content' ),
   2181 				),
   2182 			),
   2183 			'calendar'           => array(
   2184 				'calendar',
   2185 				array(
   2186 					'title' => _x( 'Calendar', 'Theme starter content' ),
   2187 				),
   2188 			),
   2189 			'categories'         => array(
   2190 				'categories',
   2191 				array(
   2192 					'title' => _x( 'Categories', 'Theme starter content' ),
   2193 				),
   2194 			),
   2195 			'meta'               => array(
   2196 				'meta',
   2197 				array(
   2198 					'title' => _x( 'Meta', 'Theme starter content' ),
   2199 				),
   2200 			),
   2201 			'recent-comments'    => array(
   2202 				'recent-comments',
   2203 				array(
   2204 					'title' => _x( 'Recent Comments', 'Theme starter content' ),
   2205 				),
   2206 			),
   2207 			'recent-posts'       => array(
   2208 				'recent-posts',
   2209 				array(
   2210 					'title' => _x( 'Recent Posts', 'Theme starter content' ),
   2211 				),
   2212 			),
   2213 			'search'             => array(
   2214 				'search',
   2215 				array(
   2216 					'title' => _x( 'Search', 'Theme starter content' ),
   2217 				),
   2218 			),
   2219 		),
   2220 		'nav_menus' => array(
   2221 			'link_home'       => array(
   2222 				'type'  => 'custom',
   2223 				'title' => _x( 'Home', 'Theme starter content' ),
   2224 				'url'   => home_url( '/' ),
   2225 			),
   2226 			'page_home'       => array( // Deprecated in favor of 'link_home'.
   2227 				'type'      => 'post_type',
   2228 				'object'    => 'page',
   2229 				'object_id' => '{{home}}',
   2230 			),
   2231 			'page_about'      => array(
   2232 				'type'      => 'post_type',
   2233 				'object'    => 'page',
   2234 				'object_id' => '{{about}}',
   2235 			),
   2236 			'page_blog'       => array(
   2237 				'type'      => 'post_type',
   2238 				'object'    => 'page',
   2239 				'object_id' => '{{blog}}',
   2240 			),
   2241 			'page_news'       => array(
   2242 				'type'      => 'post_type',
   2243 				'object'    => 'page',
   2244 				'object_id' => '{{news}}',
   2245 			),
   2246 			'page_contact'    => array(
   2247 				'type'      => 'post_type',
   2248 				'object'    => 'page',
   2249 				'object_id' => '{{contact}}',
   2250 			),
   2251 
   2252 			'link_email'      => array(
   2253 				'title' => _x( 'Email', 'Theme starter content' ),
   2254 				'url'   => 'mailto:wordpress@example.com',
   2255 			),
   2256 			'link_facebook'   => array(
   2257 				'title' => _x( 'Facebook', 'Theme starter content' ),
   2258 				'url'   => 'https://www.facebook.com/wordpress',
   2259 			),
   2260 			'link_foursquare' => array(
   2261 				'title' => _x( 'Foursquare', 'Theme starter content' ),
   2262 				'url'   => 'https://foursquare.com/',
   2263 			),
   2264 			'link_github'     => array(
   2265 				'title' => _x( 'GitHub', 'Theme starter content' ),
   2266 				'url'   => 'https://github.com/wordpress/',
   2267 			),
   2268 			'link_instagram'  => array(
   2269 				'title' => _x( 'Instagram', 'Theme starter content' ),
   2270 				'url'   => 'https://www.instagram.com/explore/tags/wordcamp/',
   2271 			),
   2272 			'link_linkedin'   => array(
   2273 				'title' => _x( 'LinkedIn', 'Theme starter content' ),
   2274 				'url'   => 'https://www.linkedin.com/company/1089783',
   2275 			),
   2276 			'link_pinterest'  => array(
   2277 				'title' => _x( 'Pinterest', 'Theme starter content' ),
   2278 				'url'   => 'https://www.pinterest.com/',
   2279 			),
   2280 			'link_twitter'    => array(
   2281 				'title' => _x( 'Twitter', 'Theme starter content' ),
   2282 				'url'   => 'https://twitter.com/wordpress',
   2283 			),
   2284 			'link_yelp'       => array(
   2285 				'title' => _x( 'Yelp', 'Theme starter content' ),
   2286 				'url'   => 'https://www.yelp.com',
   2287 			),
   2288 			'link_youtube'    => array(
   2289 				'title' => _x( 'YouTube', 'Theme starter content' ),
   2290 				'url'   => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
   2291 			),
   2292 		),
   2293 		'posts'     => array(
   2294 			'home'             => array(
   2295 				'post_type'    => 'page',
   2296 				'post_title'   => _x( 'Home', 'Theme starter content' ),
   2297 				'post_content' => sprintf(
   2298 					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
   2299 					_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' )
   2300 				),
   2301 			),
   2302 			'about'            => array(
   2303 				'post_type'    => 'page',
   2304 				'post_title'   => _x( 'About', 'Theme starter content' ),
   2305 				'post_content' => sprintf(
   2306 					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
   2307 					_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' )
   2308 				),
   2309 			),
   2310 			'contact'          => array(
   2311 				'post_type'    => 'page',
   2312 				'post_title'   => _x( 'Contact', 'Theme starter content' ),
   2313 				'post_content' => sprintf(
   2314 					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
   2315 					_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' )
   2316 				),
   2317 			),
   2318 			'blog'             => array(
   2319 				'post_type'  => 'page',
   2320 				'post_title' => _x( 'Blog', 'Theme starter content' ),
   2321 			),
   2322 			'news'             => array(
   2323 				'post_type'  => 'page',
   2324 				'post_title' => _x( 'News', 'Theme starter content' ),
   2325 			),
   2326 
   2327 			'homepage-section' => array(
   2328 				'post_type'    => 'page',
   2329 				'post_title'   => _x( 'A homepage section', 'Theme starter content' ),
   2330 				'post_content' => sprintf(
   2331 					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
   2332 					_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' )
   2333 				),
   2334 			),
   2335 		),
   2336 	);
   2337 
   2338 	$content = array();
   2339 
   2340 	foreach ( $config as $type => $args ) {
   2341 		switch ( $type ) {
   2342 			// Use options and theme_mods as-is.
   2343 			case 'options':
   2344 			case 'theme_mods':
   2345 				$content[ $type ] = $config[ $type ];
   2346 				break;
   2347 
   2348 			// Widgets are grouped into sidebars.
   2349 			case 'widgets':
   2350 				foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
   2351 					foreach ( $widgets as $id => $widget ) {
   2352 						if ( is_array( $widget ) ) {
   2353 
   2354 							// Item extends core content.
   2355 							if ( ! empty( $core_content[ $type ][ $id ] ) ) {
   2356 								$widget = array(
   2357 									$core_content[ $type ][ $id ][0],
   2358 									array_merge( $core_content[ $type ][ $id ][1], $widget ),
   2359 								);
   2360 							}
   2361 
   2362 							$content[ $type ][ $sidebar_id ][] = $widget;
   2363 						} elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
   2364 							$content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
   2365 						}
   2366 					}
   2367 				}
   2368 				break;
   2369 
   2370 			// And nav menu items are grouped into nav menus.
   2371 			case 'nav_menus':
   2372 				foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
   2373 
   2374 					// Ensure nav menus get a name.
   2375 					if ( empty( $nav_menu['name'] ) ) {
   2376 						$nav_menu['name'] = $nav_menu_location;
   2377 					}
   2378 
   2379 					$content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
   2380 
   2381 					foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
   2382 						if ( is_array( $nav_menu_item ) ) {
   2383 
   2384 							// Item extends core content.
   2385 							if ( ! empty( $core_content[ $type ][ $id ] ) ) {
   2386 								$nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
   2387 							}
   2388 
   2389 							$content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
   2390 						} elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
   2391 							$content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
   2392 						}
   2393 					}
   2394 				}
   2395 				break;
   2396 
   2397 			// Attachments are posts but have special treatment.
   2398 			case 'attachments':
   2399 				foreach ( $config[ $type ] as $id => $item ) {
   2400 					if ( ! empty( $item['file'] ) ) {
   2401 						$content[ $type ][ $id ] = $item;
   2402 					}
   2403 				}
   2404 				break;
   2405 
   2406 			// All that's left now are posts (besides attachments).
   2407 			// Not a default case for the sake of clarity and future work.
   2408 			case 'posts':
   2409 				foreach ( $config[ $type ] as $id => $item ) {
   2410 					if ( is_array( $item ) ) {
   2411 
   2412 						// Item extends core content.
   2413 						if ( ! empty( $core_content[ $type ][ $id ] ) ) {
   2414 							$item = array_merge( $core_content[ $type ][ $id ], $item );
   2415 						}
   2416 
   2417 						// Enforce a subset of fields.
   2418 						$content[ $type ][ $id ] = wp_array_slice_assoc(
   2419 							$item,
   2420 							array(
   2421 								'post_type',
   2422 								'post_title',
   2423 								'post_excerpt',
   2424 								'post_name',
   2425 								'post_content',
   2426 								'menu_order',
   2427 								'comment_status',
   2428 								'thumbnail',
   2429 								'template',
   2430 							)
   2431 						);
   2432 					} elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
   2433 						$content[ $type ][ $item ] = $core_content[ $type ][ $item ];
   2434 					}
   2435 				}
   2436 				break;
   2437 		}
   2438 	}
   2439 
   2440 	/**
   2441 	 * Filters the expanded array of starter content.
   2442 	 *
   2443 	 * @since 4.7.0
   2444 	 *
   2445 	 * @param array $content Array of starter content.
   2446 	 * @param array $config  Array of theme-specific starter content configuration.
   2447 	 */
   2448 	return apply_filters( 'get_theme_starter_content', $content, $config );
   2449 }
   2450 
   2451 /**
   2452  * Registers theme support for a given feature.
   2453  *
   2454  * Must be called in the theme's functions.php file to work.
   2455  * If attached to a hook, it must be {@see 'after_setup_theme'}.
   2456  * The {@see 'init'} hook may be too late for some features.
   2457  *
   2458  * Example usage:
   2459  *
   2460  *     add_theme_support( 'title-tag' );
   2461  *     add_theme_support( 'custom-logo', array(
   2462  *         'height' => 480,
   2463  *         'width'  => 720,
   2464  *     ) );
   2465  *
   2466  * @since 2.9.0
   2467  * @since 3.4.0 The `custom-header-uploads` feature was deprecated.
   2468  * @since 3.6.0 The `html5` feature was added.
   2469  * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'.
   2470  * @since 4.1.0 The `title-tag` feature was added.
   2471  * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added.
   2472  * @since 4.7.0 The `starter-content` feature was added.
   2473  * @since 5.0.0 The `responsive-embeds`, `align-wide`, `dark-editor-style`, `disable-custom-colors`,
   2474  *              `disable-custom-font-sizes`, `editor-color-palette`, `editor-font-sizes`,
   2475  *              `editor-styles`, and `wp-block-styles` features were added.
   2476  * @since 5.3.0 The `html5` feature now also accepts 'script' and 'style'.
   2477  * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
   2478  *              by adding it to the function signature.
   2479  * @since 5.5.0 The `core-block-patterns` feature was added and is enabled by default.
   2480  * @since 5.5.0 The `custom-logo` feature now also accepts 'unlink-homepage-logo'.
   2481  * @since 5.6.0 The `post-formats` feature warns if no array is passed.
   2482  * @since 5.8.0 The `widgets-block-editor` feature enables the Widgets block editor.
   2483  *
   2484  * @global array $_wp_theme_features
   2485  *
   2486  * @param string $feature The feature being added. Likely core values include:
   2487  *                          - 'admin-bar'
   2488  *                          - 'align-wide'
   2489  *                          - 'automatic-feed-links'
   2490  *                          - 'core-block-patterns'
   2491  *                          - 'custom-background'
   2492  *                          - 'custom-header'
   2493  *                          - 'custom-line-height'
   2494  *                          - 'custom-logo'
   2495  *                          - 'customize-selective-refresh-widgets'
   2496  *                          - 'custom-spacing'
   2497  *                          - 'custom-units'
   2498  *                          - 'dark-editor-style'
   2499  *                          - 'disable-custom-colors'
   2500  *                          - 'disable-custom-font-sizes'
   2501  *                          - 'editor-color-palette'
   2502  *                          - 'editor-gradient-presets'
   2503  *                          - 'editor-font-sizes'
   2504  *                          - 'editor-styles'
   2505  *                          - 'featured-content'
   2506  *                          - 'html5'
   2507  *                          - 'menus'
   2508  *                          - 'post-formats'
   2509  *                          - 'post-thumbnails'
   2510  *                          - 'responsive-embeds'
   2511  *                          - 'starter-content'
   2512  *                          - 'title-tag'
   2513  *                          - 'wp-block-styles'
   2514  *                          - 'widgets'
   2515  *                          - 'widgets-block-editor'
   2516  * @param mixed  ...$args Optional extra arguments to pass along with certain features.
   2517  * @return void|false Void on success, false on failure.
   2518  */
   2519 function add_theme_support( $feature, ...$args ) {
   2520 	global $_wp_theme_features;
   2521 
   2522 	if ( ! $args ) {
   2523 		$args = true;
   2524 	}
   2525 
   2526 	switch ( $feature ) {
   2527 		case 'post-thumbnails':
   2528 			// All post types are already supported.
   2529 			if ( true === get_theme_support( 'post-thumbnails' ) ) {
   2530 				return;
   2531 			}
   2532 
   2533 			/*
   2534 			 * Merge post types with any that already declared their support
   2535 			 * for post thumbnails.
   2536 			 */
   2537 			if ( isset( $args[0] ) && is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
   2538 				$args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
   2539 			}
   2540 
   2541 			break;
   2542 
   2543 		case 'post-formats':
   2544 			if ( isset( $args[0] ) && is_array( $args[0] ) ) {
   2545 				$post_formats = get_post_format_slugs();
   2546 				unset( $post_formats['standard'] );
   2547 
   2548 				$args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
   2549 			} else {
   2550 				_doing_it_wrong( "add_theme_support( 'post-formats' )", __( 'You need to pass an array of post formats.' ), '5.6.0' );
   2551 				return false;
   2552 			}
   2553 			break;
   2554 
   2555 		case 'html5':
   2556 			// You can't just pass 'html5', you need to pass an array of types.
   2557 			if ( empty( $args[0] ) ) {
   2558 				// Build an array of types for back-compat.
   2559 				$args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
   2560 			} elseif ( ! isset( $args[0] ) || ! is_array( $args[0] ) ) {
   2561 				_doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
   2562 				return false;
   2563 			}
   2564 
   2565 			// Calling 'html5' again merges, rather than overwrites.
   2566 			if ( isset( $_wp_theme_features['html5'] ) ) {
   2567 				$args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
   2568 			}
   2569 			break;
   2570 
   2571 		case 'custom-logo':
   2572 			if ( true === $args ) {
   2573 				$args = array( 0 => array() );
   2574 			}
   2575 			$defaults = array(
   2576 				'width'                => null,
   2577 				'height'               => null,
   2578 				'flex-width'           => false,
   2579 				'flex-height'          => false,
   2580 				'header-text'          => '',
   2581 				'unlink-homepage-logo' => false,
   2582 			);
   2583 			$args[0]  = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
   2584 
   2585 			// Allow full flexibility if no size is specified.
   2586 			if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
   2587 				$args[0]['flex-width']  = true;
   2588 				$args[0]['flex-height'] = true;
   2589 			}
   2590 			break;
   2591 
   2592 		case 'custom-header-uploads':
   2593 			return add_theme_support( 'custom-header', array( 'uploads' => true ) );
   2594 
   2595 		case 'custom-header':
   2596 			if ( true === $args ) {
   2597 				$args = array( 0 => array() );
   2598 			}
   2599 
   2600 			$defaults = array(
   2601 				'default-image'          => '',
   2602 				'random-default'         => false,
   2603 				'width'                  => 0,
   2604 				'height'                 => 0,
   2605 				'flex-height'            => false,
   2606 				'flex-width'             => false,
   2607 				'default-text-color'     => '',
   2608 				'header-text'            => true,
   2609 				'uploads'                => true,
   2610 				'wp-head-callback'       => '',
   2611 				'admin-head-callback'    => '',
   2612 				'admin-preview-callback' => '',
   2613 				'video'                  => false,
   2614 				'video-active-callback'  => 'is_front_page',
   2615 			);
   2616 
   2617 			$jit = isset( $args[0]['__jit'] );
   2618 			unset( $args[0]['__jit'] );
   2619 
   2620 			// Merge in data from previous add_theme_support() calls.
   2621 			// The first value registered wins. (A child theme is set up first.)
   2622 			if ( isset( $_wp_theme_features['custom-header'] ) ) {
   2623 				$args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
   2624 			}
   2625 
   2626 			// Load in the defaults at the end, as we need to insure first one wins.
   2627 			// This will cause all constants to be defined, as each arg will then be set to the default.
   2628 			if ( $jit ) {
   2629 				$args[0] = wp_parse_args( $args[0], $defaults );
   2630 			}
   2631 
   2632 			/*
   2633 			 * If a constant was defined, use that value. Otherwise, define the constant to ensure
   2634 			 * the constant is always accurate (and is not defined later,  overriding our value).
   2635 			 * As stated above, the first value wins.
   2636 			 * Once we get to wp_loaded (just-in-time), define any constants we haven't already.
   2637 			 * Constants are lame. Don't reference them. This is just for backward compatibility.
   2638 			 */
   2639 
   2640 			if ( defined( 'NO_HEADER_TEXT' ) ) {
   2641 				$args[0]['header-text'] = ! NO_HEADER_TEXT;
   2642 			} elseif ( isset( $args[0]['header-text'] ) ) {
   2643 				define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
   2644 			}
   2645 
   2646 			if ( defined( 'HEADER_IMAGE_WIDTH' ) ) {
   2647 				$args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
   2648 			} elseif ( isset( $args[0]['width'] ) ) {
   2649 				define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
   2650 			}
   2651 
   2652 			if ( defined( 'HEADER_IMAGE_HEIGHT' ) ) {
   2653 				$args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
   2654 			} elseif ( isset( $args[0]['height'] ) ) {
   2655 				define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
   2656 			}
   2657 
   2658 			if ( defined( 'HEADER_TEXTCOLOR' ) ) {
   2659 				$args[0]['default-text-color'] = HEADER_TEXTCOLOR;
   2660 			} elseif ( isset( $args[0]['default-text-color'] ) ) {
   2661 				define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
   2662 			}
   2663 
   2664 			if ( defined( 'HEADER_IMAGE' ) ) {
   2665 				$args[0]['default-image'] = HEADER_IMAGE;
   2666 			} elseif ( isset( $args[0]['default-image'] ) ) {
   2667 				define( 'HEADER_IMAGE', $args[0]['default-image'] );
   2668 			}
   2669 
   2670 			if ( $jit && ! empty( $args[0]['default-image'] ) ) {
   2671 				$args[0]['random-default'] = false;
   2672 			}
   2673 
   2674 			// If headers are supported, and we still don't have a defined width or height,
   2675 			// we have implicit flex sizes.
   2676 			if ( $jit ) {
   2677 				if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) ) {
   2678 					$args[0]['flex-width'] = true;
   2679 				}
   2680 				if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) ) {
   2681 					$args[0]['flex-height'] = true;
   2682 				}
   2683 			}
   2684 
   2685 			break;
   2686 
   2687 		case 'custom-background':
   2688 			if ( true === $args ) {
   2689 				$args = array( 0 => array() );
   2690 			}
   2691 
   2692 			$defaults = array(
   2693 				'default-image'          => '',
   2694 				'default-preset'         => 'default',
   2695 				'default-position-x'     => 'left',
   2696 				'default-position-y'     => 'top',
   2697 				'default-size'           => 'auto',
   2698 				'default-repeat'         => 'repeat',
   2699 				'default-attachment'     => 'scroll',
   2700 				'default-color'          => '',
   2701 				'wp-head-callback'       => '_custom_background_cb',
   2702 				'admin-head-callback'    => '',
   2703 				'admin-preview-callback' => '',
   2704 			);
   2705 
   2706 			$jit = isset( $args[0]['__jit'] );
   2707 			unset( $args[0]['__jit'] );
   2708 
   2709 			// Merge in data from previous add_theme_support() calls. The first value registered wins.
   2710 			if ( isset( $_wp_theme_features['custom-background'] ) ) {
   2711 				$args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
   2712 			}
   2713 
   2714 			if ( $jit ) {
   2715 				$args[0] = wp_parse_args( $args[0], $defaults );
   2716 			}
   2717 
   2718 			if ( defined( 'BACKGROUND_COLOR' ) ) {
   2719 				$args[0]['default-color'] = BACKGROUND_COLOR;
   2720 			} elseif ( isset( $args[0]['default-color'] ) || $jit ) {
   2721 				define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
   2722 			}
   2723 
   2724 			if ( defined( 'BACKGROUND_IMAGE' ) ) {
   2725 				$args[0]['default-image'] = BACKGROUND_IMAGE;
   2726 			} elseif ( isset( $args[0]['default-image'] ) || $jit ) {
   2727 				define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
   2728 			}
   2729 
   2730 			break;
   2731 
   2732 		// Ensure that 'title-tag' is accessible in the admin.
   2733 		case 'title-tag':
   2734 			// Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
   2735 			if ( did_action( 'wp_loaded' ) ) {
   2736 				_doing_it_wrong(
   2737 					"add_theme_support( 'title-tag' )",
   2738 					sprintf(
   2739 						/* translators: 1: title-tag, 2: wp_loaded */
   2740 						__( 'Theme support for %1$s should be registered before the %2$s hook.' ),
   2741 						'<code>title-tag</code>',
   2742 						'<code>wp_loaded</code>'
   2743 					),
   2744 					'4.1.0'
   2745 				);
   2746 
   2747 				return false;
   2748 			}
   2749 	}
   2750 
   2751 	$_wp_theme_features[ $feature ] = $args;
   2752 }
   2753 
   2754 /**
   2755  * Registers the internal custom header and background routines.
   2756  *
   2757  * @since 3.4.0
   2758  * @access private
   2759  *
   2760  * @global Custom_Image_Header $custom_image_header
   2761  * @global Custom_Background   $custom_background
   2762  */
   2763 function _custom_header_background_just_in_time() {
   2764 	global $custom_image_header, $custom_background;
   2765 
   2766 	if ( current_theme_supports( 'custom-header' ) ) {
   2767 		// In case any constants were defined after an add_custom_image_header() call, re-run.
   2768 		add_theme_support( 'custom-header', array( '__jit' => true ) );
   2769 
   2770 		$args = get_theme_support( 'custom-header' );
   2771 		if ( $args[0]['wp-head-callback'] ) {
   2772 			add_action( 'wp_head', $args[0]['wp-head-callback'] );
   2773 		}
   2774 
   2775 		if ( is_admin() ) {
   2776 			require_once ABSPATH . 'wp-admin/includes/class-custom-image-header.php';
   2777 			$custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
   2778 		}
   2779 	}
   2780 
   2781 	if ( current_theme_supports( 'custom-background' ) ) {
   2782 		// In case any constants were defined after an add_custom_background() call, re-run.
   2783 		add_theme_support( 'custom-background', array( '__jit' => true ) );
   2784 
   2785 		$args = get_theme_support( 'custom-background' );
   2786 		add_action( 'wp_head', $args[0]['wp-head-callback'] );
   2787 
   2788 		if ( is_admin() ) {
   2789 			require_once ABSPATH . 'wp-admin/includes/class-custom-background.php';
   2790 			$custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
   2791 		}
   2792 	}
   2793 }
   2794 
   2795 /**
   2796  * Adds CSS to hide header text for custom logo, based on Customizer setting.
   2797  *
   2798  * @since 4.5.0
   2799  * @access private
   2800  */
   2801 function _custom_logo_header_styles() {
   2802 	if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
   2803 		$classes = (array) get_theme_support( 'custom-logo', 'header-text' );
   2804 		$classes = array_map( 'sanitize_html_class', $classes );
   2805 		$classes = '.' . implode( ', .', $classes );
   2806 
   2807 		$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
   2808 		?>
   2809 		<!-- Custom Logo: hide header text -->
   2810 		<style id="custom-logo-css"<?php echo $type_attr; ?>>
   2811 			<?php echo $classes; ?> {
   2812 				position: absolute;
   2813 				clip: rect(1px, 1px, 1px, 1px);
   2814 			}
   2815 		</style>
   2816 		<?php
   2817 	}
   2818 }
   2819 
   2820 /**
   2821  * Gets the theme support arguments passed when registering that support.
   2822  *
   2823  * Example usage:
   2824  *
   2825  *     get_theme_support( 'custom-logo' );
   2826  *     get_theme_support( 'custom-header', 'width' );
   2827  *
   2828  * @since 3.1.0
   2829  * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
   2830  *              by adding it to the function signature.
   2831  *
   2832  * @global array $_wp_theme_features
   2833  *
   2834  * @param string $feature The feature to check. See add_theme_support() for the list
   2835  *                        of possible values.
   2836  * @param mixed  ...$args Optional extra arguments to be checked against certain features.
   2837  * @return mixed The array of extra arguments or the value for the registered feature.
   2838  */
   2839 function get_theme_support( $feature, ...$args ) {
   2840 	global $_wp_theme_features;
   2841 	if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
   2842 		return false;
   2843 	}
   2844 
   2845 	if ( ! $args ) {
   2846 		return $_wp_theme_features[ $feature ];
   2847 	}
   2848 
   2849 	switch ( $feature ) {
   2850 		case 'custom-logo':
   2851 		case 'custom-header':
   2852 		case 'custom-background':
   2853 			if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) ) {
   2854 				return $_wp_theme_features[ $feature ][0][ $args[0] ];
   2855 			}
   2856 			return false;
   2857 
   2858 		default:
   2859 			return $_wp_theme_features[ $feature ];
   2860 	}
   2861 }
   2862 
   2863 /**
   2864  * Allows a theme to de-register its support of a certain feature
   2865  *
   2866  * Should be called in the theme's functions.php file. Generally would
   2867  * be used for child themes to override support from the parent theme.
   2868  *
   2869  * @since 3.0.0
   2870  *
   2871  * @see add_theme_support()
   2872  *
   2873  * @param string $feature The feature being removed. See add_theme_support() for the list
   2874  *                        of possible values.
   2875  * @return bool|void Whether feature was removed.
   2876  */
   2877 function remove_theme_support( $feature ) {
   2878 	// Do not remove internal registrations that are not used directly by themes.
   2879 	if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ), true ) ) {
   2880 		return false;
   2881 	}
   2882 
   2883 	return _remove_theme_support( $feature );
   2884 }
   2885 
   2886 /**
   2887  * Do not use. Removes theme support internally without knowledge of those not used
   2888  * by themes directly.
   2889  *
   2890  * @access private
   2891  * @since 3.1.0
   2892  * @global array               $_wp_theme_features
   2893  * @global Custom_Image_Header $custom_image_header
   2894  * @global Custom_Background   $custom_background
   2895  *
   2896  * @param string $feature The feature being removed. See add_theme_support() for the list
   2897  *                        of possible values.
   2898  * @return bool True if support was removed, false if the feature was not registered.
   2899  */
   2900 function _remove_theme_support( $feature ) {
   2901 	global $_wp_theme_features;
   2902 
   2903 	switch ( $feature ) {
   2904 		case 'custom-header-uploads':
   2905 			if ( ! isset( $_wp_theme_features['custom-header'] ) ) {
   2906 				return false;
   2907 			}
   2908 			add_theme_support( 'custom-header', array( 'uploads' => false ) );
   2909 			return; // Do not continue - custom-header-uploads no longer exists.
   2910 	}
   2911 
   2912 	if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
   2913 		return false;
   2914 	}
   2915 
   2916 	switch ( $feature ) {
   2917 		case 'custom-header':
   2918 			if ( ! did_action( 'wp_loaded' ) ) {
   2919 				break;
   2920 			}
   2921 			$support = get_theme_support( 'custom-header' );
   2922 			if ( isset( $support[0]['wp-head-callback'] ) ) {
   2923 				remove_action( 'wp_head', $support[0]['wp-head-callback'] );
   2924 			}
   2925 			if ( isset( $GLOBALS['custom_image_header'] ) ) {
   2926 				remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
   2927 				unset( $GLOBALS['custom_image_header'] );
   2928 			}
   2929 			break;
   2930 
   2931 		case 'custom-background':
   2932 			if ( ! did_action( 'wp_loaded' ) ) {
   2933 				break;
   2934 			}
   2935 			$support = get_theme_support( 'custom-background' );
   2936 			if ( isset( $support[0]['wp-head-callback'] ) ) {
   2937 				remove_action( 'wp_head', $support[0]['wp-head-callback'] );
   2938 			}
   2939 			remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
   2940 			unset( $GLOBALS['custom_background'] );
   2941 			break;
   2942 	}
   2943 
   2944 	unset( $_wp_theme_features[ $feature ] );
   2945 
   2946 	return true;
   2947 }
   2948 
   2949 /**
   2950  * Checks a theme's support for a given feature.
   2951  *
   2952  * Example usage:
   2953  *
   2954  *     current_theme_supports( 'custom-logo' );
   2955  *     current_theme_supports( 'html5', 'comment-form' );
   2956  *
   2957  * @since 2.9.0
   2958  * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
   2959  *              by adding it to the function signature.
   2960  *
   2961  * @global array $_wp_theme_features
   2962  *
   2963  * @param string $feature The feature being checked. See add_theme_support() for the list
   2964  *                        of possible values.
   2965  * @param mixed  ...$args Optional extra arguments to be checked against certain features.
   2966  * @return bool True if the current theme supports the feature, false otherwise.
   2967  */
   2968 function current_theme_supports( $feature, ...$args ) {
   2969 	global $_wp_theme_features;
   2970 
   2971 	if ( 'custom-header-uploads' === $feature ) {
   2972 		return current_theme_supports( 'custom-header', 'uploads' );
   2973 	}
   2974 
   2975 	if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
   2976 		return false;
   2977 	}
   2978 
   2979 	// If no args passed then no extra checks need be performed.
   2980 	if ( ! $args ) {
   2981 		return true;
   2982 	}
   2983 
   2984 	switch ( $feature ) {
   2985 		case 'post-thumbnails':
   2986 			/*
   2987 			 * post-thumbnails can be registered for only certain content/post types
   2988 			 * by passing an array of types to add_theme_support().
   2989 			 * If no array was passed, then any type is accepted.
   2990 			 */
   2991 			if ( true === $_wp_theme_features[ $feature ] ) {  // Registered for all types.
   2992 				return true;
   2993 			}
   2994 			$content_type = $args[0];
   2995 			return in_array( $content_type, $_wp_theme_features[ $feature ][0], true );
   2996 
   2997 		case 'html5':
   2998 		case 'post-formats':
   2999 			/*
   3000 			 * Specific post formats can be registered by passing an array of types
   3001 			 * to add_theme_support().
   3002 			 *
   3003 			 * Specific areas of HTML5 support *must* be passed via an array to add_theme_support().
   3004 			 */
   3005 			$type = $args[0];
   3006 			return in_array( $type, $_wp_theme_features[ $feature ][0], true );
   3007 
   3008 		case 'custom-logo':
   3009 		case 'custom-header':
   3010 		case 'custom-background':
   3011 			// Specific capabilities can be registered by passing an array to add_theme_support().
   3012 			return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
   3013 	}
   3014 
   3015 	/**
   3016 	 * Filters whether the current theme supports a specific feature.
   3017 	 *
   3018 	 * The dynamic portion of the hook name, `$feature`, refers to the specific
   3019 	 * theme feature. See add_theme_support() for the list of possible values.
   3020 	 *
   3021 	 * @since 3.4.0
   3022 	 *
   3023 	 * @param bool   $supports Whether the current theme supports the given feature. Default true.
   3024 	 * @param array  $args     Array of arguments for the feature.
   3025 	 * @param string $feature  The theme feature.
   3026 	 */
   3027 	return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
   3028 }
   3029 
   3030 /**
   3031  * Checks a theme's support for a given feature before loading the functions which implement it.
   3032  *
   3033  * @since 2.9.0
   3034  *
   3035  * @param string $feature The feature being checked. See add_theme_support() for the list
   3036  *                        of possible values.
   3037  * @param string $include Path to the file.
   3038  * @return bool True if the current theme supports the supplied feature, false otherwise.
   3039  */
   3040 function require_if_theme_supports( $feature, $include ) {
   3041 	if ( current_theme_supports( $feature ) ) {
   3042 		require $include;
   3043 		return true;
   3044 	}
   3045 	return false;
   3046 }
   3047 
   3048 /**
   3049  * Registers a theme feature for use in add_theme_support().
   3050  *
   3051  * This does not indicate that the current theme supports the feature, it only describes
   3052  * the feature's supported options.
   3053  *
   3054  * @since 5.5.0
   3055  *
   3056  * @see add_theme_support()
   3057  *
   3058  * @global array $_wp_registered_theme_features
   3059  *
   3060  * @param string $feature The name uniquely identifying the feature. See add_theme_support()
   3061  *                        for the list of possible values.
   3062  * @param array  $args {
   3063  *     Data used to describe the theme.
   3064  *
   3065  *     @type string     $type         The type of data associated with this feature.
   3066  *                                    Valid values are 'string', 'boolean', 'integer',
   3067  *                                    'number', 'array', and 'object'. Defaults to 'boolean'.
   3068  *     @type boolean    $variadic     Does this feature utilize the variadic support
   3069  *                                    of add_theme_support(), or are all arguments specified
   3070  *                                    as the second parameter. Must be used with the "array" type.
   3071  *     @type string     $description  A short description of the feature. Included in
   3072  *                                    the Themes REST API schema. Intended for developers.
   3073  *     @type bool|array $show_in_rest {
   3074  *         Whether this feature should be included in the Themes REST API endpoint.
   3075  *         Defaults to not being included. When registering an 'array' or 'object' type,
   3076  *         this argument must be an array with the 'schema' key.
   3077  *
   3078  *         @type array    $schema           Specifies the JSON Schema definition describing
   3079  *                                          the feature. If any objects in the schema do not include
   3080  *                                          the 'additionalProperties' keyword, it is set to false.
   3081  *         @type string   $name             An alternate name to be used as the property name
   3082  *                                          in the REST API.
   3083  *         @type callable $prepare_callback A function used to format the theme support in the REST API.
   3084  *                                          Receives the raw theme support value.
   3085  *      }
   3086  * }
   3087  * @return true|WP_Error True if the theme feature was successfully registered, a WP_Error object if not.
   3088  */
   3089 function register_theme_feature( $feature, $args = array() ) {
   3090 	global $_wp_registered_theme_features;
   3091 
   3092 	if ( ! is_array( $_wp_registered_theme_features ) ) {
   3093 		$_wp_registered_theme_features = array();
   3094 	}
   3095 
   3096 	$defaults = array(
   3097 		'type'         => 'boolean',
   3098 		'variadic'     => false,
   3099 		'description'  => '',
   3100 		'show_in_rest' => false,
   3101 	);
   3102 
   3103 	$args = wp_parse_args( $args, $defaults );
   3104 
   3105 	if ( true === $args['show_in_rest'] ) {
   3106 		$args['show_in_rest'] = array();
   3107 	}
   3108 
   3109 	if ( is_array( $args['show_in_rest'] ) ) {
   3110 		$args['show_in_rest'] = wp_parse_args(
   3111 			$args['show_in_rest'],
   3112 			array(
   3113 				'schema'           => array(),
   3114 				'name'             => $feature,
   3115 				'prepare_callback' => null,
   3116 			)
   3117 		);
   3118 	}
   3119 
   3120 	if ( ! in_array( $args['type'], array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
   3121 		return new WP_Error(
   3122 			'invalid_type',
   3123 			__( 'The feature "type" is not valid JSON Schema type.' )
   3124 		);
   3125 	}
   3126 
   3127 	if ( true === $args['variadic'] && 'array' !== $args['type'] ) {
   3128 		return new WP_Error(
   3129 			'variadic_must_be_array',
   3130 			__( 'When registering a "variadic" theme feature, the "type" must be an "array".' )
   3131 		);
   3132 	}
   3133 
   3134 	if ( false !== $args['show_in_rest'] && in_array( $args['type'], array( 'array', 'object' ), true ) ) {
   3135 		if ( ! is_array( $args['show_in_rest'] ) || empty( $args['show_in_rest']['schema'] ) ) {
   3136 			return new WP_Error(
   3137 				'missing_schema',
   3138 				__( 'When registering an "array" or "object" feature to show in the REST API, the feature\'s schema must also be defined.' )
   3139 			);
   3140 		}
   3141 
   3142 		if ( 'array' === $args['type'] && ! isset( $args['show_in_rest']['schema']['items'] ) ) {
   3143 			return new WP_Error(
   3144 				'missing_schema_items',
   3145 				__( 'When registering an "array" feature, the feature\'s schema must include the "items" keyword.' )
   3146 			);
   3147 		}
   3148 
   3149 		if ( 'object' === $args['type'] && ! isset( $args['show_in_rest']['schema']['properties'] ) ) {
   3150 			return new WP_Error(
   3151 				'missing_schema_properties',
   3152 				__( 'When registering an "object" feature, the feature\'s schema must include the "properties" keyword.' )
   3153 			);
   3154 		}
   3155 	}
   3156 
   3157 	if ( is_array( $args['show_in_rest'] ) ) {
   3158 		if ( isset( $args['show_in_rest']['prepare_callback'] ) && ! is_callable( $args['show_in_rest']['prepare_callback'] ) ) {
   3159 			return new WP_Error(
   3160 				'invalid_rest_prepare_callback',
   3161 				sprintf(
   3162 					/* translators: %s: prepare_callback */
   3163 					__( 'The "%s" must be a callable function.' ),
   3164 					'prepare_callback'
   3165 				)
   3166 			);
   3167 		}
   3168 
   3169 		$args['show_in_rest']['schema'] = wp_parse_args(
   3170 			$args['show_in_rest']['schema'],
   3171 			array(
   3172 				'description' => $args['description'],
   3173 				'type'        => $args['type'],
   3174 				'default'     => false,
   3175 			)
   3176 		);
   3177 
   3178 		if ( is_bool( $args['show_in_rest']['schema']['default'] )
   3179 			&& ! in_array( 'boolean', (array) $args['show_in_rest']['schema']['type'], true )
   3180 		) {
   3181 			// Automatically include the "boolean" type when the default value is a boolean.
   3182 			$args['show_in_rest']['schema']['type'] = (array) $args['show_in_rest']['schema']['type'];
   3183 			array_unshift( $args['show_in_rest']['schema']['type'], 'boolean' );
   3184 		}
   3185 
   3186 		$args['show_in_rest']['schema'] = rest_default_additional_properties_to_false( $args['show_in_rest']['schema'] );
   3187 	}
   3188 
   3189 	$_wp_registered_theme_features[ $feature ] = $args;
   3190 
   3191 	return true;
   3192 }
   3193 
   3194 /**
   3195  * Gets the list of registered theme features.
   3196  *
   3197  * @since 5.5.0
   3198  *
   3199  * @global array $_wp_registered_theme_features
   3200  *
   3201  * @return array[] List of theme features, keyed by their name.
   3202  */
   3203 function get_registered_theme_features() {
   3204 	global $_wp_registered_theme_features;
   3205 
   3206 	if ( ! is_array( $_wp_registered_theme_features ) ) {
   3207 		return array();
   3208 	}
   3209 
   3210 	return $_wp_registered_theme_features;
   3211 }
   3212 
   3213 /**
   3214  * Gets the registration config for a theme feature.
   3215  *
   3216  * @since 5.5.0
   3217  *
   3218  * @global array $_wp_registered_theme_features
   3219  *
   3220  * @param string $feature The feature name. See add_theme_support() for the list
   3221  *                        of possible values.
   3222  * @return array|null The registration args, or null if the feature was not registered.
   3223  */
   3224 function get_registered_theme_feature( $feature ) {
   3225 	global $_wp_registered_theme_features;
   3226 
   3227 	if ( ! is_array( $_wp_registered_theme_features ) ) {
   3228 		return null;
   3229 	}
   3230 
   3231 	return isset( $_wp_registered_theme_features[ $feature ] ) ? $_wp_registered_theme_features[ $feature ] : null;
   3232 }
   3233 
   3234 /**
   3235  * Checks an attachment being deleted to see if it's a header or background image.
   3236  *
   3237  * If true it removes the theme modification which would be pointing at the deleted
   3238  * attachment.
   3239  *
   3240  * @access private
   3241  * @since 3.0.0
   3242  * @since 4.3.0 Also removes `header_image_data`.
   3243  * @since 4.5.0 Also removes custom logo theme mods.
   3244  *
   3245  * @param int $id The attachment ID.
   3246  */
   3247 function _delete_attachment_theme_mod( $id ) {
   3248 	$attachment_image = wp_get_attachment_url( $id );
   3249 	$header_image     = get_header_image();
   3250 	$background_image = get_background_image();
   3251 	$custom_logo_id   = get_theme_mod( 'custom_logo' );
   3252 
   3253 	if ( $custom_logo_id && $custom_logo_id == $id ) {
   3254 		remove_theme_mod( 'custom_logo' );
   3255 		remove_theme_mod( 'header_text' );
   3256 	}
   3257 
   3258 	if ( $header_image && $header_image == $attachment_image ) {
   3259 		remove_theme_mod( 'header_image' );
   3260 		remove_theme_mod( 'header_image_data' );
   3261 	}
   3262 
   3263 	if ( $background_image && $background_image == $attachment_image ) {
   3264 		remove_theme_mod( 'background_image' );
   3265 	}
   3266 }
   3267 
   3268 /**
   3269  * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
   3270  *
   3271  * See {@see 'after_switch_theme'}.
   3272  *
   3273  * @since 3.3.0
   3274  */
   3275 function check_theme_switched() {
   3276 	$stylesheet = get_option( 'theme_switched' );
   3277 	if ( $stylesheet ) {
   3278 		$old_theme = wp_get_theme( $stylesheet );
   3279 
   3280 		// Prevent widget & menu mapping from running since Customizer already called it up front.
   3281 		if ( get_option( 'theme_switched_via_customizer' ) ) {
   3282 			remove_action( 'after_switch_theme', '_wp_menus_changed' );
   3283 			remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
   3284 			update_option( 'theme_switched_via_customizer', false );
   3285 		}
   3286 
   3287 		if ( $old_theme->exists() ) {
   3288 			/**
   3289 			 * Fires on the first WP load after a theme switch if the old theme still exists.
   3290 			 *
   3291 			 * This action fires multiple times and the parameters differs
   3292 			 * according to the context, if the old theme exists or not.
   3293 			 * If the old theme is missing, the parameter will be the slug
   3294 			 * of the old theme.
   3295 			 *
   3296 			 * @since 3.3.0
   3297 			 *
   3298 			 * @param string   $old_name  Old theme name.
   3299 			 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
   3300 			 */
   3301 			do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
   3302 		} else {
   3303 			/** This action is documented in wp-includes/theme.php */
   3304 			do_action( 'after_switch_theme', $stylesheet, $old_theme );
   3305 		}
   3306 		flush_rewrite_rules();
   3307 
   3308 		update_option( 'theme_switched', false );
   3309 	}
   3310 }
   3311 
   3312 /**
   3313  * Includes and instantiates the WP_Customize_Manager class.
   3314  *
   3315  * Loads the Customizer at plugins_loaded when accessing the customize.php admin
   3316  * page or when any request includes a wp_customize=on param or a customize_changeset
   3317  * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
   3318  * WordPress is loading, especially in the Customizer preview
   3319  * or when making Customizer Ajax requests for widgets or menus.
   3320  *
   3321  * @since 3.4.0
   3322  *
   3323  * @global WP_Customize_Manager $wp_customize
   3324  */
   3325 function _wp_customize_include() {
   3326 
   3327 	$is_customize_admin_page = ( is_admin() && 'customize.php' === basename( $_SERVER['PHP_SELF'] ) );
   3328 	$should_include          = (
   3329 		$is_customize_admin_page
   3330 		||
   3331 		( isset( $_REQUEST['wp_customize'] ) && 'on' === $_REQUEST['wp_customize'] )
   3332 		||
   3333 		( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
   3334 	);
   3335 
   3336 	if ( ! $should_include ) {
   3337 		return;
   3338 	}
   3339 
   3340 	/*
   3341 	 * Note that wp_unslash() is not being used on the input vars because it is
   3342 	 * called before wp_magic_quotes() gets called. Besides this fact, none of
   3343 	 * the values should contain any characters needing slashes anyway.
   3344 	 */
   3345 	$keys       = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel', 'customize_autosaved' );
   3346 	$input_vars = array_merge(
   3347 		wp_array_slice_assoc( $_GET, $keys ),
   3348 		wp_array_slice_assoc( $_POST, $keys )
   3349 	);
   3350 
   3351 	$theme             = null;
   3352 	$autosaved         = null;
   3353 	$messenger_channel = null;
   3354 
   3355 	// Value false indicates UUID should be determined after_setup_theme
   3356 	// to either re-use existing saved changeset or else generate a new UUID if none exists.
   3357 	$changeset_uuid = false;
   3358 
   3359 	// Set initially fo false since defaults to true for back-compat;
   3360 	// can be overridden via the customize_changeset_branching filter.
   3361 	$branching = false;
   3362 
   3363 	if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
   3364 		$changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
   3365 	} elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
   3366 		$changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
   3367 	}
   3368 
   3369 	// Note that theme will be sanitized via WP_Theme.
   3370 	if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
   3371 		$theme = $input_vars['theme'];
   3372 	} elseif ( isset( $input_vars['customize_theme'] ) ) {
   3373 		$theme = $input_vars['customize_theme'];
   3374 	}
   3375 
   3376 	if ( ! empty( $input_vars['customize_autosaved'] ) ) {
   3377 		$autosaved = true;
   3378 	}
   3379 
   3380 	if ( isset( $input_vars['customize_messenger_channel'] ) ) {
   3381 		$messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
   3382 	}
   3383 
   3384 	/*
   3385 	 * Note that settings must be previewed even outside the customizer preview
   3386 	 * and also in the customizer pane itself. This is to enable loading an existing
   3387 	 * changeset into the customizer. Previewing the settings only has to be prevented
   3388 	 * here in the case of a customize_save action because this will cause WP to think
   3389 	 * there is nothing changed that needs to be saved.
   3390 	 */
   3391 	$is_customize_save_action = (
   3392 		wp_doing_ajax()
   3393 		&&
   3394 		isset( $_REQUEST['action'] )
   3395 		&&
   3396 		'customize_save' === wp_unslash( $_REQUEST['action'] )
   3397 	);
   3398 	$settings_previewed       = ! $is_customize_save_action;
   3399 
   3400 	require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
   3401 	$GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ) );
   3402 }
   3403 
   3404 /**
   3405  * Publishes a snapshot's changes.
   3406  *
   3407  * @since 4.7.0
   3408  * @access private
   3409  *
   3410  * @global wpdb                 $wpdb         WordPress database abstraction object.
   3411  * @global WP_Customize_Manager $wp_customize Customizer instance.
   3412  *
   3413  * @param string  $new_status     New post status.
   3414  * @param string  $old_status     Old post status.
   3415  * @param WP_Post $changeset_post Changeset post object.
   3416  */
   3417 function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
   3418 	global $wp_customize, $wpdb;
   3419 
   3420 	$is_publishing_changeset = (
   3421 		'customize_changeset' === $changeset_post->post_type
   3422 		&&
   3423 		'publish' === $new_status
   3424 		&&
   3425 		'publish' !== $old_status
   3426 	);
   3427 	if ( ! $is_publishing_changeset ) {
   3428 		return;
   3429 	}
   3430 
   3431 	if ( empty( $wp_customize ) ) {
   3432 		require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
   3433 		$wp_customize = new WP_Customize_Manager(
   3434 			array(
   3435 				'changeset_uuid'     => $changeset_post->post_name,
   3436 				'settings_previewed' => false,
   3437 			)
   3438 		);
   3439 	}
   3440 
   3441 	if ( ! did_action( 'customize_register' ) ) {
   3442 		/*
   3443 		 * When running from CLI or Cron, the customize_register action will need
   3444 		 * to be triggered in order for core, themes, and plugins to register their
   3445 		 * settings. Normally core will add_action( 'customize_register' ) at
   3446 		 * priority 10 to register the core settings, and if any themes/plugins
   3447 		 * also add_action( 'customize_register' ) at the same priority, they
   3448 		 * will have a $wp_customize with those settings registered since they
   3449 		 * call add_action() afterward, normally. However, when manually doing
   3450 		 * the customize_register action after the setup_theme, then the order
   3451 		 * will be reversed for two actions added at priority 10, resulting in
   3452 		 * the core settings no longer being available as expected to themes/plugins.
   3453 		 * So the following manually calls the method that registers the core
   3454 		 * settings up front before doing the action.
   3455 		 */
   3456 		remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
   3457 		$wp_customize->register_controls();
   3458 
   3459 		/** This filter is documented in /wp-includes/class-wp-customize-manager.php */
   3460 		do_action( 'customize_register', $wp_customize );
   3461 	}
   3462 	$wp_customize->_publish_changeset_values( $changeset_post->ID );
   3463 
   3464 	/*
   3465 	 * Trash the changeset post if revisions are not enabled. Unpublished
   3466 	 * changesets by default get garbage collected due to the auto-draft status.
   3467 	 * When a changeset post is published, however, it would no longer get cleaned
   3468 	 * out. This is a problem when the changeset posts are never displayed anywhere,
   3469 	 * since they would just be endlessly piling up. So here we use the revisions
   3470 	 * feature to indicate whether or not a published changeset should get trashed
   3471 	 * and thus garbage collected.
   3472 	 */
   3473 	if ( ! wp_revisions_enabled( $changeset_post ) ) {
   3474 		$wp_customize->trash_changeset_post( $changeset_post->ID );
   3475 	}
   3476 }
   3477 
   3478 /**
   3479  * Filters changeset post data upon insert to ensure post_name is intact.
   3480  *
   3481  * This is needed to prevent the post_name from being dropped when the post is
   3482  * transitioned into pending status by a contributor.
   3483  *
   3484  * @since 4.7.0
   3485  *
   3486  * @see wp_insert_post()
   3487  *
   3488  * @param array $post_data          An array of slashed post data.
   3489  * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
   3490  * @return array Filtered data.
   3491  */
   3492 function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
   3493 	if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
   3494 
   3495 		// Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
   3496 		if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
   3497 			$post_data['post_name'] = $supplied_post_data['post_name'];
   3498 		}
   3499 	}
   3500 	return $post_data;
   3501 }
   3502 
   3503 /**
   3504  * Adds settings for the customize-loader script.
   3505  *
   3506  * @since 3.4.0
   3507  */
   3508 function _wp_customize_loader_settings() {
   3509 	$admin_origin = parse_url( admin_url() );
   3510 	$home_origin  = parse_url( home_url() );
   3511 	$cross_domain = ( strtolower( $admin_origin['host'] ) != strtolower( $home_origin['host'] ) );
   3512 
   3513 	$browser = array(
   3514 		'mobile' => wp_is_mobile(),
   3515 		'ios'    => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
   3516 	);
   3517 
   3518 	$settings = array(
   3519 		'url'           => esc_url( admin_url( 'customize.php' ) ),
   3520 		'isCrossDomain' => $cross_domain,
   3521 		'browser'       => $browser,
   3522 		'l10n'          => array(
   3523 			'saveAlert'       => __( 'The changes you made will be lost if you navigate away from this page.' ),
   3524 			'mainIframeTitle' => __( 'Customizer' ),
   3525 		),
   3526 	);
   3527 
   3528 	$script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
   3529 
   3530 	$wp_scripts = wp_scripts();
   3531 	$data       = $wp_scripts->get_data( 'customize-loader', 'data' );
   3532 	if ( $data ) {
   3533 		$script = "$data\n$script";
   3534 	}
   3535 
   3536 	$wp_scripts->add_data( 'customize-loader', 'data', $script );
   3537 }
   3538 
   3539 /**
   3540  * Returns a URL to load the Customizer.
   3541  *
   3542  * @since 3.4.0
   3543  *
   3544  * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
   3545  *                           The theme's stylesheet will be urlencoded if necessary.
   3546  * @return string
   3547  */
   3548 function wp_customize_url( $stylesheet = '' ) {
   3549 	$url = admin_url( 'customize.php' );
   3550 	if ( $stylesheet ) {
   3551 		$url .= '?theme=' . urlencode( $stylesheet );
   3552 	}
   3553 	return esc_url( $url );
   3554 }
   3555 
   3556 /**
   3557  * Prints a script to check whether or not the Customizer is supported,
   3558  * and apply either the no-customize-support or customize-support class
   3559  * to the body.
   3560  *
   3561  * This function MUST be called inside the body tag.
   3562  *
   3563  * Ideally, call this function immediately after the body tag is opened.
   3564  * This prevents a flash of unstyled content.
   3565  *
   3566  * It is also recommended that you add the "no-customize-support" class
   3567  * to the body tag by default.
   3568  *
   3569  * @since 3.4.0
   3570  * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
   3571  * @since 5.5.0 IE8 and older are no longer supported.
   3572  */
   3573 function wp_customize_support_script() {
   3574 	$admin_origin = parse_url( admin_url() );
   3575 	$home_origin  = parse_url( home_url() );
   3576 	$cross_domain = ( strtolower( $admin_origin['host'] ) != strtolower( $home_origin['host'] ) );
   3577 	$type_attr    = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
   3578 	?>
   3579 	<script<?php echo $type_attr; ?>>
   3580 		(function() {
   3581 			var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
   3582 
   3583 	<?php	if ( $cross_domain ) : ?>
   3584 			request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
   3585 	<?php	else : ?>
   3586 			request = true;
   3587 	<?php	endif; ?>
   3588 
   3589 			b[c] = b[c].replace( rcs, ' ' );
   3590 			// The customizer requires postMessage and CORS (if the site is cross domain).
   3591 			b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
   3592 		}());
   3593 	</script>
   3594 	<?php
   3595 }
   3596 
   3597 /**
   3598  * Whether the site is being previewed in the Customizer.
   3599  *
   3600  * @since 4.0.0
   3601  *
   3602  * @global WP_Customize_Manager $wp_customize Customizer instance.
   3603  *
   3604  * @return bool True if the site is being previewed in the Customizer, false otherwise.
   3605  */
   3606 function is_customize_preview() {
   3607 	global $wp_customize;
   3608 
   3609 	return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
   3610 }
   3611 
   3612 /**
   3613  * Makes sure that auto-draft posts get their post_date bumped or status changed to draft to prevent premature garbage-collection.
   3614  *
   3615  * When a changeset is updated but remains an auto-draft, ensure the post_date
   3616  * for the auto-draft posts remains the same so that it will be
   3617  * garbage-collected at the same time by `wp_delete_auto_drafts()`. Otherwise,
   3618  * if the changeset is updated to be a draft then update the posts
   3619  * to have a far-future post_date so that they will never be garbage collected
   3620  * unless the changeset post itself is deleted.
   3621  *
   3622  * When a changeset is updated to be a persistent draft or to be scheduled for
   3623  * publishing, then transition any dependent auto-drafts to a draft status so
   3624  * that they likewise will not be garbage-collected but also so that they can
   3625  * be edited in the admin before publishing since there is not yet a post/page
   3626  * editing flow in the Customizer. See #39752.
   3627  *
   3628  * @link https://core.trac.wordpress.org/ticket/39752
   3629  *
   3630  * @since 4.8.0
   3631  * @access private
   3632  * @see wp_delete_auto_drafts()
   3633  *
   3634  * @global wpdb $wpdb WordPress database abstraction object.
   3635  *
   3636  * @param string   $new_status Transition to this post status.
   3637  * @param string   $old_status Previous post status.
   3638  * @param \WP_Post $post       Post data.
   3639  */
   3640 function _wp_keep_alive_customize_changeset_dependent_auto_drafts( $new_status, $old_status, $post ) {
   3641 	global $wpdb;
   3642 	unset( $old_status );
   3643 
   3644 	// Short-circuit if not a changeset or if the changeset was published.
   3645 	if ( 'customize_changeset' !== $post->post_type || 'publish' === $new_status ) {
   3646 		return;
   3647 	}
   3648 
   3649 	$data = json_decode( $post->post_content, true );
   3650 	if ( empty( $data['nav_menus_created_posts']['value'] ) ) {
   3651 		return;
   3652 	}
   3653 
   3654 	/*
   3655 	 * Actually, in lieu of keeping alive, trash any customization drafts here if the changeset itself is
   3656 	 * getting trashed. This is needed because when a changeset transitions to a draft, then any of the
   3657 	 * dependent auto-draft post/page stubs will also get transitioned to customization drafts which
   3658 	 * are then visible in the WP Admin. We cannot wait for the deletion of the changeset in which
   3659 	 * _wp_delete_customize_changeset_dependent_auto_drafts() will be called, since they need to be
   3660 	 * trashed to remove from visibility immediately.
   3661 	 */
   3662 	if ( 'trash' === $new_status ) {
   3663 		foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
   3664 			if ( ! empty( $post_id ) && 'draft' === get_post_status( $post_id ) ) {
   3665 				wp_trash_post( $post_id );
   3666 			}
   3667 		}
   3668 		return;
   3669 	}
   3670 
   3671 	$post_args = array();
   3672 	if ( 'auto-draft' === $new_status ) {
   3673 		/*
   3674 		 * Keep the post date for the post matching the changeset
   3675 		 * so that it will not be garbage-collected before the changeset.
   3676 		 */
   3677 		$post_args['post_date'] = $post->post_date; // Note wp_delete_auto_drafts() only looks at this date.
   3678 	} else {
   3679 		/*
   3680 		 * Since the changeset no longer has an auto-draft (and it is not published)
   3681 		 * it is now a persistent changeset, a long-lived draft, and so any
   3682 		 * associated auto-draft posts should likewise transition into having a draft
   3683 		 * status. These drafts will be treated differently than regular drafts in
   3684 		 * that they will be tied to the given changeset. The publish meta box is
   3685 		 * replaced with a notice about how the post is part of a set of customized changes
   3686 		 * which will be published when the changeset is published.
   3687 		 */
   3688 		$post_args['post_status'] = 'draft';
   3689 	}
   3690 
   3691 	foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
   3692 		if ( empty( $post_id ) || 'auto-draft' !== get_post_status( $post_id ) ) {
   3693 			continue;
   3694 		}
   3695 		$wpdb->update(
   3696 			$wpdb->posts,
   3697 			$post_args,
   3698 			array( 'ID' => $post_id )
   3699 		);
   3700 		clean_post_cache( $post_id );
   3701 	}
   3702 }
   3703 
   3704 /**
   3705  * Creates the initial theme features when the 'setup_theme' action is fired.
   3706  *
   3707  * See {@see 'setup_theme'}.
   3708  *
   3709  * @since 5.5.0
   3710  */
   3711 function create_initial_theme_features() {
   3712 	register_theme_feature(
   3713 		'align-wide',
   3714 		array(
   3715 			'description'  => __( 'Whether theme opts in to wide alignment CSS class.' ),
   3716 			'show_in_rest' => true,
   3717 		)
   3718 	);
   3719 	register_theme_feature(
   3720 		'automatic-feed-links',
   3721 		array(
   3722 			'description'  => __( 'Whether posts and comments RSS feed links are added to head.' ),
   3723 			'show_in_rest' => true,
   3724 		)
   3725 	);
   3726 	register_theme_feature(
   3727 		'custom-background',
   3728 		array(
   3729 			'description'  => __( 'Custom background if defined by the theme.' ),
   3730 			'type'         => 'object',
   3731 			'show_in_rest' => array(
   3732 				'schema' => array(
   3733 					'properties' => array(
   3734 						'default-image'      => array(
   3735 							'type'   => 'string',
   3736 							'format' => 'uri',
   3737 						),
   3738 						'default-preset'     => array(
   3739 							'type' => 'string',
   3740 							'enum' => array(
   3741 								'default',
   3742 								'fill',
   3743 								'fit',
   3744 								'repeat',
   3745 								'custom',
   3746 							),
   3747 						),
   3748 						'default-position-x' => array(
   3749 							'type' => 'string',
   3750 							'enum' => array(
   3751 								'left',
   3752 								'center',
   3753 								'right',
   3754 							),
   3755 						),
   3756 						'default-position-y' => array(
   3757 							'type' => 'string',
   3758 							'enum' => array(
   3759 								'left',
   3760 								'center',
   3761 								'right',
   3762 							),
   3763 						),
   3764 						'default-size'       => array(
   3765 							'type' => 'string',
   3766 							'enum' => array(
   3767 								'auto',
   3768 								'contain',
   3769 								'cover',
   3770 							),
   3771 						),
   3772 						'default-repeat'     => array(
   3773 							'type' => 'string',
   3774 							'enum' => array(
   3775 								'repeat-x',
   3776 								'repeat-y',
   3777 								'repeat',
   3778 								'no-repeat',
   3779 							),
   3780 						),
   3781 						'default-attachment' => array(
   3782 							'type' => 'string',
   3783 							'enum' => array(
   3784 								'scroll',
   3785 								'fixed',
   3786 							),
   3787 						),
   3788 						'default-color'      => array(
   3789 							'type' => 'string',
   3790 						),
   3791 					),
   3792 				),
   3793 			),
   3794 		)
   3795 	);
   3796 	register_theme_feature(
   3797 		'custom-header',
   3798 		array(
   3799 			'description'  => __( 'Custom header if defined by the theme.' ),
   3800 			'type'         => 'object',
   3801 			'show_in_rest' => array(
   3802 				'schema' => array(
   3803 					'properties' => array(
   3804 						'default-image'      => array(
   3805 							'type'   => 'string',
   3806 							'format' => 'uri',
   3807 						),
   3808 						'random-default'     => array(
   3809 							'type' => 'boolean',
   3810 						),
   3811 						'width'              => array(
   3812 							'type' => 'integer',
   3813 						),
   3814 						'height'             => array(
   3815 							'type' => 'integer',
   3816 						),
   3817 						'flex-height'        => array(
   3818 							'type' => 'boolean',
   3819 						),
   3820 						'flex-width'         => array(
   3821 							'type' => 'boolean',
   3822 						),
   3823 						'default-text-color' => array(
   3824 							'type' => 'string',
   3825 						),
   3826 						'header-text'        => array(
   3827 							'type' => 'boolean',
   3828 						),
   3829 						'uploads'            => array(
   3830 							'type' => 'boolean',
   3831 						),
   3832 						'video'              => array(
   3833 							'type' => 'boolean',
   3834 						),
   3835 					),
   3836 				),
   3837 			),
   3838 		)
   3839 	);
   3840 	register_theme_feature(
   3841 		'custom-logo',
   3842 		array(
   3843 			'type'         => 'object',
   3844 			'description'  => __( 'Custom logo if defined by the theme.' ),
   3845 			'show_in_rest' => array(
   3846 				'schema' => array(
   3847 					'properties' => array(
   3848 						'width'                => array(
   3849 							'type' => 'integer',
   3850 						),
   3851 						'height'               => array(
   3852 							'type' => 'integer',
   3853 						),
   3854 						'flex-width'           => array(
   3855 							'type' => 'boolean',
   3856 						),
   3857 						'flex-height'          => array(
   3858 							'type' => 'boolean',
   3859 						),
   3860 						'header-text'          => array(
   3861 							'type'  => 'array',
   3862 							'items' => array(
   3863 								'type' => 'string',
   3864 							),
   3865 						),
   3866 						'unlink-homepage-logo' => array(
   3867 							'type' => 'boolean',
   3868 						),
   3869 					),
   3870 				),
   3871 			),
   3872 		)
   3873 	);
   3874 	register_theme_feature(
   3875 		'customize-selective-refresh-widgets',
   3876 		array(
   3877 			'description'  => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
   3878 			'show_in_rest' => true,
   3879 		)
   3880 	);
   3881 	register_theme_feature(
   3882 		'dark-editor-style',
   3883 		array(
   3884 			'description'  => __( 'Whether theme opts in to the dark editor style UI.' ),
   3885 			'show_in_rest' => true,
   3886 		)
   3887 	);
   3888 	register_theme_feature(
   3889 		'disable-custom-colors',
   3890 		array(
   3891 			'description'  => __( 'Whether the theme disables custom colors.' ),
   3892 			'show_in_rest' => true,
   3893 		)
   3894 	);
   3895 	register_theme_feature(
   3896 		'disable-custom-font-sizes',
   3897 		array(
   3898 			'description'  => __( 'Whether the theme disables custom font sizes.' ),
   3899 			'show_in_rest' => true,
   3900 		)
   3901 	);
   3902 	register_theme_feature(
   3903 		'disable-custom-gradients',
   3904 		array(
   3905 			'description'  => __( 'Whether the theme disables custom gradients.' ),
   3906 			'show_in_rest' => true,
   3907 		)
   3908 	);
   3909 	register_theme_feature(
   3910 		'editor-color-palette',
   3911 		array(
   3912 			'type'         => 'array',
   3913 			'description'  => __( 'Custom color palette if defined by the theme.' ),
   3914 			'show_in_rest' => array(
   3915 				'schema' => array(
   3916 					'items' => array(
   3917 						'type'       => 'object',
   3918 						'properties' => array(
   3919 							'name'  => array(
   3920 								'type' => 'string',
   3921 							),
   3922 							'slug'  => array(
   3923 								'type' => 'string',
   3924 							),
   3925 							'color' => array(
   3926 								'type' => 'string',
   3927 							),
   3928 						),
   3929 					),
   3930 				),
   3931 			),
   3932 		)
   3933 	);
   3934 	register_theme_feature(
   3935 		'editor-font-sizes',
   3936 		array(
   3937 			'type'         => 'array',
   3938 			'description'  => __( 'Custom font sizes if defined by the theme.' ),
   3939 			'show_in_rest' => array(
   3940 				'schema' => array(
   3941 					'items' => array(
   3942 						'type'       => 'object',
   3943 						'properties' => array(
   3944 							'name' => array(
   3945 								'type' => 'string',
   3946 							),
   3947 							'size' => array(
   3948 								'type' => 'number',
   3949 							),
   3950 							'slug' => array(
   3951 								'type' => 'string',
   3952 							),
   3953 						),
   3954 					),
   3955 				),
   3956 			),
   3957 		)
   3958 	);
   3959 	register_theme_feature(
   3960 		'editor-gradient-presets',
   3961 		array(
   3962 			'type'         => 'array',
   3963 			'description'  => __( 'Custom gradient presets if defined by the theme.' ),
   3964 			'show_in_rest' => array(
   3965 				'schema' => array(
   3966 					'items' => array(
   3967 						'type'       => 'object',
   3968 						'properties' => array(
   3969 							'name'     => array(
   3970 								'type' => 'string',
   3971 							),
   3972 							'gradient' => array(
   3973 								'type' => 'string',
   3974 							),
   3975 							'slug'     => array(
   3976 								'type' => 'string',
   3977 							),
   3978 						),
   3979 					),
   3980 				),
   3981 			),
   3982 		)
   3983 	);
   3984 	register_theme_feature(
   3985 		'editor-styles',
   3986 		array(
   3987 			'description'  => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
   3988 			'show_in_rest' => true,
   3989 		)
   3990 	);
   3991 	register_theme_feature(
   3992 		'html5',
   3993 		array(
   3994 			'type'         => 'array',
   3995 			'description'  => __( 'Allows use of HTML5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
   3996 			'show_in_rest' => array(
   3997 				'schema' => array(
   3998 					'items' => array(
   3999 						'type' => 'string',
   4000 						'enum' => array(
   4001 							'search-form',
   4002 							'comment-form',
   4003 							'comment-list',
   4004 							'gallery',
   4005 							'caption',
   4006 							'script',
   4007 							'style',
   4008 						),
   4009 					),
   4010 				),
   4011 			),
   4012 		)
   4013 	);
   4014 	register_theme_feature(
   4015 		'post-formats',
   4016 		array(
   4017 			'type'         => 'array',
   4018 			'description'  => __( 'Post formats supported.' ),
   4019 			'show_in_rest' => array(
   4020 				'name'             => 'formats',
   4021 				'schema'           => array(
   4022 					'items'   => array(
   4023 						'type' => 'string',
   4024 						'enum' => get_post_format_slugs(),
   4025 					),
   4026 					'default' => array( 'standard' ),
   4027 				),
   4028 				'prepare_callback' => static function ( $formats ) {
   4029 					$formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
   4030 					$formats = array_merge( array( 'standard' ), $formats );
   4031 
   4032 					return $formats;
   4033 				},
   4034 			),
   4035 		)
   4036 	);
   4037 	register_theme_feature(
   4038 		'post-thumbnails',
   4039 		array(
   4040 			'type'         => 'array',
   4041 			'description'  => __( 'The post types that support thumbnails or true if all post types are supported.' ),
   4042 			'show_in_rest' => array(
   4043 				'type'   => array( 'boolean', 'array' ),
   4044 				'schema' => array(
   4045 					'items' => array(
   4046 						'type' => 'string',
   4047 					),
   4048 				),
   4049 			),
   4050 		)
   4051 	);
   4052 	register_theme_feature(
   4053 		'responsive-embeds',
   4054 		array(
   4055 			'description'  => __( 'Whether the theme supports responsive embedded content.' ),
   4056 			'show_in_rest' => true,
   4057 		)
   4058 	);
   4059 	register_theme_feature(
   4060 		'title-tag',
   4061 		array(
   4062 			'description'  => __( 'Whether the theme can manage the document title tag.' ),
   4063 			'show_in_rest' => true,
   4064 		)
   4065 	);
   4066 	register_theme_feature(
   4067 		'wp-block-styles',
   4068 		array(
   4069 			'description'  => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
   4070 			'show_in_rest' => true,
   4071 		)
   4072 	);
   4073 }