diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-5-9.php
similarity index 99%
rename from lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php
rename to lib/compat/wordpress-5.9/class-wp-theme-json-resolver-5-9.php
index 21d546fb66891f..c16f64f46cee6d 100644
--- a/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php
+++ b/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-5-9.php
@@ -15,7 +15,7 @@
*
* @access private
*/
-class WP_Theme_JSON_Resolver_Gutenberg {
+class WP_Theme_JSON_Resolver_5_9 {
/**
* Container for data coming from core.
diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-gutenberg.php b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-gutenberg.php
new file mode 100644
index 00000000000000..a79c510141be32
--- /dev/null
+++ b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-gutenberg.php
@@ -0,0 +1,92 @@
+get( 'TextDomain' ) );
+ $theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data );
+ static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data );
+
+ if ( wp_get_theme()->parent() ) {
+ // Get parent theme.json.
+ $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
+ $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
+ $parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data );
+ $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );
+
+ // Merge the child theme.json into the parent theme.json.
+ // The child theme takes precedence over the parent.
+ $parent_theme->merge( static::$theme );
+ static::$theme = $parent_theme;
+ }
+ }
+
+ /*
+ * We want the presets and settings declared in theme.json
+ * to override the ones declared via theme supports.
+ * So we take theme supports, transform it to theme.json shape
+ * and merge the static::$theme upon that.
+ */
+ $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() );
+ if ( ! static::theme_has_support() ) {
+ if ( ! isset( $theme_support_data['settings']['color'] ) ) {
+ $theme_support_data['settings']['color'] = array();
+ }
+
+ $default_palette = false;
+ if ( current_theme_supports( 'default-color-palette' ) ) {
+ $default_palette = true;
+ }
+ if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) {
+ // If the theme does not have any palette, we still want to show the core one.
+ $default_palette = true;
+ }
+ $theme_support_data['settings']['color']['defaultPalette'] = $default_palette;
+
+ $default_gradients = false;
+ if ( current_theme_supports( 'default-gradient-presets' ) ) {
+ $default_gradients = true;
+ }
+ if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) {
+ // If the theme does not have any gradients, we still want to show the core ones.
+ $default_gradients = true;
+ }
+ $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients;
+ }
+ $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data );
+ $with_theme_supports->merge( static::$theme );
+
+ return $with_theme_supports;
+ }
+}
diff --git a/lib/compat/wordpress-6.0/class-wp-webfonts-provider-local.php b/lib/compat/wordpress-6.0/class-wp-webfonts-provider-local.php
new file mode 100644
index 00000000000000..6d1da877ae6d48
--- /dev/null
+++ b/lib/compat/wordpress-6.0/class-wp-webfonts-provider-local.php
@@ -0,0 +1,259 @@
+
+ * array(
+ * 'source-serif-pro.normal.200 900' => array(
+ * 'provider' => 'local',
+ * 'font_family' => 'Source Serif Pro',
+ * 'font_weight' => '200 900',
+ * 'font_style' => 'normal',
+ * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
+ * ),
+ * 'source-serif-pro.italic.400 900' => array(
+ * 'provider' => 'local',
+ * 'font_family' => 'Source Serif Pro',
+ * 'font_weight' => '200 900',
+ * 'font_style' => 'italic',
+ * 'src' => 'https://example.com/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2' ),
+ * ),
+ * )
+ *
+ *
+ * the following `@font-face` styles are generated and returned:
+ *
+ *
+ * @font-face{
+ * font-family:"Source Serif Pro";
+ * font-style:normal;
+ * font-weight:200 900;
+ * font-stretch:normal;
+ * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2') format('woff2');
+ * }
+ * @font-face{
+ * font-family:"Source Serif Pro";
+ * font-style:italic;
+ * font-weight:200 900;
+ * font-stretch:normal;
+ * src:local("Source Serif Pro"), url('/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2') format('woff2');
+ * }
+ *
+ *
+ * @since 6.0.0
+ *
+ * @return string The `@font-face` CSS.
+ */
+ public function get_css() {
+ $css = '';
+
+ foreach ( $this->webfonts as $webfont ) {
+ // Order the webfont's `src` items to optimize for browser support.
+ $webfont = $this->order_src( $webfont );
+
+ // Build the @font-face CSS for this webfont.
+ $css .= '@font-face{' . $this->build_font_face_css( $webfont ) . '}';
+ }
+
+ return $css;
+ }
+
+ /**
+ * Order `src` items to optimize for browser support.
+ *
+ * @since 6.0.0
+ *
+ * @param array $webfont Webfont to process.
+ * @return array
+ */
+ private function order_src( array $webfont ) {
+ if ( ! is_array( $webfont['src'] ) ) {
+ $webfont['src'] = (array) $webfont['src'];
+ }
+
+ $src = array();
+ $src_ordered = array();
+
+ foreach ( $webfont['src'] as $url ) {
+ // Add data URIs first.
+ if ( 0 === strpos( trim( $url ), 'data:' ) ) {
+ $src_ordered[] = array(
+ 'url' => $url,
+ 'format' => 'data',
+ );
+ continue;
+ }
+ $format = pathinfo( $url, PATHINFO_EXTENSION );
+ $src[ $format ] = $url;
+ }
+
+ // Add woff2.
+ if ( ! empty( $src['woff2'] ) ) {
+ $src_ordered[] = array(
+ 'url' => $src['woff2'],
+ 'format' => 'woff2',
+ );
+ }
+
+ // Add woff.
+ if ( ! empty( $src['woff'] ) ) {
+ $src_ordered[] = array(
+ 'url' => $src['woff'],
+ 'format' => 'woff',
+ );
+ }
+
+ // Add ttf.
+ if ( ! empty( $src['ttf'] ) ) {
+ $src_ordered[] = array(
+ 'url' => $src['ttf'],
+ 'format' => 'truetype',
+ );
+ }
+
+ // Add eot.
+ if ( ! empty( $src['eot'] ) ) {
+ $src_ordered[] = array(
+ 'url' => $src['eot'],
+ 'format' => 'embedded-opentype',
+ );
+ }
+
+ // Add otf.
+ if ( ! empty( $src['otf'] ) ) {
+ $src_ordered[] = array(
+ 'url' => $src['otf'],
+ 'format' => 'opentype',
+ );
+ }
+ $webfont['src'] = $src_ordered;
+
+ return $webfont;
+ }
+
+ /**
+ * Builds the font-family's CSS.
+ *
+ * @since 6.0.0
+ *
+ * @param array $webfont Webfont to process.
+ * @return string This font-family's CSS.
+ */
+ private function build_font_face_css( array $webfont ) {
+ $css = '';
+
+ // Wrap font-family in quotes if it contains spaces.
+ if (
+ false !== strpos( $webfont['font-family'], ' ' ) &&
+ false === strpos( $webfont['font-family'], '"' ) &&
+ false === strpos( $webfont['font-family'], "'" )
+ ) {
+ $webfont['font-family'] = '"' . $webfont['font-family'] . '"';
+ }
+
+ foreach ( $webfont as $key => $value ) {
+
+ // Skip "provider", since it's for internal API use,
+ // and not a valid CSS property.
+ if ( 'provider' === $key ) {
+ continue;
+ }
+
+ // Compile the "src" parameter.
+ if ( 'src' === $key ) {
+ $value = $this->compile_src( $webfont['font-family'], $value );
+ }
+
+ // If font-variation-settings is an array, convert it to a string.
+ if ( 'font-variation-settings' === $key && is_array( $value ) ) {
+ $value = $this->compile_variations( $value );
+ }
+
+ if ( ! empty( $value ) ) {
+ $css .= "$key:$value;";
+ }
+ }
+
+ return $css;
+ }
+
+ /**
+ * Compiles the `src` into valid CSS.
+ *
+ * @since 6.0.0
+ *
+ * @param string $font_family Font family.
+ * @param array $value Value to process.
+ * @return string The CSS.
+ */
+ private function compile_src( $font_family, array $value ) {
+ $src = "local($font_family)";
+
+ foreach ( $value as $item ) {
+
+ if ( 0 === strpos( $item['url'], get_site_url() ) ) {
+ $item['url'] = wp_make_link_relative( $item['url'] );
+ }
+
+ $src .= ( 'data' === $item['format'] )
+ ? ", url({$item['url']})"
+ : ", url('{$item['url']}') format('{$item['format']}')";
+ }
+ return $src;
+ }
+
+ /**
+ * Compiles the font variation settings.
+ *
+ * @since 6.0.0
+ *
+ * @param array $font_variation_settings Array of font variation settings.
+ * @return string The CSS.
+ */
+ private function compile_variations( array $font_variation_settings ) {
+ $variations = '';
+
+ foreach ( $font_variation_settings as $key => $value ) {
+ $variations .= "$key $value";
+ }
+
+ return $variations;
+ }
+}
diff --git a/lib/compat/wordpress-6.0/class-wp-webfonts-provider.php b/lib/compat/wordpress-6.0/class-wp-webfonts-provider.php
new file mode 100644
index 00000000000000..96b12547986425
--- /dev/null
+++ b/lib/compat/wordpress-6.0/class-wp-webfonts-provider.php
@@ -0,0 +1,68 @@
+webfonts = $webfonts;
+ }
+
+ /**
+ * Gets the `@font-face` CSS for the provider's webfonts.
+ *
+ * This method is where the provider does it processing to build the
+ * needed `@font-face` CSS for all of its webfonts. Specifics of how
+ * this processing is done is contained in each provider.
+ *
+ * @since 6.0.0
+ *
+ * @return string The `@font-face` CSS.
+ */
+ abstract public function get_css();
+}
diff --git a/lib/compat/wordpress-6.0/class-wp-webfonts.php b/lib/compat/wordpress-6.0/class-wp-webfonts.php
new file mode 100644
index 00000000000000..9b5114b7452bb2
--- /dev/null
+++ b/lib/compat/wordpress-6.0/class-wp-webfonts.php
@@ -0,0 +1,294 @@
+register_provider( 'local', 'WP_Webfonts_Provider_Local' );
+
+ // Register callback to generate and enqueue styles.
+ if ( did_action( 'wp_enqueue_scripts' ) ) {
+ $this->stylesheet_handle = 'webfonts-footer';
+ $hook = 'wp_print_footer_scripts';
+ } else {
+ $this->stylesheet_handle = 'webfonts';
+ $hook = 'wp_enqueue_scripts';
+ }
+ add_action( $hook, array( $this, 'generate_and_enqueue_styles' ) );
+
+ // Enqueue webfonts in the block editor.
+ add_action( 'admin_init', array( $this, 'generate_and_enqueue_editor_styles' ) );
+ }
+
+ /**
+ * Get the list of fonts.
+ *
+ * @return array
+ */
+ public function get_fonts() {
+ return self::$webfonts;
+ }
+
+ /**
+ * Get the list of providers.
+ *
+ * @return array
+ */
+ public function get_providers() {
+ return self::$providers;
+ }
+
+ /**
+ * Register a webfont.
+ *
+ * @param array $font The font arguments.
+ */
+ public function register_font( $font ) {
+ $font = $this->validate_font( $font );
+ if ( $font ) {
+ $id = $this->get_font_id( $font );
+ self::$webfonts[ $id ] = $font;
+ }
+ }
+
+ /**
+ * Get the font ID.
+ *
+ * @param array $font The font arguments.
+ * @return string
+ */
+ public function get_font_id( $font ) {
+ return sanitize_title( "{$font['font-family']}-{$font['font-weight']}-{$font['font-style']}-{$font['provider']}" );
+ }
+
+ /**
+ * Validate a font.
+ *
+ * @param array $font The font arguments.
+ *
+ * @return array|false The validated font arguments, or false if the font is invalid.
+ */
+ public function validate_font( $font ) {
+ $font = wp_parse_args(
+ $font,
+ array(
+ 'provider' => 'local',
+ 'font-family' => '',
+ 'font-style' => 'normal',
+ 'font-weight' => '400',
+ 'font-display' => 'fallback',
+ )
+ );
+
+ // Check the font-family.
+ if ( empty( $font['font-family'] ) || ! is_string( $font['font-family'] ) ) {
+ trigger_error( __( 'Webfont font family must be a non-empty string.', 'gutenberg' ) );
+ return false;
+ }
+
+ // Local fonts need a "src".
+ if ( 'local' === $font['provider'] ) {
+ // Make sure that local fonts have 'src' defined.
+ if ( empty( $font['src'] ) || ( ! is_string( $font['src'] ) && ! is_array( $font['src'] ) ) ) {
+ trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.', 'gutenberg' ) );
+ return false;
+ }
+ }
+
+ // Validate the 'src' property.
+ if ( ! empty( $font['src'] ) ) {
+ foreach ( (array) $font['src'] as $src ) {
+ if ( empty( $src ) || ! is_string( $src ) ) {
+ trigger_error( __( 'Each webfont src must be a non-empty string.', 'gutenberg' ) );
+ return false;
+ }
+ }
+ }
+
+ // Check the font-weight.
+ if ( ! is_string( $font['font-weight'] ) && ! is_int( $font['font-weight'] ) ) {
+ trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.', 'gutenberg' ) );
+ return false;
+ }
+
+ // Check the font-display.
+ if ( ! in_array( $font['font-display'], array( 'auto', 'block', 'fallback', 'swap' ), true ) ) {
+ $font['font-display'] = 'fallback';
+ }
+
+ $valid_props = array(
+ 'ascend-override',
+ 'descend-override',
+ 'font-display',
+ 'font-family',
+ 'font-stretch',
+ 'font-style',
+ 'font-weight',
+ 'font-variant',
+ 'font-feature-settings',
+ 'font-variation-settings',
+ 'line-gap-override',
+ 'size-adjust',
+ 'src',
+ 'unicode-range',
+
+ // Exceptions.
+ 'provider',
+ );
+
+ foreach ( $font as $prop => $value ) {
+ if ( ! in_array( $prop, $valid_props, true ) ) {
+ unset( $font[ $prop ] );
+ }
+ }
+
+ return $font;
+ }
+
+ /**
+ * Register a provider.
+ *
+ * @param string $provider The provider name.
+ * @param string $class The provider class name.
+ *
+ * @return bool Whether the provider was registered successfully.
+ */
+ public function register_provider( $provider, $class ) {
+ if ( empty( $provider ) || empty( $class ) ) {
+ return false;
+ }
+ self::$providers[ $provider ] = $class;
+ return true;
+ }
+
+ /**
+ * Generate and enqueue webfonts styles.
+ */
+ public function generate_and_enqueue_styles() {
+ // Generate the styles.
+ $styles = $this->generate_styles();
+
+ // Bail out if there are no styles to enqueue.
+ if ( '' === $styles ) {
+ return;
+ }
+
+ // Enqueue the stylesheet.
+ wp_register_style( $this->stylesheet_handle, '' );
+ wp_enqueue_style( $this->stylesheet_handle );
+
+ // Add the styles to the stylesheet.
+ wp_add_inline_style( $this->stylesheet_handle, $styles );
+ }
+
+ /**
+ * Generate and enqueue editor styles.
+ */
+ public function generate_and_enqueue_editor_styles() {
+ // Generate the styles.
+ $styles = $this->generate_styles();
+
+ // Bail out if there are no styles to enqueue.
+ if ( '' === $styles ) {
+ return;
+ }
+
+ wp_add_inline_style( 'wp-block-library', $styles );
+ }
+
+ /**
+ * Generate styles for webfonts.
+ *
+ * @since 6.0.0
+ *
+ * @return string $styles Generated styles.
+ */
+ public function generate_styles() {
+ $styles = '';
+ $providers = $this->get_providers();
+
+ // Group webfonts by provider.
+ $webfonts_by_provider = array();
+ $registered_webfonts = $this->get_fonts();
+ foreach ( $registered_webfonts as $id => $webfont ) {
+ $provider = $webfont['provider'];
+ if ( ! isset( $providers[ $provider ] ) ) {
+ /* translators: %s is the provider name. */
+ error_log( sprintf( __( 'Webfont provider "%s" is not registered.', 'gutenberg' ), $provider ) );
+ continue;
+ }
+ $webfonts_by_provider[ $provider ] = isset( $webfonts_by_provider[ $provider ] ) ? $webfonts_by_provider[ $provider ] : array();
+ $webfonts_by_provider[ $provider ][ $id ] = $webfont;
+ }
+
+ /*
+ * Loop through each of the providers to get the CSS for their respective webfonts
+ * to incrementally generate the collective styles for all of them.
+ */
+ foreach ( $providers as $provider_id => $provider_class ) {
+
+ // Bail out if the provider class does not exist.
+ if ( ! class_exists( $provider_class ) ) {
+ /* translators: %s is the provider name. */
+ error_log( sprintf( __( 'Webfont provider "%s" is not registered.', 'gutenberg' ), $provider_id ) );
+ continue;
+ }
+
+ $provider_webfonts = isset( $webfonts_by_provider[ $provider_id ] )
+ ? $webfonts_by_provider[ $provider_id ]
+ : array();
+
+ // If there are no registered webfonts for this provider, skip it.
+ if ( empty( $provider_webfonts ) ) {
+ continue;
+ }
+
+ /*
+ * Process the webfonts by first passing them to the provider via `set_webfonts()`
+ * and then getting the CSS from the provider.
+ */
+ $provider = new $provider_class();
+ $provider->set_webfonts( $provider_webfonts );
+ $styles .= $provider->get_css();
+ }
+
+ return $styles;
+ }
+}
diff --git a/lib/compat/wordpress-6.0/register-webfonts-from-theme-json.php b/lib/compat/wordpress-6.0/register-webfonts-from-theme-json.php
new file mode 100644
index 00000000000000..f18c301764c710
--- /dev/null
+++ b/lib/compat/wordpress-6.0/register-webfonts-from-theme-json.php
@@ -0,0 +1,149 @@
+get_settings();
+
+ // Bail out early if there are no settings for webfonts.
+ if ( empty( $theme_settings['typography'] ) || empty( $theme_settings['typography']['fontFamilies'] ) ) {
+ return;
+ }
+
+ $webfonts = array();
+
+ // Look for fontFamilies.
+ foreach ( $theme_settings['typography']['fontFamilies'] as $font_families ) {
+ foreach ( $font_families as $font_family ) {
+
+ // Skip if fontFace is not defined.
+ if ( empty( $font_family['fontFace'] ) ) {
+ continue;
+ }
+
+ $font_family['fontFace'] = (array) $font_family['fontFace'];
+
+ foreach ( $font_family['fontFace'] as $font_face ) {
+ // Check if webfonts have a "src" param, and if they do account for the use of "file:./".
+ if ( ! empty( $font_face['src'] ) ) {
+ $font_face['src'] = (array) $font_face['src'];
+
+ foreach ( $font_face['src'] as $src_key => $url ) {
+ // Tweak the URL to be relative to the theme root.
+ if ( 0 !== strpos( $url, 'file:./' ) ) {
+ continue;
+ }
+ $font_face['src'][ $src_key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) );
+ }
+ }
+
+ // Convert keys to kebab-case.
+ foreach ( $font_face as $property => $value ) {
+ $kebab_case = _wp_to_kebab_case( $property );
+ $font_face[ $kebab_case ] = $value;
+ if ( $kebab_case !== $property ) {
+ unset( $font_face[ $property ] );
+ }
+ }
+
+ $webfonts[] = $font_face;
+ }
+ }
+ }
+ foreach ( $webfonts as $webfont ) {
+ wp_webfonts()->register_font( $webfont );
+ }
+}
+
+/**
+ * Add missing fonts data to the global styles.
+ *
+ * @param array $data The global styles.
+ *
+ * @return array The global styles with missing fonts data.
+ */
+function gutenberg_add_registered_webfonts_to_theme_json( $data ) {
+ $font_families_registered = wp_webfonts()->get_fonts();
+ $font_families_from_theme = array();
+ if ( ! empty( $data['settings'] ) && ! empty( $data['settings']['typography'] ) && ! empty( $data['settings']['typography']['fontFamilies'] ) ) {
+ $font_families_from_theme = $data['settings']['typography']['fontFamilies'];
+ }
+
+ /**
+ * Helper to get an array of the font-families.
+ *
+ * @param array $families_data The font-families data.
+ *
+ * @return array The font-families array.
+ */
+ $get_families = function( $families_data ) {
+ $families = array();
+ foreach ( $families_data as $family ) {
+ if ( isset( $family['font-family'] ) ) {
+ $families[] = $family['font-family'];
+ } elseif ( isset( $family['fontFamily'] ) ) {
+ $families[] = $family['fontFamily'];
+ }
+ }
+
+ // Micro-optimization: Use array_flip( array_flip( $array ) )
+ // instead of array_unique( $array ) because it's faster.
+ // The result is the same.
+ return array_flip( array_flip( $families ) );
+ };
+
+ // Diff the arrays to find the missing fonts.
+ $to_add = array_diff(
+ $get_families( $font_families_registered ),
+ $get_families( $font_families_from_theme )
+ );
+
+ // Bail out early if there are no missing fonts.
+ if ( empty( $to_add ) ) {
+ return $data;
+ }
+
+ // Make sure the path to settings.typography.fontFamilies.theme exists
+ // before adding missing fonts.
+ if ( empty( $data['settings'] ) ) {
+ $data['settings'] = array();
+ }
+ if ( empty( $data['settings']['typography'] ) ) {
+ $data['settings']['typography'] = array();
+ }
+ if ( empty( $data['settings']['typography']['fontFamilies'] ) ) {
+ $data['settings']['typography']['fontFamilies'] = array();
+ }
+
+ // Add missing fonts.
+ foreach ( $to_add as $family ) {
+ $font_face = array();
+ foreach ( $font_families_registered as $font_family ) {
+ if ( $family !== $font_family['font-family'] ) {
+ continue;
+ }
+ $camel_cased = array();
+ foreach ( $font_family as $key => $value ) {
+ $camel_cased[ lcfirst( str_replace( '-', '', ucwords( $key, '-' ) ) ) ] = $value;
+ }
+ $font_face[] = $camel_cased;
+ }
+ $data['settings']['typography']['fontFamilies'][] = array(
+ 'fontFamily' => false !== strpos( $family, ' ' ) ? "'{$family}'" : $family,
+ 'name' => $family,
+ 'slug' => sanitize_title( $family ),
+ 'fontFace' => $font_face,
+ );
+ }
+
+ return $data;
+}
+
+add_action( 'wp_loaded', 'gutenberg_register_webfonts_from_theme_json' );
diff --git a/lib/compat/wordpress-6.0/webfonts.php b/lib/compat/wordpress-6.0/webfonts.php
new file mode 100644
index 00000000000000..2bc7a714c16727
--- /dev/null
+++ b/lib/compat/wordpress-6.0/webfonts.php
@@ -0,0 +1,165 @@
+init();
+ }
+
+ return $instance;
+}
+
+/**
+ * Registers a collection of webfonts.
+ *
+ * Example of how to register Source Serif Pro font with font-weight range of 200-900
+ * and font-style of normal and italic:
+ *
+ * If the font files are contained within the theme:
+ *
+ * wp_register_webfonts(
+ * array(
+ * array(
+ * 'provider' => 'local',
+ * 'font_family' => 'Source Serif Pro',
+ * 'font_weight' => '200 900',
+ * 'font_style' => 'normal',
+ * 'src' => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
+ * ),
+ * array(
+ * 'provider' => 'local',
+ * 'font_family' => 'Source Serif Pro',
+ * 'font_weight' => '200 900',
+ * 'font_style' => 'italic',
+ * 'src' => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2' ),
+ * ),
+ * )
+ * );
+ *
+ *
+ * Webfonts should be registered in the `after_setup_theme` hook.
+ *
+ * @since 6.0.0
+ *
+ * @param array $webfonts Webfonts to be registered.
+ * This contains an array of webfonts to be registered.
+ * Each webfont is an array.
+ * See {@see WP_Webfonts_Registry::register()} for a list of
+ * supported arguments for each webfont.
+ */
+function wp_register_webfonts( array $webfonts = array() ) {
+ foreach ( $webfonts as $webfont ) {
+ wp_register_webfont( $webfont );
+ }
+}
+
+/**
+ * Registers a single webfont.
+ *
+ * Example of how to register Source Serif Pro font with font-weight range of 200-900:
+ *
+ * If the font file is contained within the theme:
+ * ```
+ * wp_register_webfont(
+ * array(
+ * 'provider' => 'local',
+ * 'font_family' => 'Source Serif Pro',
+ * 'font_weight' => '200 900',
+ * 'font_style' => 'normal',
+ * 'src' => get_theme_file_uri( 'assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2' ),
+ * )
+ * );
+ * ```
+ *
+ * @since 6.0.0
+ *
+ * @param array $webfont Webfont to be registered.
+ * See {@see WP_Webfonts_Registry::register()} for a list of supported arguments.
+ */
+function wp_register_webfont( array $webfont ) {
+ wp_webfonts()->register_font( $webfont );
+}
+
+/**
+ * Registers a custom font service provider.
+ *
+ * A webfont provider contains the business logic for how to
+ * interact with a remote font service and how to generate
+ * the `@font-face` styles for that remote service.
+ *
+ * How to register a custom font service provider:
+ * 1. Load its class file into memory before registration.
+ * 2. Pass the class' name to this function.
+ *
+ * For example, for a class named `My_Custom_Font_Service_Provider`:
+ * ```
+ * wp_register_webfont_provider( My_Custom_Font_Service_Provider::class );
+ * ```
+ *
+ * @since 6.0.0
+ *
+ * @param string $name The provider's name.
+ * @param string $classname The provider's class name.
+ * The class should be a child of `WP_Webfonts_Provider`.
+ * See {@see WP_Webfonts_Provider}.
+ *
+ * @return bool True when registered. False when provider does not exist.
+ */
+function wp_register_webfont_provider( $name, $classname ) {
+ return wp_webfonts()->register_provider( $name, $classname );
+}
+
+/**
+ * Gets all registered providers.
+ *
+ * Return an array of providers, each keyed by their unique
+ * ID (i.e. the `$id` property in the provider's object) with
+ * an instance of the provider (object):
+ * ID => provider instance
+ *
+ * Each provider contains the business logic for how to
+ * process its specific font service (i.e. local or remote)
+ * and how to generate the `@font-face` styles for its service.
+ *
+ * @since 6.0.0
+ *
+ * @return WP_Webfonts_Provider[] All registered providers,
+ * each keyed by their unique ID.
+ */
+function wp_get_webfont_providers() {
+ return wp_webfonts()->get_providers();
+}
+
+/**
+ * Add webfonts mime types.
+ */
+add_filter(
+ 'mime_types',
+ function( $mime_types ) {
+ // Webfonts formats.
+ $mime_types['woff2'] = 'font/woff2';
+ $mime_types['woff'] = 'font/woff';
+ $mime_types['ttf'] = 'font/ttf';
+ $mime_types['eot'] = 'application/vnd.ms-fontobject';
+ $mime_types['otf'] = 'application/x-font-opentype';
+
+ return $mime_types;
+ }
+);
diff --git a/lib/load.php b/lib/load.php
index 443942c98ee80a..6c97ff52c84e9c 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -82,7 +82,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/editor-settings.php';
require __DIR__ . '/compat/wordpress-5.9/class-wp-theme-json-schema-gutenberg.php';
require __DIR__ . '/compat/wordpress-5.9/class-wp-theme-json-5-9.php';
-require __DIR__ . '/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php';
+require __DIR__ . '/compat/wordpress-5.9/class-wp-theme-json-resolver-5-9.php';
require __DIR__ . '/compat/wordpress-5.9/theme.php';
require __DIR__ . '/compat/wordpress-5.9/admin-menu.php';
require __DIR__ . '/full-site-editing/edit-site-page.php';
@@ -99,6 +99,12 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php';
require __DIR__ . '/compat/wordpress-6.0/rest-api.php';
require __DIR__ . '/compat/wordpress-6.0/block-patterns.php';
+require __DIR__ . '/compat/wordpress-6.0/register-webfonts-from-theme-json.php';
+require __DIR__ . '/compat/wordpress-6.0/class-wp-theme-json-resolver-gutenberg.php';
+require __DIR__ . '/compat/wordpress-6.0/class-wp-webfonts.php';
+require __DIR__ . '/compat/wordpress-6.0/class-wp-webfonts-provider.php';
+require __DIR__ . '/compat/wordpress-6.0/class-wp-webfonts-provider-local.php';
+require __DIR__ . '/compat/wordpress-6.0/webfonts.php';
require __DIR__ . '/compat/experimental/blocks.php';
require __DIR__ . '/blocks.php';
diff --git a/phpunit/class-wp-webfonts-local-provider-test.php b/phpunit/class-wp-webfonts-local-provider-test.php
new file mode 100644
index 00000000000000..69892d277bc227
--- /dev/null
+++ b/phpunit/class-wp-webfonts-local-provider-test.php
@@ -0,0 +1,149 @@
+provider = new WP_Webfonts_Provider_Local();
+
+ $this->set_up_theme();
+ }
+
+ /**
+ * Local `src` paths to need to be relative to the theme. This method sets up the
+ * `wp-content/themes/` directory to ensure consistency when running tests.
+ */
+ private function set_up_theme() {
+ $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' );
+ $this->orig_theme_dir = $GLOBALS['wp_theme_directories'];
+ $GLOBALS['wp_theme_directories'] = array( $this->theme_root );
+
+ $theme_root_callback = function () {
+ return $this->theme_root;
+ };
+ add_filter( 'theme_root', $theme_root_callback );
+ add_filter( 'stylesheet_root', $theme_root_callback );
+ add_filter( 'template_root', $theme_root_callback );
+
+ // Clear caches.
+ wp_clean_themes_cache();
+ unset( $GLOBALS['wp_themes'] );
+ }
+
+ function tear_down() {
+ // Restore the original theme directory setup.
+ $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
+ wp_clean_themes_cache();
+ unset( $GLOBALS['wp_themes'] );
+
+ parent::tear_down();
+ }
+
+ /**
+ * @covers WP_Webfonts_Provider_Local::set_webfonts
+ */
+ public function test_set_webfonts() {
+ $webfonts = array(
+ 'source-serif-pro-200-900-normal-local' => array(
+ 'provider' => 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'normal',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
+ ),
+ 'source-serif-pro-200-900-italic-local' => array(
+ 'provider' => 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'italic',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
+ ),
+ );
+
+ $this->provider->set_webfonts( $webfonts );
+
+ $property = $this->get_webfonts_property();
+ $this->assertSame( $webfonts, $property->getValue( $this->provider ) );
+ }
+
+ /**
+ * @covers WP_Webfonts_Provider_Local::get_css
+ *
+ * @dataProvider data_get_css
+ *
+ * @param array $webfonts Prepared webfonts (to store in WP_Webfonts_Provider_Local::$webfonts property).
+ * @param string $expected Expected CSS.
+ */
+ public function test_get_css( array $webfonts, $expected ) {
+ $property = $this->get_webfonts_property();
+ $property->setValue( $this->provider, $webfonts );
+
+ $this->assertSame( $expected, $this->provider->get_css() );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_get_css() {
+ return array(
+ 'truetype format' => array(
+ 'webfonts' => array(
+ 'open-sans-bold-italic-local' => array(
+ 'provider' => 'local',
+ 'font-family' => 'Open Sans',
+ 'font-style' => 'italic',
+ 'font-weight' => 'bold',
+ 'src' => 'http://example.org/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf',
+ ),
+ ),
+ 'expected' => << array(
+ 'webfonts' => array(
+ 'source-serif-pro-200-900-normal-local' => array(
+ 'provider' => 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'normal',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
+ ),
+ 'source-serif-pro-400-900-italic-local' => array(
+ 'provider' => 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'italic',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'http://example.org/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
+ ),
+ ),
+ 'expected' => <<provider, 'webfonts' );
+ $property->setAccessible( true );
+
+ return $property;
+ }
+}
diff --git a/phpunit/class-wp-webfonts-test.php b/phpunit/class-wp-webfonts-test.php
new file mode 100644
index 00000000000000..239f2fae59fb12
--- /dev/null
+++ b/phpunit/class-wp-webfonts-test.php
@@ -0,0 +1,114 @@
+ 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'normal',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
+ 'font-display' => 'fallback',
+ ),
+ array(
+ 'provider' => 'local',
+ 'font-family' => 'Source Serif Pro',
+ 'font-style' => 'italic',
+ 'font-weight' => '200 900',
+ 'font-stretch' => 'normal',
+ 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2',
+ 'font-display' => 'fallback',
+ ),
+ );
+
+ $expected = array(
+ 'source-serif-pro-200-900-normal-local' => $fonts[0],
+ 'source-serif-pro-200-900-italic-local' => $fonts[1],
+ );
+
+ wp_register_webfonts( $fonts );
+ $this->assertEquals( $expected, wp_webfonts()->get_fonts() );
+ }
+
+ /**
+ * @covers wp_register_webfont
+ * @covers WP_Webfonts::register_provider
+ * @covers WP_Webfonts::get_providers
+ */
+ public function test_get_providers() {
+ wp_register_webfont_provider( 'test-provider', 'Test_Provider' );
+ $this->assertEquals(
+ array(
+ 'local' => 'WP_Webfonts_Provider_Local',
+ 'test-provider' => 'Test_Provider',
+ ),
+ wp_get_webfont_providers()
+ );
+ }
+
+ /**
+ * @covers WP_Webfonts::validate_font
+ */
+ public function test_validate_font() {
+ // Test empty array.
+ $this->assertFalse( wp_webfonts()->validate_font( array() ) );
+
+ $font = array(
+ 'font-family' => 'Test Font 1',
+ 'src' => 'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2',
+ );
+
+ // Test missing provider fallback to local.
+ $this->assertEquals( 'local', wp_webfonts()->validate_font( $font )['provider'] );
+
+ // Test missing font-weight fallback to 400.
+ $this->assertEquals( '400', wp_webfonts()->validate_font( $font )['font-weight'] );
+
+ // Test missing font-style fallback to normal.
+ $this->assertEquals( 'normal', wp_webfonts()->validate_font( $font )['font-style'] );
+
+ // Test missing font-display fallback to fallback.
+ $this->assertEquals( 'fallback', wp_webfonts()->validate_font( $font )['font-display'] );
+
+ // Test local font with missing "src".
+ $this->assertFalse( wp_webfonts()->validate_font( array( 'font-family' => 'Test Font 2' ) ) );
+
+ // Test valid src URL, without a protocol.
+ $font['src'] = '//example.com/SourceSerif4Variable-Roman.ttf.woff2';
+ $this->assertEquals( wp_webfonts()->validate_font( $font )['src'], $font['src'] );
+
+ // Test font-weight.
+ $font_weights = array( 100, '100', '100 900', 'normal' );
+ foreach ( $font_weights as $value ) {
+ $font['font-weight'] = $value;
+ $this->assertEquals( wp_webfonts()->validate_font( $font )['font-weight'], $value );
+ }
+
+ // Test that invalid keys get removed from the font.
+ $font['invalid-key'] = 'invalid';
+ $this->assertArrayNotHasKey( 'invalid-key', wp_webfonts()->validate_font( $font ) );
+ }
+
+ /**
+ * @covers WP_Webfonts::generate_styles
+ */
+ public function test_generate_styles() {
+ $this->assertEquals(
+ '@font-face{font-family:"Source Serif Pro";font-style:normal;font-weight:200 900;font-display:fallback;font-stretch:normal;src:local("Source Serif Pro"), url(\'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2\') format(\'woff2\');}@font-face{font-family:"Source Serif Pro";font-style:italic;font-weight:200 900;font-display:fallback;font-stretch:normal;src:local("Source Serif Pro"), url(\'https://example.com/assets/fonts/source-serif-pro/SourceSerif4Variable-Italic.ttf.woff2\') format(\'woff2\');}',
+ wp_webfonts()->generate_styles()
+ );
+ }
+}