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

Font Collections: lazy load json configuration for better performance #58530

Merged
merged 14 commits into from Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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