diff --git a/lib/experimental/fonts-api/class-wp-fonts-resolver.php b/lib/experimental/fonts-api/class-wp-fonts-resolver.php
new file mode 100644
index 00000000000000..d3d0ecba992b88
--- /dev/null
+++ b/lib/experimental/fonts-api/class-wp-fonts-resolver.php
@@ -0,0 +1,109 @@
+get_raw_data();
+ if ( isset( $user_global_styles['styles'] ) ) {
+ $user_selected_fonts = static::get_user_selected_fonts( $user_global_styles['styles'] );
+ }
+
+ if ( empty( $user_selected_fonts ) ) {
+ return array();
+ }
+
+ wp_enqueue_fonts( $user_selected_fonts );
+ return $user_selected_fonts;
+ }
+
+ /**
+ * Gets the user-selected font-family handles.
+ *
+ * @since X.X.X
+ *
+ * @param array $global_styles Global styles potentially containing user-selected fonts.
+ * @return array User-selected font-families.
+ */
+ private static function get_user_selected_fonts( array $global_styles ) {
+ $font_families = array();
+
+ foreach ( static::$global_styles_font_family_structure as $path ) {
+ $style_value = _wp_array_get( $global_styles, $path, '' );
+
+ $font_family = static::get_value_from_style( $style_value );
+ if ( '' !== $font_family ) {
+ $font_families[] = $font_family;
+ }
+ }
+
+ return array_unique( $font_families );
+ }
+
+ /**
+ * Get the value (i.e. preset slug) from the given style value.
+ *
+ * @since X.X.X
+ *
+ * @param string $style The style to parse.
+ * @param string $preset_type Optional. The type to parse. Default 'font-family'.
+ * @return string Preset slug.
+ */
+ private static function get_value_from_style( $style, $preset_type = 'font-family' ) {
+ if ( '' === $style ) {
+ return '';
+ }
+
+ $starting_pattern = "var(--wp--preset--{$preset_type}--";
+ $ending_pattern = ')';
+ if ( ! str_starts_with( $style, $starting_pattern ) ) {
+ return '';
+ }
+
+ $offset = strlen( $starting_pattern );
+ $length = strpos( $style, $ending_pattern ) - $offset;
+ return substr( $style, $offset, $length );
+ }
+}
diff --git a/lib/experimental/fonts-api/fonts-api.php b/lib/experimental/fonts-api/fonts-api.php
index 5f3f7a60ee8839..7075f20f76a217 100644
--- a/lib/experimental/fonts-api/fonts-api.php
+++ b/lib/experimental/fonts-api/fonts-api.php
@@ -198,19 +198,17 @@ function wp_print_fonts( $handles = false ) {
return array();
}
- // Skip this reassignment decision-making when using the default of `false`.
- if ( false !== $handles ) {
- // When `true`, print all registered fonts for the iframed editor.
- if ( $in_iframed_editor ) {
- $queue = $wp_fonts->queue;
- $done = $wp_fonts->done;
- $wp_fonts->done = array();
- $wp_fonts->queue = $registered;
- $handles = false;
- } elseif ( empty( $handles ) ) {
- // When falsey, assign `false` to print enqueued fonts.
- $handles = false;
- }
+ if ( empty( $handles ) ) {
+ // Automatically enqueue all user-selected fonts.
+ WP_Fonts_Resolver::enqueue_user_selected_fonts();
+ $handles = false;
+ } elseif ( $in_iframed_editor ) {
+ // Print all registered fonts for the iframed editor.
+ $queue = $wp_fonts->queue;
+ $done = $wp_fonts->done;
+ $wp_fonts->done = array();
+ $wp_fonts->queue = $registered;
+ $handles = false;
}
_wp_scripts_maybe_doing_it_wrong( __FUNCTION__ );
diff --git a/lib/load.php b/lib/load.php
index b8ec4a4d607849..a97a7cdc881f5e 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -116,7 +116,9 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/fonts-api/register-fonts-from-theme-json.php';
require __DIR__ . '/experimental/fonts-api/class-wp-fonts.php';
require __DIR__ . '/experimental/fonts-api/class-wp-fonts-provider-local.php';
+ require __DIR__ . '/experimental/fonts-api/class-wp-fonts-resolver.php';
require __DIR__ . '/experimental/fonts-api/fonts-api.php';
+
// BC Layer files, which will not be backported to WP Core.
require __DIR__ . '/experimental/fonts-api/bc-layer/class-gutenberg-fonts-api-bc-layer.php';
require __DIR__ . '/experimental/fonts-api/bc-layer/webfonts-deprecations.php';
diff --git a/phpunit/fonts-api/wp-fonts-testcase.php b/phpunit/fonts-api/wp-fonts-testcase.php
index adb70eca98b0fc..f7f7fb04b50625 100644
--- a/phpunit/fonts-api/wp-fonts-testcase.php
+++ b/phpunit/fonts-api/wp-fonts-testcase.php
@@ -59,6 +59,13 @@ abstract class WP_Fonts_TestCase extends WP_UnitTestCase {
*/
protected $orig_theme_dir;
+ /**
+ * Administrator ID.
+ *
+ * @var int
+ */
+ protected static $administrator_id = 0;
+
public static function set_up_before_class() {
parent::set_up_before_class();
@@ -346,4 +353,39 @@ protected function get_handles_for_provider( array $fonts, $provider_id ) {
return $handles;
}
+
+ protected static function set_up_admin_user() {
+ self::$administrator_id = self::factory()->user->create(
+ array(
+ 'role' => 'administrator',
+ 'user_email' => 'administrator@example.com',
+ )
+ );
+ }
+
+ /**
+ * Sets up the global styles.
+ *
+ * @param array $styles User-selected styles structure.
+ * @param array $theme Optional. Theme to switch to for the test. Default 'fonts-block-theme'.
+ */
+ protected function set_up_global_styles( array $styles, $theme = 'fonts-block-theme' ) {
+ switch_theme( $theme );
+
+ if ( empty( $styles ) ) {
+ return;
+ }
+
+ // Make sure there is data from the user origin.
+ wp_set_current_user( self::$administrator_id );
+ $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( wp_get_theme(), true );
+ $config = json_decode( $user_cpt['post_content'], true );
+
+ // Add the test styles.
+ $config['styles'] = $styles;
+
+ // Update the global styles and settings post.
+ $user_cpt['post_content'] = wp_json_encode( $config );
+ wp_update_post( $user_cpt, true, false );
+ }
}
diff --git a/phpunit/fonts-api/wp-fonts-tests-dataset.php b/phpunit/fonts-api/wp-fonts-tests-dataset.php
index 79d2387b52cf53..b882965598d360 100644
--- a/phpunit/fonts-api/wp-fonts-tests-dataset.php
+++ b/phpunit/fonts-api/wp-fonts-tests-dataset.php
@@ -1161,4 +1161,236 @@ protected function get_registered_mock_fonts() {
),
);
}
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_print_user_selected_fonts() {
+ $global_styles = $this->get_mock_user_selected_fonts_global_styles();
+ $font_faces = $this->get_registered_fonts_css();
+
+ return array(
+ 'print font1' => array(
+ 'global_styles' => $global_styles['font1'],
+ 'expected_done' => array(
+ 'font1-300-normal',
+ 'font1-300-italic',
+ 'font1-900-normal',
+ 'font1',
+ ),
+ 'expected_output' => sprintf(
+ '%s; %s; %s\n',
+ $font_faces['font1-300-normal'],
+ $font_faces['font1-300-italic'],
+ $font_faces['font1-900-normal']
+ ),
+ ),
+ 'print font2' => array(
+ 'global_styles' => $global_styles['font2'],
+ 'expected_done' => array( 'font2-200-900-normal', 'font2-200-900-italic', 'font2' ),
+ 'expected_output' => sprintf(
+ '%s; %s\n',
+ $font_faces['font2-200-900-normal'],
+ $font_faces['font2-200-900-italic']
+ ),
+ ),
+ 'print font3' => array(
+ 'global_styles' => $global_styles['font3'],
+ 'expected_done' => array( 'font3', 'font3-bold-normal' ),
+ 'expected_output' => sprintf(
+ '%s\n',
+ $font_faces['font3-bold-normal']
+ ),
+ ),
+ 'print all fonts' => array(
+ 'global_styles' => $global_styles['all'],
+ 'expected_done' => array(
+ 'font1-300-normal',
+ 'font1-300-italic',
+ 'font1-900-normal',
+ 'font1',
+ 'font2-200-900-normal',
+ 'font2-200-900-italic',
+ 'font2',
+ 'font3-bold-normal',
+ 'font3',
+ ),
+ 'expected_output' => sprintf(
+ '%s; %s; %s; %s; %s; %s\n',
+ $font_faces['font1-300-normal'],
+ $font_faces['font1-300-italic'],
+ $font_faces['font1-900-normal'],
+ $font_faces['font2-200-900-normal'],
+ $font_faces['font2-200-900-italic'],
+ $font_faces['font3-bold-normal']
+ ),
+ ),
+ 'print all valid fonts' => array(
+ 'global_styles' => $global_styles['all with invalid element'],
+ 'expected_done' => array(
+ 'font1-300-normal',
+ 'font1-300-italic',
+ 'font1-900-normal',
+ 'font1',
+ 'font2-200-900-normal',
+ 'font2-200-900-italic',
+ 'font2',
+ 'font3-bold-normal',
+ 'font3',
+ ),
+ 'expected_output' => sprintf(
+ '%s; %s; %s; %s; %s; %s\n',
+ $font_faces['font1-300-normal'],
+ $font_faces['font1-300-italic'],
+ $font_faces['font1-900-normal'],
+ $font_faces['font2-200-900-normal'],
+ $font_faces['font2-200-900-italic'],
+ $font_faces['font3-bold-normal']
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Gets user-selected fonts for global styles for the mock provider.
+ *
+ * @since X.X.X
+ *
+ * @return array
+ */
+ protected function get_mock_user_selected_fonts_global_styles() {
+ return array(
+ 'font1' => array(
+ 'elements' => array(
+ 'heading' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '300',
+ ),
+ ),
+ 'caption' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '300',
+ ),
+ ),
+ ),
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '900',
+ ),
+ ),
+ 'font2' => array(
+ 'elements' => array(
+ 'heading' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font2',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '200-900',
+ ),
+ ),
+ 'button' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font2',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '200-900',
+ ),
+ ),
+ ),
+ ),
+ 'font3' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font3',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => 'bold',
+ ),
+ ),
+ 'all' => array(
+ 'elements' => array(
+ 'link' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '300',
+ ),
+ ),
+ 'heading' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '900',
+ ),
+ ),
+ 'caption' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '300',
+ ),
+ ),
+ 'button' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font2',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '200-900',
+ ),
+ ),
+ ),
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font3',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => 'bold',
+ ),
+ ),
+ 'all with invalid element' => array(
+ 'elements' => array(
+ 'link' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '300',
+ ),
+ ),
+ 'heading' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '900',
+ ),
+ ),
+ 'caption' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '300',
+ ),
+ ),
+ 'button' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font2',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '200-900',
+ ),
+ ),
+ 'invalid' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font2',
+ 'fontStyle' => 'italic',
+ 'fontWeight' => '200-900',
+ ),
+ ),
+ ),
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font3',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => 'bold',
+ ),
+ ),
+ );
+ }
}
diff --git a/phpunit/fonts-api/wpFontsResolver/enqueueUserSelectedFonts-test.php b/phpunit/fonts-api/wpFontsResolver/enqueueUserSelectedFonts-test.php
new file mode 100644
index 00000000000000..c3ba0fd1f72ac6
--- /dev/null
+++ b/phpunit/fonts-api/wpFontsResolver/enqueueUserSelectedFonts-test.php
@@ -0,0 +1,131 @@
+user->create(
+ array(
+ 'role' => 'administrator',
+ 'user_email' => 'administrator@example.com',
+ )
+ );
+ }
+
+ /**
+ * @dataProvider data_should_not_enqueue_when_no_user_selected_fonts
+ *
+ * @param array $styles Optional. Test styles. Default empty array.
+ */
+ public function test_should_not_enqueue_when_no_user_selected_fonts( $styles = array() ) {
+ $this->set_up_global_styles( $styles );
+
+ $mock = $this->set_up_mock( 'enqueue' );
+ $mock->expects( $this->never() )
+ ->method( 'enqueue' );
+
+ $expected = array();
+ $this->assertSame( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_should_not_enqueue_when_no_user_selected_fonts() {
+ return array(
+ 'no user-selected styles' => array(),
+ 'invalid element' => array(
+ array(
+ 'elements' => array(
+ 'invalid' => array(
+ 'typography' => array(
+ 'fontFamily' => 'var:preset|font-family|font1',
+ 'fontStyle' => 'normal',
+ 'fontWeight' => '400',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider data_should_enqueue_when_user_selected_fonts
+ *
+ * @param array $styles Test styles.
+ * @param array $expected Expected results.
+ */
+ public function test_should_enqueue_when_user_selected_fonts( $styles, $expected ) {
+ $mock = $this->set_up_mock( 'enqueue' );
+ $mock->expects( $this->once() )
+ ->method( 'enqueue' )
+ ->with(
+ $this->identicalTo( $expected )
+ );
+
+ $this->set_up_global_styles( $styles );
+
+ $this->assertSameSets( $expected, WP_Fonts_Resolver::enqueue_user_selected_fonts() );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_should_enqueue_when_user_selected_fonts() {
+ $global_styles = $this->get_mock_user_selected_fonts_global_styles();
+
+ return array(
+ 'heading, caption, text' => array(
+ 'styles' => $global_styles['font1'],
+ 'expected' => array( 'font1' ),
+ ),
+ 'heading, button' => array(
+ 'styles' => $global_styles['font2'],
+ 'expected' => array( 'font2' ),
+ ),
+ 'text' => array(
+ 'styles' => $global_styles['font3'],
+ 'expected' => array( 'font3' ),
+ ),
+ 'all' => array(
+ 'styles' => $global_styles['all'],
+ 'expected' => array(
+ 0 => 'font1',
+ // font1 occurs 2 more times and gets removed as duplicates.
+ 3 => 'font2',
+ 4 => 'font3',
+ ),
+ ),
+ 'all with invalid element' => array(
+ 'styles' => $global_styles['all with invalid element'],
+ 'expected' => array(
+ 0 => 'font1',
+ // font1 occurs 2 more times and gets removed as duplicates.
+ 3 => 'font2',
+ // Skips font2 for the "invalid" element.
+ 4 => 'font3',
+ ),
+ ),
+ );
+ }
+}
diff --git a/phpunit/fonts-api/wpPrintFonts-test.php b/phpunit/fonts-api/wpPrintFonts-test.php
index cb500aaa0430f4..4f50cf6a8cd5ea 100644
--- a/phpunit/fonts-api/wpPrintFonts-test.php
+++ b/phpunit/fonts-api/wpPrintFonts-test.php
@@ -15,6 +15,14 @@
*/
class Tests_Fonts_WpPrintFonts extends WP_Fonts_TestCase {
+ public static function set_up_before_class() {
+ self::$requires_switch_theme_fixtures = true;
+
+ parent::set_up_before_class();
+
+ static::set_up_admin_user();
+ }
+
public function test_should_return_empty_array_when_no_fonts_registered() {
$this->assertSame( array(), wp_print_fonts() );
}
@@ -104,26 +112,6 @@ public function test_should_print_handles_when_not_enqueued( $setup, $expected_d
$this->assertSameSets( $expected_done, $actual_done, 'Printed handles should match' );
}
- /**
- * Sets up the dependencies for integration test.
- *
- * @param array $setup Dependencies to set up.
- * @param WP_Fonts $wp_fonts Instance of WP_Fonts.
- * @param bool $enqueue Whether to enqueue. Default true.
- */
- private function setup_integrated_deps( array $setup, $wp_fonts, $enqueue = true ) {
- foreach ( $setup['provider'] as $provider ) {
- $wp_fonts->register_provider( $provider['id'], $provider['class'] );
- }
- foreach ( $setup['registered'] as $handle => $variations ) {
- $this->setup_register( $handle, $variations, $wp_fonts );
- }
-
- if ( $enqueue ) {
- $wp_fonts->enqueue( $setup['enqueued'] );
- }
- }
-
/**
* @dataProvider data_should_print_all_registered_fonts_for_iframed_editor
*
@@ -189,4 +177,54 @@ public function data_should_print_all_registered_fonts_for_iframed_editor() {
),
);
}
+
+ /**
+ * Integration test for printing user-selected global fonts.
+ * This test registers providers and fonts and then enqueues before testing the printing functionality.
+ *
+ * @dataProvider data_print_user_selected_fonts
+ *
+ * @param array $global_styles Test set up information for provider, fonts, and enqueued.
+ * @param array $expected_done Expected array of printed handles.
+ * @param string $expected_output Expected printed output.
+ */
+ public function test_should_print_user_selected_fonts( $global_styles, $expected_done, $expected_output ) {
+ $wp_fonts = wp_fonts();
+
+ $setup = array(
+ 'provider' => array( 'mock' => $this->get_provider_definitions( 'mock' ) ),
+ 'registered' => $this->get_registered_mock_fonts(),
+ 'global_styles' => $global_styles,
+ );
+ $this->setup_integrated_deps( $setup, $wp_fonts, false );
+
+ $this->expectOutputString( $expected_output );
+ $actual_printed_fonts = wp_print_fonts();
+ $this->assertSameSets( $expected_done, $actual_printed_fonts, 'Should print font-faces for given user-selected fonts' );
+ }
+
+
+ /**
+ * Sets up the dependencies for integration test.
+ *
+ * @param array $setup Dependencies to set up.
+ * @param WP_Fonts $wp_fonts Instance of WP_Fonts.
+ * @param bool $enqueue Whether to enqueue. Default true.
+ */
+ private function setup_integrated_deps( array $setup, $wp_fonts, $enqueue = true ) {
+ foreach ( $setup['provider'] as $provider ) {
+ $wp_fonts->register_provider( $provider['id'], $provider['class'] );
+ }
+ foreach ( $setup['registered'] as $handle => $variations ) {
+ $this->setup_register( $handle, $variations, $wp_fonts );
+ }
+
+ if ( $enqueue ) {
+ $wp_fonts->enqueue( $setup['enqueued'] );
+ }
+
+ if ( ! empty( $setup['global_styles'] ) ) {
+ $this->set_up_global_styles( $setup['global_styles'] );
+ }
+ }
}