diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-font-collection.php b/lib/compat/wordpress-6.5/fonts/class-wp-font-collection.php index f628b90d31b00..9f5946fbe5dcc 100644 --- a/lib/compat/wordpress-6.5/fonts/class-wp-font-collection.php +++ b/lib/compat/wordpress-6.5/fonts/class-wp-font-collection.php @@ -31,7 +31,7 @@ final class WP_Font_Collection { * * @since 6.5.0 * - * @var array + * @var array|WP_Error|null */ private $data; @@ -40,58 +40,10 @@ final class WP_Font_Collection { * * @since 6.5.0 * - * @var string + * @var string|null */ private $src; - /** - * Font collection sanitization schema. - * - * @since 6.5.0 - * - * @var array - */ - const COLLECTION_SANITIZATION_SCHEMA = array( - 'name' => 'sanitize_text_field', - 'description' => 'sanitize_text_field', - 'font_families' => array( - array( - 'font_family_settings' => array( - 'name' => 'sanitize_text_field', - 'slug' => 'sanitize_title', - 'fontFamily' => 'sanitize_text_field', - 'preview' => 'sanitize_url', - 'fontFace' => array( - array( - 'fontFamily' => 'sanitize_text_field', - 'fontStyle' => 'sanitize_text_field', - 'fontWeight' => 'sanitize_text_field', - 'src' => 'WP_Font_Collection::sanitize_src_property', - 'preview' => 'sanitize_url', - 'fontDisplay' => 'sanitize_text_field', - 'fontStretch' => 'sanitize_text_field', - 'ascentOverride' => 'sanitize_text_field', - 'descentOverride' => 'sanitize_text_field', - 'fontVariant' => 'sanitize_text_field', - 'fontFeatureSettings' => 'sanitize_text_field', - 'fontVariationSettings' => 'sanitize_text_field', - 'lineGapOverride' => 'sanitize_text_field', - 'sizeAdjust' => 'sanitize_text_field', - 'unicodeRange' => 'sanitize_text_field', - ), - ), - ), - 'categories' => array( 'sanitize_title' ), - ), - ), - 'categories' => array( - array( - 'name' => 'sanitize_text_field', - 'slug' => 'sanitize_title', - ), - ), - ); - /** * WP_Font_Collection constructor. * @@ -110,10 +62,10 @@ final class WP_Font_Collection { 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; + $this->data = $this->sanitize_and_validate_data( $data_or_file ); } else { + // JSON data is lazy loaded by ::get_data(). $this->src = $data_or_file; } @@ -135,30 +87,25 @@ public function __construct( $slug, $data_or_file ) { * @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 the collection uses JSON data, load it and cache the data/error. if ( $this->src && empty( $this->data ) ) { - $data = $this->load_from_json( $this->src ); - if ( is_wp_error( $data ) ) { - return $data; - } - - $this->data = $data; + $this->data = $this->load_from_json( $this->src ); } - // Validate required properties are not empty. - $data = $this->validate_andd_sanitize( $this->data ); - if ( is_wp_error( $data ) ) { - return $data; + $data_or_error = $this->data; + + if ( ! is_wp_error( $data_or_error ) ) { + // Set defaults for optional properties. + $data_or_error = wp_parse_args( + $this->data, + array( + 'description' => '', + 'categories' => array(), + ) + ); } - // Set defaults for optional properties. - return wp_parse_args( - $data, - array( - 'description' => '', - 'categories' => array(), - ) - ); + return $data_or_error; } /** @@ -199,7 +146,7 @@ private function load_from_file( $file ) { return new WP_Error( 'font_collection_decode_error', __( 'Error decoding the font collection JSON file contents.', 'gutenberg' ) ); } - return $data; + return $this->sanitize_and_validate_data( $data ); } /** @@ -228,8 +175,8 @@ private 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' ) ); } - // Make sure the data is valid before caching it. - $data = $this->validate_andd_sanitize( $data ); + // Make sure the data is valid before storing it in a transient. + $data = $this->sanitize_and_validate_data( $data ); if ( is_wp_error( $data ) ) { return $data; } @@ -241,15 +188,18 @@ private function load_from_url( $url ) { } /** - * Validates the font collection configuration. + * Sanitizes and validates the font collection data. * * @since 6.5.0 * - * @param array $data Font collection configuration to validate. - * @return array|WP_Error Array of data if valid, otherwise a WP_Error instance. + * @param array $data Font collection data. + * + * @return array|WP_Error Sanitized data if valid, otherwise a WP_Error instance. */ - private function validate_andd_sanitize( $data ) { - $data = WP_Font_Utils::sanitize_from_schema( $data, self::COLLECTION_SANITIZATION_SCHEMA ); + private function sanitize_and_validate_data( $data ) { + $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 ] ) ) { @@ -268,26 +218,57 @@ private function validate_andd_sanitize( $data ) { } /** - * Sanitizes a src property. - * - * Font faces can have a src property consisting on a string or an array of strings. - * This method sanitizes the src property value. + * Retrieves the font collection sanitization schema. * * @since 6.5.0 * - * @param string|array $value src property value to sanitize. - * - * @return string|array Sanitized URL value. + * @return array Font collection sanitization schema. */ - public static function sanitize_src_property( $value ) { - if ( is_array( $value ) ) { - foreach ( $value as $key => $val ) { - $value[ $key ] = sanitize_url( $val ); - } - return $value; - } - - return sanitize_url( $value ); + private static function get_sanitization_schema() { + return array( + 'name' => 'sanitize_text_field', + 'description' => 'sanitize_text_field', + 'font_families' => array( + array( + 'font_family_settings' => array( + 'name' => 'sanitize_text_field', + 'slug' => 'sanitize_title', + 'fontFamily' => 'sanitize_text_field', + 'preview' => 'sanitize_url', + 'fontFace' => array( + array( + 'fontFamily' => 'sanitize_text_field', + 'fontStyle' => 'sanitize_text_field', + 'fontWeight' => 'sanitize_text_field', + 'src' => function ( $value ) { + return is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : sanitize_text_field( $value ); + }, + 'preview' => 'sanitize_url', + 'fontDisplay' => 'sanitize_text_field', + 'fontStretch' => 'sanitize_text_field', + 'ascentOverride' => 'sanitize_text_field', + 'descentOverride' => 'sanitize_text_field', + 'fontVariant' => 'sanitize_text_field', + 'fontFeatureSettings' => 'sanitize_text_field', + 'fontVariationSettings' => 'sanitize_text_field', + 'lineGapOverride' => 'sanitize_text_field', + 'sizeAdjust' => 'sanitize_text_field', + 'unicodeRange' => 'sanitize_text_field', + ), + ), + ), + 'categories' => array( 'sanitize_title' ), + ), + ), + 'categories' => array( + array( + 'name' => 'sanitize_text_field', + 'slug' => 'sanitize_title', + ), + ), + ); } } } diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php b/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php index 792a5aaa80eef..415e9e7f1f350 100644 --- a/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php +++ b/lib/compat/wordpress-6.5/fonts/class-wp-font-utils.php @@ -158,7 +158,7 @@ public static function sanitize_from_schema( $tree, $schema ) { } $is_value_array = is_array( $value ); - $is_schema_array = is_array( $schema[ $key ] ); + $is_schema_array = is_array( $schema[ $key ] ) && ! is_callable( $schema[ $key ] ); if ( $is_value_array && $is_schema_array ) { if ( wp_is_numeric_array( $value ) ) { @@ -193,6 +193,7 @@ public static function sanitize_from_schema( $tree, $schema ) { * Apply the sanitizer to the value. * * @since 6.5.0 + * * @param mixed $value The value to sanitize. * @param mixed $sanitizer The sanitizer to apply. * diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php index 53a1ce0a5482b..a0693ce341456 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php @@ -14,8 +14,12 @@ class Tests_Fonts_WpFontCollection_Construct extends WP_UnitTestCase { public function test_should_do_it_wrong_with_invalid_slug() { $this->setExpectedIncorrectUsage( 'WP_Font_Collection::__construct' ); + $mock_collection_data = array( + 'name' => 'Test Collection', + 'font_families' => array( 'mock ' ), + ); - $collection = new WP_Font_Collection( 'slug with spaces', array() ); + $collection = new WP_Font_Collection( 'slug with spaces', $mock_collection_data ); $this->assertSame( 'slug-with-spaces', $collection->slug, 'Slug is not sanitized.' ); } diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php b/phpunit/tests/fonts/font-library/wpFontCollection/getData.php index c77456dde1405..1cb16e271db61 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getData.php @@ -167,14 +167,14 @@ public function data_create_font_collection() { 'fontFamily' => 'Open Sans', 'fontStyle' => 'normal', 'fontWeight' => '400', - 'src' => 'https://example.com/src-as-string.ttf?a=scriptalert(xss)/script', + 'src' => 'https://example.com/src-as-string.ttf?a=', ), array( 'fontFamily' => 'Open Sans', 'fontStyle' => 'normal', 'fontWeight' => '400', 'src' => array( - 'https://example.com/src-as-array.woff2?a=scriptalert(xss)/script', + 'https://example.com/src-as-array.woff2?a=', 'https://example.com/src-as-array.ttf', ), ), @@ -195,7 +195,7 @@ public function data_create_font_collection() { * @param array $config Font collection config. */ public function test_should_error_when_missing_properties( $config ) { - $this->setExpectedIncorrectUsage( 'WP_Font_Collection::validate_andd_sanitize' ); + $this->setExpectedIncorrectUsage( 'WP_Font_Collection::sanitize_and_validate_data' ); $collection = new WP_Font_Collection( 'my-collection', $config ); $data = $collection->get_data(); diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php index bc6b6d6005d1c..2c246de80b8b9 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -13,7 +13,12 @@ class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_Font_Library_UnitTestCase { public function test_should_get_font_collection() { - wp_register_font_collection( 'my-font-collection', array( 'font_families' => array( 'mock' ) ) ); + $mock_collection_data = array( + 'name' => 'Test Collection', + 'font_families' => array( 'mock' ), + ); + + wp_register_font_collection( 'my-font-collection', $mock_collection_data ); $font_collection = WP_Font_Library::get_font_collection( 'my-font-collection' ); $this->assertInstanceOf( 'WP_Font_Collection', $font_collection ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php index 1ba9ab1d32fb4..24529d0dd090b 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -13,6 +13,7 @@ class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_Font_Library_UnitTestCase { public function test_should_register_font_collection() { $config = array( + 'name' => 'My Collection', 'font_families' => array( 'mock' ), ); @@ -21,14 +22,19 @@ public function test_should_register_font_collection() { } public function test_should_return_error_if_slug_is_repeated() { + $mock_collection_data = array( + 'name' => 'Test Collection', + 'font_families' => array( 'mock' ), + ); + // Register first collection. - $collection1 = WP_Font_Library::register_font_collection( 'my-collection-1', array( 'font_families' => array( 'mock' ) ) ); + $collection1 = WP_Font_Library::register_font_collection( 'my-collection-1', $mock_collection_data ); $this->assertInstanceOf( 'WP_Font_Collection', $collection1, 'A collection should be registered.' ); // Expects a _doing_it_wrong notice. $this->setExpectedIncorrectUsage( 'WP_Font_Library::register_font_collection' ); // Try to register a second collection with same slug. - WP_Font_Library::register_font_collection( 'my-collection-1', array( 'font_families' => array( 'mock' ) ) ); + WP_Font_Library::register_font_collection( 'my-collection-1', $mock_collection_data ); } } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php index 9546d0082eff9..657d3bb07aaaf 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php @@ -13,9 +13,14 @@ class Tests_Fonts_WpFontLibrary_UnregisterFontCollection extends WP_Font_Library_UnitTestCase { public function test_should_unregister_font_collection() { + $mock_collection_data = array( + 'name' => 'Test Collection', + 'font_families' => array( 'mock' ), + ); + // Registers two mock font collections. - WP_Font_Library::register_font_collection( 'mock-font-collection-1', array( 'font_families' => array( 'mock' ) ) ); - WP_Font_Library::register_font_collection( 'mock-font-collection-2', array( 'font_families' => array( 'mock' ) ) ); + WP_Font_Library::register_font_collection( 'mock-font-collection-1', $mock_collection_data ); + WP_Font_Library::register_font_collection( 'mock-font-collection-2', $mock_collection_data ); // Unregister mock font collection. WP_Font_Library::unregister_font_collection( 'mock-font-collection-1' ); @@ -36,6 +41,6 @@ public function unregister_non_existing_collection() { // Unregisters non-existing font collection. WP_Font_Library::unregister_font_collection( 'non-existing-collection' ); $collections = WP_Font_Library::get_font_collections(); - $this->assertEmpty( $collections, 'Should not be registered collections.' ); + $this->assertEmpty( $collections, 'No collections should be registered.' ); } }