Skip to content

Commit

Permalink
Font Face: backport from Core [56500] (#54218)
Browse files Browse the repository at this point in the history
* Merges the changes made in Core while retaining plugin specifics.
* Adds `@core-merge` notes where appropriate.

References:
* https://core.trac.wordpress.org/changeset/56500
* https://core.trac.wordpress.org/changeset/56540
  • Loading branch information
hellofromtonya authored Sep 13, 2023
1 parent 557d84b commit 46ecc04
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* @package WordPress
* @subpackage Fonts
* @since 6.4.0
*
* @core-merge: this file is located in `wp-includes/fonts/`.
*/

if ( class_exists( 'WP_Font_Face_Resolver' ) ) {
Expand Down Expand Up @@ -33,7 +35,7 @@ public static function get_fonts_from_theme_json() {
$settings = gutenberg_get_global_settings();

// Bail out early if there are no font settings.
if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) {
if ( empty( $settings['typography']['fontFamilies'] ) ) {
return array();
}

Expand Down
110 changes: 64 additions & 46 deletions lib/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* @package WordPress
* @subpackage Fonts
* @since 6.4.0
*
* @core-merge: this file is located in `wp-includes/fonts/`.
*/

if ( class_exists( 'WP_Font_Face' ) ) {
Expand Down Expand Up @@ -82,23 +84,6 @@ class WP_Font_Face {
* @since 6.4.0
*/
public function __construct() {
/**
* Filters the font-face property defaults.
*
* @since 6.4.0
*
* @param array $defaults {
* An array of required font-face properties and defaults.
*
* @type string $provider The provider ID. Default 'local'.
* @type string $font-family The font-family property. Default empty string.
* @type string $font-style The font-style property. Default 'normal'.
* @type string $font-weight The font-weight property. Default '400'.
* @type string $font-display The font-display property. Default 'fallback'.
* }
*/
$this->font_face_property_defaults = apply_filters( 'wp_font_face_property_defaults', $this->font_face_property_defaults );

if (
function_exists( 'is_admin' ) && ! is_admin()
&&
Expand All @@ -113,7 +98,9 @@ function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5'
*
* @since 6.4.0
*
* @param array $fonts The fonts to generate and print @font-face styles.
* @param array[][] $fonts Optional. The font-families and their font variations.
* See {@see wp_print_font_faces()} for the supported fields.
* Default empty array.
*/
public function generate_and_print( array $fonts ) {
$fonts = $this->validate_fonts( $fonts );
Expand All @@ -123,10 +110,21 @@ public function generate_and_print( array $fonts ) {
return;
}

printf(
$this->get_style_element(),
$this->get_css( $fonts )
);
$css = $this->get_css( $fonts );

/*
* The font-face CSS is contained within <style> tags and can only be interpreted
* as CSS in the browser. Using wp_strip_all_tags() is sufficient escaping
* to avoid malicious attempts to close </style> and open a <script>.
*/
$css = wp_strip_all_tags( $css );

// Bail out if there is no CSS to print.
if ( empty( $css ) ) {
return;
}

printf( $this->get_style_element(), $css );
}

/**
Expand All @@ -142,7 +140,7 @@ private function validate_fonts( array $fonts ) {

foreach ( $fonts as $font_faces ) {
foreach ( $font_faces as $font_face ) {
$font_face = $this->validate_font_face_properties( $font_face );
$font_face = $this->validate_font_face_declarations( $font_face );
// Skip if failed validation.
if ( false === $font_face ) {
continue;
Expand All @@ -156,41 +154,59 @@ private function validate_fonts( array $fonts ) {
}

/**
* Validates each font-face property.
* Validates each font-face declaration (property and value pairing).
*
* @since 6.4.0
*
* @param array $font_face Font face properties to validate.
* @return false|array Validated font-face on success. Else, false.
* @param array $font_face Font face property and value pairings to validate.
* @return array|false Validated font-face on success, or false on failure.
*/
private function validate_font_face_properties( array $font_face ) {
private function validate_font_face_declarations( array $font_face ) {
$font_face = wp_parse_args( $font_face, $this->font_face_property_defaults );

// Check the font-family.
if ( empty( $font_face['font-family'] ) || ! is_string( $font_face['font-family'] ) ) {
trigger_error( 'Font font-family must be a non-empty string.' );
// @todo replace with `wp_trigger_error()`.
_doing_it_wrong(
__METHOD__,
__( 'Font font-family must be a non-empty string.' ),
'6.4.0'
);
return false;
}

// Make sure that local fonts have 'src' defined.
if ( empty( $font_face['src'] ) || ( ! is_string( $font_face['src'] ) && ! is_array( $font_face['src'] ) ) ) {
trigger_error( 'Font src must be a non-empty string or an array of strings.' );
// @todo replace with `wp_trigger_error()`.
_doing_it_wrong(
__METHOD__,
__( 'Font src must be a non-empty string or an array of strings.' ),
'6.4.0'
);
return false;
}

// Validate the 'src' property.
if ( ! empty( $font_face['src'] ) ) {
foreach ( (array) $font_face['src'] as $src ) {
if ( empty( $src ) || ! is_string( $src ) ) {
trigger_error( 'Each font src must be a non-empty string.' );
return false;
}
foreach ( (array) $font_face['src'] as $src ) {
if ( empty( $src ) || ! is_string( $src ) ) {
// @todo replace with `wp_trigger_error()`.
_doing_it_wrong(
__METHOD__,
__( 'Each font src must be a non-empty string.' ),
'6.4.0'
);
return false;
}
}

// Check the font-weight.
if ( ! is_string( $font_face['font-weight'] ) && ! is_int( $font_face['font-weight'] ) ) {
trigger_error( 'Font font-weight must be a properly formatted string or integer.' );
// @todo replace with `wp_trigger_error()`.
_doing_it_wrong(
__METHOD__,
__( 'Font font-weight must be a properly formatted string or integer.' ),
'6.4.0'
);
return false;
}

Expand All @@ -200,17 +216,17 @@ private function validate_font_face_properties( array $font_face ) {
}

// Remove invalid properties.
foreach ( $font_face as $prop => $value ) {
if ( ! in_array( $prop, $this->valid_font_face_properties, true ) ) {
unset( $font_face[ $prop ] );
foreach ( $font_face as $property => $value ) {
if ( ! in_array( $property, $this->valid_font_face_properties, true ) ) {
unset( $font_face[ $property ] );
}
}

return $font_face;
}

/**
* Gets the `<style>` element for wrapping the `@font-face` CSS.
* Gets the style element for wrapping the `@font-face` CSS.
*
* @since 6.4.0
*
Expand Down Expand Up @@ -253,11 +269,11 @@ private function get_css( $font_faces ) {
$css = '';

foreach ( $font_faces as $font_face ) {
// Order the font's `src` items to optimize for browser support.
$font_face = $this->order_src( $font_face );
// Order the font's `src` items to optimize for browser support.
$font_face = $this->order_src( $font_face );

// Build the @font-face CSS for this font.
$css .= '@font-face{' . $this->build_font_face_css( $font_face ) . '}' . "\n";
// Build the @font-face CSS for this font.
$css .= '@font-face{' . $this->build_font_face_css( $font_face ) . '}' . "\n";
}

// Don't print the last newline character.
Expand Down Expand Up @@ -348,8 +364,10 @@ private function order_src( array $font_face ) {
private function build_font_face_css( array $font_face ) {
$css = '';

// Wrap font-family in quotes if it contains spaces
// and is not already wrapped in quotes.
/*
* Wrap font-family in quotes if it contains spaces
* and is not already wrapped in quotes.
*/
if (
str_contains( $font_face['font-family'], ' ' ) &&
! str_contains( $font_face['font-family'], '"' ) &&
Expand Down
29 changes: 27 additions & 2 deletions lib/compat/wordpress-6.4/fonts/fonts.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* @package WordPress
* @subpackage Fonts
* @since 6.4.0
*
* @core-merge: this file is located in `wp-includes/fonts.php`. It will contain Font Face and Font Library functions.
*/

if ( ! function_exists( 'wp_print_font_faces' ) ) {
Expand All @@ -18,8 +20,31 @@
*
* @since 6.4.0
*
* @param array $fonts Optional. The fonts to generate and print @font-face styles.
* Default empty array.
* @param array[][] $fonts {
* Optional. The font-families and their font variations. Default empty array.
*
* @type string $font-family => array[] $variations {
* Optional. An associated array of font variations for this font-family.
* Each variation has the following structure.
*
* @type array $font_variation {
* @type string $font-family The font-family property.
* @type string|string[] $src The URL(s) to each resource containing the font data.
* @type string $font_style Optional. The font-style property. Default 'normal'.
* @type string $font-weight Optional. The font-weight property. Default '400'.
* @type string $font-display Optional. The font-display property. Default 'fallback'.
* @type string $ascent-override Optional. The ascent-override property.
* @type string $descent-override Optional. The descent-override property.
* @type string $font-stretch Optional. The font-stretch property.
* @type string $font-variant Optional. The font-variant property.
* @type string $font-feature-settings Optional. The font-feature-settings property.
* @type string $font-variation-settings Optional. The font-variation-settings property.
* @type string $line-gap-override Optional. The line-gap-override property.
* @type string $size-adjust Optional. The size-adjust property.
* @type string $unicode-range Optional. The unicode-range property.
* }
* }
* }
*/
function wp_print_font_faces( $fonts = array() ) {

Expand Down
73 changes: 11 additions & 62 deletions phpunit/tests/fonts/font-face/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* Abstracts the common tasks for the Font Face tests.
*/
abstract class WP_Font_Face_TestCase extends WP_UnitTestCase {
abstract class WP_Font_Face_UnitTestCase extends WP_UnitTestCase {
use WP_Font_Face_Tests_Datasets;

/**
Expand Down Expand Up @@ -62,6 +62,7 @@ public static function set_up_before_class() {
parent::set_up_before_class();

if ( self::$requires_switch_theme_fixtures ) {
// @core-merge Use `DIR_TESTDATA` instead of `GUTENBERG_DIR_TESTDATA`.
self::$theme_root = realpath( GUTENBERG_DIR_TESTDATA . '/themedir1' );
}
}
Expand Down Expand Up @@ -103,85 +104,33 @@ public function tear_down() {
}

if ( self::$requires_switch_theme_fixtures ) {
// Clean up the filters to modify the theme root.
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );

WP_Theme_JSON_Resolver::clean_cached_data();
if ( class_exists( 'WP_Theme_JSON_Resolver_Gutenberg' ) ) {
WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data();
}
}

parent::tear_down();
}

public function clean_up_global_scope() {
parent::clean_up_global_scope();

if ( self::$requires_switch_theme_fixtures ) {
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
wp_clean_themes_cache();

// @core-merge: the function_exists() is not needed in Core.
if ( function_exists( 'wp_clean_theme_json_cache' ) ) {
wp_clean_theme_json_cache();
}

// @core-merge: start of the plugin only code block. Do not merge into Core.
if ( class_exists( 'WP_Theme_JSON_Resolver_Gutenberg' ) ) {
WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data();
}
if ( function_exists( '_gutenberg_clean_theme_json_caches' ) ) {
_gutenberg_clean_theme_json_caches();
}
// @core-merge: end of the plugin only code block.

unset( $GLOBALS['wp_themes'] );
}

parent::tear_down();
}

public function filter_set_theme_root() {
return self::$theme_root;
}

protected function get_reflection_property( $property_name, $class_name = 'WP_Fonts' ) {
$property = new ReflectionProperty( $class_name, $property_name );
$property->setAccessible( true );

return $property;
}

protected function get_property_value( $property_name, $class_name, $wp_fonts = null ) {
$property = $this->get_reflection_property( $property_name, $class_name );

if ( ! $wp_fonts ) {
$wp_fonts = wp_fonts();
}

return $property->getValue( $wp_fonts );
}

protected function setup_property( $class_name, $property_name ) {
$key = $this->get_property_key( $class_name, $property_name );

if ( ! isset( $this->property[ $key ] ) ) {
$this->property[ $key ] = new ReflectionProperty( $class_name, 'providers' );
$this->property[ $key ]->setAccessible( true );
}

return $this->property[ $key ];
}

protected function get_property_key( $class_name, $property_name ) {
return $class_name . '::$' . $property_name;
}

/**
* Opens the accessibility to access the given private or protected method.
*
* @param string $method_name Name of the method to open.
* @return ReflectionMethod Instance of the method, ie to invoke it in the test.
*/
protected function get_reflection_method( $method_name ) {
$method = new ReflectionMethod( WP_Fonts::class, $method_name );
$method->setAccessible( true );

return $method;
}
}
10 changes: 4 additions & 6 deletions phpunit/tests/fonts/font-face/wpFontFace/generateAndPrint.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@
*
* @package WordPress
* @subpackage Fonts
*
* @since 6.4.0
*/

// @core-merge this line of code is not needed when merging into Core.
require_once dirname( __DIR__ ) . '/base.php';

/**
* Test WP_Font_Face::generate_and_print().
*
* @package WordPress
* @subpackage Fonts
*
* @since X.X.X
* @group fonts
* @group fontface
*
* @covers WP_Font_Face::generate_and_print
*/
class Tests_Fonts_WPFontFace_GenerateAndPrint extends WP_UnitTestCase {
Expand Down
Loading

1 comment on commit 46ecc04

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 46ecc04.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6175541340
📝 Reported issues:

Please sign in to comment.