Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5dbb7fc
Font Library: add wp_font_face post type and scaffold font face REST …
creativecoder Jan 9, 2024
e89854a
Font Library: create font faces through the REST API (#57702)
creativecoder Jan 11, 2024
998f084
Refactor Font Family Controller (#57785)
creativecoder Jan 12, 2024
90b5717
Font Family and Font Face REST API endpoints: better data handling an…
creativecoder Jan 15, 2024
efffcc8
Font Families REST API endpoint: ensure unique font family slugs (#57…
creativecoder Jan 16, 2024
c263a04
Font Library: delete child font faces and font assets when deleting p…
creativecoder Jan 16, 2024
e8ca12c
Font Library: refactor client side install functions to work with rev…
jffng Jan 17, 2024
13b5640
Cleanup/font library view error handling (#57926)
pbking Jan 17, 2024
3e37968
Font Faces endpoint: prevent creating font faces with duplicate setti…
creativecoder Jan 17, 2024
d1f8dcf
Font Library: Update uninstall/delete on client side (#57932)
mikachan Jan 18, 2024
3e5e987
Update packages/edit-site/src/components/global-styles/font-library-m…
mikachan Jan 18, 2024
dd885b5
Font Library: address JS feedback in #57688 (#57961)
mikachan Jan 18, 2024
2ed7a3b
Font Library REST API endpoints: address initial feedback from featur…
creativecoder Jan 19, 2024
c0e9949
Font Library: font collection refactor to use the new schema (#57884)
matiasbenedetto Jan 19, 2024
51345f0
Fix font asset download when font faces are installed (#58021)
creativecoder Jan 19, 2024
d45d540
Font Families and Faces: disable autosaves using empty class (#58018)
creativecoder Jan 19, 2024
1320d20
Adds migration for legacy font family content (#58032)
creativecoder Jan 22, 2024
4dce262
Font Library: Fix font collection filtering (#58091)
matiasbenedetto Jan 23, 2024
3da3f45
Merge branch 'trunk' into try/font-library-refactor
mikachan Jan 23, 2024
2743793
Fix load.php
mikachan Jan 23, 2024
b98c028
Font Library: fix to activate and display the right activation state …
matiasbenedetto Jan 23, 2024
921ec13
Fix font face files not being deleted with family (#58128)
creativecoder Jan 23, 2024
14b9e53
Font Library Preview: fix quoting of fontFamily property (#58127)
creativecoder Jan 23, 2024
f460811
Fix Font Library Tests_Font_Library_Hooks test missed in #58128
creativecoder Jan 23, 2024
51e4bed
Font library: Fix React key prop warnings (#57939)
mikachan Jan 23, 2024
23ce4e9
removing repeated comment
matiasbenedetto Jan 23, 2024
eb30f83
Update default Google fonts collection URL
mikachan Jan 23, 2024
dde2059
Font Library: Prevent error when installing a system font twice (#58141)
creativecoder Jan 23, 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
Prev Previous commit
Next Next commit
Font Faces endpoint: prevent creating font faces with duplicate setti…
…ngs (#57903)
  • Loading branch information
creativecoder committed Jan 22, 2024
commit 3e37968ee91a0ecae8224238d9cc78ff38853fe7
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static function format_font_family( $font_family ) {
function ( $family ) {
$trimmed = trim( $family );
if ( ! empty( $trimmed ) && strpos( $trimmed, ' ' ) !== false && strpos( $trimmed, "'" ) === false && strpos( $trimmed, '"' ) === false ) {
return "'" . $trimmed . "'";
return '"' . $trimmed . '"';
}
return $trimmed;
},
Expand All @@ -107,4 +107,84 @@ function ( $family ) {

return $font_family;
}

/**
* Generates a slug from font face properties, e.g. `open sans;normal;400;100%;U+0-10FFFF`
*
* Used for comparison with other font faces in the same family, to prevent duplicates
* that would both match according the CSS font matching spec. Uses only simple case-insensitive
* matching for fontFamily and unicodeRange, so does not handle overlapping font-family lists or
* unicode ranges.
*
* @since 6.5.0
*
* @link https://drafts.csswg.org/css-fonts/#font-style-matching
*
* @param array $settings {
* Font face settings.
*
* @type string $fontFamily Font family name.
* @type string $fontStyle Optional font style, defaults to 'normal'.
* @type string $fontWeight Optional font weight, defaults to 400.
* @type string $fontStretch Optional font stretch, defaults to '100%'.
* @type string $unicodeRange Optional unicode range, defaults to 'U+0-10FFFF'.
* }
* @return string Font face slug.
*/
public static function get_font_face_slug( $settings ) {
$settings = wp_parse_args(
$settings,
array(
'fontFamily' => '',
'fontStyle' => 'normal',
'fontWeight' => '400',
'fontStretch' => '100%',
'unicodeRange' => 'U+0-10FFFF',
)
);

// Convert all values to lowercase for comparison.
// Font family names may use multibyte characters.
$font_family = mb_strtolower( $settings['fontFamily'] );
$font_style = strtolower( $settings['fontStyle'] );
$font_weight = strtolower( $settings['fontWeight'] );
$font_stretch = strtolower( $settings['fontStretch'] );
$unicode_range = strtoupper( $settings['unicodeRange'] );

// Convert weight keywords to numeric strings.
$font_weight = str_replace( 'normal', '400', $font_weight );
$font_weight = str_replace( 'bold', '700', $font_weight );

// Convert stretch keywords to numeric strings.
$font_stretch_map = array(
'ultra-condensed' => '50%',
'extra-condensed' => '62.5%',
'condensed' => '75%',
'semi-condensed' => '87.5%',
'normal' => '100%',
'semi-expanded' => '112.5%',
'expanded' => '125%',
'extra-expanded' => '150%',
'untra-expanded' => '200%',
);
$font_stretch = str_replace( array_keys( $font_stretch_map ), array_values( $font_stretch_map ), $font_stretch );

$slug_elements = array( $font_family, $font_style, $font_weight, $font_stretch, $unicode_range );

$slug_elements = array_map(
function ( $elem ) {
// Remove quotes to normalize font-family names, and ';' to use as a separator.
$elem = trim( str_replace( array( '"', "'", ';' ), '', $elem ) );

// Normalize comma separated lists by removing whitespace in between items,
// but keep whitespace within items (e.g. "Open Sans" and "OpenSans" are different fonts).
// CSS spec for whitespace includes: U+000A LINE FEED, U+0009 CHARACTER TABULATION, or U+0020 SPACE,
// which by default are all matched by \s in PHP.
return preg_replace( '/,\s+/', ',', $elem );
},
$slug_elements
);

return join( ';', $slug_elements );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,22 @@ public function create_item( $request ) {
$settings = $request->get_param( 'font_face_settings' );
$file_params = $request->get_file_params();

// Check that the necessary font face properties are unique.
$existing_font_face = get_posts(
array(
'post_type' => $this->post_type,
'posts_per_page' => 1,
'title' => WP_Font_Family_Utils::get_font_face_slug( $settings ),
)
);
if ( ! empty( $existing_font_face ) ) {
return new WP_Error(
'rest_duplicate_font_face',
__( 'A font face matching those settings already exists.', 'gutenberg' ),
array( 'status' => 400 )
);
}

// Move the uploaded font asset from the temp folder to the fonts directory.
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
Expand Down Expand Up @@ -648,11 +664,15 @@ protected function prepare_item_for_database( $request ) {
// Settings have already been decoded by ::sanitize_font_face_settings().
$settings = $request->get_param( 'font_face_settings' );

// Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting,
// which may contain multibyte characters.
$title = WP_Font_Family_Utils::get_font_face_slug( $settings );

$prepared_post->post_type = $this->post_type;
$prepared_post->post_parent = $request['font_family_id'];
$prepared_post->post_status = 'publish';
$prepared_post->post_title = $settings['fontFamily'];
$prepared_post->post_name = sanitize_title( $settings['fontFamily'] );
$prepared_post->post_title = $title;
$prepared_post->post_name = sanitize_title( $title );
$prepared_post->post_content = wp_json_encode( $settings );

return $prepared_post;
Expand Down Expand Up @@ -751,10 +771,10 @@ protected function get_settings_from_post( $post ) {
// Provide required, empty settings if needed.
if ( null === $settings ) {
$settings = array(
'src' => array(),
'fontFamily' => '',
'src' => array(),
);
}
$settings['fontFamily'] = $post->post_title ?? '';

// Only return the properties defined in the schema.
return array_intersect_key( $settings, $properties );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ public function data_should_format_font_family() {
return array(
'data_families_with_spaces_and_numbers' => array(
'font_family' => 'Rock 3D , Open Sans,serif',
'expected' => "'Rock 3D', 'Open Sans', serif",
'expected' => '"Rock 3D", "Open Sans", serif',
),
'data_single_font_family' => array(
'font_family' => 'Rock 3D',
'expected' => "'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\"",
'expected' => '"Rock 3D serif", serif, sans-serif, "Open Sans"',
),
'data_empty_family' => array(
'font_family' => ' ',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/**
* Test WP_Font_Family_Utils::get_font_face_slug().
*
* @package WordPress
* @subpackage Font Library
* *
* @covers WP_Font_Family_Utils::get_font_face_slug
*/
class Tests_Fonts_WpFontsFamilyUtils_GetFontFamilySlug extends WP_UnitTestCase {
/**
* @dataProvider data_get_font_face_slug_normalizes_values
*/
public function test_get_font_face_slug_normalizes_values( $settings, $expected_slug ) {
$slug = WP_Font_Family_Utils::get_font_face_slug( $settings );

$this->assertSame( $expected_slug, $slug );
}

public function data_get_font_face_slug_normalizes_values() {
return array(
'Sets defaults' => array(
'settings' => array(
'fontFamily' => 'Open Sans',
),
'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
),
'Converts normal weight to 400' => array(
'settings' => array(
'fontFamily' => 'Open Sans',
'fontWeight' => 'normal',
),
'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
),
'Converts bold weight to 700' => array(
'settings' => array(
'fontFamily' => 'Open Sans',
'fontWeight' => 'bold',
),
'expected_slug' => 'open sans;normal;700;100%;U+0-10FFFF',
),
'Converts normal font-stretch to 100%' => array(
'settings' => array(
'fontFamily' => 'Open Sans',
'fontStretch' => 'normal',
),
'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
),
'Removes double quotes from fontFamilies' => array(
'settings' => array(
'fontFamily' => '"Open Sans"',
),
'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
),
'Removes single quotes from fontFamilies' => array(
'settings' => array(
'fontFamily' => "'Open Sans'",
),
'expected_slug' => 'open sans;normal;400;100%;U+0-10FFFF',
),
'Removes spaces between comma separated font families' => array(
'settings' => array(
'fontFamily' => 'Open Sans, serif',
),
'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
),
'Removes tabs between comma separated font families' => array(
'settings' => array(
'fontFamily' => "Open Sans,\tserif",
),
'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
),
'Removes new lines between comma separated font families' => array(
'settings' => array(
'fontFamily' => "Open Sans,\nserif",
),
'expected_slug' => 'open sans,serif;normal;400;100%;U+0-10FFFF',
),
);
}
}
Loading