diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index 91b7343f56..dc359aec50 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -820,80 +820,7 @@ function rest_validate_request_arg( $value, $request, $param ) { } $args = $attributes['args'][ $param ]; - if ( ! empty( $args['enum'] ) ) { - if ( ! in_array( $value, $args['enum'], true ) ) { - return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); - } - } - - if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) { - return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); - } - - if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { - return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); - } - - if ( 'string' === $args['type'] && ! is_string( $value ) ) { - return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); - } - - if ( isset( $args['format'] ) ) { - switch ( $args['format'] ) { - case 'date-time' : - if ( ! rest_parse_date( $value ) ) { - return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) ); - } - break; - - case 'email' : - if ( ! is_email( $value ) ) { - return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) ); - } - break; - case 'ipv4' : - if ( ! rest_is_ip_address( $value ) ) { - return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); - } - break; - } - } - - if ( in_array( $args['type'], array( 'numeric', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { - if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { - if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) ); - } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) ); - } - } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { - if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { - return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) ); - } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { - return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) ); - } - } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { - if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { - if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', 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'] ) ); - } - } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { - if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', 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'] ) ); - } - } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { - if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', 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'] ) ); - } - } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { - if ( $value > $args['maximum'] || $value < $args['minimum'] ) { - return new WP_Error( 'rest_invalid_param', 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; + return rest_validate_value_from_schema( $value, $args, $param ); } /** @@ -913,34 +840,7 @@ function rest_sanitize_request_arg( $value, $request, $param ) { } $args = $attributes['args'][ $param ]; - if ( 'integer' === $args['type'] ) { - return (int) $value; - } - - if ( 'boolean' === $args['type'] ) { - return rest_sanitize_boolean( $value ); - } - - if ( isset( $args['format'] ) ) { - switch ( $args['format'] ) { - case 'date-time' : - return sanitize_text_field( $value ); - - case 'email' : - /* - * sanitize_email() validates, which would be unexpected - */ - return sanitize_text_field( $value ); - - case 'uri' : - return esc_url_raw( $value ); - - case 'ipv4' : - return sanitize_text_field( $value ); - } - } - - return $value; + return rest_sanitize_value_from_schema( $value, $args, $param ); } /** @@ -1084,3 +984,154 @@ function rest_get_avatar_sizes() { */ return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); } + +/** + * Validate a value based on a schema. + * + * @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_value_from_schema( $value, $args, $param = '' ) { + if ( 'array' === $args['type'] ) { + if ( ! is_array( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'array' ) ); + } + 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 ( ! empty( $args['enum'] ) ) { + if ( ! in_array( $value, $args['enum'], true ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); + } + } + + if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) ); + } + + if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); + } + + if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); + } + + if ( 'string' === $args['type'] && ! is_string( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); + } + + if ( isset( $args['format'] ) ) { + switch ( $args['format'] ) { + case 'date-time' : + if ( ! rest_parse_date( $value ) ) { + return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) ); + } + break; + + case 'email' : + if ( ! is_email( $value ) ) { + return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) ); + } + break; + case 'ipv4' : + if ( ! rest_is_ip_address( $value ) ) { + return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); + } + 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'] ) { + return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) ); + } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { + return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) ); + } + } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { + if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { + return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) ); + } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { + return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) ); + } + } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { + if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { + if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { + return new WP_Error( 'rest_invalid_param', 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'] ) ); + } + } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { + if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { + return new WP_Error( 'rest_invalid_param', 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'] ) ); + } + } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { + if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { + return new WP_Error( 'rest_invalid_param', 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'] ) ); + } + } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { + if ( $value > $args['maximum'] || $value < $args['minimum'] ) { + return new WP_Error( 'rest_invalid_param', 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; +} + +/** + * Sanitize a value based on a schema. + * + * @param mixed $value The value to sanitize. + * @param array $args Schema array to use for sanitization. + * @return true|WP_Error + */ +function rest_sanitize_value_from_schema( $value, $args ) { + if ( 'array' === $args['type'] ) { + if ( empty( $args['items'] ) ) { + return (array) $value; + } + foreach ( $value as $index => $v ) { + $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] ); + } + return $value; + } + if ( 'integer' === $args['type'] ) { + return (int) $value; + } + + if ( 'number' === $args['type'] ) { + return (float) $value; + } + + if ( 'boolean' === $args['type'] ) { + return rest_sanitize_boolean( $value ); + } + + if ( isset( $args['format'] ) ) { + switch ( $args['format'] ) { + case 'date-time' : + return sanitize_text_field( $value ); + + case 'email' : + /* + * sanitize_email() validates, which would be unexpected. + */ + return sanitize_text_field( $value ); + + case 'uri' : + return esc_url_raw( $value ); + + case 'ipv4' : + return sanitize_text_field( $value ); + } + } + + return $value; +} diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php index b4d9f65517..9bf2624fd9 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php @@ -559,7 +559,7 @@ abstract class WP_REST_Controller { $endpoint_args[ $field_id ]['required'] = true; } - foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) { + foreach ( array( 'type', 'format', 'enum', 'items' ) as $schema_prop ) { if ( isset( $params[ $schema_prop ] ) ) { $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; } diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index a9d8f3c557..49d030bad7 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1971,6 +1971,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { $schema['properties'][ $base ] = array( 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ), 'type' => 'array', + 'items' => array( + 'type' => 'integer', + ), 'context' => array( 'view', 'edit' ), ); $schema['properties'][ $base . '_exclude' ] = array( diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php index 66110efe8c..0f1ead65f2 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php @@ -288,8 +288,30 @@ class WP_REST_Settings_Controller extends WP_REST_Controller { foreach ( $options as $option_name => $option ) { $schema['properties'][ $option_name ] = $option['schema']; + $schema['properties'][ $option_name ]['arg_options'] = array( + 'sanitize_callback' => array( $this, 'sanitize_callback' ), + ); } return $this->add_additional_fields_schema( $schema ); } + + /** + * Custom sanitize callback used for all options to allow the use of 'null'. + * + * By default, the schema of settings will throw an error if a value is set to + * `null` as it's not a valid value for something like "type => string". We + * provide a wrapper sanitizer to whitelist the use of `null`. + * + * @param mixed $value The value for the setting. + * @param WP_REST_Request $request The request object. + * @param string $param The parameter name. + * @return mixed|WP_Error + */ + public function sanitize_callback( $value, $request, $param ) { + if ( is_null( $value ) ) { + return $value; + } + return rest_parse_request_arg( $value, $request, $param ); + } } diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index 48d649a10b..d366c70371 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -1006,6 +1006,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller { 'roles' => array( 'description' => __( 'Roles assigned to the resource.' ), 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), 'context' => array( 'edit' ), ), 'password' => array( diff --git a/wp-includes/version.php b/wp-includes/version.php index 7a8e3b2bc3..6fa2803807 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.7-beta1-39045'; +$wp_version = '4.7-beta1-39046'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.