diff -r 34716fd837a4 -r be944660c56a wp/wp-includes/rest-api.php
--- a/wp/wp-includes/rest-api.php Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-includes/rest-api.php Wed Sep 21 18:19:35 2022 +0200
@@ -94,7 +94,7 @@
_doing_it_wrong(
__FUNCTION__,
sprintf(
- /* translators: 1. The REST API route being registered. 2. The argument name. 3. The suggested function name. */
+ /* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */
__( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ),
'' . $clean_namespace . '/' . trim( $route, '/' ) . '
',
'permission_callback
',
@@ -130,7 +130,7 @@
* @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
* is 'null', the value cannot be set or updated. The function will be passed
* the model object, like WP_Post.
- * @type array|null $schema Optional. The callback function used to create the schema for this field.
+ * @type array|null $schema Optional. The schema for this field.
* Default is 'null', no schema entry will be returned.
* }
*/
@@ -153,7 +153,7 @@
}
/**
- * Registers rewrite rules for the API.
+ * Registers rewrite rules for the REST API.
*
* @since 4.4.0
*
@@ -209,6 +209,7 @@
add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
+ add_filter( 'rest_index', 'rest_add_application_passwords_to_index' );
}
/**
@@ -264,10 +265,20 @@
$controller = new WP_REST_Users_Controller;
$controller->register_routes();
+ // Application Passwords
+ $controller = new WP_REST_Application_Passwords_Controller();
+ $controller->register_routes();
+
// Comments.
$controller = new WP_REST_Comments_Controller;
$controller->register_routes();
+ $search_handlers = array(
+ new WP_REST_Post_Search_Handler(),
+ new WP_REST_Term_Search_Handler(),
+ new WP_REST_Post_Format_Search_Handler(),
+ );
+
/**
* Filters the search handlers to use in the REST search controller.
*
@@ -277,7 +288,7 @@
* handler instance must extend the `WP_REST_Search_Handler` class.
* Default is only a handler for posts.
*/
- $search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) );
+ $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers );
$controller = new WP_REST_Search_Controller( $search_handlers );
$controller->register_routes();
@@ -302,10 +313,30 @@
$controller = new WP_REST_Plugins_Controller();
$controller->register_routes();
+ // Sidebars.
+ $controller = new WP_REST_Sidebars_Controller();
+ $controller->register_routes();
+
+ // Widget Types.
+ $controller = new WP_REST_Widget_Types_Controller();
+ $controller->register_routes();
+
+ // Widgets.
+ $controller = new WP_REST_Widgets_Controller();
+ $controller->register_routes();
+
// Block Directory.
$controller = new WP_REST_Block_Directory_Controller();
$controller->register_routes();
+ // Pattern Directory.
+ $controller = new WP_REST_Pattern_Directory_Controller();
+ $controller->register_routes();
+
+ // Site Health.
+ $site_health = WP_Site_Health::get_instance();
+ $controller = new WP_REST_Site_Health_Controller( $site_health );
+ $controller->register_routes();
}
/**
@@ -370,9 +401,9 @@
* @todo Check if this is even necessary
* @global WP_Rewrite $wp_rewrite WordPress rewrite component.
*
- * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
- * @param string $path Optional. REST route. Default '/'.
- * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
+ * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog.
+ * @param string $path Optional. REST route. Default '/'.
+ * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
* @return string Full URL to the endpoint.
*/
function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
@@ -426,10 +457,10 @@
*
* @since 4.4.0
*
- * @param string $url REST URL.
- * @param string $path REST route.
- * @param int $blog_id Blog ID.
- * @param string $scheme Sanitization scheme.
+ * @param string $url REST URL.
+ * @param string $path REST route.
+ * @param int|null $blog_id Blog ID.
+ * @param string $scheme Sanitization scheme.
*/
return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
}
@@ -483,7 +514,7 @@
/**
* Filters the REST Server Class.
*
- * This filter allows you to adjust the server class used by the API, using a
+ * This filter allows you to adjust the server class used by the REST API, using a
* different class to handle requests.
*
* @since 4.4.0
@@ -494,7 +525,7 @@
$wp_rest_server = new $wp_rest_server_class;
/**
- * Fires when preparing to serve an API request.
+ * Fires when preparing to serve a REST API request.
*
* Endpoint objects should be created and register their hooks on this action rather
* than another action to ensure they're only loaded when needed.
@@ -787,7 +818,7 @@
}
/**
- * Filter the API response to include only a white-listed set of response object fields.
+ * Filters the REST API response to include only a white-listed set of response object fields.
*
* @since 4.8.0
*
@@ -997,7 +1028,7 @@
$result = wp_verify_nonce( $nonce, 'wp_rest' );
if ( ! $result ) {
- return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
+ return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
}
// Send a refreshed nonce in header.
@@ -1030,6 +1061,104 @@
}
/**
+ * Collects the status of authenticating with an application password.
+ *
+ * @since 5.6.0
+ * @since 5.7.0 Added the `$app_password` parameter.
+ *
+ * @global WP_User|WP_Error|null $wp_rest_application_password_status
+ * @global string|null $wp_rest_application_password_uuid
+ *
+ * @param WP_Error $user_or_error The authenticated user or error instance.
+ * @param array $app_password The Application Password used to authenticate.
+ */
+function rest_application_password_collect_status( $user_or_error, $app_password = array() ) {
+ global $wp_rest_application_password_status, $wp_rest_application_password_uuid;
+
+ $wp_rest_application_password_status = $user_or_error;
+
+ if ( empty( $app_password['uuid'] ) ) {
+ $wp_rest_application_password_uuid = null;
+ } else {
+ $wp_rest_application_password_uuid = $app_password['uuid'];
+ }
+}
+
+/**
+ * Gets the Application Password used for authenticating the request.
+ *
+ * @since 5.7.0
+ *
+ * @global string|null $wp_rest_application_password_uuid
+ *
+ * @return string|null The App Password UUID, or null if Application Passwords was not used.
+ */
+function rest_get_authenticated_app_password() {
+ global $wp_rest_application_password_uuid;
+
+ return $wp_rest_application_password_uuid;
+}
+
+/**
+ * Checks for errors when using application password-based authentication.
+ *
+ * @since 5.6.0
+ *
+ * @global WP_User|WP_Error|null $wp_rest_application_password_status
+ *
+ * @param WP_Error|null|true $result Error from another authentication handler,
+ * null if we should handle it, or another value if not.
+ * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true.
+ */
+function rest_application_password_check_errors( $result ) {
+ global $wp_rest_application_password_status;
+
+ if ( ! empty( $result ) ) {
+ return $result;
+ }
+
+ if ( is_wp_error( $wp_rest_application_password_status ) ) {
+ $data = $wp_rest_application_password_status->get_error_data();
+
+ if ( ! isset( $data['status'] ) ) {
+ $data['status'] = 401;
+ }
+
+ $wp_rest_application_password_status->add_data( $data );
+
+ return $wp_rest_application_password_status;
+ }
+
+ if ( $wp_rest_application_password_status instanceof WP_User ) {
+ return true;
+ }
+
+ return $result;
+}
+
+/**
+ * Adds Application Passwords info to the REST API index.
+ *
+ * @since 5.6.0
+ *
+ * @param WP_REST_Response $response The index response object.
+ * @return WP_REST_Response
+ */
+function rest_add_application_passwords_to_index( $response ) {
+ if ( ! wp_is_application_passwords_available() ) {
+ return $response;
+ }
+
+ $response->data['authentication']['application-passwords'] = array(
+ 'endpoints' => array(
+ 'authorization' => admin_url( 'authorize-application.php' ),
+ ),
+ );
+
+ return $response;
+}
+
+/**
* Retrieves the avatar urls in various sizes.
*
* @since 4.7.0
@@ -1161,7 +1290,7 @@
*
* @since 4.7.0
*
- * @return integer 401 if the user is not logged in, 403 if the user is logged in.
+ * @return int 401 if the user is not logged in, 403 if the user is logged in.
*/
function rest_authorization_required_code() {
return is_user_logged_in() ? 403 : 401;
@@ -1258,7 +1387,7 @@
* @since 4.7.0
*
* @param bool|string|int $value The value being evaluated.
- * @return boolean Returns the proper associated boolean value.
+ * @return bool Returns the proper associated boolean value.
*/
function rest_sanitize_boolean( $value ) {
// String values are translated to `true`; make sure 'false' is false.
@@ -1279,7 +1408,7 @@
* @since 4.7.0
*
* @param bool|string $maybe_bool The value being evaluated.
- * @return boolean True if a boolean, otherwise false.
+ * @return bool True if a boolean, otherwise false.
*/
function rest_is_boolean( $maybe_bool ) {
if ( is_bool( $maybe_bool ) ) {
@@ -1315,7 +1444,7 @@
* @return bool True if an integer, otherwise false.
*/
function rest_is_integer( $maybe_integer ) {
- return is_numeric( $maybe_integer ) && round( floatval( $maybe_integer ) ) === floatval( $maybe_integer );
+ return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer;
}
/**
@@ -1462,7 +1591,7 @@
if ( $invalid_types ) {
_doing_it_wrong(
__FUNCTION__,
- /* translators: 1. Parameter. 2. List of allowed types. */
+ /* translators: 1: Parameter, 2: List of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
@@ -1540,6 +1669,362 @@
}
/**
+ * Validates if the JSON Schema pattern matches a value.
+ *
+ * @since 5.6.0
+ *
+ * @param string $pattern The pattern to match against.
+ * @param string $value The value to check.
+ * @return bool True if the pattern matches the given value, false otherwise.
+ */
+function rest_validate_json_schema_pattern( $pattern, $value ) {
+ $escaped_pattern = str_replace( '#', '\\#', $pattern );
+
+ return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
+}
+
+/**
+ * Finds the schema for a property using the patternProperties keyword.
+ *
+ * @since 5.6.0
+ *
+ * @param string $property The property name to check.
+ * @param array $args The schema array to use.
+ * @return array|null The schema of matching pattern property, or null if no patterns match.
+ */
+function rest_find_matching_pattern_property_schema( $property, $args ) {
+ if ( isset( $args['patternProperties'] ) ) {
+ foreach ( $args['patternProperties'] as $pattern => $child_schema ) {
+ if ( rest_validate_json_schema_pattern( $pattern, $property ) ) {
+ return $child_schema;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Formats a combining operation error into a WP_Error object.
+ *
+ * @since 5.6.0
+ *
+ * @param string $param The parameter name.
+ * @param array $error The error details.
+ * @return WP_Error
+ */
+function rest_format_combining_operation_error( $param, $error ) {
+ $position = $error['index'];
+ $reason = $error['error_object']->get_error_message();
+
+ if ( isset( $error['schema']['title'] ) ) {
+ $title = $error['schema']['title'];
+
+ return new WP_Error(
+ 'rest_no_matching_schema',
+ /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
+ sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
+ array( 'position' => $position )
+ );
+ }
+
+ return new WP_Error(
+ 'rest_no_matching_schema',
+ /* translators: 1: Parameter, 2: Reason. */
+ sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
+ array( 'position' => $position )
+ );
+}
+
+/**
+ * Gets the error of combining operation.
+ *
+ * @since 5.6.0
+ *
+ * @param array $value The value to validate.
+ * @param string $param The parameter name, used in error messages.
+ * @param array $errors The errors array, to search for possible error.
+ * @return WP_Error The combining operation error.
+ */
+function rest_get_combining_operation_error( $value, $param, $errors ) {
+ // If there is only one error, simply return it.
+ if ( 1 === count( $errors ) ) {
+ return rest_format_combining_operation_error( $param, $errors[0] );
+ }
+
+ // Filter out all errors related to type validation.
+ $filtered_errors = array();
+ foreach ( $errors as $error ) {
+ $error_code = $error['error_object']->get_error_code();
+ $error_data = $error['error_object']->get_error_data();
+
+ if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
+ $filtered_errors[] = $error;
+ }
+ }
+
+ // If there is only one error left, simply return it.
+ if ( 1 === count( $filtered_errors ) ) {
+ return rest_format_combining_operation_error( $param, $filtered_errors[0] );
+ }
+
+ // If there are only errors related to object validation, try choosing the most appropriate one.
+ if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
+ $result = null;
+ $number = 0;
+
+ foreach ( $filtered_errors as $error ) {
+ if ( isset( $error['schema']['properties'] ) ) {
+ $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
+ if ( $n > $number ) {
+ $result = $error;
+ $number = $n;
+ }
+ }
+ }
+
+ if ( null !== $result ) {
+ return rest_format_combining_operation_error( $param, $result );
+ }
+ }
+
+ // If each schema has a title, include those titles in the error message.
+ $schema_titles = array();
+ foreach ( $errors as $error ) {
+ if ( isset( $error['schema']['title'] ) ) {
+ $schema_titles[] = $error['schema']['title'];
+ }
+ }
+
+ if ( count( $schema_titles ) === count( $errors ) ) {
+ /* translators: 1: Parameter, 2: Schema titles. */
+ return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
+ }
+
+ /* translators: %s: Parameter. */
+ return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) );
+}
+
+/**
+ * Finds the matching schema among the "anyOf" schemas.
+ *
+ * @since 5.6.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args The schema array to use.
+ * @param string $param The parameter name, used in error messages.
+ * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
+ */
+function rest_find_any_matching_schema( $value, $args, $param ) {
+ $errors = array();
+
+ foreach ( $args['anyOf'] as $index => $schema ) {
+ if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
+ $schema['type'] = $args['type'];
+ }
+
+ $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
+ if ( ! is_wp_error( $is_valid ) ) {
+ return $schema;
+ }
+
+ $errors[] = array(
+ 'error_object' => $is_valid,
+ 'schema' => $schema,
+ 'index' => $index,
+ );
+ }
+
+ return rest_get_combining_operation_error( $value, $param, $errors );
+}
+
+/**
+ * Finds the matching schema among the "oneOf" schemas.
+ *
+ * @since 5.6.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args The schema array to use.
+ * @param string $param The parameter name, used in error messages.
+ * @param bool $stop_after_first_match Optional. Whether the process should stop after the first successful match.
+ * @return array|WP_Error The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
+ */
+function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
+ $matching_schemas = array();
+ $errors = array();
+
+ foreach ( $args['oneOf'] as $index => $schema ) {
+ if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
+ $schema['type'] = $args['type'];
+ }
+
+ $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
+ if ( ! is_wp_error( $is_valid ) ) {
+ if ( $stop_after_first_match ) {
+ return $schema;
+ }
+
+ $matching_schemas[] = array(
+ 'schema_object' => $schema,
+ 'index' => $index,
+ );
+ } else {
+ $errors[] = array(
+ 'error_object' => $is_valid,
+ 'schema' => $schema,
+ 'index' => $index,
+ );
+ }
+ }
+
+ if ( ! $matching_schemas ) {
+ return rest_get_combining_operation_error( $value, $param, $errors );
+ }
+
+ if ( count( $matching_schemas ) > 1 ) {
+ $schema_positions = array();
+ $schema_titles = array();
+
+ foreach ( $matching_schemas as $schema ) {
+ $schema_positions[] = $schema['index'];
+
+ if ( isset( $schema['schema_object']['title'] ) ) {
+ $schema_titles[] = $schema['schema_object']['title'];
+ }
+ }
+
+ // If each schema has a title, include those titles in the error message.
+ if ( count( $schema_titles ) === count( $matching_schemas ) ) {
+ return new WP_Error(
+ 'rest_one_of_multiple_matches',
+ /* translators: 1: Parameter, 2: Schema titles. */
+ wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
+ array( 'positions' => $schema_positions )
+ );
+ }
+
+ return new WP_Error(
+ 'rest_one_of_multiple_matches',
+ /* translators: %s: Parameter. */
+ sprintf( __( '%s matches more than one of the expected formats.' ), $param ),
+ array( 'positions' => $schema_positions )
+ );
+ }
+
+ return $matching_schemas[0]['schema_object'];
+}
+
+/**
+ * Checks the equality of two values, following JSON Schema semantics.
+ *
+ * Property order is ignored for objects.
+ *
+ * Values must have been previously sanitized/coerced to their native types.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value1 The first value to check.
+ * @param mixed $value2 The second value to check.
+ * @return bool True if the values are equal or false otherwise.
+ */
+function rest_are_values_equal( $value1, $value2 ) {
+ if ( is_array( $value1 ) && is_array( $value2 ) ) {
+ if ( count( $value1 ) !== count( $value2 ) ) {
+ return false;
+ }
+
+ foreach ( $value1 as $index => $value ) {
+ if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ if ( is_int( $value1 ) && is_float( $value2 )
+ || is_float( $value1 ) && is_int( $value2 )
+ ) {
+ return (float) $value1 === (float) $value2;
+ }
+
+ return $value1 === $value2;
+}
+
+/**
+ * Validates that the given value is a member of the JSON Schema "enum".
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args The schema array to use.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
+ */
+function rest_validate_enum( $value, $args, $param ) {
+ $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
+ if ( is_wp_error( $sanitized_value ) ) {
+ return $sanitized_value;
+ }
+
+ foreach ( $args['enum'] as $enum_value ) {
+ if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
+ return true;
+ }
+ }
+
+ $encoded_enum_values = array();
+ foreach ( $args['enum'] as $enum_value ) {
+ $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
+ }
+
+ if ( count( $encoded_enum_values ) === 1 ) {
+ /* translators: 1: Parameter, 2: Valid values. */
+ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
+ }
+
+ /* translators: 1: Parameter, 2: List of valid values. */
+ return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
+}
+
+/**
+ * Get all valid JSON schema properties.
+ *
+ * @since 5.6.0
+ *
+ * @return string[] All valid JSON schema properties.
+ */
+function rest_get_allowed_schema_keywords() {
+ return array(
+ 'title',
+ 'description',
+ 'default',
+ 'type',
+ 'format',
+ 'enum',
+ 'items',
+ 'properties',
+ 'additionalProperties',
+ 'patternProperties',
+ 'minProperties',
+ 'maxProperties',
+ 'minimum',
+ 'maximum',
+ 'exclusiveMinimum',
+ 'exclusiveMaximum',
+ 'multipleOf',
+ 'minLength',
+ 'maxLength',
+ 'pattern',
+ 'minItems',
+ 'maxItems',
+ 'uniqueItems',
+ 'anyOf',
+ 'oneOf',
+ );
+}
+
+/**
* Validate a value based on a schema.
*
* @since 4.7.0
@@ -1551,6 +2036,10 @@
* Support the "minLength", "maxLength" and "pattern" keywords for strings.
* Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
* Validate required properties.
+ * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
+ * Support the "multipleOf" keyword for numbers and integers.
+ * Support the "patternProperties" keyword for objects.
+ * Support the "anyOf" and "oneOf" keywords.
*
* @param mixed $value The value to validate.
* @param array $args Schema array to use for validation.
@@ -1558,10 +2047,32 @@
* @return true|WP_Error
*/
function rest_validate_value_from_schema( $value, $args, $param = '' ) {
+ if ( isset( $args['anyOf'] ) ) {
+ $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
+ if ( is_wp_error( $matching_schema ) ) {
+ return $matching_schema;
+ }
+
+ if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
+ $args['type'] = $matching_schema['type'];
+ }
+ }
+
+ if ( isset( $args['oneOf'] ) ) {
+ $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
+ if ( is_wp_error( $matching_schema ) ) {
+ return $matching_schema;
+ }
+
+ if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
+ $args['type'] = $matching_schema['type'];
+ }
+ }
+
$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
if ( ! isset( $args['type'] ) ) {
- /* translators: 1. Parameter */
+ /* translators: %s: Parameter. */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
}
@@ -1569,8 +2080,12 @@
$best_type = rest_handle_multi_type_schema( $value, $args, $param );
if ( ! $best_type ) {
- /* translators: 1: Parameter, 2: List of types. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: List of types. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
+ array( 'param' => $param )
+ );
}
$args['type'] = $best_type;
@@ -1579,158 +2094,47 @@
if ( ! in_array( $args['type'], $allowed_types, true ) ) {
_doing_it_wrong(
__FUNCTION__,
- /* translators: 1. Parameter 2. The list of allowed types. */
+ /* translators: 1: Parameter, 2: The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
}
- if ( 'array' === $args['type'] ) {
- if ( ! rest_is_array( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
- }
-
- $value = rest_sanitize_array( $value );
-
- if ( isset( $args['items'] ) ) {
- foreach ( $value as $index => $v ) {
- $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
- if ( is_wp_error( $is_valid ) ) {
- return $is_valid;
- }
- }
- }
-
- if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
- /* translators: 1: Parameter, 2: Number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) );
- }
-
- if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
- /* translators: 1: Parameter, 2: Number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) );
- }
-
- if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
- /* translators: 1: Parameter */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
- }
+ switch ( $args['type'] ) {
+ case 'null':
+ $is_valid = rest_validate_null_value_from_schema( $value, $param );
+ break;
+ case 'boolean':
+ $is_valid = rest_validate_boolean_value_from_schema( $value, $param );
+ break;
+ case 'object':
+ $is_valid = rest_validate_object_value_from_schema( $value, $args, $param );
+ break;
+ case 'array':
+ $is_valid = rest_validate_array_value_from_schema( $value, $args, $param );
+ break;
+ case 'number':
+ $is_valid = rest_validate_number_value_from_schema( $value, $args, $param );
+ break;
+ case 'string':
+ $is_valid = rest_validate_string_value_from_schema( $value, $args, $param );
+ break;
+ case 'integer':
+ $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param );
+ break;
+ default:
+ $is_valid = true;
+ break;
}
- if ( 'object' === $args['type'] ) {
- if ( ! rest_is_object( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
- }
-
- $value = rest_sanitize_object( $value );
-
- if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
- foreach ( $args['required'] as $name ) {
- if ( ! array_key_exists( $name, $value ) ) {
- /* translators: 1: Property of an object, 2: Parameter. */
- return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
- }
- }
- } elseif ( isset( $args['properties'] ) ) { // schema version 3
- foreach ( $args['properties'] as $name => $property ) {
- if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
- /* translators: 1: Property of an object, 2: Parameter. */
- return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
- }
- }
- }
-
- foreach ( $value as $property => $v ) {
- if ( isset( $args['properties'][ $property ] ) ) {
- $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
- if ( is_wp_error( $is_valid ) ) {
- return $is_valid;
- }
- } elseif ( isset( $args['additionalProperties'] ) ) {
- if ( false === $args['additionalProperties'] ) {
- /* translators: %s: Property of an object. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
- }
-
- if ( is_array( $args['additionalProperties'] ) ) {
- $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
- if ( is_wp_error( $is_valid ) ) {
- return $is_valid;
- }
- }
- }
- }
- }
-
- if ( 'null' === $args['type'] ) {
- if ( null !== $value ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
- }
-
- return true;
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
}
if ( ! empty( $args['enum'] ) ) {
- if ( ! in_array( $value, $args['enum'], true ) ) {
- /* translators: 1: Parameter, 2: List of valid values. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
- }
- }
-
- if ( in_array( $args['type'], array( 'integer', 'number' ), true ) && ! is_numeric( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
- }
-
- if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
- }
-
- if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
- }
-
- if ( 'string' === $args['type'] ) {
- if ( ! is_string( $value ) ) {
- /* translators: 1: Parameter, 2: Type name. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
- }
-
- if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
- return new WP_Error(
- 'rest_invalid_param',
- sprintf(
- /* translators: 1: Parameter, 2: Number of characters. */
- _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ),
- $param,
- number_format_i18n( $args['minLength'] )
- )
- );
- }
-
- if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
- return new WP_Error(
- 'rest_invalid_param',
- sprintf(
- /* translators: 1: Parameter, 2: Number of characters. */
- _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ),
- $param,
- number_format_i18n( $args['maxLength'] )
- )
- );
- }
-
- if ( isset( $args['pattern'] ) ) {
- $pattern = str_replace( '#', '\\#', $args['pattern'] );
- if ( ! preg_match( '#' . $pattern . '#u', $value ) ) {
- /* translators: 1: Parameter, 2: Pattern. */
- return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
- }
+ $enum_contains_value = rest_validate_enum( $value, $args, $param );
+ if ( is_wp_error( $enum_contains_value ) ) {
+ return $enum_contains_value;
}
}
@@ -1760,60 +2164,468 @@
case 'ip':
if ( ! rest_is_ip_address( $value ) ) {
/* translators: %s: IP address. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
+ return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) );
}
break;
case 'uuid':
if ( ! wp_is_uuid( $value ) ) {
- /* translators: %s is the name of a JSON field expecting a valid uuid. */
+ /* translators: %s: The name of a JSON field expecting a valid UUID. */
return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
}
break;
}
}
- if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
- if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
- if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
- } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
- }
- } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
- if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
- /* translators: 1: Parameter, 2: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
- } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
- /* translators: 1: Parameter, 2: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
+ return true;
+}
+
+/**
+ * Validates a null value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_null_value_from_schema( $value, $param ) {
+ if ( null !== $value ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
+ array( 'param' => $param )
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Validates a boolean value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_boolean_value_from_schema( $value, $param ) {
+ if ( ! rest_is_boolean( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
+ array( 'param' => $param )
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Validates an object value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_object_value_from_schema( $value, $args, $param ) {
+ if ( ! rest_is_object( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
+ array( 'param' => $param )
+ );
+ }
+
+ $value = rest_sanitize_object( $value );
+
+ if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
+ foreach ( $args['required'] as $name ) {
+ if ( ! array_key_exists( $name, $value ) ) {
+ return new WP_Error(
+ 'rest_property_required',
+ /* translators: 1: Property of an object, 2: Parameter. */
+ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
+ );
}
- } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
- if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
- if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
- }
- } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
- if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
- }
- } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
- if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
- }
- } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
- if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
- /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
+ }
+ } elseif ( isset( $args['properties'] ) ) { // schema version 3
+ foreach ( $args['properties'] as $name => $property ) {
+ if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
+ return new WP_Error(
+ 'rest_property_required',
+ /* translators: 1: Property of an object, 2: Parameter. */
+ sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
+ );
+ }
+ }
+ }
+
+ foreach ( $value as $property => $v ) {
+ if ( isset( $args['properties'][ $property ] ) ) {
+ $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
+ }
+ continue;
+ }
+
+ $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
+ if ( null !== $pattern_property_schema ) {
+ $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
+ }
+ continue;
+ }
+
+ if ( isset( $args['additionalProperties'] ) ) {
+ if ( false === $args['additionalProperties'] ) {
+ return new WP_Error(
+ 'rest_additional_properties_forbidden',
+ /* translators: %s: Property of an object. */
+ sprintf( __( '%1$s is not a valid property of Object.' ), $property )
+ );
+ }
+
+ if ( is_array( $args['additionalProperties'] ) ) {
+ $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
}
}
}
}
+ if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
+ return new WP_Error(
+ 'rest_too_few_properties',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number. */
+ _n(
+ '%1$s must contain at least %2$s property.',
+ '%1$s must contain at least %2$s properties.',
+ $args['minProperties']
+ ),
+ $param,
+ number_format_i18n( $args['minProperties'] )
+ )
+ );
+ }
+
+ if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
+ return new WP_Error(
+ 'rest_too_many_properties',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number. */
+ _n(
+ '%1$s must contain at most %2$s property.',
+ '%1$s must contain at most %2$s properties.',
+ $args['maxProperties']
+ ),
+ $param,
+ number_format_i18n( $args['maxProperties'] )
+ )
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Validates an array value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_array_value_from_schema( $value, $args, $param ) {
+ if ( ! rest_is_array( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
+ array( 'param' => $param )
+ );
+ }
+
+ $value = rest_sanitize_array( $value );
+
+ if ( isset( $args['items'] ) ) {
+ foreach ( $value as $index => $v ) {
+ $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
+ if ( is_wp_error( $is_valid ) ) {
+ return $is_valid;
+ }
+ }
+ }
+
+ if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
+ return new WP_Error(
+ 'rest_too_few_items',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number. */
+ _n(
+ '%1$s must contain at least %2$s item.',
+ '%1$s must contain at least %2$s items.',
+ $args['minItems']
+ ),
+ $param,
+ number_format_i18n( $args['minItems'] )
+ )
+ );
+ }
+
+ if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
+ return new WP_Error(
+ 'rest_too_many_items',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number. */
+ _n(
+ '%1$s must contain at most %2$s item.',
+ '%1$s must contain at most %2$s items.',
+ $args['maxItems']
+ ),
+ $param,
+ number_format_i18n( $args['maxItems'] )
+ )
+ );
+ }
+
+ if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
+ /* translators: %s: Parameter. */
+ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
+ }
+
+ return true;
+}
+
+/**
+ * Validates a number value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_number_value_from_schema( $value, $args, $param ) {
+ if ( ! is_numeric( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
+ array( 'param' => $param )
+ );
+ }
+
+ if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
+ return new WP_Error(
+ 'rest_invalid_multiple',
+ /* translators: 1: Parameter, 2: Multiplier. */
+ sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] )
+ );
+ }
+
+ if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
+ if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ /* translators: 1: Parameter, 2: Minimum number. */
+ sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] )
+ );
+ }
+
+ if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ /* translators: 1: Parameter, 2: Minimum number. */
+ sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] )
+ );
+ }
+ }
+
+ if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
+ if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ /* translators: 1: Parameter, 2: Maximum number. */
+ sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] )
+ );
+ }
+
+ if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ /* translators: 1: Parameter, 2: Maximum number. */
+ sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] )
+ );
+ }
+ }
+
+ if ( isset( $args['minimum'], $args['maximum'] ) ) {
+ if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
+ if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ sprintf(
+ /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
+ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ),
+ $param,
+ $args['minimum'],
+ $args['maximum']
+ )
+ );
+ }
+ }
+
+ if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
+ if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ sprintf(
+ /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
+ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ),
+ $param,
+ $args['minimum'],
+ $args['maximum']
+ )
+ );
+ }
+ }
+
+ if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) {
+ if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ sprintf(
+ /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
+ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ),
+ $param,
+ $args['minimum'],
+ $args['maximum']
+ )
+ );
+ }
+ }
+
+ if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
+ if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
+ return new WP_Error(
+ 'rest_out_of_bounds',
+ sprintf(
+ /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
+ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ),
+ $param,
+ $args['minimum'],
+ $args['maximum']
+ )
+ );
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Validates a string value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_string_value_from_schema( $value, $args, $param ) {
+ if ( ! is_string( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
+ array( 'param' => $param )
+ );
+ }
+
+ if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
+ return new WP_Error(
+ 'rest_too_short',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number of characters. */
+ _n(
+ '%1$s must be at least %2$s character long.',
+ '%1$s must be at least %2$s characters long.',
+ $args['minLength']
+ ),
+ $param,
+ number_format_i18n( $args['minLength'] )
+ )
+ );
+ }
+
+ if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
+ return new WP_Error(
+ 'rest_too_long',
+ sprintf(
+ /* translators: 1: Parameter, 2: Number of characters. */
+ _n(
+ '%1$s must be at most %2$s character long.',
+ '%1$s must be at most %2$s characters long.',
+ $args['maxLength']
+ ),
+ $param,
+ number_format_i18n( $args['maxLength'] )
+ )
+ );
+ }
+
+ if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_pattern',
+ /* translators: 1: Parameter, 2: Pattern. */
+ sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] )
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Validates an integer value based on a schema.
+ *
+ * @since 5.7.0
+ *
+ * @param mixed $value The value to validate.
+ * @param array $args Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_integer_value_from_schema( $value, $args, $param ) {
+ $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param );
+ if ( is_wp_error( $is_valid_number ) ) {
+ return $is_valid_number;
+ }
+
+ if ( ! rest_is_integer( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_type',
+ /* translators: 1: Parameter, 2: Type name. */
+ sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
+ array( 'param' => $param )
+ );
+ }
+
return true;
}
@@ -1822,6 +2634,7 @@
*
* @since 4.7.0
* @since 5.5.0 Added the `$param` parameter.
+ * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
*
* @param mixed $value The value to sanitize.
* @param array $args Schema array to use for sanitization.
@@ -1829,10 +2642,36 @@
* @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
*/
function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
+ if ( isset( $args['anyOf'] ) ) {
+ $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
+ if ( is_wp_error( $matching_schema ) ) {
+ return $matching_schema;
+ }
+
+ if ( ! isset( $args['type'] ) ) {
+ $args['type'] = $matching_schema['type'];
+ }
+
+ $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
+ }
+
+ if ( isset( $args['oneOf'] ) ) {
+ $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
+ if ( is_wp_error( $matching_schema ) ) {
+ return $matching_schema;
+ }
+
+ if ( ! isset( $args['type'] ) ) {
+ $args['type'] = $matching_schema['type'];
+ }
+
+ $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
+ }
+
$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
if ( ! isset( $args['type'] ) ) {
- /* translators: 1. Parameter */
+ /* translators: %s: Parameter. */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
}
@@ -1849,7 +2688,7 @@
if ( ! in_array( $args['type'], $allowed_types, true ) ) {
_doing_it_wrong(
__FUNCTION__,
- /* translators: 1. Parameter. 2. The list of allowed types. */
+ /* translators: 1: Parameter, 2: The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
@@ -1865,8 +2704,8 @@
}
if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
- /* translators: 1: Parameter */
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
+ /* translators: %s: Parameter. */
+ return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
}
return $value;
@@ -1878,7 +2717,16 @@
foreach ( $value as $property => $v ) {
if ( isset( $args['properties'][ $property ] ) ) {
$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
- } elseif ( isset( $args['additionalProperties'] ) ) {
+ continue;
+ }
+
+ $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
+ if ( null !== $pattern_property_schema ) {
+ $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
+ continue;
+ }
+
+ if ( isset( $args['additionalProperties'] ) ) {
if ( false === $args['additionalProperties'] ) {
unset( $value[ $property ] );
} elseif ( is_array( $args['additionalProperties'] ) ) {
@@ -1933,7 +2781,7 @@
}
if ( 'string' === $args['type'] ) {
- return strval( $value );
+ return (string) $value;
}
return $value;
@@ -1984,11 +2832,8 @@
$response = rest_do_request( $request );
if ( 200 === $response->status ) {
$server = rest_get_server();
- $data = (array) $response->get_data();
- $links = $server::get_compact_response_links( $response );
- if ( ! empty( $links ) ) {
- $data['_links'] = $links;
- }
+ $embed = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false;
+ $data = (array) $server->response_to_data( $response, $embed );
if ( 'OPTIONS' === $method ) {
$response = rest_send_allow_header( $response, $server, $request );
@@ -2034,6 +2879,8 @@
* Filters the response to remove any fields not available in the given context.
*
* @since 5.5.0
+ * @since 5.6.0 Support the "patternProperties" keyword for objects.
+ * Support the "anyOf" and "oneOf" keywords.
*
* @param array|object $data The response data to modify.
* @param array $schema The schema for the endpoint used to filter the response.
@@ -2041,6 +2888,28 @@
* @return array|object The filtered response data.
*/
function rest_filter_response_by_context( $data, $schema, $context ) {
+ if ( isset( $schema['anyOf'] ) ) {
+ $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
+ if ( ! is_wp_error( $matching_schema ) ) {
+ if ( ! isset( $schema['type'] ) ) {
+ $schema['type'] = $matching_schema['type'];
+ }
+
+ $data = rest_filter_response_by_context( $data, $matching_schema, $context );
+ }
+ }
+
+ if ( isset( $schema['oneOf'] ) ) {
+ $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
+ if ( ! is_wp_error( $matching_schema ) ) {
+ if ( ! isset( $schema['type'] ) ) {
+ $schema['type'] = $matching_schema['type'];
+ }
+
+ $data = rest_filter_response_by_context( $data, $matching_schema, $context );
+ }
+ }
+
if ( ! is_array( $data ) && ! is_object( $data ) ) {
return $data;
}
@@ -2074,8 +2943,13 @@
} elseif ( $is_object_type ) {
if ( isset( $schema['properties'][ $key ] ) ) {
$check = $schema['properties'][ $key ];
- } elseif ( $has_additional_properties ) {
- $check = $schema['additionalProperties'];
+ } else {
+ $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
+ if ( null !== $pattern_property_schema ) {
+ $check = $pattern_property_schema;
+ } elseif ( $has_additional_properties ) {
+ $check = $schema['additionalProperties'];
+ }
}
}
@@ -2113,6 +2987,7 @@
* Sets the "additionalProperties" to false by default for all object definitions in the schema.
*
* @since 5.5.0
+ * @since 5.6.0 Support the "patternProperties" keyword.
*
* @param array $schema The schema to modify.
* @return array The modified schema.
@@ -2127,6 +3002,12 @@
}
}
+ if ( isset( $schema['patternProperties'] ) ) {
+ foreach ( $schema['patternProperties'] as $key => $child_schema ) {
+ $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
+ }
+ }
+
if ( ! isset( $schema['additionalProperties'] ) ) {
$schema['additionalProperties'] = false;
}
@@ -2214,7 +3095,7 @@
$route = '';
// The only controller that works is the Terms controller.
- if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) {
+ if ( $controller instanceof WP_REST_Terms_Controller ) {
$namespace = 'wp/v2';
$rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
$route = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
@@ -2258,3 +3139,122 @@
*/
return apply_filters( 'rest_queried_resource_route', $route );
}
+
+/**
+ * Retrieves an array of endpoint arguments from the item schema and endpoint method.
+ *
+ * @since 5.6.0
+ *
+ * @param array $schema The full JSON schema for the endpoint.
+ * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
+ * checked for required values and may fall-back to a given default, this is not done
+ * on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
+ * @return array The endpoint arguments.
+ */
+function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
+
+ $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
+ $endpoint_args = array();
+ $valid_schema_properties = rest_get_allowed_schema_keywords();
+ $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
+
+ foreach ( $schema_properties as $field_id => $params ) {
+
+ // Arguments specified as `readonly` are not allowed to be set.
+ if ( ! empty( $params['readonly'] ) ) {
+ continue;
+ }
+
+ $endpoint_args[ $field_id ] = array(
+ 'validate_callback' => 'rest_validate_request_arg',
+ 'sanitize_callback' => 'rest_sanitize_request_arg',
+ );
+
+ if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
+ $endpoint_args[ $field_id ]['default'] = $params['default'];
+ }
+
+ if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
+ $endpoint_args[ $field_id ]['required'] = true;
+ }
+
+ foreach ( $valid_schema_properties as $schema_prop ) {
+ if ( isset( $params[ $schema_prop ] ) ) {
+ $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
+ }
+ }
+
+ // Merge in any options provided by the schema property.
+ if ( isset( $params['arg_options'] ) ) {
+
+ // Only use required / default from arg_options on CREATABLE endpoints.
+ if ( WP_REST_Server::CREATABLE !== $method ) {
+ $params['arg_options'] = array_diff_key(
+ $params['arg_options'],
+ array(
+ 'required' => '',
+ 'default' => '',
+ )
+ );
+ }
+
+ $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
+ }
+ }
+
+ return $endpoint_args;
+}
+
+
+/**
+ * Converts an error to a response object.
+ *
+ * This iterates over all error codes and messages to change it into a flat
+ * array. This enables simpler client behaviour, as it is represented as a
+ * list in JSON rather than an object/map.
+ *
+ * @since 5.7.0
+ *
+ * @param WP_Error $error WP_Error instance.
+ *
+ * @return WP_REST_Response List of associative arrays with code and message keys.
+ */
+function rest_convert_error_to_response( $error ) {
+ $status = array_reduce(
+ $error->get_all_error_data(),
+ function ( $status, $error_data ) {
+ return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status;
+ },
+ 500
+ );
+
+ $errors = array();
+
+ foreach ( (array) $error->errors as $code => $messages ) {
+ $all_data = $error->get_all_error_data( $code );
+ $last_data = array_pop( $all_data );
+
+ foreach ( (array) $messages as $message ) {
+ $formatted = array(
+ 'code' => $code,
+ 'message' => $message,
+ 'data' => $last_data,
+ );
+
+ if ( $all_data ) {
+ $formatted['additional_data'] = $all_data;
+ }
+
+ $errors[] = $formatted;
+ }
+ }
+
+ $data = $errors[0];
+ if ( count( $errors ) > 1 ) {
+ // Remove the primary error.
+ array_shift( $errors );
+ $data['additional_errors'] = $errors;
+ }
+
+ return new WP_REST_Response( $data, $status );
+}