diff --git a/src/wp-includes/fonts.php b/src/wp-includes/fonts.php index dfd0b45857..9940761e73 100644 --- a/src/wp-includes/fonts.php +++ b/src/wp-includes/fonts.php @@ -53,28 +53,30 @@ function wp_print_font_faces( $fonts = array() ) { } /** - * Registers a new Font Collection in the Font Library. + * Registers a new font collection in the font library. + * + * See {@link https://schemas.wp.org/trunk/font-collection.json} for the schema + * the font collection data must adhere to. * * @since 6.5.0 * - * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, + * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, * and underscores. See sanitize_title(). - * @param array|string $data_or_file { - * Font collection data array or a path/URL to a JSON file containing the font collection. + * @param array $args { + * Font collection data. * - * @link https://schemas.wp.org/trunk/font-collection.json - * - * @type string $name Required. Name of the font collection shown in the Font Library. - * @type string $description Optional. A short descriptive summary of the font collection. Default empty. - * @type array $font_families Required. Array of font family definitions that are in the collection. - * @type array $categories Optional. Array of categories, each with a name and slug, that are used by the - * fonts in the collection. Default empty. + * @type string $name Required. Name of the font collection shown in the Font Library. + * @type string $description Optional. A short descriptive summary of the font collection. Default empty. + * @type array|string $font_families Required. Array of font family definitions that are in the collection, + * or a string containing the path or URL to a JSON file containing the font collection. + * @type array $categories Optional. Array of categories, each with a name and slug, that are used by the + * fonts in the collection. Default empty. * } * @return WP_Font_Collection|WP_Error A font collection if it was registered * successfully, or WP_Error object on failure. */ -function wp_register_font_collection( $slug, $data_or_file ) { - return WP_Font_Library::get_instance()->register_font_collection( $slug, $data_or_file ); +function wp_register_font_collection( string $slug, array $args ) { + return WP_Font_Library::get_instance()->register_font_collection( $slug, $args ); } /** @@ -85,7 +87,7 @@ function wp_register_font_collection( $slug, $data_or_file ) { * @param string $slug Font collection slug. * @return bool True if the font collection was unregistered successfully, else false. */ -function wp_unregister_font_collection( $slug ) { +function wp_unregister_font_collection( string $slug ) { return WP_Font_Library::get_instance()->unregister_font_collection( $slug ); } @@ -196,5 +198,34 @@ function _wp_before_delete_font_face( $post_id, $post ) { * @since 6.5.0 */ function _wp_register_default_font_collections() { - wp_register_font_collection( 'google-fonts', 'https://s.w.org/images/fonts/17.7/collections/google-fonts-with-preview.json' ); + wp_register_font_collection( + 'google-fonts', + array( + 'name' => _x( 'Google Fonts', 'font collection name' ), + 'description' => __( 'Install from Google Fonts. Fonts are copied to and served from your site.' ), + 'font_families' => 'https://s.w.org/images/fonts/17.7/collections/google-fonts-with-preview.json', + 'categories' => array( + array( + 'name' => _x( 'Sans Serif', 'font category' ), + 'slug' => 'sans-serif', + ), + array( + 'name' => _x( 'Display', 'font category' ), + 'slug' => 'display', + ), + array( + 'name' => _x( 'Serif', 'font category' ), + 'slug' => 'serif', + ), + array( + 'name' => _x( 'Handwriting', 'font category' ), + 'slug' => 'handwriting', + ), + array( + 'name' => _x( 'Monospace', 'font category' ), + 'slug' => 'monospace', + ), + ), + ) + ); } diff --git a/src/wp-includes/fonts/class-wp-font-collection.php b/src/wp-includes/fonts/class-wp-font-collection.php index 889d1af998..b915e3ea58 100644 --- a/src/wp-includes/fonts/class-wp-font-collection.php +++ b/src/wp-includes/fonts/class-wp-font-collection.php @@ -46,12 +46,11 @@ final class WP_Font_Collection { * * @since 6.5.0 * - * @param string $slug Font collection slug. - * @param array|string $data_or_file Font collection data array or a path/URL to a JSON file - * containing the font collection. - * See {@see wp_register_font_collection()} for the supported fields. + * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, + * and underscores. See sanitize_title(). + * @param array $args Font collection data. See wp_register_font_collection() for information on accepted arguments. */ - public function __construct( $slug, $data_or_file ) { + public function __construct( string $slug, array $args ) { $this->slug = sanitize_title( $slug ); if ( $this->slug !== $slug ) { _doing_it_wrong( @@ -62,12 +61,17 @@ final class WP_Font_Collection { ); } - if ( is_array( $data_or_file ) ) { - $this->data = $this->sanitize_and_validate_data( $data_or_file ); - } else { + $required_properties = array( 'name', 'font_families' ); + + if ( isset( $args['font_families'] ) && is_string( $args['font_families'] ) ) { // JSON data is lazy loaded by ::get_data(). - $this->src = $data_or_file; + $this->src = $args['font_families']; + unset( $args['font_families'] ); + + $required_properties = array( 'name' ); } + + $this->data = $this->sanitize_and_validate_data( $args, $required_properties ); } /** @@ -78,8 +82,12 @@ final class WP_Font_Collection { * @return array|WP_Error An array containing the font collection data, or a WP_Error on failure. */ public function get_data() { + if ( is_wp_error( $this->data ) ) { + return $this->data; + } + // If the collection uses JSON data, load it and cache the data/error. - if ( $this->src && empty( $this->data ) ) { + if ( isset( $this->src ) ) { $this->data = $this->load_from_json( $this->src ); } @@ -116,7 +124,26 @@ final class WP_Font_Collection { return new WP_Error( 'font_collection_json_missing', $message ); } - return $url ? $this->load_from_url( $url ) : $this->load_from_file( $file ); + $data = $url ? $this->load_from_url( $url ) : $this->load_from_file( $file ); + + if ( is_wp_error( $data ) ) { + return $data; + } + + $data = array( + 'name' => $this->data['name'], + 'font_families' => $data['font_families'], + ); + + if ( isset( $this->data['description'] ) ) { + $data['description'] = $this->data['description']; + } + + if ( isset( $this->data['categories'] ) ) { + $data['categories'] = $this->data['categories']; + } + + return $data; } /** @@ -134,7 +161,7 @@ final class WP_Font_Collection { return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.' ) ); } - return $this->sanitize_and_validate_data( $data ); + return $this->sanitize_and_validate_data( $data, array( 'font_families' ) ); } /** @@ -154,8 +181,14 @@ final class WP_Font_Collection { if ( false === $data ) { $response = wp_safe_remote_get( $url ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { - // translators: %s: Font collection URL. - return new WP_Error( 'font_collection_request_error', sprintf( __( 'Error fetching the font collection data from "%s".' ), $url ) ); + return new WP_Error( + 'font_collection_request_error', + sprintf( + // translators: %s: Font collection URL. + __( 'Error fetching the font collection data from "%s".' ), + $url + ) + ); } $data = json_decode( wp_remote_retrieve_body( $response ), true ); @@ -164,7 +197,7 @@ final class WP_Font_Collection { } // Make sure the data is valid before storing it in a transient. - $data = $this->sanitize_and_validate_data( $data ); + $data = $this->sanitize_and_validate_data( $data, array( 'font_families' ) ); if ( is_wp_error( $data ) ) { return $data; } @@ -180,14 +213,14 @@ final class WP_Font_Collection { * * @since 6.5.0 * - * @param array $data Font collection data to sanitize and validate. + * @param array $data Font collection data to sanitize and validate. + * @param array $required_properties Required properties that must exist in the passed data. * @return array|WP_Error Sanitized data if valid, otherwise a WP_Error instance. */ - private function sanitize_and_validate_data( $data ) { + private function sanitize_and_validate_data( $data, $required_properties = array() ) { $schema = self::get_sanitization_schema(); $data = WP_Font_Utils::sanitize_from_schema( $data, $schema ); - $required_properties = array( 'name', 'font_families' ); foreach ( $required_properties as $property ) { if ( empty( $data[ $property ] ) ) { $message = sprintf( diff --git a/src/wp-includes/fonts/class-wp-font-library.php b/src/wp-includes/fonts/class-wp-font-library.php index f9ca903271..3069ecb582 100644 --- a/src/wp-includes/fonts/class-wp-font-library.php +++ b/src/wp-includes/fonts/class-wp-font-library.php @@ -37,15 +37,14 @@ class WP_Font_Library { * * @since 6.5.0 * - * @param string $slug Font collection slug. - * @param array $data_or_file Font collection data array or a path/URL to a JSON file - * containing the font collection. - * See {@see wp_register_font_collection()} for the supported fields. + * @param string $slug Font collection slug. May only contain alphanumeric characters, dashes, + * and underscores. See sanitize_title(). + * @param array $args Font collection data. See wp_register_font_collection() for information on accepted arguments. * @return WP_Font_Collection|WP_Error A font collection if it was registered successfully, * or WP_Error object on failure. */ - public function register_font_collection( $slug, $data_or_file ) { - $new_collection = new WP_Font_Collection( $slug, $data_or_file ); + public function register_font_collection( string $slug, array $args ) { + $new_collection = new WP_Font_Collection( $slug, $args ); if ( $this->is_collection_registered( $new_collection->slug ) ) { $error_message = sprintf( @@ -72,7 +71,7 @@ class WP_Font_Library { * @param string $slug Font collection slug. * @return bool True if the font collection was unregistered successfully and false otherwise. */ - public function unregister_font_collection( $slug ) { + public function unregister_font_collection( string $slug ) { if ( ! $this->is_collection_registered( $slug ) ) { _doing_it_wrong( __METHOD__, @@ -94,7 +93,7 @@ class WP_Font_Library { * @param string $slug Font collection slug. * @return bool True if the font collection is registered and false otherwise. */ - private function is_collection_registered( $slug ) { + private function is_collection_registered( string $slug ) { return array_key_exists( $slug, $this->collections ); } @@ -117,7 +116,7 @@ class WP_Font_Library { * @param string $slug Font collection slug. * @return WP_Font_Collection|null Font collection object, or null if the font collection doesn't exist. */ - public function get_font_collection( $slug ) { + public function get_font_collection( string $slug ) { if ( $this->is_collection_registered( $slug ) ) { return $this->collections[ $slug ]; } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php index 3ede1dacdc..b409af4fc2 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php @@ -104,7 +104,7 @@ class WP_REST_Font_Collections_Controller extends WP_REST_Controller { $response = rest_ensure_response( $items ); $response->header( 'X-WP-Total', (int) $total_items ); - $response->header( 'X-WP-TotalPages', (int) $max_pages ); + $response->header( 'X-WP-TotalPages', $max_pages ); $request_params = $request->get_query_params(); $collection_url = rest_url( $this->namespace . '/' . $this->rest_base ); diff --git a/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php b/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php index 0921379b80..97ea664d48 100644 --- a/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php +++ b/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php @@ -40,11 +40,17 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { $mock_file = wp_tempnam( 'my-collection-data-' ); file_put_contents( $mock_file, wp_json_encode( $config ) ); - $collection = new WP_Font_Collection( $slug, $mock_file ); + $collection = new WP_Font_Collection( + $slug, + array_merge( + $config, + array( 'font_families' => $mock_file ) + ) + ); $data = $collection->get_data(); $this->assertSame( $slug, $collection->slug, 'The slug should match.' ); - $this->assertSame( $expected_data, $data, 'The collection data should match.' ); + $this->assertEqualSetsWithIndex( $expected_data, $data, 'The collection data should match.' ); } /** @@ -58,13 +64,21 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { add_filter( 'pre_http_request', array( $this, 'mock_request' ), 10, 3 ); self::$mock_collection_data = $config; - $collection = new WP_Font_Collection( $slug, 'https://example.com/fonts/mock-font-collection.json' ); + $collection = new WP_Font_Collection( + $slug, + array_merge( + $config, + array( + 'font_families' => 'https://example.com/fonts/mock-font-collection.json', + ) + ) + ); $data = $collection->get_data(); remove_filter( 'pre_http_request', array( $this, 'mock_request' ) ); $this->assertSame( $slug, $collection->slug, 'The slug should match.' ); - $this->assertSame( $expected_data, $data, 'The collection data should match.' ); + $this->assertEqualSetsWithIndex( $expected_data, $data, 'The collection data should match.' ); } /** @@ -74,7 +88,6 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { */ public function data_create_font_collection() { return array( - 'font collection with required data' => array( 'slug' => 'my-collection', 'config' => array( @@ -185,7 +198,6 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { ), ), ), - ); } @@ -202,8 +214,8 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { $this->assertWPError( $data, 'Error is not returned when property is missing or invalid.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_missing_property', + $data->get_error_code(), 'Incorrect error code when property is missing or invalid.' ); } @@ -243,13 +255,19 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { public function test_should_error_with_invalid_json_file_path() { $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); - $collection = new WP_Font_Collection( 'my-collection', 'non-existing.json' ); + $collection = new WP_Font_Collection( + 'my-collection', + array( + 'name' => 'My collection', + 'font_families' => 'non-existing.json', + ) + ); $data = $collection->get_data(); $this->assertWPError( $data, 'Error is not returned when invalid file path is provided.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_json_missing', + $data->get_error_code(), 'Incorrect error code when invalid file path is provided.' ); } @@ -258,15 +276,21 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { $mock_file = wp_tempnam( 'my-collection-data-' ); file_put_contents( $mock_file, 'invalid-json' ); - $collection = new WP_Font_Collection( 'my-collection', $mock_file ); + $collection = new WP_Font_Collection( + 'my-collection', + array( + 'name' => 'Invalid collection', + 'font_families' => $mock_file, + ) + ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Testing error response returned by `load_from_json`, not the underlying error from `wp_json_file_decode`. $data = @$collection->get_data(); $this->assertWPError( $data, 'Error is not returned with invalid json file contents.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_decode_error', + $data->get_error_code(), 'Incorrect error code with invalid json file contents.' ); } @@ -274,13 +298,19 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { public function test_should_error_with_invalid_url() { $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); - $collection = new WP_Font_Collection( 'my-collection', 'not-a-url' ); + $collection = new WP_Font_Collection( + 'my-collection', + array( + 'name' => 'Invalid collection', + 'font_families' => 'not-a-url', + ) + ); $data = $collection->get_data(); $this->assertWPError( $data, 'Error is not returned when invalid url is provided.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_json_missing', + $data->get_error_code(), 'Incorrect error code when invalid url is provided.' ); } @@ -288,31 +318,43 @@ class Tests_Fonts_WpFontCollection_GetData extends WP_UnitTestCase { public function test_should_error_with_unsuccessful_response_status() { add_filter( 'pre_http_request', array( $this, 'mock_request_unsuccessful_response' ), 10, 3 ); - $collection = new WP_Font_Collection( 'my-collection', 'https://example.com/fonts/missing-collection.json' ); + $collection = new WP_Font_Collection( + 'my-collection', + array( + 'name' => 'Missing collection', + 'font_families' => 'https://example.com/fonts/missing-collection.json', + ) + ); $data = $collection->get_data(); remove_filter( 'pre_http_request', array( $this, 'mock_request_unsuccessful_response' ) ); $this->assertWPError( $data, 'Error is not returned when response is unsuccessful.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_request_error', - 'Incorrect error code when response is unsuccussful.' + $data->get_error_code(), + 'Incorrect error code when response is unsuccessful.' ); } public function test_should_error_with_invalid_json_from_url() { add_filter( 'pre_http_request', array( $this, 'mock_request_invalid_json' ), 10, 3 ); - $collection = new WP_Font_Collection( 'my-collection', 'https://example.com/fonts/invalid-collection.json' ); + $collection = new WP_Font_Collection( + 'my-collection', + array( + 'name' => 'Invalid collection', + 'font_families' => 'https://example.com/fonts/invalid-collection.json', + ) + ); $data = $collection->get_data(); remove_filter( 'pre_http_request', array( $this, 'mock_request_invalid_json' ) ); $this->assertWPError( $data, 'Error is not returned when response is invalid json.' ); $this->assertSame( - $data->get_error_code(), 'font_collection_decode_error', + $data->get_error_code(), 'Incorrect error code when response is invalid json.' ); } diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php index 0f7fe8f9c6..f5af774d0d 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php @@ -38,7 +38,13 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller $mock_file = wp_tempnam( 'my-collection-data-' ); file_put_contents( $mock_file, '{"name": "Mock Collection", "font_families": [ "mock" ], "categories": [ "mock" ] }' ); - wp_register_font_collection( 'mock-col-slug', $mock_file ); + wp_register_font_collection( + 'mock-col-slug', + array( + 'name' => 'My collection', + 'font_families' => $mock_file, + ) + ); } public static function wpTearDownAfterClass() { @@ -78,7 +84,13 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller $this->setExpectedIncorrectUsage( 'WP_Font_Collection::load_from_json' ); wp_set_current_user( self::$admin_id ); - wp_register_font_collection( 'invalid-collection', 'invalid-collection-file' ); + wp_register_font_collection( + 'invalid-collection', + array( + 'name' => 'My collection', + 'font_families' => 'invalid-collection-file', + ) + ); $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections' ); $response = rest_get_server()->dispatch( $request ); @@ -132,14 +144,20 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller wp_set_current_user( self::$admin_id ); $slug = 'invalid-collection'; - wp_register_font_collection( $slug, 'invalid-collection-file' ); + wp_register_font_collection( + $slug, + array( + 'name' => 'My collection', + 'font_families' => 'invalid-collection-file', + ) + ); $request = new WP_REST_Request( 'GET', '/wp/v2/font-collections/' . $slug ); $response = rest_get_server()->dispatch( $request ); wp_unregister_font_collection( $slug ); - $this->assertErrorResponse( 'font_collection_json_missing', $response, 500, 'When the collection json file is invalid, the response should return an error for "font_collection_json_missing" with 500 status.' ); + $this->assertErrorResponse( 'font_collection_json_missing', $response, 500 ); } /** @@ -150,11 +168,11 @@ class Tests_REST_WpRestFontCollectionsController extends WP_Test_REST_Controller wp_set_current_user( 0 ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_read', $response, 401, 'The response status should be 401 for non-authenticated users.' ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); wp_set_current_user( self::$editor_id ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_cannot_read', $response, 403, 'The response status should be 403 for users without the right permissions.' ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); } /**