Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webfonts API: expose enqueueing method instead of directly enqueueing fonts on registration #39327

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ public static function get_theme_data( $deprecated = array() ) {
if ( null === static::$theme ) {
$theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
$theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data );
$theme_json_data = gutenberg_add_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_json_data = gutenberg_add_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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ 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'] );
if ( 0 === strpos( $item['url'], 'file:./' ) ) {
$absolute_path_to_url = get_stylesheet_directory_uri() . '/' . str_replace( 'file:./', '', $item['url'] );
$item['url'] = wp_make_link_relative( $absolute_path_to_url );
}

$src .= ( 'data' === $item['format'] )
Expand Down
95 changes: 77 additions & 18 deletions lib/compat/wordpress-6.0/class-wp-webfonts.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ class WP_Webfonts {
* @access private
* @var array
*/
private $webfonts = array();
private $registered_webfonts = array();

/**
* An array of enqueued webfonts.
*
* @access private
* @var array
*/
private $enqueued_webfonts = array();

/**
* An array of registered providers.
Expand Down Expand Up @@ -56,12 +64,21 @@ public function init() {
}

/**
* Get the list of fonts.
* Get the list of registered fonts.
*
* @return array
*/
public function get_fonts() {
return $this->webfonts;
public function get_registered_fonts() {
return $this->registered_webfonts;
}

/**
* Get the list of enqueued fonts.
*
* @return array
*/
public function get_enqueued_fonts() {
return $this->enqueued_webfonts;
}

/**
Expand All @@ -76,24 +93,55 @@ public function get_providers() {
/**
* Register a webfont.
*
* @param array $font The font arguments.
* @param string $id The unique identifier for the webfont.
* @param array $font The font arguments.
*/
public function register_font( $font ) {
public function register_font( $id, $font ) {
if ( ! $id ) {
trigger_error( __( 'An ID is necessary when registering a webfont.', 'gutenberg' ) );
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that using the WP internal _doing_it_wrong here would be better. Plugins and themes do all kinds of weird things and we generally don't want a users' site to WSOD on account of it.

_doing_it_wrong has the virtue of throwing actual errors when WP_DEBUG is defined, which is typical for developers, who will get the more aggressive treatment.

I see several other uses of trigger_error in here and would replace all of them.

return false;
}

if ( isset( $this->registered_webfonts[ $id ] ) ) {
return;
}

$font = $this->validate_font( $font );
if ( $font ) {
$id = $this->get_font_id( $font );
$this->webfonts[ $id ] = $font;
$this->registered_webfonts[ $id ] = $font;
}
}

/**
* Get the font ID.
* Enqueue a webfont.
*
* @param array $font The font arguments.
* @return string
* @param string $id The unique identifier for the webfont.
* @param array|null $font The font arguments.
*/
public function get_font_id( $font ) {
return sanitize_title( "{$font['font-family']}-{$font['font-weight']}-{$font['font-style']}-{$font['provider']}" );
public function enqueue_font( $id, $font = null ) {
if ( ! $id ) {
trigger_error( __( 'An ID is necessary when enqueueing a webfont.', 'gutenberg' ) );
return false;
}

if ( isset( $this->enqueued_webfonts[ $id ] ) ) {
return;
}

if ( $font ) {
$font = $this->validate_font( $font );
$this->enqueued_webfonts[ $id ] = $font;

if ( isset( $this->registered_webfonts[ $id ] ) ) {
unset( $this->registered_webfonts[ $id ] );
}
} elseif ( isset( $this->registered_webfonts[ $id ] ) ) {
$this->enqueued_webfonts[ $id ] = $this->registered_webfonts[ $id ];

unset( $this->registered_webfonts[ $id ] );
} else {
trigger_error( __( 'The given webfont ID is not registered, therefore the webfont cannot be enqueued.', 'gutenberg' ) );
}
}

/**
Expand Down Expand Up @@ -201,7 +249,7 @@ public function register_provider( $provider, $class ) {
*/
public function generate_and_enqueue_styles() {
// Generate the styles.
$styles = $this->generate_styles();
$styles = $this->generate_styles( $this->get_enqueued_fonts() );

// Bail out if there are no styles to enqueue.
if ( '' === $styles ) {
Expand All @@ -216,18 +264,28 @@ public function generate_and_enqueue_styles() {
wp_add_inline_style( $this->stylesheet_handle, $styles );
}

/**
* Returns a list of registered and enqueued webfonts.
* We need this so all webfonts show up in the editor,
* regardless of their state.
*/
public function get_all_webfonts() {
return array_merge( $this->get_registered_fonts(), $this->get_enqueued_fonts() );
}

/**
* Generate and enqueue editor styles.
*/
public function generate_and_enqueue_editor_styles() {
// Generate the styles.
$styles = $this->generate_styles();
$styles = $this->generate_styles( $this->get_all_webfonts() );

// Bail out if there are no styles to enqueue.
if ( '' === $styles ) {
return;
}

wp_enqueue_style( 'wp-block-library' );
wp_add_inline_style( 'wp-block-library', $styles );
}

Expand All @@ -236,16 +294,17 @@ public function generate_and_enqueue_editor_styles() {
*
* @since 6.0.0
*
* @param array $webfonts The fonts that are going to the output.
*
* @return string $styles Generated styles.
*/
public function generate_styles() {
public function generate_styles( $webfonts ) {
$styles = '';
$providers = $this->get_providers();

// Group webfonts by provider.
$webfonts_by_provider = array();
$registered_webfonts = $this->get_fonts();
foreach ( $registered_webfonts as $id => $webfont ) {
foreach ( $webfonts as $id => $webfont ) {
$provider = $webfont['provider'];
if ( ! isset( $providers[ $provider ] ) ) {
/* translators: %s is the provider name. */
Expand Down
34 changes: 16 additions & 18 deletions lib/compat/wordpress-6.0/register-webfonts-from-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
* @package gutenberg
*/

/**
* Generates a font id based on the provided font object.
*
* @param array $font The font object.
* @return string
*/
function gutenberg_generate_font_id( $font ) {
return sanitize_title( "{$font['font-family']}-{$font['font-weight']}-{$font['font-style']}-{$font['provider']}" );
}

/**
* Register webfonts defined in theme.json.
*/
Expand All @@ -31,19 +41,6 @@ function gutenberg_register_webfonts_from_theme_json() {
$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 );
Expand All @@ -58,7 +55,8 @@ function gutenberg_register_webfonts_from_theme_json() {
}
}
foreach ( $webfonts as $webfont ) {
wp_webfonts()->register_font( $webfont );
$id = isset( $webfont['id'] ) ? $webfont['id'] : gutenberg_generate_font_id( $webfont );
Copy link
Member Author

@zaguiini zaguiini Mar 17, 2022

Choose a reason for hiding this comment

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

The sole reason we're doing this ternary is that it might break themes already using the Webfont API and registering webfonts, but without specifying the font id.

The thing is, the Webfont API hasn't been made public yet, so we can do whatever we want with the signature... Including breaking changes such as this one.

"Why are we making the id key required?"

The idea is that developers provide their own hashing mechanism so they can register IDs that works for them, since they're creating their own.

I do believe that adding the gutenberg_generate_font_id function is confusing. The reason why it's there is that no theme specifies the ID when registering a font through theme.json so we wanted to maintain it backward compatible. That's why this function is used there.

So, now that we're separating registration from enqueueing, I do think it makes sense for the developers to come with their own IDs, so that they can register and enqueue the fonts as they need instead of relying on an abstract mechanism that they're not aware of. Explicit is better than implicit.

wp_register_webfont( $id, $webfont );
}
}

Expand All @@ -69,8 +67,8 @@ function gutenberg_register_webfonts_from_theme_json() {
*
* @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();
function gutenberg_add_webfonts_to_theme_json( $data ) {
$webfonts = wp_webfonts()->get_all_webfonts();
$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'];
Expand Down Expand Up @@ -101,7 +99,7 @@ function gutenberg_add_registered_webfonts_to_theme_json( $data ) {

// Diff the arrays to find the missing fonts.
$to_add = array_diff(
$get_families( $font_families_registered ),
$get_families( $webfonts ),
$get_families( $font_families_from_theme )
);

Expand All @@ -125,7 +123,7 @@ function gutenberg_add_registered_webfonts_to_theme_json( $data ) {
// Add missing fonts.
foreach ( $to_add as $family ) {
$font_face = array();
foreach ( $font_families_registered as $font_family ) {
foreach ( $webfonts as $font_family ) {
if ( $family !== $font_family['font-family'] ) {
continue;
}
Expand Down
Loading