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 Library: font collection refactor to use the new schema #57884

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6ca902d
google fonts collection data provisional url
matiasbenedetto Jan 16, 2024
9e3c3f8
rename controller methods
matiasbenedetto Jan 16, 2024
78c6941
fix get_items parameters
matiasbenedetto Jan 16, 2024
399cf88
fix endpoint return
matiasbenedetto Jan 16, 2024
b600531
rafactor font collection class
matiasbenedetto Jan 17, 2024
adaebd6
fix tests for the refactored class
matiasbenedetto Jan 17, 2024
2e57945
refactor font collections rest controller
matiasbenedetto Jan 17, 2024
cc7067f
update font collection tests
matiasbenedetto Jan 17, 2024
8fe9713
update the frontend to use the new endpoint data schema
matiasbenedetto Jan 17, 2024
9f4cfe2
format php
matiasbenedetto Jan 17, 2024
8dcdcf2
Merge branch 'try/font-library-refactor' into try/font-collection-new…
matiasbenedetto Jan 17, 2024
823a580
adding linter line ignore rul
matiasbenedetto Jan 17, 2024
6dff6f1
replacing throwing an exception by calling doing_it_wrong
matiasbenedetto Jan 18, 2024
778d27b
add translation marks
matiasbenedetto Jan 18, 2024
3c011c8
user ternary operator
matiasbenedetto Jan 18, 2024
e698680
correct translation formatting and comments
matiasbenedetto Jan 18, 2024
3a5b423
renaming function
matiasbenedetto Jan 19, 2024
6f9463b
renaming tests
matiasbenedetto Jan 19, 2024
9d1d78b
improve url matching
matiasbenedetto Jan 19, 2024
ed27754
return error without rest_ensure_response
matiasbenedetto Jan 19, 2024
ae2f519
Merge branch 'try/font-collection-new-schema' of github.com:WordPress…
matiasbenedetto Jan 19, 2024
21b21d5
fix contradictory if condition
matiasbenedetto Jan 19, 2024
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
183 changes: 140 additions & 43 deletions lib/experimental/fonts/font-library/class-wp-font-collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,128 @@
class WP_Font_Collection {

/**
* Font collection configuration.
* The unique slug for the font collection.
*
* @since 6.5.0
*
* @var string
*/
private $slug;
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved

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

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

/**
* Source of the font collection.
*
* @since 6.5.0
*
* @var string
*/
private $src;

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

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


/**
* WP_Font_Collection constructor.
*
* @since 6.5.0
*
* @param array $config Font collection config options.
* See {@see wp_register_font_collection()} for the supported fields.
* @throws Exception If the required parameters are missing.
* @param array $config Font collection config options. {
* @type string $slug The font collection's unique slug.
* @type string $name The font collection's name.
* @type string $description The font collection's description.
* @type string $src The font collection's source.
* @type array $font_families An array of font families in the font collection.
* @type array $categories The font collection's categories.
* }
*/
public function __construct( $config ) {
if ( empty( $config ) || ! is_array( $config ) ) {
throw new Exception( 'Font Collection config options is required as a non-empty array.' );
}
$this->is_config_valid( $config );

$this->slug = isset( $config['slug'] ) ? $config['slug'] : '';
$this->name = isset( $config['name'] ) ? $config['name'] : '';
$this->description = isset( $config['description'] ) ? $config['description'] : '';
$this->src = isset( $config['src'] ) ? $config['src'] : '';
$this->font_families = isset( $config['font_families'] ) ? $config['font_families'] : array();
$this->categories = isset( $config['categories'] ) ? $config['categories'] : array();
}

if ( empty( $config['slug'] ) || ! is_string( $config['slug'] ) ) {
throw new Exception( 'Font Collection config slug is required as a non-empty string.' );
/**
* Checks if the font collection config is valid.
*
* @since 6.5.0
*
* @param array $config Font collection config options. {
* @type string $slug The font collection's unique slug.
* @type string $name The font collection's name.
* @type string $description The font collection's description.
* @type string $src The font collection's source.
* @type array $font_families An array of font families in the font collection.
* @type array $categories The font collection's categories.
* }
* @return bool True if the font collection config is valid and false otherwise.
*/
public static function is_config_valid( $config ) {
if ( empty( $config ) || ! is_array( $config ) ) {
_doing_it_wrong( __METHOD__, __( 'Font Collection config options are required as a non-empty array.', 'gutenberg' ), '6.5.0' );
return false;
}

if ( empty( $config['name'] ) || ! is_string( $config['name'] ) ) {
throw new Exception( 'Font Collection config name is required as a non-empty string.' );
$required_keys = array( 'slug', 'name' );
foreach ( $required_keys as $key ) {
if ( empty( $config[ $key ] ) ) {
_doing_it_wrong(
__METHOD__,
// translators: %s: Font collection config key.
sprintf( __( 'Font Collection config %s is required as a non-empty string.', 'gutenberg' ), $key ),
'6.5.0'
);
return false;
}
}

if ( ( empty( $config['src'] ) || ! is_string( $config['src'] ) ) && ( empty( $config['data'] ) ) ) {
throw new Exception( 'Font Collection config "src" option OR "data" option is required.' );
if (
( empty( $config['src'] ) && empty( $config['font_families'] ) ) ||
( ! empty( $config['src'] ) && ! empty( $config['font_families'] ) )
) {
_doing_it_wrong( __METHOD__, __( 'Font Collection config "src" option OR "font_families" option are required.', 'gutenberg' ), '6.5.0' );
return false;
}

$this->config = $config;
return true;
}

/**
Expand All @@ -73,56 +160,59 @@ public function __construct( $config ) {
*/
public function get_config() {
return array(
'slug' => $this->config['slug'],
'name' => $this->config['name'],
'description' => $this->config['description'] ?? '',
'slug' => $this->slug,
'name' => $this->name,
'description' => $this->description,
);
}

/**
* Gets the font collection config and data.
* Gets the font collection content.
*
* This function returns an array containing the font collection's unique ID,
* name, and its data as a PHP array.
* Load the font collection data from the src if it is not already loaded.
*
* @since 6.5.0
*
* @return array {
* An array of font collection config and data.
* @return array|WP_Error {
* An array of font collection contents.
*
* @type string $slug The font collection's unique ID.
* @type string $name The font collection's name.
* @type string $description The font collection's description.
* @type array $data The font collection's data as a PHP array.
* @type array $font_families The font collection's font families.
* @type string $categories The font collection's categories.
* }
*
* A WP_Error object if there was an error loading the font collection data.
*/
public function get_config_and_data() {
$config_and_data = $this->get_config();
$config_and_data['data'] = $this->load_data();
return $config_and_data;
public function get_content() {
// If the font families are not loaded, and the src is not empty, load the data from the src.
if ( empty( $this->font_families ) && ! empty( $this->src ) ) {
$data = $this->load_contents_from_src();
if ( is_wp_error( $data ) ) {
return $data;
}
}

return array(
'font_families' => $this->font_families,
'categories' => $this->categories,
);
}

/**
* Loads the font collection data.
* Loads the font collection data from the src.
*
* @since 6.5.0
*
* @return array|WP_Error An array containing the list of font families in font-collection.json format on success,
* else an instance of WP_Error on failure.
*/
public function load_data() {

if ( ! empty( $this->config['data'] ) ) {
return $this->config['data'];
}

private function load_contents_from_src() {
// If the src is a URL, fetch the data from the URL.
if ( str_contains( $this->config['src'], 'http' ) && str_contains( $this->config['src'], '://' ) ) {
if ( ! wp_http_validate_url( $this->config['src'] ) ) {
if ( str_contains( $this->src, 'http' ) && str_contains( $this->src, '://' ) ) {
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
if ( ! wp_http_validate_url( $this->src ) ) {
return new WP_Error( 'font_collection_read_error', __( 'Invalid URL for Font Collection data.', 'gutenberg' ) );
}

$response = wp_remote_get( $this->config['src'] );
$response = wp_remote_get( $this->src );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return new WP_Error( 'font_collection_read_error', __( 'Error fetching the Font Collection data from a URL.', 'gutenberg' ) );
}
Expand All @@ -133,15 +223,22 @@ public function load_data() {
}
// If the src is a file path, read the data from the file.
} else {
if ( ! file_exists( $this->config['src'] ) ) {
if ( ! file_exists( $this->src ) ) {
return new WP_Error( 'font_collection_read_error', __( 'Font Collection data JSON file does not exist.', 'gutenberg' ) );
}
$data = wp_json_file_decode( $this->config['src'], array( 'associative' => true ) );
$data = wp_json_file_decode( $this->src, array( 'associative' => true ) );
if ( empty( $data ) ) {
return new WP_Error( 'font_collection_read_error', __( 'Error reading the Font Collection data JSON file contents.', 'gutenberg' ) );
}
}

if ( empty( $data['font_families'] ) ) {
return new WP_Error( 'font_collection_contents_error', __( 'Font Collection data JSON file does not contain font families.', 'gutenberg' ) );
}

$this->font_families = $data['font_families'];
$this->categories = $data['categories'] ?? array();

return $data;
}
}
12 changes: 9 additions & 3 deletions lib/experimental/fonts/font-library/class-wp-font-library.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,17 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio
* @return WP_Font_Collection|WP_Error A font collection is it was registered successfully and a WP_Error otherwise.
*/
public static function register_font_collection( $config ) {
Copy link
Contributor

@creativecoder creativecoder Jan 19, 2024

Choose a reason for hiding this comment

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

For both register_font_collection and WP_Font_Collection::__construct, I think using $slug as the first arg, and then an array of config args second, would be closer to how other register_ functions tend to work (register_post_type, for example).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented in this PR: #58269

if ( ! WP_Font_Collection::is_config_valid( $config ) ) {
$error_message = __( 'Font collection config is invalid.', 'gutenberg' );
return new WP_Error( 'font_collection_registration_error', $error_message );
}

$new_collection = new WP_Font_Collection( $config );
if ( self::is_collection_registered( $config['slug'] ) ) {

if ( self::is_collection_registered( $new_collection->get_config()['slug'] ) ) {
$error_message = sprintf(
/* translators: %s: Font collection slug. */
__( 'Font collection with slug: "%s" is already registered.', 'default' ),
__( 'Font collection with slug: "%s" is already registered.', 'gutenberg' ),
$config['slug']
);
_doing_it_wrong(
Expand All @@ -76,7 +82,7 @@ public static function register_font_collection( $config ) {
);
return new WP_Error( 'font_collection_registration_error', $error_message );
}
self::$collections[ $config['slug'] ] = $new_collection;
self::$collections[ $new_collection->get_config()['slug'] ] = $new_collection;
return $new_collection;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public function register_routes() {
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_font_collections' ),
'permission_callback' => array( $this, 'update_font_library_permissions_check' ),
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_fonts_collection_permissions_check' ),
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
),
)
);
Expand All @@ -54,8 +54,8 @@ public function register_routes() {
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_font_collection' ),
'permission_callback' => array( $this, 'update_font_library_permissions_check' ),
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_fonts_collection_permissions_check' ),
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
),
)
);
Expand All @@ -69,24 +69,27 @@ public function register_routes() {
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_font_collection( $request ) {
public function get_item( $request ) {
$slug = $request->get_param( 'slug' );
$collection = WP_Font_Library::get_font_collection( $slug );

// If the collection doesn't exist returns a 404.
if ( is_wp_error( $collection ) ) {
$collection->add_data( array( 'status' => 404 ) );
return $collection;
return rest_ensure_response( $collection );
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
}
$config_and_data = $collection->get_config_and_data();
$collection_data = $config_and_data['data'];

$config = $collection->get_config();
$contents = $collection->get_content();

// If there was an error getting the collection data, return the error.
if ( is_wp_error( $collection_data ) ) {
$collection_data->add_data( array( 'status' => 500 ) );
return $collection_data;
if ( is_wp_error( $contents ) ) {
$contents->add_data( array( 'status' => 500 ) );
return rest_ensure_response( $contents );
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
}

return new WP_REST_Response( $config_and_data );
$collection_data = array_merge( $config, $contents );
return rest_ensure_response( $collection_data );
}

/**
Expand All @@ -96,27 +99,27 @@ public function get_font_collection( $request ) {
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_font_collections() {
public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
$collections = array();
foreach ( WP_Font_Library::get_font_collections() as $collection ) {
$collections[] = $collection->get_config_and_data();
$collections[] = $collection->get_config();
}

return new WP_REST_Response( $collections, 200 );
return rest_ensure_response( $collections, 200 );
}

/**
* Checks whether the user has permissions to update the Font Library.
* Checks whether the user has permissions to use the Fonts Collections.
*
* @since 6.5.0
*
* @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
*/
public function update_font_library_permissions_check() {
public function get_fonts_collection_permissions_check() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_update_font_library',
__( 'Sorry, you are not allowed to update the Font Library on this site.', 'gutenberg' ),
'rest_cannot_read',
__( 'Sorry, you are not allowed to use the Font Library on this site.', 'gutenberg' ),
array(
'status' => rest_authorization_required_code(),
)
Expand Down
3 changes: 2 additions & 1 deletion lib/experimental/fonts/font-library/font-library.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ function wp_unregister_font_collection( $collection_id ) {
'slug' => 'default-font-collection',
'name' => 'Google Fonts',
'description' => __( 'Add from Google Fonts. Fonts are copied to and served from your site.', 'gutenberg' ),
'src' => 'https://s.w.org/images/fonts/16.7/collections/google-fonts-with-preview.json',
// TODO: This URL needs to be updated to the wporg hosted one prior to the Gutenberg 17.6 release.
'src' => 'https://raw.githubusercontent.com/WordPress/google-fonts-to-wordpress-collection/main/releases/gutenberg-17.6/google-fonts.json',
);

wp_register_font_collection( $default_font_collection );
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the font collection registered on every request? If the font collection is only needed in wp-admin and rest api requests, maybe there is a hook we can for collection registration.

Expand Down
Loading
Loading