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'] ); + } + } }