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..6d975753d5e96 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
@@ -21,35 +21,41 @@
*/
class WP_Font_Utils {
/**
- * Format font family names.
+ * Sanitizes and formats font family names.
*
- * Adds surrounding quotes to font family names containing spaces and not already quoted.
+ * - Applies `sanitize_text_field`
+ * - Adds surrounding quotes to names that contain spaces and are not already quoted
*
* @since 6.5.0
* @access private
*
+ * @see sanitize_text_field()
+ *
* @param string $font_family Font family name(s), comma-separated.
- * @return string Formatted font family name(s).
+ * @return string Sanitized and formatted font family name(s).
*/
- public static function format_font_family( $font_family ) {
- if ( $font_family ) {
- $font_families = explode( ',', $font_family );
- $wrapped_font_families = array_map(
- function ( $family ) {
- $trimmed = trim( $family );
- if ( ! empty( $trimmed ) && strpos( $trimmed, ' ' ) !== false && strpos( $trimmed, "'" ) === false && strpos( $trimmed, '"' ) === false ) {
- return '"' . $trimmed . '"';
- }
- return $trimmed;
- },
- $font_families
- );
-
- if ( count( $wrapped_font_families ) === 1 ) {
- $font_family = $wrapped_font_families[0];
- } else {
- $font_family = implode( ', ', $wrapped_font_families );
- }
+ public static function sanitize_font_family( $font_family ) {
+ if ( ! $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 ) && false !== strpos( $trimmed, ' ' ) && false === strpos( $trimmed, "'" ) && false === strpos( $trimmed, '"' ) ) {
+ return '"' . $trimmed . '"';
+ }
+ return $trimmed;
+ },
+ $font_families
+ );
+
+ if ( count( $wrapped_font_families ) === 1 ) {
+ $font_family = $wrapped_font_families[0];
+ } else {
+ $font_family = implode( ', ', $wrapped_font_families );
}
return $font_family;
@@ -128,7 +134,7 @@ function ( $elem ) {
$slug_elements
);
- return join( ';', $slug_elements );
+ return sanitize_text_field( join( ';', $slug_elements ) );
}
/**
diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php
index efecd6c6821c3..22a843e7e69ed 100644
--- a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php
+++ b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-faces-controller.php
@@ -187,20 +187,32 @@ public function validate_create_font_face_settings( $value, $request ) {
}
}
- $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] );
+ $srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] );
+ $files = $request->get_file_params();
- // Check that srcs are non-empty strings.
- $filtered_src = array_filter( array_filter( $srcs, 'is_string' ) );
- if ( empty( $filtered_src ) ) {
- return new WP_Error(
- 'rest_invalid_param',
- __( 'font_face_settings[src] values must be non-empty strings.', 'gutenberg' ),
- array( 'status' => 400 )
- );
+ foreach ( $srcs as $src ) {
+ // Check that each src is a non-empty string.
+ $src = ltrim( $src );
+ if ( empty( $src ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'font_face_settings[src] values must be non-empty strings.', 'gutenberg' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ // Check that srcs are valid URLs or file references.
+ if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ /* translators: %s: src value in the font face settings. */
+ sprintf( __( 'font_face_settings[src] value "%s" must be a valid URL or file reference.', 'gutenberg' ), $src ),
+ array( 'status' => 400 )
+ );
+ }
}
// Check that each file in the request references a src in the settings.
- $files = $request->get_file_params();
foreach ( array_keys( $files ) as $file ) {
if ( ! in_array( $file, $srcs, true ) ) {
return new WP_Error(
@@ -227,9 +239,12 @@ public function validate_create_font_face_settings( $value, $request ) {
public function sanitize_font_face_settings( $value ) {
// Settings arrive as stringified JSON, since this is a multipart/form-data request.
$settings = json_decode( $value, true );
+ $schema = $this->get_item_schema()['properties']['font_face_settings']['properties'];
- if ( isset( $settings['fontFamily'] ) ) {
- $settings['fontFamily'] = WP_Font_Utils::format_font_family( $settings['fontFamily'] );
+ // Sanitize settings based on callbacks in the schema.
+ foreach ( $settings as $key => $value ) {
+ $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
+ $settings[ $key ] = call_user_func( $sanitize_callback, $value );
}
return $settings;
@@ -509,11 +524,17 @@ public function get_item_schema() {
'description' => __( 'CSS font-family value.', 'gutenberg' ),
'type' => 'string',
'default' => '',
+ 'arg_options' => array(
+ 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
+ ),
),
'fontStyle' => array(
'description' => __( 'CSS font-style value.', 'gutenberg' ),
'type' => 'string',
'default' => 'normal',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'fontWeight' => array(
'description' => __( 'List of available font weights, separated by a space.', 'gutenberg' ),
@@ -521,6 +542,9 @@ public function get_item_schema() {
// Changed from `oneOf` to avoid errors from loose type checking.
// e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check.
'type' => array( 'string', 'integer' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'fontDisplay' => array(
'description' => __( 'CSS font-display value.', 'gutenberg' ),
@@ -533,10 +557,14 @@ public function get_item_schema() {
'swap',
'optional',
),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'src' => array(
'description' => __( 'Paths or URLs to the font files.', 'gutenberg' ),
- // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array.
+ // Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array,
+ // and causing a "matches more than one of the expected formats" error.
'anyOf' => array(
array(
'type' => 'string',
@@ -549,46 +577,83 @@ public function get_item_schema() {
),
),
'default' => array(),
+ 'arg_options' => array(
+ 'sanitize_callback' => function ( $value ) {
+ return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value );
+ },
+ ),
),
'fontStretch' => array(
'description' => __( 'CSS font-stretch value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'ascentOverride' => array(
'description' => __( 'CSS ascent-override value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'descentOverride' => array(
'description' => __( 'CSS descent-override value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'fontVariant' => array(
'description' => __( 'CSS font-variant value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'fontFeatureSettings' => array(
'description' => __( 'CSS font-feature-settings value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'fontVariationSettings' => array(
'description' => __( 'CSS font-variation-settings value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'lineGapOverride' => array(
'description' => __( 'CSS line-gap-override value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'sizeAdjust' => array(
'description' => __( 'CSS size-adjust value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'unicodeRange' => array(
'description' => __( 'CSS unicode-range value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'preview' => array(
'description' => __( 'URL to a preview image of the font face.', 'gutenberg' ),
'type' => 'string',
+ 'format' => 'uri',
+ 'default' => '',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_url',
+ ),
),
),
'required' => array( 'fontFamily', 'src' ),
@@ -602,6 +667,26 @@ public function get_item_schema() {
return $this->add_additional_fields_schema( $this->schema );
}
+ /**
+ * Retrieves the item's schema for display / public consumption purposes.
+ *
+ * @since 6.5.0
+ *
+ * @return array Public item schema data.
+ */
+ public function get_public_item_schema() {
+
+ $schema = parent::get_public_item_schema();
+
+ // Also remove `arg_options' from child font_family_settings properties, since the parent
+ // controller only handles the top level properties.
+ foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) {
+ unset( $property['arg_options'] );
+ }
+
+ return $schema;
+ }
+
/**
* Retrieves the query params for the font face collection.
*
@@ -739,6 +824,20 @@ protected function prepare_item_for_database( $request ) {
return $prepared_post;
}
+ /**
+ * Sanitizes a single src value for a font face.
+ *
+ * @since 6.5.0
+ *
+ * @param string $value Font face src that is a URL or the key for a $_FILES array item.
+ *
+ * @return string Sanitized value.
+ */
+ protected function sanitize_src( $value ) {
+ $value = ltrim( $value );
+ return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value );
+ }
+
/**
* Handles the upload of a font file using wp_handle_upload().
*
diff --git a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-families-controller.php b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-families-controller.php
index 7586fe0209329..e4a2b2f8e9781 100644
--- a/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-families-controller.php
+++ b/lib/compat/wordpress-6.5/fonts/class-wp-rest-font-families-controller.php
@@ -141,15 +141,14 @@ public function validate_font_family_settings( $value, $request ) {
* @return array Decoded array font family settings.
*/
public function sanitize_font_family_settings( $value ) {
+ // Settings arrive as stringified JSON, since this is a multipart/form-data request.
$settings = json_decode( $value, true );
+ $schema = $this->get_item_schema()['properties']['font_family_settings']['properties'];
- if ( isset( $settings['fontFamily'] ) ) {
- $settings['fontFamily'] = WP_Font_Utils::format_font_family( $settings['fontFamily'] );
- }
-
- // Provide default for preview, if not provided.
- if ( ! isset( $settings['preview'] ) ) {
- $settings['preview'] = '';
+ // Sanitize settings based on callbacks in the schema.
+ foreach ( $settings as $key => $value ) {
+ $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
+ $settings[ $key ] = call_user_func( $sanitize_callback, $value );
}
return $settings;
@@ -307,25 +306,39 @@ public function get_item_schema() {
// Font family settings come directly from theme.json schema
// See https://schemas.wp.org/trunk/theme.json
'font_family_settings' => array(
- 'description' => __( 'font-face declaration in theme.json format.', 'gutenberg' ),
+ 'description' => __( 'font-face definition in theme.json format.', 'gutenberg' ),
'type' => 'object',
'context' => array( 'view', 'edit', 'embed' ),
'properties' => array(
'name' => array(
- 'description' => 'Name of the font family preset, translatable.',
+ 'description' => __( 'Name of the font family preset, translatable.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
),
'slug' => array(
- 'description' => 'Kebab-case unique identifier for the font family preset.',
+ 'description' => __( 'Kebab-case unique identifier for the font family preset.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_title',
+ ),
),
'fontFamily' => array(
- 'description' => 'CSS font-family value.',
+ 'description' => __( 'CSS font-family value.', 'gutenberg' ),
'type' => 'string',
+ 'arg_options' => array(
+ 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
+ ),
),
'preview' => array(
- 'description' => 'URL to a preview image of the font family.',
+ 'description' => __( 'URL to a preview image of the font family.', 'gutenberg' ),
'type' => 'string',
+ 'format' => 'uri',
+ 'default' => '',
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_url',
+ ),
),
),
'required' => array( 'name', 'slug', 'fontFamily' ),
@@ -339,6 +352,26 @@ public function get_item_schema() {
return $this->add_additional_fields_schema( $this->schema );
}
+ /**
+ * Retrieves the item's schema for display / public consumption purposes.
+ *
+ * @since 6.5.0
+ *
+ * @return array Public item schema data.
+ */
+ public function get_public_item_schema() {
+
+ $schema = parent::get_public_item_schema();
+
+ // Also remove `arg_options' from child font_family_settings properties, since the parent
+ // controller only handles the top level properties.
+ foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) {
+ unset( $property['arg_options'] );
+ }
+
+ return $schema;
+ }
+
/**
* Retrieves the query params for the font family collection.
*
diff --git a/phpunit/tests/fonts/font-library/wpFontUtils/formatFontFamily.php b/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php
similarity index 65%
rename from phpunit/tests/fonts/font-library/wpFontUtils/formatFontFamily.php
rename to phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php
index f1acf5422a8b4..71511331c65dc 100644
--- a/phpunit/tests/fonts/font-library/wpFontUtils/formatFontFamily.php
+++ b/phpunit/tests/fonts/font-library/wpFontUtils/sanitizeFontFamily.php
@@ -1,6 +1,6 @@
assertSame(
$expected,
- WP_Font_Utils::format_font_family(
+ WP_Font_Utils::sanitize_font_family(
$font_family
)
);
@@ -32,7 +32,7 @@ public function test_should_format_font_family( $font_family, $expected ) {
*
* @return array
*/
- public function data_should_format_font_family() {
+ public function data_should_sanitize_font_family() {
return array(
'data_families_with_spaces_and_numbers' => array(
'font_family' => 'Rock 3D , Open Sans,serif',
@@ -54,6 +54,10 @@ public function data_should_format_font_family() {
'font_family' => ' ',
'expected' => '',
),
+ 'data_font_family_with_whitespace_tags_new_lines' => array(
+ 'font_family' => " Rock 3D\n ",
+ 'expected' => '"Rock 3D"',
+ ),
);
}
}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php
index 3067e485822c8..273862e3047e8 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontFacesController.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontFacesController.php
@@ -711,12 +711,13 @@ public function test_create_item_invalid_file_src() {
$files = $this->setup_font_file_upload( array( 'woff2' ) );
wp_set_current_user( self::$admin_id );
+ $src = 'invalid';
$request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
$request->set_param( 'theme_json_version', 2 );
$request->set_param(
'font_face_settings',
wp_json_encode(
- array_merge( self::$default_settings, array( 'src' => 'invalid' ) )
+ array_merge( self::$default_settings, array( 'src' => $src ) )
)
);
$request->set_file_params( $files );
@@ -724,30 +725,57 @@ public function test_create_item_invalid_file_src() {
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' );
- $expected_message = 'File ' . array_keys( $files )[0] . ' must be used in font_face_settings[src].';
+ $expected_message = 'font_face_settings[src] value "' . $src . '" must be a valid URL or file reference.';
$message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings'];
$this->assertSame( $expected_message, $message, 'The response error message should match.' );
}
/**
- * @dataProvider data_create_item_sanitize_font_family
+ * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ */
+ public function test_create_item_missing_file_src() {
+ $files = $this->setup_font_file_upload( array( 'woff2', 'woff' ) );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
+ $request->set_param( 'theme_json_version', 2 );
+ $request->set_param(
+ 'font_face_settings',
+ wp_json_encode(
+ array_merge( self::$default_settings, array( 'src' => array( array_keys( $files )[0] ) ) )
+ )
+ );
+ $request->set_file_params( $files );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertErrorResponse( 'rest_invalid_param', $response, 400, 'The response should return an error for "rest_invalid_param" with 400 status.' );
+ $expected_message = 'File ' . array_keys( $files )[1] . ' must be used in font_face_settings[src].';
+ $message = $response->as_error()->get_all_error_data()[0]['params']['font_face_settings'];
+ $this->assertSame( $expected_message, $message, 'The response error message should match.' );
+ }
+
+ /**
+ * @dataProvider data_sanitize_font_face_settings
*
* @covers WP_REST_Font_Face_Controller::sanitize_font_face_settings
*
- * @param string $font_family_setting Setting to test.
- * @param string $expected Expected result.
+ * @param string $settings Settings to test.
+ * @param string $expected Expected settings result.
*/
- public function test_create_item_sanitize_font_family( $font_family_setting, $expected ) {
- $settings = array_merge( self::$default_settings, array( 'fontFamily' => $font_family_setting ) );
+ public function test_create_item_sanitize_font_face_settings( $settings, $expected ) {
+ $settings = array_merge( self::$default_settings, $settings );
+ $expected = array_merge( self::$default_settings, $expected );
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . self::$font_family_id . '/font-faces' );
$request->set_param( 'font_face_settings', wp_json_encode( $settings ) );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
+ wp_delete_post( $data['id'], true );
$this->assertSame( 201, $response->get_status(), 'The response status should be 201.' );
- $this->assertSame( $expected, $data['font_face_settings']['fontFamily'], 'The response fontFamily should match.' );
+ $this->assertSame( $expected, $data['font_face_settings'], 'The response font_face_settings should match.' );
}
/**
@@ -755,19 +783,65 @@ public function test_create_item_sanitize_font_family( $font_family_setting, $ex
*
* @return array
*/
- public function data_create_item_sanitize_font_family() {
+ public function data_sanitize_font_face_settings() {
return array(
- 'multiword font with integer' => array(
- 'font_family_setting' => 'Libre Barcode 128 Text',
- 'expected' => '"Libre Barcode 128 Text"',
+ 'settings with tags, extra whitespace, new lines' => array(
+ 'settings' => array(
+ 'fontFamily' => " Open Sans\n ",
+ 'fontStyle' => " oblique 20deg 50deg\n ",
+ 'fontWeight' => " 200\n ",
+ 'src' => " https://example.com/ ",
+ 'fontStretch' => " expanded\n ",
+ 'ascentOverride' => " 70%\n ",
+ 'descentOverride' => " 30%\n ",
+ 'fontVariant' => " normal\n ",
+ 'fontFeatureSettings' => " \"swsh\" 2\n ",
+ 'fontVariationSettings' => " \"xhgt\" 0.7\n ",
+ 'lineGapOverride' => " 10%\n ",
+ 'sizeAdjust' => " 90%\n ",
+ 'unicodeRange' => " U+0025-00FF, U+4??\n ",
+ 'preview' => " https://example.com/ ",
+ ),
+ 'expected' => array(
+ 'fontFamily' => '"Open Sans"',
+ 'fontStyle' => 'oblique 20deg 50deg',
+ 'fontWeight' => '200',
+ 'src' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20',
+ 'fontStretch' => 'expanded',
+ 'ascentOverride' => '70%',
+ 'descentOverride' => '30%',
+ 'fontVariant' => 'normal',
+ 'fontFeatureSettings' => '"swsh" 2',
+ 'fontVariationSettings' => '"xhgt" 0.7',
+ 'lineGapOverride' => '10%',
+ 'sizeAdjust' => '90%',
+ 'unicodeRange' => 'U+0025-00FF, U+4??',
+ 'preview' => 'https://example.com//stylescriptalert(\'XSS\');/script%20%20%20%20%20%20',
+ ),
+ ),
+ 'multiword font family name with integer' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Libre Barcode 128 Text',
+ ),
+ 'expected' => array(
+ 'fontFamily' => '"Libre Barcode 128 Text"',
+ ),
),
- 'multiword font' => array(
- 'font_family_setting' => 'B612 Mono',
- 'expected' => '"B612 Mono"',
+ 'multiword font family name' => array(
+ 'settings' => array(
+ 'fontFamily' => 'B612 Mono',
+ ),
+ 'expected' => array(
+ 'fontFamily' => '"B612 Mono"',
+ ),
),
- 'comma-separated fonts' => array(
- 'font_family_setting' => 'Open Sans, Noto Sans, sans-serif',
- 'expected' => '"Open Sans", "Noto Sans", sans-serif',
+ 'comma-separated font family names' => array(
+ 'settings' => array(
+ 'fontFamily' => 'Open Sans, Noto Sans, sans-serif',
+ ),
+ 'expected' => array(
+ 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif',
+ ),
),
);
}
@@ -905,6 +979,40 @@ public function test_get_item_schema() {
$this->assertArrayHasKey( 'font_face_settings', $properties, 'The id property should exist in the schema::properties data.' );
}
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_item_schema
+ */
+ public function test_get_item_schema_font_face_settings_should_all_have_sanitize_callbacks() {
+ $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_item_schema();
+ $font_face_settings_schema = $schema['properties']['font_face_settings'];
+
+ $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' );
+ $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' );
+
+ // arg_options should be removed for each setting property.
+ foreach ( $font_face_settings_schema['properties'] as $property ) {
+ $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' );
+ $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' );
+ $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'The sanitize_callback value should be callable.' );
+ }
+ }
+
+ /**
+ * @covers WP_REST_Font_Faces_Controller::get_public_item_schema
+ */
+ public function test_get_public_item_schema_should_not_have_arg_options() {
+ $schema = ( new WP_REST_Font_Faces_Controller( 'wp_font_face' ) )->get_public_item_schema();
+ $font_face_settings_schema = $schema['properties']['font_face_settings'];
+
+ $this->assertArrayHasKey( 'properties', $font_face_settings_schema, 'font_face_settings schema is missing properties.' );
+ $this->assertIsArray( $font_face_settings_schema['properties'], 'font_face_settings properties should be an array.' );
+
+ // arg_options should be removed for each setting property.
+ foreach ( $font_face_settings_schema['properties'] as $property ) {
+ $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' );
+ }
+ }
+
protected function check_font_face_data( $data, $post_id, $links ) {
self::$post_ids_for_cleanup[] = $post_id;
$post = get_post( $post_id );
diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php
index 6468eff7e24a4..94ad5eccd7e57 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php
@@ -124,7 +124,7 @@ public static function create_font_family_post( $settings = array() ) {
}
/**
- * @covers WP_REST_Font_Faces_Controller::register_routes
+ * @covers WP_REST_Font_Families_Controller::register_routes
*/
public function test_register_routes() {
$routes = rest_get_server()->get_routes();
@@ -209,7 +209,7 @@ public function data_get_context_param() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_items
+ * @covers WP_REST_Font_Families_Controller::get_items
*/
public function test_get_items() {
wp_set_current_user( self::$admin_id );
@@ -226,7 +226,7 @@ public function test_get_items() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_items
+ * @covers WP_REST_Font_Families_Controller::get_items
*/
public function test_get_items_by_slug() {
$font_family = get_post( self::$font_family_id2 );
@@ -244,7 +244,7 @@ public function test_get_items_by_slug() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_items
+ * @covers WP_REST_Font_Families_Controller::get_items
*/
public function test_get_items_no_permission() {
wp_set_current_user( 0 );
@@ -259,7 +259,7 @@ public function test_get_items_no_permission() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_item
+ * @covers WP_REST_Font_Families_Controller::get_item
*/
public function test_get_item() {
wp_set_current_user( self::$admin_id );
@@ -272,7 +272,7 @@ public function test_get_item() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ * @covers WP_REST_Font_Families_Controller::prepare_item_for_response
*/
public function test_get_item_embedded_font_faces() {
wp_set_current_user( self::$admin_id );
@@ -344,7 +344,7 @@ public function test_get_item_malformed_post_content_returns_empty_settings() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_item
+ * @covers WP_REST_Font_Families_Controller::get_item
*/
public function test_get_item_invalid_font_family_id() {
wp_set_current_user( self::$admin_id );
@@ -354,7 +354,7 @@ public function test_get_item_invalid_font_family_id() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_item
+ * @covers WP_REST_Font_Families_Controller::get_item
*/
public function test_get_item_no_permission() {
wp_set_current_user( 0 );
@@ -369,7 +369,7 @@ public function test_get_item_no_permission() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::create_item
+ * @covers WP_REST_Font_Families_Controller::create_item
*/
public function test_create_item() {
$settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
@@ -390,7 +390,7 @@ public function test_create_item() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_request
+ * @covers WP_REST_Font_Families_Controller::validate_create_font_face_request
*/
public function test_create_item_default_theme_json_version() {
$settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
@@ -411,7 +411,7 @@ public function test_create_item_default_theme_json_version() {
/**
* @dataProvider data_create_item_invalid_theme_json_version
*
- * @covers WP_REST_Font_Faces_Controller::create_item
+ * @covers WP_REST_Font_Families_Controller::create_item
*
* @param int $theme_json_version Version to test.
*/
@@ -440,7 +440,7 @@ public function data_create_item_invalid_theme_json_version() {
/**
* @dataProvider data_create_item_with_default_preview
*
- * @covers WP_REST_Font_Faces_Controller::sanitize_font_family_settings
+ * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings
*
* @param array $settings Settings to test.
*/
@@ -481,10 +481,88 @@ public function data_create_item_with_default_preview() {
);
}
+ /**
+ * @dataProvider data_sanitize_font_family_settings
+ *
+ * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings
+ *
+ * @param string $settings Font family settings to test.
+ * @param string $expected Expected settings result.
+ */
+ public function test_create_item_santize_font_family_settings( $settings, $expected ) {
+ $settings = array_merge( self::$default_settings, $settings );
+ $expected = array_merge( self::$default_settings, $expected );
+
+ wp_set_current_user( self::$admin_id );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families' );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ static::$post_ids_to_cleanup[] = $data['id'];
+
+ $this->assertSame( 201, $response->get_status(), 'The response status should be 201.' );
+ $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_sanitize_font_family_settings() {
+ return array(
+ 'settings with tags, extra whitespace, new lines' => array(
+ 'settings' => array(
+ 'name' => " Opening Sans\n ",
+ 'slug' => " OPENing SanS \n ",
+ 'fontFamily' => " Opening Sans\n ",
+ 'preview' => " https://example.com/ ",
+ ),
+ 'expected' => array(
+ 'name' => 'Opening Sans',
+ 'slug' => 'opening-sans-alertxss',
+ 'fontFamily' => '"Opening Sans"',
+ 'preview' => "https://example.com//stylescriptalert('XSS');/script%20%20%20%20%20%20",
+ ),
+ ),
+ 'multiword font family name with integer' => array(
+ 'settings' => array(
+ 'slug' => 'libre-barcode-128-text',
+ 'fontFamily' => 'Libre Barcode 128 Text',
+ ),
+ 'expected' => array(
+ 'slug' => 'libre-barcode-128-text',
+ 'fontFamily' => '"Libre Barcode 128 Text"',
+ ),
+ ),
+ 'multiword font family name' => array(
+ 'settings' => array(
+ 'slug' => 'b612-mono',
+ 'fontFamily' => 'B612 Mono',
+ ),
+ 'expected' => array(
+ 'slug' => 'b612-mono',
+ 'fontFamily' => '"B612 Mono"',
+ ),
+ ),
+ 'comma-separated font family names' => array(
+ 'settings' => array(
+ 'slug' => 'open-sans-noto-sans',
+ 'fontFamily' => 'Open Sans, Noto Sans, sans-serif',
+ ),
+ 'expected' => array(
+ 'slug' => 'open-sans-noto-sans',
+ 'fontFamily' => '"Open Sans", "Noto Sans", sans-serif',
+ ),
+ ),
+ );
+ }
+
/**
* @dataProvider data_create_item_invalid_settings
*
- * @covers WP_REST_Font_Faces_Controller::validate_create_font_face_settings
+ * @covers WP_REST_Font_Families_Controller::validate_create_font_face_settings
*
* @param array $settings Settings to test.
*/
@@ -570,7 +648,7 @@ public function test_create_item_with_duplicate_slug() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::create_item
+ * @covers WP_REST_Font_Families_Controller::create_item
*/
public function test_create_item_no_permission() {
$settings = array_merge( self::$default_settings, array( 'slug' => 'open-sans-2' ) );
@@ -669,52 +747,36 @@ public function data_update_item_individual_settings() {
}
/**
- * @dataProvider data_update_item_santize_font_family
+ * @dataProvider data_sanitize_font_family_settings
*
- * @covers WP_REST_Font_Families_Controller::sanitize_font_face_settings
+ * @covers WP_REST_Font_Families_Controller::sanitize_font_family_settings
*
- * @param string $font_family_setting Font family setting to test.
- * @param string $expected Expected result.
+ * @param string $settings Font family settings to test.
+ * @param string $expected Expected settings result.
*/
- public function test_update_item_santize_font_family( $font_family_setting, $expected ) {
+ public function test_update_item_santize_font_family_settings( $settings, $expected ) {
+ // Unset/modify slug from the data provider, since we're updating rather than creating.
+ unset( $settings['slug'] );
+ $initial_settings = array( 'slug' => 'open-sans-update' );
+ $expected = array_merge( self::$default_settings, $expected, $initial_settings );
+
wp_set_current_user( self::$admin_id );
+ $font_family_id = self::create_font_family_post( $initial_settings );
+ static::$post_ids_to_cleanup[] = $font_family_id;
- $font_family_id = self::create_font_family_post();
- $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id );
- $request->set_param( 'font_family_settings', wp_json_encode( array( 'fontFamily' => $font_family_setting ) ) );
+ $request = new WP_REST_Request( 'POST', '/wp/v2/font-families/' . $font_family_id );
+ $request->set_param( 'font_family_settings', wp_json_encode( $settings ) );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
- $this->assertSame( $expected, $data['font_family_settings']['fontFamily'], 'The font family should match.' );
- }
-
- /**
- * Data provider.
- *
- * @return array
- */
- public function data_update_item_santize_font_family() {
- return array(
- 'multiword font with integer' => array(
- 'font_family_setting' => 'Libre Barcode 128 Text',
- 'expected' => '"Libre Barcode 128 Text"',
- ),
- 'multiword font' => array(
- 'font_family_setting' => 'B612 Mono',
- 'expected' => '"B612 Mono"',
- ),
- 'comma-separated fonts' => array(
- 'font_family_setting' => 'Open Sans, Noto Sans, sans-serif',
- 'expected' => '"Open Sans", "Noto Sans", sans-serif',
- ),
- );
+ $this->assertSame( $expected, $data['font_family_settings'], 'The response font_family_settings should match.' );
}
/**
* @dataProvider data_update_item_invalid_settings
*
- * @covers WP_REST_Font_Faces_Controller::update_item
+ * @covers WP_REST_Font_Families_Controller::update_item
*
* @param array $settings Settings to test.
*/
@@ -752,7 +814,7 @@ public function data_update_item_invalid_settings() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::update_item
+ * @covers WP_REST_Font_Families_Controller::update_item
*/
public function test_update_item_update_slug_not_allowed() {
wp_set_current_user( self::$admin_id );
@@ -770,7 +832,7 @@ public function test_update_item_update_slug_not_allowed() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::update_item
+ * @covers WP_REST_Font_Families_Controller::update_item
*/
public function test_update_item_invalid_font_family_id() {
$settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) );
@@ -783,7 +845,7 @@ public function test_update_item_invalid_font_family_id() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::update_item
+ * @covers WP_REST_Font_Families_Controller::update_item
*/
public function test_update_item_no_permission() {
$settings = array_diff_key( self::$default_settings, array( 'slug' => '' ) );
@@ -803,7 +865,7 @@ public function test_update_item_no_permission() {
/**
- * @covers WP_REST_Font_Faces_Controller::delete_item
+ * @covers WP_REST_Font_Families_Controller::delete_item
*/
public function test_delete_item() {
wp_set_current_user( self::$admin_id );
@@ -817,7 +879,7 @@ public function test_delete_item() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::delete_item
+ * @covers WP_REST_Font_Families_Controller::delete_item
*/
public function test_delete_item_no_trash() {
wp_set_current_user( self::$admin_id );
@@ -838,7 +900,7 @@ public function test_delete_item_no_trash() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::delete_item
+ * @covers WP_REST_Font_Families_Controller::delete_item
*/
public function test_delete_item_invalid_font_family_id() {
wp_set_current_user( self::$admin_id );
@@ -848,7 +910,7 @@ public function test_delete_item_invalid_font_family_id() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::delete_item
+ * @covers WP_REST_Font_Families_Controller::delete_item
*/
public function test_delete_item_no_permissions() {
$font_family_id = self::create_font_family_post();
@@ -865,7 +927,7 @@ public function test_delete_item_no_permissions() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::prepare_item_for_response
+ * @covers WP_REST_Font_Families_Controller::prepare_item_for_response
*/
public function test_prepare_item() {
wp_set_current_user( self::$admin_id );
@@ -878,7 +940,7 @@ public function test_prepare_item() {
}
/**
- * @covers WP_REST_Font_Faces_Controller::get_item_schema
+ * @covers WP_REST_Font_Families_Controller::get_item_schema
*/
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/font-families' );
@@ -894,6 +956,40 @@ public function test_get_item_schema() {
$this->assertArrayHasKey( 'font_family_settings', $properties, 'The font_family_settings property should exist in the schema::properties data.' );
}
+ /**
+ * @covers WP_REST_Font_Families_Controller::get_item_schema
+ */
+ public function test_get_item_schema_font_family_settings_should_all_have_sanitize_callbacks() {
+ $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_item_schema();
+ $font_family_settings_schema = $schema['properties']['font_family_settings'];
+
+ $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' );
+ $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' );
+
+ // arg_options should be removed for each setting property.
+ foreach ( $font_family_settings_schema['properties'] as $property ) {
+ $this->assertArrayHasKey( 'arg_options', $property, 'Setting schema should have arg_options.' );
+ $this->assertArrayHasKey( 'sanitize_callback', $property['arg_options'], 'Setting schema should have a sanitize_callback.' );
+ $this->assertIsCallable( $property['arg_options']['sanitize_callback'], 'That sanitize_callback value should be callable.' );
+ }
+ }
+
+ /**
+ * @covers WP_REST_Font_Families_Controller::get_public_item_schema
+ */
+ public function test_get_public_item_schema_should_not_have_arg_options() {
+ $schema = ( new WP_REST_Font_Families_Controller( 'wp_font_family' ) )->get_public_item_schema();
+ $font_family_settings_schema = $schema['properties']['font_family_settings'];
+
+ $this->assertArrayHasKey( 'properties', $font_family_settings_schema, 'font_family_settings schema is missing properties.' );
+ $this->assertIsArray( $font_family_settings_schema['properties'], 'font_family_settings properties should be an array.' );
+
+ // arg_options should be removed for each setting property.
+ foreach ( $font_family_settings_schema['properties'] as $property ) {
+ $this->assertArrayNotHasKey( 'arg_options', $property, 'arg_options should be removed from the schema for each setting.' );
+ }
+ }
+
protected function check_font_family_data( $data, $post_id, $links ) {
static::$post_ids_to_cleanup[] = $post_id;
$post = get_post( $post_id );