diff --git a/src/wp-includes/fonts/class-wp-font-collection.php b/src/wp-includes/fonts/class-wp-font-collection.php index 240bba35e94f8..889d1af998a85 100644 --- a/src/wp-includes/fonts/class-wp-font-collection.php +++ b/src/wp-includes/fonts/class-wp-font-collection.php @@ -219,8 +219,10 @@ private static function get_sanitization_schema() { array( 'font_family_settings' => array( 'name' => 'sanitize_text_field', - 'slug' => 'sanitize_title', - 'fontFamily' => 'sanitize_text_field', + 'slug' => static function ( $value ) { + return _wp_to_kebab_case( sanitize_title( $value ) ); + }, + 'fontFamily' => 'WP_Font_Utils::sanitize_font_family', 'preview' => 'sanitize_url', 'fontFace' => array( array( diff --git a/src/wp-includes/fonts/class-wp-font-utils.php b/src/wp-includes/fonts/class-wp-font-utils.php index e9155b8e59719..3c2d5057e450e 100644 --- a/src/wp-includes/fonts/class-wp-font-utils.php +++ b/src/wp-includes/fonts/class-wp-font-utils.php @@ -18,11 +18,36 @@ * @access private */ class WP_Font_Utils { + /** + * Adds surrounding quotes to font family names that contain special characters. + * + * It follows the recommendations from the CSS Fonts Module Level 4. + * @link https://www.w3.org/TR/css-fonts-4/#font-family-prop + * + * @since 6.5.0 + * + * @param string $item A font family name. + * @return string The font family name with surrounding quotes, if necessary. + */ + private static function maybe_add_quotes( $item ) { + // Matches strings that are not exclusively alphabetic characters or hyphens, and do not exactly follow the pattern generic(alphabetic characters or hyphens). + $regex = '/^(?!generic\([a-zA-Z\-]+\)$)(?!^[a-zA-Z\-]+$).+/'; + $item = trim( $item ); + if ( preg_match( $regex, $item ) ) { + $item = trim( $item, "\"'" ); + return '"' . $item . '"'; + } + return $item; + } + /** * Sanitizes and formats font family names. * - * - Applies `sanitize_text_field` - * - Adds surrounding quotes to names that contain spaces and are not already quoted + * - Applies `sanitize_text_field`. + * - Adds surrounding quotes to names containing any characters that are not alphabetic or dashes. + * + * It follows the recommendations from the CSS Fonts Module Level 4. + * @link https://www.w3.org/TR/css-fonts-4/#font-family-prop * * @since 6.5.0 * @access private @@ -37,26 +62,19 @@ public static function sanitize_font_family( $font_family ) { return ''; } - $font_family = sanitize_text_field( $font_family ); - $font_families = explode( ',', $font_family ); - $wrapped_font_families = array_map( - function ( $family ) { - $trimmed = trim( $family ); - if ( ! empty( $trimmed ) && str_contains( $trimmed, ' ' ) && ! str_contains( $trimmed, "'" ) && ! str_contains( $trimmed, '"' ) ) { - return '"' . $trimmed . '"'; + $output = sanitize_text_field( $font_family ); + $formatted_items = array(); + if ( str_contains( $output, ',' ) ) { + $items = explode( ',', $output ); + foreach ( $items as $item ) { + $formatted_item = self::maybe_add_quotes( $item ); + if ( ! empty( $formatted_item ) ) { + $formatted_items[] = $formatted_item; } - return $trimmed; - }, - $font_families - ); - - if ( count( $wrapped_font_families ) === 1 ) { - $font_family = $wrapped_font_families[0]; - } else { - $font_family = implode( ', ', $wrapped_font_families ); + } + return implode( ', ', $formatted_items ); } - - return $font_family; + return self::maybe_add_quotes( $output ); } /** diff --git a/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php b/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php index 8a0af0c97d967..0921379b801c3 100644 --- a/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php +++ b/tests/phpunit/tests/fonts/font-library/wpFontCollection/getData.php @@ -159,7 +159,7 @@ public function data_create_font_collection() { 'font_families' => array( array( 'font_family_settings' => array( - 'fontFamily' => 'Open Sans, sans-serif', + 'fontFamily' => '"Open Sans", sans-serif', 'slug' => 'open-sans', 'name' => 'Open Sans', 'fontFace' => array( diff --git a/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php index 71511331c65dc..ff6b083ecaebd 100644 --- a/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php +++ b/tests/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php @@ -35,17 +35,13 @@ public function test_should_sanitize_font_family( $font_family, $expected ) { public function data_should_sanitize_font_family() { return array( 'data_families_with_spaces_and_numbers' => array( - 'font_family' => 'Rock 3D , Open Sans,serif', - 'expected' => '"Rock 3D", "Open Sans", serif', + 'font_family' => 'Arial, Rock 3D , Open Sans,serif', + 'expected' => 'Arial, "Rock 3D", "Open Sans", serif', ), 'data_single_font_family' => array( 'font_family' => 'Rock 3D', 'expected' => '"Rock 3D"', ), - 'data_no_spaces' => array( - 'font_family' => 'Rock3D', - 'expected' => 'Rock3D', - ), 'data_many_spaces_and_existing_quotes' => array( 'font_family' => 'Rock 3D serif, serif,sans-serif, "Open Sans"', 'expected' => '"Rock 3D serif", serif, sans-serif, "Open Sans"', @@ -58,6 +54,10 @@ public function data_should_sanitize_font_family() { 'font_family' => " Rock 3D\n ", 'expected' => '"Rock 3D"', ), + 'data_font_family_with_generic_names' => array( + 'font_family' => 'generic(kai), generic(font[name]), generic(fangsong), Rock 3D', + 'expected' => 'generic(kai), "generic(font[name])", generic(fangsong), "Rock 3D"', + ), ); } } diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php index 0676c3ce4b88d..c78efdf03e449 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -681,7 +681,7 @@ public function test_update_item() { $settings = array( 'name' => 'Open Sans', - 'fontFamily' => '"Open Sans, "Noto Sans", sans-serif', + 'fontFamily' => 'Open Sans, "Noto Sans", sans-serif', 'preview' => 'https://s.w.org/images/fonts/16.9/previews/open-sans/open-sans-400-normal.svg', ); @@ -700,7 +700,7 @@ public function test_update_item() { $expected_settings = array( 'name' => $settings['name'], 'slug' => 'open-sans-2', - 'fontFamily' => $settings['fontFamily'], + 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif', 'preview' => $settings['preview'], ); $this->assertSame( $expected_settings, $data['font_family_settings'], 'The response font_family_settings should match expected settings.' );