Skip to content

Commit

Permalink
Font Collections: lazy load json configuration for better performance (
Browse files Browse the repository at this point in the history
…#58530)

* Font Collections: lazy load json configuration for better performance

* Use explicit get_data method for font collections and restore WP_Error returns

* Do all loading and data validation in WP_Font_Collection::get_data

* Refactored the Font Collection class unit tests

* Fix test - suppress file loading error to test get_data() error response

* Fix linting errors

* Move tests back to one file per method and align with standards

* Remove outdated test

* Remove test file not wanted from rebase

* When getting collections from the REST API, only return valid collections

* Update test to use wp_json_encode

* Update google fonts collection url to release version

Still using the Github url until s.w.org is updated.

* Revert "Update google fonts collection url to release version"

This reverts commit 4cc23a5.

* Fix collection controller test

---------

Co-authored-by: Jason Crist <jcrist@pbking.com>
Co-authored-by: creativecoder <grantmkin@git.wordpress.org>
Co-authored-by: pbking <pbking@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: matiasbenedetto <mmaattiiaass@git.wordpress.org>
  • Loading branch information
6 people committed Feb 2, 2024
1 parent eb05364 commit 0ec7554
Show file tree
Hide file tree
Showing 11 changed files with 454 additions and 371 deletions.
176 changes: 91 additions & 85 deletions lib/compat/wordpress-6.5/fonts/class-wp-font-collection.php
Expand Up @@ -16,7 +16,7 @@
*
* @since 6.5.0
*/
class WP_Font_Collection {
final class WP_Font_Collection {
/**
* The unique slug for the font collection.
*
Expand All @@ -27,71 +27,47 @@ class WP_Font_Collection {
public $slug;

/**
* The name of the font collection.
*
* @since 6.5.0
*
* @var string
*/
public $name;

/**
* Description of the font collection.
*
* @since 6.5.0
*
* @var string
*/
public $description;

/**
* Array of font families in the collection.
*
* @since 6.5.0
*
* @var array
*/
public $font_families;

/**
* Categories associated with the font collection.
* Font collection data.
*
* @since 6.5.0
*
* @var array
*/
public $categories;
private $data;

/**
* Font collection json cache.
* Font collection JSON file path or url.
*
* @since 6.5.0
*
* @var array
* @var string
*/
private static $collection_json_cache = array();
private $src;

/**
* WP_Font_Collection constructor.
*
* @since 6.5.0
*
* @param string $slug Font collection slug.
* @param array $args {
* Optional. Font collection associative array of configuration options.
* @param string $slug Font collection slug.
* @param array|string $data_or_file {
* Font collection data array or a file path or url to a JSON file containing the font collection.
*
* @type string $name Name of the font collection.
* @type string $description Description of the font collection.
* @type array $font_families Array of font family definitions that are in the collection.
* @type array $categories Array of categories for the fonts that are in the collection.
* }
*/
public function __construct( $slug, $args = array() ) {
$this->slug = sanitize_title( $slug );
$this->name = isset( $args['name'] ) ? $args['name'] : __( 'Unnamed Font Collection', 'gutenberg' );
$this->description = isset( $args['description'] ) ? $args['description'] : '';
$this->font_families = isset( $args['font_families'] ) ? $args['font_families'] : array();
$this->categories = isset( $args['categories'] ) ? $args['categories'] : array();
public function __construct( $slug, $data_or_file ) {
$this->slug = sanitize_title( $slug );

// Data or json are lazy loaded and validated in get_data().
if ( is_array( $data_or_file ) ) {
$this->data = $data_or_file;
} else {
$this->src = $data_or_file;
}

if ( $this->slug !== $slug ) {
_doing_it_wrong(
Expand All @@ -101,87 +77,95 @@ public function __construct( $slug, $args = array() ) {
'6.5.0'
);
}
}

if ( empty( $args['font_families'] ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s: Font collection slug. */
sprintf( __( 'Font collection "%s" does not contain any font families.', 'gutenberg' ), $slug ),
'6.5.0'
);
/**
* Retrieves the font collection data.
*
* @since 6.5.0
*
* @return array|WP_Error An array containing the font collection data, or a WP_Error on failure.
*/
public function get_data() {
// If we have a JSON config, load it and cache the data if it's valid.
if ( $this->src && empty( $this->data ) ) {
$data = $this->load_from_json( $this->src );
if ( is_wp_error( $data ) ) {
return $data;
}

$this->data = $data;
}

return true;
// Validate required properties are not empty.
$data = $this->validate_data( $this->data );
if ( is_wp_error( $data ) ) {
return $data;
}

// Set defaults for optional properties.
$data = wp_parse_args(
$data,
array(
'description' => '',
'categories' => array(),
)
);

return $data;
}

/**
* Loads the font collection data from a json file path or url.
* Loads the font collection data from a JSON file path or url.
*
* @since 6.5.0
*
* @param string $file_or_url File path or url to a json file containing the font collection data.
* @param string $file_or_url File path or url to a JSON file containing the font collection data.
* @return array|WP_Error An array containing the font collection data on success,
* else an instance of WP_Error on failure.
*/
public static function load_from_json( $file_or_url ) {
private function load_from_json( $file_or_url ) {
$url = wp_http_validate_url( $file_or_url );
$file = file_exists( $file_or_url ) ? wp_normalize_path( realpath( $file_or_url ) ) : false;

if ( ! $url && ! $file ) {
// translators: %s: File path or url to font collection json file.
$message = sprintf( __( 'Font collection JSON file "%s" is invalid or does not exist.', 'gutenberg' ), $file_or_url );
// translators: %s: File path or url to font collection JSON file.
$message = __( 'Font collection JSON file is invalid or does not exist.', 'gutenberg' );
_doing_it_wrong( __METHOD__, $message, '6.5.0' );
return new WP_Error( 'font_collection_json_missing', $message );
}

return $url ? self::load_from_url( $url ) : self::load_from_file( $file );
return $url ? $this->load_from_url( $url ) : $this->load_from_file( $file );
}

/**
* Loads the font collection data from a json file path.
* Loads the font collection data from a JSON file path.
*
* @since 6.5.0
*
* @param string $file File path to a json file containing the font collection data.
* @param string $file File path to a JSON file containing the font collection data.
* @return array|WP_Error An array containing the font collection data on success,
* else an instance of WP_Error on failure.
*/
private static function load_from_file( $file ) {
if ( array_key_exists( $file, static::$collection_json_cache ) ) {
return static::$collection_json_cache[ $file ];
}

private function load_from_file( $file ) {
$data = wp_json_file_decode( $file, array( 'associative' => true ) );
if ( empty( $data ) ) {
return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.', 'gutenberg' ) );
}

if ( empty( $data['slug'] ) ) {
// translators: %s: Font collection JSON URL.
$message = sprintf( __( 'Font collection JSON file "%s" requires a slug.', 'gutenberg' ), $file );
_doing_it_wrong( __METHOD__, $message, '6.5.0' );
return new WP_Error( 'font_collection_invalid_json', $message );
}

static::$collection_json_cache[ $file ] = $data;

return $data;
}

/**
* Loads the font collection data from a json file url.
* Loads the font collection data from a JSON file url.
*
* @since 6.5.0
*
* @param string $url Url to a json file containing the font collection data.
* @param string $url Url to a JSON file containing the font collection data.
* @return array|WP_Error An array containing the font collection data on success,
* else an instance of WP_Error on failure.
*/
private static function load_from_url( $url ) {
if ( array_key_exists( $url, static::$collection_json_cache ) ) {
return static::$collection_json_cache[ $url ];
}

private function load_from_url( $url ) {
// Limit key to 167 characters to avoid failure in the case of a long url.
$transient_key = substr( 'wp_font_collection_url_' . $url, 0, 167 );
$data = get_site_transient( $transient_key );
Expand All @@ -198,18 +182,40 @@ private static function load_from_url( $url ) {
return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection data from the REST response JSON.', 'gutenberg' ) );
}

if ( empty( $data['slug'] ) ) {
// translators: %s: Font collection JSON URL.
$message = sprintf( __( 'Font collection JSON file "%s" requires a slug.', 'gutenberg' ), $url );
_doing_it_wrong( __METHOD__, $message, '6.5.0' );
return new WP_Error( 'font_collection_invalid_json', $message );
// Make sure the data is valid before caching it.
$data = $this->validate_data( $data );
if ( is_wp_error( $data ) ) {
return $data;
}

set_site_transient( $transient_key, $data, DAY_IN_SECONDS );
}

static::$collection_json_cache[ $url ] = $data;
return $data;
}

/**
* Validates the font collection configuration.
*
* @since 6.5.0
*
* @param array $data Font collection configuration.
* @return array|WP_Error Array of data if valid, otherwise a WP_Error instance.
*/
private function validate_data( $data ) {
$required_properties = array( 'name', 'font_families' );
foreach ( $required_properties as $property ) {
if ( empty( $data[ $property ] ) ) {
$message = sprintf(
// translators: 1: Font collection slug, 2: Missing property name.
__( 'Font collection "%1$s" has missing or empty property: "%2$s."', 'gutenberg' ),
$this->slug,
$property
);
_doing_it_wrong( __METHOD__, $message, '6.5.0' );
return new WP_Error( 'font_collection_missing_property', $message );
}
}
return $data;
}
}
Expand Down
28 changes: 6 additions & 22 deletions lib/compat/wordpress-6.5/fonts/class-wp-font-library.php
Expand Up @@ -55,13 +55,14 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio
*
* @since 6.5.0
*
* @param string $slug Font collection slug.
* @param array $args Font collection config options.
* See {@see wp_register_font_collection()} for the supported fields.
* @param string $slug Font collection slug.
* @param array $data_or_file Font collection data array or a file path or url to a JSON file
* containing the font collection.
* See {@see wp_register_font_collection()} for the supported fields.
* @return WP_Font_Collection|WP_Error A font collection if registration was successful, else WP_Error.
*/
public static function register_font_collection( $slug, $args = array() ) {
$new_collection = new WP_Font_Collection( $slug, $args );
public static function register_font_collection( $slug, $data_or_file ) {
$new_collection = new WP_Font_Collection( $slug, $data_or_file );

if ( self::is_collection_registered( $new_collection->slug ) ) {
$error_message = sprintf(
Expand All @@ -80,23 +81,6 @@ public static function register_font_collection( $slug, $args = array() ) {
return $new_collection;
}

/**
* Register a new font collection from a json file.
*
* @since 6.5.0
*
* @param string $file_or_url File path or URL to a JSON file containing the font collection data.
* @return WP_Font_Collection|WP_Error A font collection if registration was successful, else WP_Error.
*/
public static function register_font_collection_from_json( $file_or_url ) {
$args = WP_Font_Collection::load_from_json( $file_or_url );
if ( is_wp_error( $args ) ) {
return $args;
}

return self::register_font_collection( $args['slug'], $args );
}

/**
* Unregisters a previously registered font collection.
*
Expand Down
Expand Up @@ -94,8 +94,10 @@ public function get_items( $request ) {
$items = array();
foreach ( $collections_page as $collection ) {
$item = $this->prepare_item_for_response( $collection, $request );

// If there's an error loading a collection, skip it and continue loading valid collections.
if ( is_wp_error( $item ) ) {
return $item;
continue;
}
$item = $this->prepare_response_for_collection( $item );
$items[] = $item;
Expand Down Expand Up @@ -170,10 +172,23 @@ public function prepare_item_for_response( $collection, $request ) {
$fields = $this->get_fields_for_response( $request );
$item = array();

$config_fields = array( 'slug', 'name', 'description', 'font_families', 'categories' );
foreach ( $config_fields as $field ) {
if ( in_array( $field, $fields, true ) ) {
$item[ $field ] = $collection->$field;
if ( rest_is_field_included( 'slug', $fields ) ) {
$item['slug'] = $collection->slug;
}

// If any data fields are requested, get the collection data.
$data_fields = array( 'name', 'description', 'font_families', 'categories' );
if ( ! empty( array_intersect( $fields, $data_fields ) ) ) {
$collection_data = $collection->get_data();
if ( is_wp_error( $collection_data ) ) {
$collection_data->add_data( array( 'status' => 500 ) );
return $collection_data;
}

foreach ( $data_fields as $field ) {
if ( rest_is_field_included( $field, $fields ) ) {
$item[ $field ] = $collection_data[ $field ];
}
}
}

Expand Down
Expand Up @@ -151,7 +151,7 @@ public function get_item_permissions_check( $request ) {
*
* @param string $value Encoded JSON string of font face settings.
* @param WP_REST_Request $request Request object.
* @return false|WP_Error True if the settings are valid, otherwise a WP_Error object.
* @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
*/
public function validate_create_font_face_settings( $value, $request ) {
$settings = json_decode( $value, true );
Expand Down
Expand Up @@ -77,7 +77,7 @@ public function get_item_permissions_check( $request ) {
*
* @param string $value Encoded JSON string of font family settings.
* @param WP_REST_Request $request Request object.
* @return false|WP_Error True if the settings are valid, otherwise a WP_Error object.
* @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
*/
public function validate_font_family_settings( $value, $request ) {
$settings = json_decode( $value, true );
Expand Down

0 comments on commit 0ec7554

Please sign in to comment.