wp/wp-includes/theme.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
--- a/wp/wp-includes/theme.php	Tue Oct 22 16:11:46 2019 +0200
+++ b/wp/wp-includes/theme.php	Tue Dec 15 13:49:49 2020 +0100
@@ -15,18 +15,20 @@
  * @since 3.4.0
  *
  * @global array $wp_theme_directories
- * @staticvar array $_themes
  *
  * @param array $args {
  *     Optional. The search arguments.
  *
- *     @type mixed $errors  True to return themes with errors, false to return themes without errors, null to return all themes.
- *                          Defaults to false.
- *     @type mixed $allowed (Multisite) True to return only allowed themes for a site. False to return only disallowed themes for a site.
- *                          'site' to return only site-allowed themes. 'network' to return only network-allowed themes.
- *                          Null to return all themes. Defaults to null.
- *     @type int   $blog_id (Multisite) The blog ID used to calculate which themes are allowed.
- *                          Defaults to 0, synonymous for the current blog.
+ *     @type mixed $errors  True to return themes with errors, false to return
+ *                          themes without errors, null to return all themes.
+ *                          Default false.
+ *     @type mixed $allowed (Multisite) True to return only allowed themes for a site.
+ *                          False to return only disallowed themes for a site.
+ *                          'site' to return only site-allowed themes.
+ *                          'network' to return only network-allowed themes.
+ *                          Null to return all themes. Default null.
+ *     @type int   $blog_id (Multisite) The blog ID used to calculate which themes
+ *                          are allowed. Default 0, synonymous for the current blog.
  * }
  * @return WP_Theme[] Array of WP_Theme objects.
  */
@@ -48,7 +50,7 @@
 		$current_theme = get_stylesheet();
 		if ( isset( $theme_directories[ $current_theme ] ) ) {
 			$root_of_current_theme = get_raw_theme_root( $current_theme );
-			if ( ! in_array( $root_of_current_theme, $wp_theme_directories ) ) {
+			if ( ! in_array( $root_of_current_theme, $wp_theme_directories, true ) ) {
 				$root_of_current_theme = WP_CONTENT_DIR . $root_of_current_theme;
 			}
 			$theme_directories[ $current_theme ]['theme_root'] = $root_of_current_theme;
@@ -79,7 +81,9 @@
 		if ( isset( $_themes[ $theme_root['theme_root'] . '/' . $theme ] ) ) {
 			$themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ];
 		} else {
-			$themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
+			$themes[ $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
+
+			$_themes[ $theme_root['theme_root'] . '/' . $theme ] = $themes[ $theme ];
 		}
 	}
 
@@ -101,12 +105,14 @@
  *
  * @global array $wp_theme_directories
  *
- * @param string $stylesheet Directory name for the theme. Optional. Defaults to current theme.
- * @param string $theme_root Absolute path of the theme root to look in. Optional. If not specified, get_raw_theme_root()
- *                           is used to calculate the theme root for the $stylesheet provided (or current theme).
- * @return WP_Theme Theme object. Be sure to check the object's exists() method if you need to confirm the theme's existence.
+ * @param string $stylesheet Optional. Directory name for the theme. Defaults to current theme.
+ * @param string $theme_root Optional. Absolute path of the theme root to look in.
+ *                           If not specified, get_raw_theme_root() is used to calculate
+ *                           the theme root for the $stylesheet provided (or current theme).
+ * @return WP_Theme Theme object. Be sure to check the object's exists() method
+ *                  if you need to confirm the theme's existence.
  */
-function wp_get_theme( $stylesheet = null, $theme_root = null ) {
+function wp_get_theme( $stylesheet = '', $theme_root = '' ) {
 	global $wp_theme_directories;
 
 	if ( empty( $stylesheet ) ) {
@@ -117,7 +123,7 @@
 		$theme_root = get_raw_theme_root( $stylesheet );
 		if ( false === $theme_root ) {
 			$theme_root = WP_CONTENT_DIR . '/themes';
-		} elseif ( ! in_array( $theme_root, (array) $wp_theme_directories ) ) {
+		} elseif ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
 			$theme_root = WP_CONTENT_DIR . $theme_root;
 		}
 	}
@@ -129,7 +135,7 @@
  * Clears the cache held by get_theme_roots() and WP_Theme.
  *
  * @since 3.5.0
- * @param bool $clear_update_cache Whether to clear the Theme updates cache
+ * @param bool $clear_update_cache Whether to clear the theme updates cache.
  */
 function wp_clean_themes_cache( $clear_update_cache = true ) {
 	if ( $clear_update_cache ) {
@@ -146,20 +152,19 @@
  *
  * @since 3.0.0
  *
- * @return bool true if a child theme is in use, false otherwise.
+ * @return bool True if a child theme is in use, false otherwise.
  */
 function is_child_theme() {
 	return ( TEMPLATEPATH !== STYLESHEETPATH );
 }
 
 /**
- * Retrieve name of the current stylesheet.
+ * Retrieves name of the current stylesheet.
  *
- * The theme name that the administrator has currently set the front end theme
- * as.
+ * The theme name that is currently set as the front end theme.
  *
- * For all intents and purposes, the template name and the stylesheet name are
- * going to be the same for most cases.
+ * For all intents and purposes, the template name and the stylesheet name
+ * are going to be the same for most cases.
  *
  * @since 1.5.0
  *
@@ -177,11 +182,11 @@
 }
 
 /**
- * Retrieve stylesheet directory path for current theme.
+ * Retrieves stylesheet directory path for current theme.
  *
  * @since 1.5.0
  *
- * @return string Path to current theme directory.
+ * @return string Path to current theme's stylesheet directory.
  */
 function get_stylesheet_directory() {
 	$stylesheet     = get_stylesheet();
@@ -201,11 +206,11 @@
 }
 
 /**
- * Retrieve stylesheet directory URI.
+ * Retrieves stylesheet directory URI for current theme.
  *
  * @since 1.5.0
  *
- * @return string
+ * @return string URI to current theme's stylesheet directory.
  */
 function get_stylesheet_directory_uri() {
 	$stylesheet         = str_replace( '%2F', '/', rawurlencode( get_stylesheet() ) );
@@ -225,14 +230,14 @@
 }
 
 /**
- * Retrieves the URI of current theme stylesheet.
+ * Retrieves stylesheet URI for current theme.
  *
  * The stylesheet file name is 'style.css' which is appended to the stylesheet directory URI path.
  * See get_stylesheet_directory_uri().
  *
  * @since 1.5.0
  *
- * @return string
+ * @return string URI to current theme's stylesheet.
  */
 function get_stylesheet_uri() {
 	$stylesheet_dir_uri = get_stylesheet_directory_uri();
@@ -265,9 +270,9 @@
  *
  * @since 2.1.0
  *
- * @global WP_Locale $wp_locale
+ * @global WP_Locale $wp_locale WordPress date and time locale object.
  *
- * @return string
+ * @return string URI to current theme's localized stylesheet.
  */
 function get_locale_stylesheet_uri() {
 	global $wp_locale;
@@ -293,7 +298,7 @@
 }
 
 /**
- * Retrieve name of the current theme.
+ * Retrieves name of the current theme.
  *
  * @since 1.5.0
  *
@@ -311,11 +316,11 @@
 }
 
 /**
- * Retrieve current theme directory.
+ * Retrieves template directory path for current theme.
  *
  * @since 1.5.0
  *
- * @return string Template directory path.
+ * @return string Path to current theme's template directory.
  */
 function get_template_directory() {
 	$template     = get_template();
@@ -327,7 +332,7 @@
 	 *
 	 * @since 1.5.0
 	 *
-	 * @param string $template_dir The URI of the current theme directory.
+	 * @param string $template_dir The path of the current theme directory.
 	 * @param string $template     Directory name of the current theme.
 	 * @param string $theme_root   Absolute path to the themes directory.
 	 */
@@ -335,11 +340,11 @@
 }
 
 /**
- * Retrieve theme directory URI.
+ * Retrieves template directory URI for current theme.
  *
  * @since 1.5.0
  *
- * @return string Template directory URI.
+ * @return string URI to current theme's template directory.
  */
 function get_template_directory_uri() {
 	$template         = str_replace( '%2F', '/', rawurlencode( get_template() ) );
@@ -359,13 +364,14 @@
 }
 
 /**
- * Retrieve theme roots.
+ * Retrieves theme roots.
  *
  * @since 2.9.0
  *
  * @global array $wp_theme_directories
  *
- * @return array|string An array of theme roots keyed by template/stylesheet or a single theme root if all themes have the same root.
+ * @return array|string An array of theme roots keyed by template/stylesheet
+ *                      or a single theme root if all themes have the same root.
  */
 function get_theme_roots() {
 	global $wp_theme_directories;
@@ -383,22 +389,24 @@
 }
 
 /**
- * Register a directory that contains themes.
+ * Registers a directory that contains themes.
  *
  * @since 2.9.0
  *
  * @global array $wp_theme_directories
  *
- * @param string $directory Either the full filesystem path to a theme folder or a folder within WP_CONTENT_DIR
- * @return bool
+ * @param string $directory Either the full filesystem path to a theme folder
+ *                          or a folder within WP_CONTENT_DIR.
+ * @return bool True if successfully registered a directory that contains themes,
+ *              false if the directory does not exist.
  */
 function register_theme_directory( $directory ) {
 	global $wp_theme_directories;
 
 	if ( ! file_exists( $directory ) ) {
-		// Try prepending as the theme directory could be relative to the content directory
+		// Try prepending as the theme directory could be relative to the content directory.
 		$directory = WP_CONTENT_DIR . '/' . $directory;
-		// If this directory does not exist, return and do not register
+		// If this directory does not exist, return and do not register.
 		if ( ! file_exists( $directory ) ) {
 			return false;
 		}
@@ -409,7 +417,7 @@
 	}
 
 	$untrailed = untrailingslashit( $directory );
-	if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories ) ) {
+	if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories, true ) ) {
 		$wp_theme_directories[] = $untrailed;
 	}
 
@@ -417,15 +425,14 @@
 }
 
 /**
- * Search all registered theme directories for complete and valid themes.
+ * Searches all registered theme directories for complete and valid themes.
  *
  * @since 2.9.0
  *
  * @global array $wp_theme_directories
- * @staticvar array $found_themes
  *
- * @param bool $force Optional. Whether to force a new directory scan. Defaults to false.
- * @return array|false Valid themes found
+ * @param bool $force Optional. Whether to force a new directory scan. Default false.
+ * @return array|false Valid themes found on success, false on failure.
  */
 function search_theme_directories( $force = false ) {
 	global $wp_theme_directories;
@@ -444,9 +451,11 @@
 	$wp_theme_directories = (array) $wp_theme_directories;
 	$relative_theme_roots = array();
 
-	// Set up maybe-relative, maybe-absolute array of theme directories.
-	// We always want to return absolute, but we need to cache relative
-	// to use in get_theme_root().
+	/*
+	 * Set up maybe-relative, maybe-absolute array of theme directories.
+	 * We always want to return absolute, but we need to cache relative
+	 * to use in get_theme_root().
+	 */
 	foreach ( $wp_theme_directories as $theme_root ) {
 		if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) ) {
 			$relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
@@ -461,9 +470,11 @@
 	 * @since 3.4.0
 	 *
 	 * @param bool   $cache_expiration Whether to get the cache of the theme directories. Default false.
-	 * @param string $cache_directory  Directory to be searched for the cache.
+	 * @param string $context          The class or function name calling the filter.
 	 */
-	if ( $cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' ) ) {
+	$cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' );
+
+	if ( $cache_expiration ) {
 		$cached_roots = get_site_transient( 'theme_roots' );
 		if ( is_array( $cached_roots ) ) {
 			foreach ( $cached_roots as $theme_dir => $theme_root ) {
@@ -479,10 +490,10 @@
 			return $found_themes;
 		}
 		if ( ! is_int( $cache_expiration ) ) {
-			$cache_expiration = 1800; // half hour
+			$cache_expiration = 30 * MINUTE_IN_SECONDS;
 		}
 	} else {
-		$cache_expiration = 1800; // half hour
+		$cache_expiration = 30 * MINUTE_IN_SECONDS;
 	}
 
 	/* Loop the registered theme directories and extract all themes */
@@ -495,12 +506,12 @@
 			continue;
 		}
 		foreach ( $dirs as $dir ) {
-			if ( ! is_dir( $theme_root . '/' . $dir ) || $dir[0] == '.' || $dir == 'CVS' ) {
+			if ( ! is_dir( $theme_root . '/' . $dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
 				continue;
 			}
 			if ( file_exists( $theme_root . '/' . $dir . '/style.css' ) ) {
 				// wp-content/themes/a-single-theme
-				// wp-content/themes is $theme_root, a-single-theme is $dir
+				// wp-content/themes is $theme_root, a-single-theme is $dir.
 				$found_themes[ $dir ] = array(
 					'theme_file' => $dir . '/style.css',
 					'theme_root' => $theme_root,
@@ -508,14 +519,14 @@
 			} else {
 				$found_theme = false;
 				// wp-content/themes/a-folder-of-themes/*
-				// wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs
+				// wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs.
 				$sub_dirs = @ scandir( $theme_root . '/' . $dir );
 				if ( ! $sub_dirs ) {
 					trigger_error( "$theme_root/$dir is not readable", E_USER_NOTICE );
 					continue;
 				}
 				foreach ( $sub_dirs as $sub_dir ) {
-					if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || $dir[0] == '.' || $dir == 'CVS' ) {
+					if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
 						continue;
 					}
 					if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) ) {
@@ -548,7 +559,7 @@
 		$theme_roots[ $theme_dir ] = $relative_theme_roots[ $theme_data['theme_root'] ]; // Convert absolute to relative.
 	}
 
-	if ( $theme_roots != get_site_transient( 'theme_roots' ) ) {
+	if ( get_site_transient( 'theme_roots' ) != $theme_roots ) {
 		set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
 	}
 
@@ -556,7 +567,7 @@
 }
 
 /**
- * Retrieve path to themes directory.
+ * Retrieves path to themes directory.
  *
  * Does not have trailing slash.
  *
@@ -564,19 +575,27 @@
  *
  * @global array $wp_theme_directories
  *
- * @param string $stylesheet_or_template The stylesheet or template name of the theme
- * @return string Theme path.
+ * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
+ *                                       Default is to leverage the main theme root.
+ * @return string Themes directory path.
  */
-function get_theme_root( $stylesheet_or_template = false ) {
+function get_theme_root( $stylesheet_or_template = '' ) {
 	global $wp_theme_directories;
 
-	if ( $stylesheet_or_template && $theme_root = get_raw_theme_root( $stylesheet_or_template ) ) {
-		// Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
-		// This gives relative theme roots the benefit of the doubt when things go haywire.
-		if ( ! in_array( $theme_root, (array) $wp_theme_directories ) ) {
-			$theme_root = WP_CONTENT_DIR . $theme_root;
+	$theme_root = '';
+
+	if ( $stylesheet_or_template ) {
+		$theme_root = get_raw_theme_root( $stylesheet_or_template );
+		if ( $theme_root ) {
+			// Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
+			// This gives relative theme roots the benefit of the doubt when things go haywire.
+			if ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
+				$theme_root = WP_CONTENT_DIR . $theme_root;
+			}
 		}
-	} else {
+	}
+
+	if ( ! $theme_root ) {
 		$theme_root = WP_CONTENT_DIR . '/themes';
 	}
 
@@ -591,7 +610,7 @@
 }
 
 /**
- * Retrieve URI for themes directory.
+ * Retrieves URI for themes directory.
  *
  * Does not have trailing slash.
  *
@@ -601,11 +620,11 @@
  *
  * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
  *                                       Default is to leverage the main theme root.
- * @param string $theme_root             Optional. The theme root for which calculations will be based, preventing
- *                                       the need for a get_raw_theme_root() call.
- * @return string Themes URI.
+ * @param string $theme_root             Optional. The theme root for which calculations will be based,
+ *                                       preventing the need for a get_raw_theme_root() call. Default empty.
+ * @return string Themes directory URI.
  */
-function get_theme_root_uri( $stylesheet_or_template = false, $theme_root = false ) {
+function get_theme_root_uri( $stylesheet_or_template = '', $theme_root = '' ) {
 	global $wp_theme_directories;
 
 	if ( $stylesheet_or_template && ! $theme_root ) {
@@ -613,7 +632,7 @@
 	}
 
 	if ( $stylesheet_or_template && $theme_root ) {
-		if ( in_array( $theme_root, (array) $wp_theme_directories ) ) {
+		if ( in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
 			// Absolute path. Make an educated guess. YMMV -- but note the filter below.
 			if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) ) {
 				$theme_root_uri = content_url( str_replace( WP_CONTENT_DIR, '', $theme_root ) );
@@ -638,22 +657,22 @@
 	 *
 	 * @param string $theme_root_uri         The URI for themes directory.
 	 * @param string $siteurl                WordPress web address which is set in General Options.
-	 * @param string $stylesheet_or_template Stylesheet or template name of the theme.
+	 * @param string $stylesheet_or_template The stylesheet or template name of the theme.
 	 */
 	return apply_filters( 'theme_root_uri', $theme_root_uri, get_option( 'siteurl' ), $stylesheet_or_template );
 }
 
 /**
- * Get the raw theme root relative to the content directory with no filters applied.
+ * Gets the raw theme root relative to the content directory with no filters applied.
  *
  * @since 3.1.0
  *
  * @global array $wp_theme_directories
  *
- * @param string $stylesheet_or_template The stylesheet or template name of the theme
+ * @param string $stylesheet_or_template The stylesheet or template name of the theme.
  * @param bool   $skip_cache             Optional. Whether to skip the cache.
  *                                       Defaults to false, meaning the cache is used.
- * @return string Theme root
+ * @return string Theme root.
  */
 function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
 	global $wp_theme_directories;
@@ -664,7 +683,7 @@
 
 	$theme_root = false;
 
-	// If requesting the root for the current theme, consult options to avoid calling get_theme_roots()
+	// If requesting the root for the current theme, consult options to avoid calling get_theme_roots().
 	if ( ! $skip_cache ) {
 		if ( get_option( 'stylesheet' ) == $stylesheet_or_template ) {
 			$theme_root = get_option( 'stylesheet_root' );
@@ -684,7 +703,7 @@
 }
 
 /**
- * Display localized stylesheet link element.
+ * Displays localized stylesheet link element.
  *
  * @since 2.1.0
  */
@@ -693,7 +712,14 @@
 	if ( empty( $stylesheet ) ) {
 		return;
 	}
-	echo '<link rel="stylesheet" href="' . $stylesheet . '" type="text/css" media="screen" />';
+
+	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
+
+	printf(
+		'<link rel="stylesheet" href="%s"%s media="screen" />',
+		$stylesheet,
+		$type_attr
+	);
 }
 
 /**
@@ -708,11 +734,16 @@
  * @global WP_Customize_Manager $wp_customize
  * @global array                $sidebars_widgets
  *
- * @param string $stylesheet Stylesheet name
+ * @param string $stylesheet Stylesheet name.
  */
 function switch_theme( $stylesheet ) {
 	global $wp_theme_directories, $wp_customize, $sidebars_widgets;
 
+	$requirements = validate_theme_requirements( $stylesheet );
+	if ( is_wp_error( $requirements ) ) {
+		wp_die( $requirements );
+	}
+
 	$_sidebars_widgets = null;
 	if ( 'wp_ajax_customize_save' === current_action() ) {
 		$old_sidebars_widgets_data_setting = $wp_customize->get_setting( 'old_sidebars_widgets_data' );
@@ -799,16 +830,17 @@
 }
 
 /**
- * Checks that current theme files 'index.php' and 'style.css' exists.
+ * Checks that the current theme has 'index.php' and 'style.css' files.
  *
  * Does not initially check the default theme, which is the fallback and should always exist.
  * But if it doesn't exist, it'll fall back to the latest core default theme that does exist.
  * Will switch theme to the fallback theme if current theme does not validate.
  *
- * You can use the {@see 'validate_current_theme'} filter to return false to
- * disable this functionality.
+ * You can use the {@see 'validate_current_theme'} filter to return false to disable
+ * this functionality.
  *
  * @since 1.5.0
+ *
  * @see WP_DEFAULT_THEME
  *
  * @return bool
@@ -845,6 +877,7 @@
 	/**
 	 * If we're in an invalid state but WP_DEFAULT_THEME doesn't exist,
 	 * switch to the latest core default theme that's installed.
+	 *
 	 * If it turns out that this latest core default theme is our current
 	 * theme, then there's nothing we can do about that, so we have to bail,
 	 * rather than going into an infinite loop. (This is why there are
@@ -861,7 +894,79 @@
 }
 
 /**
- * Retrieve all theme modifications.
+ * Validates the theme requirements for WordPress version and PHP version.
+ *
+ * Uses the information from `Requires at least` and `Requires PHP` headers
+ * defined in the theme's `style.css` file.
+ *
+ * If the headers are not present in the theme's stylesheet file,
+ * `readme.txt` is also checked as a fallback.
+ *
+ * @since 5.5.0
+ *
+ * @param string $stylesheet Directory name for the theme.
+ * @return true|WP_Error True if requirements are met, WP_Error on failure.
+ */
+function validate_theme_requirements( $stylesheet ) {
+	$theme = wp_get_theme( $stylesheet );
+
+	$requirements = array(
+		'requires'     => ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '',
+		'requires_php' => ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '',
+	);
+
+	$readme_file = $theme->theme_root . '/' . $stylesheet . '/readme.txt';
+
+	if ( file_exists( $readme_file ) ) {
+		$readme_headers = get_file_data(
+			$readme_file,
+			array(
+				'requires'     => 'Requires at least',
+				'requires_php' => 'Requires PHP',
+			),
+			'theme'
+		);
+
+		$requirements = array_merge( $readme_headers, $requirements );
+	}
+
+	$compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
+	$compatible_php = is_php_version_compatible( $requirements['requires_php'] );
+
+	if ( ! $compatible_wp && ! $compatible_php ) {
+		return new WP_Error(
+			'theme_wp_php_incompatible',
+			sprintf(
+				/* translators: %s: Theme name. */
+				_x( '<strong>Error:</strong> Current WordPress and PHP versions do not meet minimum requirements for %s.', 'theme' ),
+				$theme->display( 'Name' )
+			)
+		);
+	} elseif ( ! $compatible_php ) {
+		return new WP_Error(
+			'theme_php_incompatible',
+			sprintf(
+				/* translators: %s: Theme name. */
+				_x( '<strong>Error:</strong> Current PHP version does not meet minimum requirements for %s.', 'theme' ),
+				$theme->display( 'Name' )
+			)
+		);
+	} elseif ( ! $compatible_wp ) {
+		return new WP_Error(
+			'theme_wp_incompatible',
+			sprintf(
+				/* translators: %s: Theme name. */
+				_x( '<strong>Error:</strong> Current WordPress version does not meet minimum requirements for %s.', 'theme' ),
+				$theme->display( 'Name' )
+			)
+		);
+	}
+
+	return true;
+}
+
+/**
+ * Retrieves all theme modifications.
  *
  * @since 3.1.0
  *
@@ -885,18 +990,18 @@
 }
 
 /**
- * Retrieve theme modification value for the current theme.
+ * Retrieves theme modification value for the current theme.
  *
  * If the modification name does not exist, then the $default will be passed
- * through {@link https://secure.php.net/sprintf sprintf()} PHP function with the first
- * string the template directory URI and the second string the stylesheet
- * directory URI.
+ * through {@link https://www.php.net/sprintf sprintf()} PHP function with
+ * the template directory URI as the first string and the stylesheet directory URI
+ * as the second string.
  *
  * @since 2.1.0
  *
- * @param string      $name    Theme modification name.
- * @param bool|string $default
- * @return mixed
+ * @param string       $name    Theme modification name.
+ * @param string|false $default Optional. Theme modification default value. Default false.
+ * @return mixed Theme modification value.
  */
 function get_theme_mod( $name, $default = false ) {
 	$mods = get_theme_mods();
@@ -905,10 +1010,9 @@
 		/**
 		 * Filters the theme modification, or 'theme_mod', value.
 		 *
-		 * The dynamic portion of the hook name, `$name`, refers to
-		 * the key name of the modification array. For example,
-		 * 'header_textcolor', 'header_image', and so on depending
-		 * on the theme options.
+		 * The dynamic portion of the hook name, `$name`, refers to the key name
+		 * of the modification array. For example, 'header_textcolor', 'header_image',
+		 * and so on depending on the theme options.
 		 *
 		 * @since 2.2.0
 		 *
@@ -918,7 +1022,10 @@
 	}
 
 	if ( is_string( $default ) ) {
-		$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
+		// Only run the replacement if an sprintf() string format pattern was found.
+		if ( preg_match( '#(?<!%)%(?:\d+\$?)?s#', $default ) ) {
+			$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
+		}
 	}
 
 	/** This filter is documented in wp-includes/theme.php */
@@ -926,7 +1033,7 @@
 }
 
 /**
- * Update theme modification value for the current theme.
+ * Updates theme modification value for the current theme.
  *
  * @since 2.1.0
  *
@@ -938,16 +1045,16 @@
 	$old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;
 
 	/**
-	 * Filters the theme mod value on save.
+	 * Filters the theme modification, or 'theme_mod', value on save.
 	 *
-	 * The dynamic portion of the hook name, `$name`, refers to the key name of
-	 * the modification array. For example, 'header_textcolor', 'header_image',
+	 * The dynamic portion of the hook name, `$name`, refers to the key name
+	 * of the modification array. For example, 'header_textcolor', 'header_image',
 	 * and so on depending on the theme options.
 	 *
 	 * @since 3.9.0
 	 *
-	 * @param string $value     The new value of the theme mod.
-	 * @param string $old_value The current value of the theme mod.
+	 * @param string $value     The new value of the theme modification.
+	 * @param string $old_value The current value of the theme modification.
 	 */
 	$mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );
 
@@ -956,10 +1063,10 @@
 }
 
 /**
- * Remove theme modification name from current theme list.
+ * Removes theme modification name from current theme list.
  *
- * If removing the name also removes all elements, then the entire option will
- * be removed.
+ * If removing the name also removes all elements, then the entire option
+ * will be removed.
  *
  * @since 2.1.0
  *
@@ -983,7 +1090,7 @@
 }
 
 /**
- * Remove theme modifications option for current theme.
+ * Removes theme modifications option for current theme.
  *
  * @since 2.1.0
  */
@@ -1035,7 +1142,7 @@
 }
 
 /**
- * Check whether a header image is set or not.
+ * Checks whether a header image is set or not.
  *
  * @since 4.2.0
  *
@@ -1048,7 +1155,7 @@
 }
 
 /**
- * Retrieve header image for custom header.
+ * Retrieves header image for custom header.
  *
  * @since 2.1.0
  *
@@ -1057,7 +1164,7 @@
 function get_header_image() {
 	$url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
 
-	if ( 'remove-header' == $url ) {
+	if ( 'remove-header' === $url ) {
 		return false;
 	}
 
@@ -1069,7 +1176,7 @@
 }
 
 /**
- * Create image tag markup for a custom header image.
+ * Creates image tag markup for a custom header image.
  *
  * @since 4.4.0
  *
@@ -1136,7 +1243,7 @@
 }
 
 /**
- * Display the image markup for a custom header image.
+ * Displays the image markup for a custom header image.
  *
  * @since 4.4.0
  *
@@ -1147,14 +1254,13 @@
 }
 
 /**
- * Get random header image data from registered images in theme.
+ * Gets random header image data from registered images in theme.
  *
  * @since 3.4.0
  *
  * @access private
  *
- * @global array  $_wp_default_headers
- * @staticvar object $_wp_random_header
+ * @global array $_wp_default_headers
  *
  * @return object
  */
@@ -1166,10 +1272,10 @@
 		$header_image_mod = get_theme_mod( 'header_image', '' );
 		$headers          = array();
 
-		if ( 'random-uploaded-image' == $header_image_mod ) {
+		if ( 'random-uploaded-image' === $header_image_mod ) {
 			$headers = get_uploaded_header_images();
 		} elseif ( ! empty( $_wp_default_headers ) ) {
-			if ( 'random-default-image' == $header_image_mod ) {
+			if ( 'random-default-image' === $header_image_mod ) {
 				$headers = $_wp_default_headers;
 			} else {
 				if ( current_theme_supports( 'custom-header', 'random-default' ) ) {
@@ -1187,26 +1293,29 @@
 		$_wp_random_header->url           = sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
 		$_wp_random_header->thumbnail_url = sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
 	}
+
 	return $_wp_random_header;
 }
 
 /**
- * Get random header image url from registered images in theme.
+ * Gets random header image URL from registered images in theme.
  *
  * @since 3.2.0
  *
- * @return string Path to header image
+ * @return string Path to header image.
  */
 function get_random_header_image() {
 	$random_image = _get_random_header_data();
+
 	if ( empty( $random_image->url ) ) {
 		return '';
 	}
+
 	return $random_image->url;
 }
 
 /**
- * Check if random header image is in use.
+ * Checks if random header image is in use.
  *
  * Always true if user expressly chooses the option in Appearance > Header.
  * Also true if theme has multiple header images registered, no specific header image
@@ -1214,20 +1323,24 @@
  *
  * @since 3.2.0
  *
- * @param string $type The random pool to use. any|default|uploaded
+ * @param string $type The random pool to use. Possible values include 'any',
+ *                     'default', 'uploaded'. Default 'any'.
  * @return bool
  */
 function is_random_header_image( $type = 'any' ) {
 	$header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
 
-	if ( 'any' == $type ) {
-		if ( 'random-default-image' == $header_image_mod || 'random-uploaded-image' == $header_image_mod || ( '' != get_random_header_image() && empty( $header_image_mod ) ) ) {
+	if ( 'any' === $type ) {
+		if ( 'random-default-image' === $header_image_mod
+			|| 'random-uploaded-image' === $header_image_mod
+			|| ( '' !== get_random_header_image() && empty( $header_image_mod ) )
+		) {
 			return true;
 		}
 	} else {
-		if ( "random-$type-image" == $header_image_mod ) {
+		if ( "random-$type-image" === $header_image_mod ) {
 			return true;
-		} elseif ( 'default' == $type && empty( $header_image_mod ) && '' != get_random_header_image() ) {
+		} elseif ( 'default' === $type && empty( $header_image_mod ) && '' !== get_random_header_image() ) {
 			return true;
 		}
 	}
@@ -1236,19 +1349,20 @@
 }
 
 /**
- * Display header image URL.
+ * Displays header image URL.
  *
  * @since 2.1.0
  */
 function header_image() {
 	$image = get_header_image();
+
 	if ( $image ) {
 		echo esc_url( $image );
 	}
 }
 
 /**
- * Get the header images uploaded for the current theme.
+ * Gets the header images uploaded for the current theme.
  *
  * @since 3.2.0
  *
@@ -1257,7 +1371,7 @@
 function get_uploaded_header_images() {
 	$header_images = array();
 
-	// @todo caching
+	// @todo Caching.
 	$headers = get_posts(
 		array(
 			'post_type'  => 'attachment',
@@ -1296,7 +1410,7 @@
 }
 
 /**
- * Get the header image data.
+ * Gets the header image data.
  *
  * @since 3.4.0
  *
@@ -1312,9 +1426,10 @@
 	} else {
 		$data = get_theme_mod( 'header_image_data' );
 		if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
-			$directory_args = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
-			$data           = array();
-			$data['url']    = $data['thumbnail_url'] = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
+			$directory_args        = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
+			$data                  = array();
+			$data['url']           = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
+			$data['thumbnail_url'] = $data['url'];
 			if ( ! empty( $_wp_default_headers ) ) {
 				foreach ( (array) $_wp_default_headers as $default_header ) {
 					$url = vsprintf( $default_header['url'], $directory_args );
@@ -1340,13 +1455,14 @@
 }
 
 /**
- * Register a selection of default headers to be displayed by the custom header admin UI.
+ * Registers a selection of default headers to be displayed by the custom header admin UI.
  *
  * @since 3.0.0
  *
  * @global array $_wp_default_headers
  *
- * @param array $headers Array of headers keyed by a string id. The ids point to arrays containing 'url', 'thumbnail_url', and 'description' keys.
+ * @param array $headers Array of headers keyed by a string ID. The IDs point to arrays
+ *                       containing 'url', 'thumbnail_url', and 'description' keys.
  */
 function register_default_headers( $headers ) {
 	global $_wp_default_headers;
@@ -1355,7 +1471,7 @@
 }
 
 /**
- * Unregister default headers.
+ * Unregisters default headers.
  *
  * This function must be called after register_default_headers() has already added the
  * header you want to remove.
@@ -1382,7 +1498,7 @@
 }
 
 /**
- * Check whether a header video is set or not.
+ * Checks whether a header video is set or not.
  *
  * @since 4.7.0
  *
@@ -1395,7 +1511,7 @@
 }
 
 /**
- * Retrieve header video URL for custom header.
+ * Retrieves header video URL for custom header.
  *
  * Uses a local video if present, or falls back to an external video.
  *
@@ -1404,12 +1520,13 @@
  * @return string|false Header video URL or false if there is no video.
  */
 function get_header_video_url() {
-	$id  = absint( get_theme_mod( 'header_video' ) );
-	$url = esc_url( get_theme_mod( 'external_header_video' ) );
+	$id = absint( get_theme_mod( 'header_video' ) );
 
 	if ( $id ) {
 		// Get the file URL from the attachment ID.
 		$url = wp_get_attachment_url( $id );
+	} else {
+		$url = get_theme_mod( 'external_header_video' );
 	}
 
 	/**
@@ -1429,19 +1546,20 @@
 }
 
 /**
- * Display header video URL.
+ * Displays header video URL.
  *
  * @since 4.7.0
  */
 function the_header_video_url() {
 	$video = get_header_video_url();
+
 	if ( $video ) {
 		echo esc_url( $video );
 	}
 }
 
 /**
- * Retrieve header video settings.
+ * Retrieves header video settings.
  *
  * @since 4.7.0
  *
@@ -1485,7 +1603,7 @@
 }
 
 /**
- * Check whether a custom header is set or not.
+ * Checks whether a custom header is set or not.
  *
  * @since 4.7.0
  *
@@ -1520,7 +1638,7 @@
 	}
 
 	/**
-	 * Modify whether the custom header video is eligible to show on the current page.
+	 * Filters whether the custom header video is eligible to show on the current page.
 	 *
 	 * @since 4.7.0
 	 *
@@ -1532,7 +1650,7 @@
 }
 
 /**
- * Retrieve the markup for a custom header.
+ * Retrieves the markup for a custom header.
  *
  * The container div will always be returned in the Customizer preview.
  *
@@ -1552,7 +1670,7 @@
 }
 
 /**
- * Print the markup for a custom header.
+ * Prints the markup for a custom header.
  *
  * A container div will always be printed in the Customizer preview.
  *
@@ -1573,7 +1691,7 @@
 }
 
 /**
- * Retrieve background image for custom background.
+ * Retrieves background image for custom background.
  *
  * @since 3.0.0
  *
@@ -1584,7 +1702,7 @@
 }
 
 /**
- * Display background image path.
+ * Displays background image path.
  *
  * @since 3.0.0
  */
@@ -1593,7 +1711,7 @@
 }
 
 /**
- * Retrieve value for custom background color.
+ * Retrieves value for custom background color.
  *
  * @since 3.0.0
  *
@@ -1604,7 +1722,7 @@
 }
 
 /**
- * Display background color value.
+ * Displays background color value.
  *
  * @since 3.0.0
  */
@@ -1625,13 +1743,15 @@
 	// A default has to be specified in style.css. It will not be printed here.
 	$color = get_background_color();
 
-	if ( $color === get_theme_support( 'custom-background', 'default-color' ) ) {
+	if ( get_theme_support( 'custom-background', 'default-color' ) === $color ) {
 		$color = false;
 	}
 
+	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
+
 	if ( ! $background && ! $color ) {
 		if ( is_customize_preview() ) {
-			echo '<style type="text/css" id="custom-background-css"></style>';
+			printf( '<style%s id="custom-background-css"></style>', $type_attr );
 		}
 		return;
 	}
@@ -1685,22 +1805,23 @@
 		$style .= $image . $position . $size . $repeat . $attachment;
 	}
 	?>
-<style type="text/css" id="custom-background-css">
+<style<?php echo $type_attr; ?> id="custom-background-css">
 body.custom-background { <?php echo trim( $style ); ?> }
 </style>
 	<?php
 }
 
 /**
- * Render the Custom CSS style element.
+ * Renders the Custom CSS style element.
  *
  * @since 4.7.0
  */
 function wp_custom_css_cb() {
 	$styles = wp_get_custom_css();
 	if ( $styles || is_customize_preview() ) :
+		$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
 		?>
-		<style type="text/css" id="wp-custom-css">
+		<style<?php echo $type_attr; ?> id="wp-custom-css">
 			<?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
 		</style>
 		<?php
@@ -1708,7 +1829,7 @@
 }
 
 /**
- * Fetch the `custom_css` post for a given theme.
+ * Fetches the `custom_css` post for a given theme.
  *
  * @since 4.7.0
  *
@@ -1759,7 +1880,7 @@
 }
 
 /**
- * Fetch the saved Custom CSS content for rendering.
+ * Fetches the saved Custom CSS content for rendering.
  *
  * @since 4.7.0
  *
@@ -1792,7 +1913,7 @@
 }
 
 /**
- * Update the `custom_css` post for a given theme.
+ * Updates the `custom_css` post for a given theme.
  *
  * Inserts a `custom_css` post when one doesn't yet exist.
  *
@@ -1892,7 +2013,7 @@
 }
 
 /**
- * Add callback for custom TinyMCE editor stylesheets.
+ * Adds callback for custom TinyMCE editor stylesheets.
  *
  * The parameter $stylesheet is the name of the stylesheet, relative to
  * the theme root. It also accepts an array of stylesheets.
@@ -1914,15 +2035,13 @@
  *                                 Defaults to 'editor-style.css'
  */
 function add_editor_style( $stylesheet = 'editor-style.css' ) {
+	global $editor_styles;
+
 	add_theme_support( 'editor-style' );
 
-	if ( ! is_admin() ) {
-		return;
-	}
-
-	global $editor_styles;
 	$editor_styles = (array) $editor_styles;
 	$stylesheet    = (array) $stylesheet;
+
 	if ( is_rtl() ) {
 		$rtl_stylesheet = str_replace( '.css', '-rtl.css', $stylesheet[0] );
 		$stylesheet[]   = $rtl_stylesheet;
@@ -1952,17 +2071,17 @@
 }
 
 /**
- * Retrieve any registered editor stylesheets
+ * Retrieves any registered editor stylesheet URLs.
  *
  * @since 4.0.0
  *
  * @global array $editor_styles Registered editor stylesheets
  *
- * @return array If registered, a list of editor stylesheet URLs.
+ * @return string[] If registered, a list of editor stylesheet URLs.
  */
 function get_editor_stylesheets() {
 	$stylesheets = array();
-	// load editor_style.css if the current theme supports it
+	// Load editor_style.css if the current theme supports it.
 	if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
 		$editor_styles = $GLOBALS['editor_styles'];
 
@@ -1998,17 +2117,17 @@
 	}
 
 	/**
-	 * Filters the array of stylesheets applied to the editor.
+	 * Filters the array of URLs of stylesheets applied to the editor.
 	 *
 	 * @since 4.3.0
 	 *
-	 * @param array $stylesheets Array of stylesheets to be applied to the editor.
+	 * @param string[] $stylesheets Array of URLs of stylesheets to be applied to the editor.
 	 */
 	return apply_filters( 'editor_stylesheets', $stylesheets );
 }
 
 /**
- * Expand a theme's starter content configuration using core-provided data.
+ * Expands a theme's starter content configuration using core-provided data.
  *
  * @since 4.7.0
  *
@@ -2034,7 +2153,7 @@
 							'<strong>' . _x( 'Address', 'Theme starter content' ) . "</strong>\n",
 							_x( '123 Main Street', 'Theme starter content' ) . "\n" . _x( 'New York, NY 10001', 'Theme starter content' ) . "\n\n",
 							'<strong>' . _x( 'Hours', 'Theme starter content' ) . "</strong>\n",
-							_x( 'Monday&mdash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . "\n" . _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ),
+							_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' ),
 						)
 					),
 					'filter' => true,
@@ -2099,7 +2218,7 @@
 				'title' => _x( 'Home', 'Theme starter content' ),
 				'url'   => home_url( '/' ),
 			),
-			'page_home'       => array( // Deprecated in favor of link_home.
+			'page_home'       => array( // Deprecated in favor of 'link_home'.
 				'type'      => 'post_type',
 				'object'    => 'page',
 				'object_id' => '{{home}}',
@@ -2170,17 +2289,26 @@
 			'home'             => array(
 				'post_type'    => 'page',
 				'post_title'   => _x( 'Home', 'Theme starter content' ),
-				'post_content' => _x( 'Welcome to your site! This is your homepage, which is what most visitors will see when they come to your site for the first time.', 'Theme starter content' ),
+				'post_content' => sprintf(
+					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
+					_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' )
+				),
 			),
 			'about'            => array(
 				'post_type'    => 'page',
 				'post_title'   => _x( 'About', 'Theme starter content' ),
-				'post_content' => _x( 'You might be an artist who would like to introduce yourself and your work here or maybe you&rsquo;re a business with a mission to describe.', 'Theme starter content' ),
+				'post_content' => sprintf(
+					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
+					_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' )
+				),
 			),
 			'contact'          => array(
 				'post_type'    => 'page',
 				'post_title'   => _x( 'Contact', 'Theme starter content' ),
-				'post_content' => _x( 'This is a page with some basic contact information, such as an address and phone number. You might also try a plugin to add a contact form.', 'Theme starter content' ),
+				'post_content' => sprintf(
+					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
+					_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' )
+				),
 			),
 			'blog'             => array(
 				'post_type'  => 'page',
@@ -2194,7 +2322,10 @@
 			'homepage-section' => array(
 				'post_type'    => 'page',
 				'post_title'   => _x( 'A homepage section', 'Theme starter content' ),
-				'post_content' => _x( 'This is an example of a homepage section. Homepage sections can be any page other than the homepage itself, including the page that shows your latest blog posts.', 'Theme starter content' ),
+				'post_content' => sprintf(
+					"<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
+					_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' )
+				),
 			),
 		),
 	);
@@ -2267,7 +2398,8 @@
 				}
 				break;
 
-			// All that's left now are posts (besides attachments). Not a default case for the sake of clarity and future work.
+			// All that's left now are posts (besides attachments).
+			// Not a default case for the sake of clarity and future work.
 			case 'posts':
 				foreach ( $config[ $type ] as $id => $item ) {
 					if ( is_array( $item ) ) {
@@ -2318,32 +2450,46 @@
  * If attached to a hook, it must be {@see 'after_setup_theme'}.
  * The {@see 'init'} hook may be too late for some features.
  *
+ * Example usage:
+ *
+ *     add_theme_support( 'title-tag' );
+ *     add_theme_support( 'custom-logo', array(
+ *         'height' => 480,
+ *         'width'  => 720,
+ *     ) );
+ *
  * @since 2.9.0
- * @since 3.6.0 The `html5` feature was added
- * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'
- * @since 4.1.0 The `title-tag` feature was added
- * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added
- * @since 4.7.0 The `starter-content` feature was added
+ * @since 3.4.0 The `custom-header-uploads` feature was deprecated.
+ * @since 3.6.0 The `html5` feature was added.
+ * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'.
+ * @since 4.1.0 The `title-tag` feature was added.
+ * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added.
+ * @since 4.7.0 The `starter-content` feature was added.
  * @since 5.0.0 The `responsive-embeds`, `align-wide`, `dark-editor-style`, `disable-custom-colors`,
- *              `disable-custom-font-sizes`, `editor-color-pallete`, `editor-font-sizes`,
+ *              `disable-custom-font-sizes`, `editor-color-palette`, `editor-font-sizes`,
  *              `editor-styles`, and `wp-block-styles` features were added.
+ * @since 5.3.0 The `html5` feature now also accepts 'script' and 'style'.
+ * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
+ *              by adding it to the function signature.
+ * @since 5.5.0 The `core-block-patterns` feature was added and is enabled by default.
+ * @since 5.5.0 The `custom-logo` feature now also accepts 'unlink-homepage-logo'.
  *
  * @global array $_wp_theme_features
  *
- * @param string $feature  The feature being added. Likely core values include 'post-formats',
- *                         'post-thumbnails', 'html5', 'custom-logo', 'custom-header-uploads',
- *                         'custom-header', 'custom-background', 'title-tag', 'starter-content',
- *                         'responsive-embeds', etc.
- * @param mixed  $args,... Optional extra arguments to pass along with certain features.
+ * @param string $feature The feature being added. Likely core values include 'post-formats', 'post-thumbnails',
+ *                        'custom-header', 'custom-background', 'custom-logo', 'menus', 'automatic-feed-links',
+ *                        'html5', 'title-tag', 'customize-selective-refresh-widgets', 'starter-content',
+ *                        'responsive-embeds', 'align-wide', 'dark-editor-style', 'disable-custom-colors',
+ *                        'disable-custom-font-sizes', 'editor-color-palette', 'editor-font-sizes',
+ *                        'editor-styles', 'wp-block-styles', and 'core-block-patterns'.
+ * @param mixed  ...$args Optional extra arguments to pass along with certain features.
  * @return void|bool False on failure, void otherwise.
  */
-function add_theme_support( $feature ) {
+function add_theme_support( $feature, ...$args ) {
 	global $_wp_theme_features;
 
-	if ( func_num_args() == 1 ) {
+	if ( ! $args ) {
 		$args = true;
-	} else {
-		$args = array_slice( func_get_args(), 1 );
 	}
 
 	switch ( $feature ) {
@@ -2357,14 +2503,14 @@
 			 * Merge post types with any that already declared their support
 			 * for post thumbnails.
 			 */
-			if ( is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
+			if ( isset( $args[0] ) && is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
 				$args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
 			}
 
 			break;
 
 		case 'post-formats':
-			if ( is_array( $args[0] ) ) {
+			if ( isset( $args[0] ) && is_array( $args[0] ) ) {
 				$post_formats = get_post_format_slugs();
 				unset( $post_formats['standard'] );
 
@@ -2377,7 +2523,7 @@
 			if ( empty( $args[0] ) ) {
 				// Build an array of types for back-compat.
 				$args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
-			} elseif ( ! is_array( $args[0] ) ) {
+			} elseif ( ! isset( $args[0] ) || ! is_array( $args[0] ) ) {
 				_doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
 				return false;
 			}
@@ -2389,15 +2535,16 @@
 			break;
 
 		case 'custom-logo':
-			if ( ! is_array( $args ) ) {
+			if ( true === $args ) {
 				$args = array( 0 => array() );
 			}
 			$defaults = array(
-				'width'       => null,
-				'height'      => null,
-				'flex-width'  => false,
-				'flex-height' => false,
-				'header-text' => '',
+				'width'                => null,
+				'height'               => null,
+				'flex-width'           => false,
+				'flex-height'          => false,
+				'header-text'          => '',
+				'unlink-homepage-logo' => false,
 			);
 			$args[0]  = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
 
@@ -2412,7 +2559,7 @@
 			return add_theme_support( 'custom-header', array( 'uploads' => true ) );
 
 		case 'custom-header':
-			if ( ! is_array( $args ) ) {
+			if ( true === $args ) {
 				$args = array( 0 => array() );
 			}
 
@@ -2448,11 +2595,13 @@
 				$args[0] = wp_parse_args( $args[0], $defaults );
 			}
 
-			// If a constant was defined, use that value. Otherwise, define the constant to ensure
-			// the constant is always accurate (and is not defined later,  overriding our value).
-			// As stated above, the first value wins.
-			// Once we get to wp_loaded (just-in-time), define any constants we haven't already.
-			// Constants are lame. Don't reference them. This is just for backward compatibility.
+			/*
+			 * If a constant was defined, use that value. Otherwise, define the constant to ensure
+			 * the constant is always accurate (and is not defined later,  overriding our value).
+			 * As stated above, the first value wins.
+			 * Once we get to wp_loaded (just-in-time), define any constants we haven't already.
+			 * Constants are lame. Don't reference them. This is just for backward compatibility.
+			 */
 
 			if ( defined( 'NO_HEADER_TEXT' ) ) {
 				$args[0]['header-text'] = ! NO_HEADER_TEXT;
@@ -2502,7 +2651,7 @@
 			break;
 
 		case 'custom-background':
-			if ( ! is_array( $args ) ) {
+			if ( true === $args ) {
 				$args = array( 0 => array() );
 			}
 
@@ -2550,10 +2699,10 @@
 		case 'title-tag':
 			// Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
 			if ( did_action( 'wp_loaded' ) ) {
-				/* translators: 1: title-tag, 2: wp_loaded */
 				_doing_it_wrong(
 					"add_theme_support( 'title-tag' )",
 					sprintf(
+						/* translators: 1: title-tag, 2: wp_loaded */
 						__( 'Theme support for %1$s should be registered before the %2$s hook.' ),
 						'<code>title-tag</code>',
 						'<code>wp_loaded</code>'
@@ -2590,7 +2739,7 @@
 		}
 
 		if ( is_admin() ) {
-			require_once( ABSPATH . 'wp-admin/custom-header.php' );
+			require_once ABSPATH . 'wp-admin/includes/class-custom-image-header.php';
 			$custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
 		}
 	}
@@ -2603,7 +2752,7 @@
 		add_action( 'wp_head', $args[0]['wp-head-callback'] );
 
 		if ( is_admin() ) {
-			require_once( ABSPATH . 'wp-admin/custom-background.php' );
+			require_once ABSPATH . 'wp-admin/includes/class-custom-background.php';
 			$custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
 		}
 	}
@@ -2621,9 +2770,10 @@
 		$classes = array_map( 'sanitize_html_class', $classes );
 		$classes = '.' . implode( ', .', $classes );
 
+		$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
 		?>
 		<!-- Custom Logo: hide header text -->
-		<style id="custom-logo-css" type="text/css">
+		<style id="custom-logo-css"<?php echo $type_attr; ?>>
 			<?php echo $classes; ?> {
 				position: absolute;
 				clip: rect(1px, 1px, 1px, 1px);
@@ -2634,26 +2784,34 @@
 }
 
 /**
- * Gets the theme support arguments passed when registering that support
+ * Gets the theme support arguments passed when registering that support.
+ *
+ * Example usage:
+ *
+ *     get_theme_support( 'custom-logo' );
+ *     get_theme_support( 'custom-header', 'width' );
  *
  * @since 3.1.0
+ * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
+ *              by adding it to the function signature.
  *
  * @global array $_wp_theme_features
  *
- * @param string $feature The feature to check.
+ * @param string $feature The feature to check. See add_theme_support() for the list
+ *                        of possible values.
+ * @param mixed  ...$args Optional extra arguments to be checked against certain features.
  * @return mixed The array of extra arguments or the value for the registered feature.
  */
-function get_theme_support( $feature ) {
+function get_theme_support( $feature, ...$args ) {
 	global $_wp_theme_features;
 	if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
 		return false;
 	}
 
-	if ( func_num_args() <= 1 ) {
+	if ( ! $args ) {
 		return $_wp_theme_features[ $feature ];
 	}
 
-	$args = array_slice( func_get_args(), 1 );
 	switch ( $feature ) {
 		case 'custom-logo':
 		case 'custom-header':
@@ -2675,13 +2833,16 @@
  * be used for child themes to override support from the parent theme.
  *
  * @since 3.0.0
+ *
  * @see add_theme_support()
- * @param string $feature The feature being removed.
+ *
+ * @param string $feature The feature being removed. See add_theme_support() for the list
+ *                        of possible values.
  * @return bool|void Whether feature was removed.
  */
 function remove_theme_support( $feature ) {
-	// Blacklist: for internal registrations not used directly by themes.
-	if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) ) {
+	// Do not remove internal registrations that are not used directly by themes.
+	if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ), true ) ) {
 		return false;
 	}
 
@@ -2689,16 +2850,18 @@
 }
 
 /**
- * Do not use. Removes theme support internally, ignorant of the blacklist.
+ * Do not use. Removes theme support internally without knowledge of those not used
+ * by themes directly.
  *
  * @access private
  * @since 3.1.0
- *
  * @global array               $_wp_theme_features
  * @global Custom_Image_Header $custom_image_header
  * @global Custom_Background   $custom_background
  *
- * @param string $feature
+ * @param string $feature The feature being removed. See add_theme_support() for the list
+ *                        of possible values.
+ * @return bool True if support was removed, false if the feature was not registered.
  */
 function _remove_theme_support( $feature ) {
 	global $_wp_theme_features;
@@ -2745,23 +2908,33 @@
 	}
 
 	unset( $_wp_theme_features[ $feature ] );
+
 	return true;
 }
 
 /**
  * Checks a theme's support for a given feature.
  *
+ * Example usage:
+ *
+ *     current_theme_supports( 'custom-logo' );
+ *     current_theme_supports( 'html5', 'comment-form' );
+ *
  * @since 2.9.0
+ * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
+ *              by adding it to the function signature.
  *
  * @global array $_wp_theme_features
  *
- * @param string $feature The feature being checked.
+ * @param string $feature The feature being checked. See add_theme_support() for the list
+ *                        of possible values.
+ * @param mixed  ...$args Optional extra arguments to be checked against certain features.
  * @return bool True if the current theme supports the feature, false otherwise.
  */
-function current_theme_supports( $feature ) {
+function current_theme_supports( $feature, ...$args ) {
 	global $_wp_theme_features;
 
-	if ( 'custom-header-uploads' == $feature ) {
+	if ( 'custom-header-uploads' === $feature ) {
 		return current_theme_supports( 'custom-header', 'uploads' );
 	}
 
@@ -2769,33 +2942,34 @@
 		return false;
 	}
 
-	// If no args passed then no extra checks need be performed
-	if ( func_num_args() <= 1 ) {
+	// If no args passed then no extra checks need be performed.
+	if ( ! $args ) {
 		return true;
 	}
 
-	$args = array_slice( func_get_args(), 1 );
-
 	switch ( $feature ) {
 		case 'post-thumbnails':
-			// post-thumbnails can be registered for only certain content/post types by passing
-			// an array of types to add_theme_support(). If no array was passed, then
-			// any type is accepted
-			if ( true === $_wp_theme_features[ $feature ] ) {  // Registered for all types
+			/*
+			 * post-thumbnails can be registered for only certain content/post types
+			 * by passing an array of types to add_theme_support().
+			 * If no array was passed, then any type is accepted.
+			 */
+			if ( true === $_wp_theme_features[ $feature ] ) {  // Registered for all types.
 				return true;
 			}
 			$content_type = $args[0];
-			return in_array( $content_type, $_wp_theme_features[ $feature ][0] );
+			return in_array( $content_type, $_wp_theme_features[ $feature ][0], true );
 
 		case 'html5':
 		case 'post-formats':
-			// specific post formats can be registered by passing an array of types to
-			// add_theme_support()
-
-			// Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
-
+			/*
+			 * Specific post formats can be registered by passing an array of types
+			 * to add_theme_support().
+			 *
+			 * Specific areas of HTML5 support *must* be passed via an array to add_theme_support().
+			 */
 			$type = $args[0];
-			return in_array( $type, $_wp_theme_features[ $feature ][0] );
+			return in_array( $type, $_wp_theme_features[ $feature ][0], true );
 
 		case 'custom-logo':
 		case 'custom-header':
@@ -2807,18 +2981,16 @@
 	/**
 	 * Filters whether the current theme supports a specific feature.
 	 *
-	 * The dynamic portion of the hook name, `$feature`, refers to the specific theme
-	 * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
-	 * 'custom-header', 'menus', 'automatic-feed-links', 'html5',
-	 * 'starter-content', and 'customize-selective-refresh-widgets'.
+	 * The dynamic portion of the hook name, `$feature`, refers to the specific
+	 * theme feature. See add_theme_support() for the list of possible values.
 	 *
 	 * @since 3.4.0
 	 *
-	 * @param bool   true     Whether the current theme supports the given feature. Default true.
-	 * @param array  $args    Array of arguments for the feature.
-	 * @param string $feature The theme feature.
+	 * @param bool   $supports Whether the current theme supports the given feature. Default true.
+	 * @param array  $args     Array of arguments for the feature.
+	 * @param string $feature  The theme feature.
 	 */
-	return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] );
+	return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
 }
 
 /**
@@ -2826,19 +2998,206 @@
  *
  * @since 2.9.0
  *
- * @param string $feature The feature being checked.
+ * @param string $feature The feature being checked. See add_theme_support() for the list
+ *                        of possible values.
  * @param string $include Path to the file.
  * @return bool True if the current theme supports the supplied feature, false otherwise.
  */
 function require_if_theme_supports( $feature, $include ) {
 	if ( current_theme_supports( $feature ) ) {
-		require( $include );
+		require $include;
 		return true;
 	}
 	return false;
 }
 
 /**
+ * Registers a theme feature for use in add_theme_support().
+ *
+ * This does not indicate that the current theme supports the feature, it only describes
+ * the feature's supported options.
+ *
+ * @since 5.5.0
+ *
+ * @see add_theme_support()
+ *
+ * @global array $_wp_registered_theme_features
+ *
+ * @param string $feature The name uniquely identifying the feature. See add_theme_support()
+ *                        for the list of possible values.
+ * @param array  $args {
+ *     Data used to describe the theme.
+ *
+ *     @type string     $type         The type of data associated with this feature.
+ *                                    Valid values are 'string', 'boolean', 'integer',
+ *                                    'number', 'array', and 'object'. Defaults to 'boolean'.
+ *     @type boolean    $variadic     Does this feature utilize the variadic support
+ *                                    of add_theme_support(), or are all arguments specified
+ *                                    as the second parameter. Must be used with the "array" type.
+ *     @type string     $description  A short description of the feature. Included in
+ *                                    the Themes REST API schema. Intended for developers.
+ *     @type bool|array $show_in_rest {
+ *         Whether this feature should be included in the Themes REST API endpoint.
+ *         Defaults to not being included. When registering an 'array' or 'object' type,
+ *         this argument must be an array with the 'schema' key.
+ *
+ *         @type array    $schema           Specifies the JSON Schema definition describing
+ *                                          the feature. If any objects in the schema do not include
+ *                                          the 'additionalProperties' keyword, it is set to false.
+ *         @type string   $name             An alternate name to be used as the property name
+ *                                          in the REST API.
+ *         @type callable $prepare_callback A function used to format the theme support in the REST API.
+ *                                          Receives the raw theme support value.
+ *      }
+ * }
+ * @return true|WP_Error True if the theme feature was successfully registered, a WP_Error object if not.
+ */
+function register_theme_feature( $feature, $args = array() ) {
+	global $_wp_registered_theme_features;
+
+	if ( ! is_array( $_wp_registered_theme_features ) ) {
+		$_wp_registered_theme_features = array();
+	}
+
+	$defaults = array(
+		'type'         => 'boolean',
+		'variadic'     => false,
+		'description'  => '',
+		'show_in_rest' => false,
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+
+	if ( true === $args['show_in_rest'] ) {
+		$args['show_in_rest'] = array();
+	}
+
+	if ( is_array( $args['show_in_rest'] ) ) {
+		$args['show_in_rest'] = wp_parse_args(
+			$args['show_in_rest'],
+			array(
+				'schema'           => array(),
+				'name'             => $feature,
+				'prepare_callback' => null,
+			)
+		);
+	}
+
+	if ( ! in_array( $args['type'], array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
+		return new WP_Error(
+			'invalid_type',
+			__( 'The feature "type" is not valid JSON Schema type.' )
+		);
+	}
+
+	if ( true === $args['variadic'] && 'array' !== $args['type'] ) {
+		return new WP_Error(
+			'variadic_must_be_array',
+			__( 'When registering a "variadic" theme feature, the "type" must be an "array".' )
+		);
+	}
+
+	if ( false !== $args['show_in_rest'] && in_array( $args['type'], array( 'array', 'object' ), true ) ) {
+		if ( ! is_array( $args['show_in_rest'] ) || empty( $args['show_in_rest']['schema'] ) ) {
+			return new WP_Error(
+				'missing_schema',
+				__( 'When registering an "array" or "object" feature to show in the REST API, the feature\'s schema must also be defined.' )
+			);
+		}
+
+		if ( 'array' === $args['type'] && ! isset( $args['show_in_rest']['schema']['items'] ) ) {
+			return new WP_Error(
+				'missing_schema_items',
+				__( 'When registering an "array" feature, the feature\'s schema must include the "items" keyword.' )
+			);
+		}
+
+		if ( 'object' === $args['type'] && ! isset( $args['show_in_rest']['schema']['properties'] ) ) {
+			return new WP_Error(
+				'missing_schema_properties',
+				__( 'When registering an "object" feature, the feature\'s schema must include the "properties" keyword.' )
+			);
+		}
+	}
+
+	if ( is_array( $args['show_in_rest'] ) ) {
+		if ( isset( $args['show_in_rest']['prepare_callback'] ) && ! is_callable( $args['show_in_rest']['prepare_callback'] ) ) {
+			return new WP_Error(
+				'invalid_rest_prepare_callback',
+				sprintf(
+					/* translators: %s: prepare_callback */
+					__( 'The "%s" must be a callable function.' ),
+					'prepare_callback'
+				)
+			);
+		}
+
+		$args['show_in_rest']['schema'] = wp_parse_args(
+			$args['show_in_rest']['schema'],
+			array(
+				'description' => $args['description'],
+				'type'        => $args['type'],
+				'default'     => false,
+			)
+		);
+
+		if ( is_bool( $args['show_in_rest']['schema']['default'] )
+			&& ! in_array( 'boolean', (array) $args['show_in_rest']['schema']['type'], true )
+		) {
+			// Automatically include the "boolean" type when the default value is a boolean.
+			$args['show_in_rest']['schema']['type'] = (array) $args['show_in_rest']['schema']['type'];
+			array_unshift( $args['show_in_rest']['schema']['type'], 'boolean' );
+		}
+
+		$args['show_in_rest']['schema'] = rest_default_additional_properties_to_false( $args['show_in_rest']['schema'] );
+	}
+
+	$_wp_registered_theme_features[ $feature ] = $args;
+
+	return true;
+}
+
+/**
+ * Gets the list of registered theme features.
+ *
+ * @since 5.5.0
+ *
+ * @global array $_wp_registered_theme_features
+ *
+ * @return array[] List of theme features, keyed by their name.
+ */
+function get_registered_theme_features() {
+	global $_wp_registered_theme_features;
+
+	if ( ! is_array( $_wp_registered_theme_features ) ) {
+		return array();
+	}
+
+	return $_wp_registered_theme_features;
+}
+
+/**
+ * Gets the registration config for a theme feature.
+ *
+ * @since 5.5.0
+ *
+ * @global array $_wp_registered_theme_features
+ *
+ * @param string $feature The feature name. See add_theme_support() for the list
+ *                        of possible values.
+ * @return array|null The registration args, or null if the feature was not registered.
+ */
+function get_registered_theme_feature( $feature ) {
+	global $_wp_registered_theme_features;
+
+	if ( ! is_array( $_wp_registered_theme_features ) ) {
+		return null;
+	}
+
+	return isset( $_wp_registered_theme_features[ $feature ] ) ? $_wp_registered_theme_features[ $feature ] : null;
+}
+
+/**
  * Checks an attachment being deleted to see if it's a header or background image.
  *
  * If true it removes the theme modification which would be pointing at the deleted
@@ -2849,7 +3208,7 @@
  * @since 4.3.0 Also removes `header_image_data`.
  * @since 4.5.0 Also removes custom logo theme mods.
  *
- * @param int $id The attachment id.
+ * @param int $id The attachment ID.
  */
 function _delete_attachment_theme_mod( $id ) {
 	$attachment_image = wp_get_attachment_url( $id );
@@ -2880,10 +3239,11 @@
  * @since 3.3.0
  */
 function check_theme_switched() {
-	if ( $stylesheet = get_option( 'theme_switched' ) ) {
+	$stylesheet = get_option( 'theme_switched' );
+	if ( $stylesheet ) {
 		$old_theme = wp_get_theme( $stylesheet );
 
-		// Prevent widget & menu mapping from running since Customizer already called it up front
+		// Prevent widget & menu mapping from running since Customizer already called it up front.
 		if ( get_option( 'theme_switched_via_customizer' ) ) {
 			remove_action( 'after_switch_theme', '_wp_menus_changed' );
 			remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
@@ -2930,11 +3290,11 @@
  */
 function _wp_customize_include() {
 
-	$is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
+	$is_customize_admin_page = ( is_admin() && 'customize.php' === basename( $_SERVER['PHP_SELF'] ) );
 	$should_include          = (
 		$is_customize_admin_page
 		||
-		( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
+		( isset( $_REQUEST['wp_customize'] ) && 'on' === $_REQUEST['wp_customize'] )
 		||
 		( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
 	);
@@ -2955,10 +3315,16 @@
 	);
 
 	$theme             = null;
-	$changeset_uuid    = false; // Value false indicates UUID should be determined after_setup_theme to either re-use existing saved changeset or else generate a new UUID if none exists.
+	$autosaved         = null;
 	$messenger_channel = null;
-	$autosaved         = null;
-	$branching         = false; // Set initially fo false since defaults to true for back-compat; can be overridden via the customize_changeset_branching filter.
+
+	// Value false indicates UUID should be determined after_setup_theme
+	// to either re-use existing saved changeset or else generate a new UUID if none exists.
+	$changeset_uuid = false;
+
+	// Set initially fo false since defaults to true for back-compat;
+	// can be overridden via the customize_changeset_branching filter.
+	$branching = false;
 
 	if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
 		$changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
@@ -3082,11 +3448,12 @@
  * transitioned into pending status by a contributor.
  *
  * @since 4.7.0
+ *
  * @see wp_insert_post()
  *
  * @param array $post_data          An array of slashed post data.
  * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
- * @returns array Filtered data.
+ * @return array Filtered data.
  */
 function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
 	if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
@@ -3144,7 +3511,7 @@
  *                           The theme's stylesheet will be urlencoded if necessary.
  * @return string
  */
-function wp_customize_url( $stylesheet = null ) {
+function wp_customize_url( $stylesheet = '' ) {
 	$url = admin_url( 'customize.php' );
 	if ( $stylesheet ) {
 		$url .= '?theme=' . urlencode( $stylesheet );
@@ -3167,35 +3534,29 @@
  *
  * @since 3.4.0
  * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
+ * @since 5.5.0 IE8 and older are no longer supported.
  */
 function wp_customize_support_script() {
 	$admin_origin = parse_url( admin_url() );
 	$home_origin  = parse_url( home_url() );
 	$cross_domain = ( strtolower( $admin_origin['host'] ) != strtolower( $home_origin['host'] ) );
-
+	$type_attr    = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
 	?>
-	<!--[if lte IE 8]>
-		<script type="text/javascript">
-			document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
-		</script>
-	<![endif]-->
-	<!--[if gte IE 9]><!-->
-		<script type="text/javascript">
-			(function() {
-				var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
-
-		<?php	if ( $cross_domain ) : ?>
-				request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
-		<?php	else : ?>
-				request = true;
-		<?php	endif; ?>
-
-				b[c] = b[c].replace( rcs, ' ' );
-				// The customizer requires postMessage and CORS (if the site is cross domain)
-				b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
-			}());
-		</script>
-	<!--<![endif]-->
+	<script<?php echo $type_attr; ?>>
+		(function() {
+			var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
+
+	<?php	if ( $cross_domain ) : ?>
+			request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
+	<?php	else : ?>
+			request = true;
+	<?php	endif; ?>
+
+			b[c] = b[c].replace( rcs, ' ' );
+			// The customizer requires postMessage and CORS (if the site is cross domain).
+			b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
+		}());
+	</script>
 	<?php
 }
 
@@ -3215,7 +3576,7 @@
 }
 
 /**
- * Make sure that auto-draft posts get their post_date bumped or status changed to draft to prevent premature garbage-collection.
+ * Makes sure that auto-draft posts get their post_date bumped or status changed to draft to prevent premature garbage-collection.
  *
  * When a changeset is updated but remains an auto-draft, ensure the post_date
  * for the auto-draft posts remains the same so that it will be
@@ -3236,10 +3597,11 @@
  * @access private
  * @see wp_delete_auto_drafts()
  *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
  * @param string   $new_status Transition to this post status.
  * @param string   $old_status Previous post status.
  * @param \WP_Post $post       Post data.
- * @global wpdb $wpdb
  */
 function _wp_keep_alive_customize_changeset_dependent_auto_drafts( $new_status, $old_status, $post ) {
 	global $wpdb;
@@ -3304,3 +3666,374 @@
 		clean_post_cache( $post_id );
 	}
 }
+
+/**
+ * Creates the initial theme features when the 'setup_theme' action is fired.
+ *
+ * See {@see 'setup_theme'}.
+ *
+ * @since 5.5.0
+ */
+function create_initial_theme_features() {
+	register_theme_feature(
+		'align-wide',
+		array(
+			'description'  => __( 'Whether theme opts in to wide alignment CSS class.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'automatic-feed-links',
+		array(
+			'description'  => __( 'Whether posts and comments RSS feed links are added to head.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'custom-background',
+		array(
+			'description'  => __( 'Custom background if defined by the theme.' ),
+			'type'         => 'object',
+			'show_in_rest' => array(
+				'schema' => array(
+					'properties' => array(
+						'default-image'      => array(
+							'type'   => 'string',
+							'format' => 'uri',
+						),
+						'default-preset'     => array(
+							'type' => 'string',
+							'enum' => array(
+								'default',
+								'fill',
+								'fit',
+								'repeat',
+								'custom',
+							),
+						),
+						'default-position-x' => array(
+							'type' => 'string',
+							'enum' => array(
+								'left',
+								'center',
+								'right',
+							),
+						),
+						'default-position-y' => array(
+							'type' => 'string',
+							'enum' => array(
+								'left',
+								'center',
+								'right',
+							),
+						),
+						'default-size'       => array(
+							'type' => 'string',
+							'enum' => array(
+								'auto',
+								'contain',
+								'cover',
+							),
+						),
+						'default-repeat'     => array(
+							'type' => 'string',
+							'enum' => array(
+								'repeat-x',
+								'repeat-y',
+								'repeat',
+								'no-repeat',
+							),
+						),
+						'default-attachment' => array(
+							'type' => 'string',
+							'enum' => array(
+								'scroll',
+								'fixed',
+							),
+						),
+						'default-color'      => array(
+							'type' => 'string',
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'custom-header',
+		array(
+			'description'  => __( 'Custom header if defined by the theme.' ),
+			'type'         => 'object',
+			'show_in_rest' => array(
+				'schema' => array(
+					'properties' => array(
+						'default-image'      => array(
+							'type'   => 'string',
+							'format' => 'uri',
+						),
+						'random-default'     => array(
+							'type' => 'boolean',
+						),
+						'width'              => array(
+							'type' => 'integer',
+						),
+						'height'             => array(
+							'type' => 'integer',
+						),
+						'flex-height'        => array(
+							'type' => 'boolean',
+						),
+						'flex-width'         => array(
+							'type' => 'boolean',
+						),
+						'default-text-color' => array(
+							'type' => 'string',
+						),
+						'header-text'        => array(
+							'type' => 'boolean',
+						),
+						'uploads'            => array(
+							'type' => 'boolean',
+						),
+						'video'              => array(
+							'type' => 'boolean',
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'custom-logo',
+		array(
+			'type'         => 'object',
+			'description'  => __( 'Custom logo if defined by the theme.' ),
+			'show_in_rest' => array(
+				'schema' => array(
+					'properties' => array(
+						'width'                => array(
+							'type' => 'integer',
+						),
+						'height'               => array(
+							'type' => 'integer',
+						),
+						'flex-width'           => array(
+							'type' => 'boolean',
+						),
+						'flex-height'          => array(
+							'type' => 'boolean',
+						),
+						'header-text'          => array(
+							'type'  => 'array',
+							'items' => array(
+								'type' => 'string',
+							),
+						),
+						'unlink-homepage-logo' => array(
+							'type' => 'boolean',
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'customize-selective-refresh-widgets',
+		array(
+			'description'  => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'dark-editor-style',
+		array(
+			'description'  => __( 'Whether theme opts in to the dark editor style UI.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'disable-custom-colors',
+		array(
+			'description'  => __( 'Whether the theme disables custom colors.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'disable-custom-font-sizes',
+		array(
+			'description'  => __( 'Whether the theme disables custom font sizes.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'disable-custom-gradients',
+		array(
+			'description'  => __( 'Whether the theme disables custom gradients.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'editor-color-palette',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'Custom color palette if defined by the theme.' ),
+			'show_in_rest' => array(
+				'schema' => array(
+					'items' => array(
+						'type'       => 'object',
+						'properties' => array(
+							'name'  => array(
+								'type' => 'string',
+							),
+							'slug'  => array(
+								'type' => 'string',
+							),
+							'color' => array(
+								'type' => 'string',
+							),
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'editor-font-sizes',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'Custom font sizes if defined by the theme.' ),
+			'show_in_rest' => array(
+				'schema' => array(
+					'items' => array(
+						'type'       => 'object',
+						'properties' => array(
+							'name' => array(
+								'type' => 'string',
+							),
+							'size' => array(
+								'type' => 'number',
+							),
+							'slug' => array(
+								'type' => 'string',
+							),
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'editor-gradient-presets',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'Custom gradient presets if defined by the theme.' ),
+			'show_in_rest' => array(
+				'schema' => array(
+					'items' => array(
+						'type'       => 'object',
+						'properties' => array(
+							'name'     => array(
+								'type' => 'string',
+							),
+							'gradient' => array(
+								'type' => 'string',
+							),
+							'slug'     => array(
+								'type' => 'string',
+							),
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'editor-styles',
+		array(
+			'description'  => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'html5',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'Allows use of HTML5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
+			'show_in_rest' => array(
+				'schema' => array(
+					'items' => array(
+						'type' => 'string',
+						'enum' => array(
+							'search-form',
+							'comment-form',
+							'comment-list',
+							'gallery',
+							'caption',
+							'script',
+							'style',
+						),
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'post-formats',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'Post formats supported.' ),
+			'show_in_rest' => array(
+				'name'             => 'formats',
+				'schema'           => array(
+					'items'   => array(
+						'type' => 'string',
+						'enum' => get_post_format_slugs(),
+					),
+					'default' => array( 'standard' ),
+				),
+				'prepare_callback' => static function ( $formats ) {
+					$formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
+					$formats = array_merge( array( 'standard' ), $formats );
+
+					return $formats;
+				},
+			),
+		)
+	);
+	register_theme_feature(
+		'post-thumbnails',
+		array(
+			'type'         => 'array',
+			'description'  => __( 'The post types that support thumbnails or true if all post types are supported.' ),
+			'show_in_rest' => array(
+				'type'   => array( 'boolean', 'array' ),
+				'schema' => array(
+					'items' => array(
+						'type' => 'string',
+					),
+				),
+			),
+		)
+	);
+	register_theme_feature(
+		'responsive-embeds',
+		array(
+			'description'  => __( 'Whether the theme supports responsive embedded content.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'title-tag',
+		array(
+			'description'  => __( 'Whether the theme can manage the document title tag.' ),
+			'show_in_rest' => true,
+		)
+	);
+	register_theme_feature(
+		'wp-block-styles',
+		array(
+			'description'  => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
+			'show_in_rest' => true,
+		)
+	);
+}