wp/wp-includes/rest-api/class-wp-rest-request.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
--- a/wp/wp-includes/rest-api/class-wp-rest-request.php	Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-includes/rest-api/class-wp-rest-request.php	Wed Sep 21 18:19:35 2022 +0200
@@ -324,9 +324,22 @@
 	}
 
 	/**
+	 * Checks if the request has specified a JSON content-type.
+	 *
+	 * @since 5.6.0
+	 *
+	 * @return bool True if the content-type header is JSON.
+	 */
+	public function is_json_content_type() {
+		$content_type = $this->get_content_type();
+
+		return isset( $content_type['value'] ) && wp_is_json_media_type( $content_type['value'] );
+	}
+
+	/**
 	 * Retrieves the parameter priority order.
 	 *
-	 * Used when checking parameters in get_param().
+	 * Used when checking parameters in WP_REST_Request::get_param().
 	 *
 	 * @since 4.4.0
 	 *
@@ -335,8 +348,7 @@
 	protected function get_parameter_order() {
 		$order = array();
 
-		$content_type = $this->get_content_type();
-		if ( isset( $content_type['value'] ) && 'application/json' === $content_type['value'] ) {
+		if ( $this->is_json_content_type() ) {
 			$order[] = 'JSON';
 		}
 
@@ -359,10 +371,10 @@
 		$order[] = 'defaults';
 
 		/**
-		 * Filters the parameter order.
+		 * Filters the parameter priority order for a REST API request.
 		 *
-		 * The order affects which parameters are checked when using get_param() and family.
-		 * This acts similarly to PHP's `request_order` setting.
+		 * The order affects which parameters are checked when using WP_REST_Request::get_param()
+		 * and family. This acts similarly to PHP's `request_order` setting.
 		 *
 		 * @since 4.4.0
 		 *
@@ -658,9 +670,7 @@
 		$this->parsed_json = true;
 
 		// Check that we actually got JSON.
-		$content_type = $this->get_content_type();
-
-		if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
+		if ( ! $this->is_json_content_type() ) {
 			return true;
 		}
 
@@ -792,7 +802,8 @@
 
 		$order = $this->get_parameter_order();
 
-		$invalid_params = array();
+		$invalid_params  = array();
+		$invalid_details = array();
 
 		foreach ( $order as $type ) {
 			if ( empty( $this->params[ $type ] ) ) {
@@ -815,10 +826,12 @@
 					continue;
 				}
 
+				/** @var mixed|WP_Error $sanitized_value */
 				$sanitized_value = call_user_func( $param_args['sanitize_callback'], $value, $this, $key );
 
 				if ( is_wp_error( $sanitized_value ) ) {
-					$invalid_params[ $key ] = $sanitized_value->get_error_message();
+					$invalid_params[ $key ]  = implode( ' ', $sanitized_value->get_error_messages() );
+					$invalid_details[ $key ] = rest_convert_error_to_response( $sanitized_value )->get_data();
 				} else {
 					$this->params[ $type ][ $key ] = $sanitized_value;
 				}
@@ -831,8 +844,9 @@
 				/* translators: %s: List of invalid parameters. */
 				sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ),
 				array(
-					'status' => 400,
-					'params' => $invalid_params,
+					'status'  => 400,
+					'params'  => $invalid_params,
+					'details' => $invalid_details,
 				)
 			);
 		}
@@ -845,7 +859,7 @@
 	 *
 	 * @since 4.4.0
 	 *
-	 * @return bool|WP_Error True if there are no parameters to validate or if all pass validation,
+	 * @return true|WP_Error True if there are no parameters to validate or if all pass validation,
 	 *                       WP_Error if required parameters are missing.
 	 */
 	public function has_valid_params() {
@@ -858,13 +872,9 @@
 		$attributes = $this->get_attributes();
 		$required   = array();
 
-		// No arguments set, skip validation.
-		if ( empty( $attributes['args'] ) ) {
-			return true;
-		}
+		$args = empty( $attributes['args'] ) ? array() : $attributes['args'];
 
-		foreach ( $attributes['args'] as $key => $arg ) {
-
+		foreach ( $args as $key => $arg ) {
 			$param = $this->get_param( $key );
 			if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
 				$required[] = $key;
@@ -888,13 +898,15 @@
 		 *
 		 * This is done after required checking as required checking is cheaper.
 		 */
-		$invalid_params = array();
+		$invalid_params  = array();
+		$invalid_details = array();
 
-		foreach ( $attributes['args'] as $key => $arg ) {
+		foreach ( $args as $key => $arg ) {
 
 			$param = $this->get_param( $key );
 
 			if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
+				/** @var bool|\WP_Error $valid_check */
 				$valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
 
 				if ( false === $valid_check ) {
@@ -902,7 +914,8 @@
 				}
 
 				if ( is_wp_error( $valid_check ) ) {
-					$invalid_params[ $key ] = $valid_check->get_error_message();
+					$invalid_params[ $key ]  = implode( ' ', $valid_check->get_error_messages() );
+					$invalid_details[ $key ] = rest_convert_error_to_response( $valid_check )->get_data();
 				}
 			}
 		}
@@ -913,14 +926,27 @@
 				/* translators: %s: List of invalid parameters. */
 				sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ),
 				array(
-					'status' => 400,
-					'params' => $invalid_params,
+					'status'  => 400,
+					'params'  => $invalid_params,
+					'details' => $invalid_details,
 				)
 			);
 		}
 
+		if ( isset( $attributes['validate_callback'] ) ) {
+			$valid_check = call_user_func( $attributes['validate_callback'], $this );
+
+			if ( is_wp_error( $valid_check ) ) {
+				return $valid_check;
+			}
+
+			if ( false === $valid_check ) {
+				// A WP_Error instance is preferred, but false is supported for parity with the per-arg validate_callback.
+				return new WP_Error( 'rest_invalid_params', __( 'Invalid parameters.' ), array( 'status' => 400 ) );
+			}
+		}
+
 		return true;
-
 	}
 
 	/**
@@ -1017,7 +1043,7 @@
 		}
 
 		/**
-		 * Filters the request generated from a URL.
+		 * Filters the REST API request generated from a URL.
 		 *
 		 * @since 4.5.0
 		 *