diff --git a/src/wp-includes/rest-api/class-wp-rest-request.php b/src/wp-includes/rest-api/class-wp-rest-request.php index 73548d4f38..937b5684cd 100644 --- a/src/wp-includes/rest-api/class-wp-rest-request.php +++ b/src/wp-includes/rest-api/class-wp-rest-request.php @@ -858,13 +858,9 @@ class WP_REST_Request implements ArrayAccess { $attributes = $this->get_attributes(); $required = array(); - // No arguments set, skip validation. - if ( empty( $attributes['args'] ) ) { - return true; - } - - foreach ( $attributes['args'] as $key => $arg ) { + $args = empty( $attributes['args'] ) ? array() : $attributes['args']; + foreach ( $args as $key => $arg ) { $param = $this->get_param( $key ); if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) { $required[] = $key; @@ -890,7 +886,7 @@ class WP_REST_Request implements ArrayAccess { */ $invalid_params = array(); - foreach ( $attributes['args'] as $key => $arg ) { + foreach ( $args as $key => $arg ) { $param = $this->get_param( $key ); @@ -919,8 +915,20 @@ class WP_REST_Request implements ArrayAccess { ); } - return true; + 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; } /** diff --git a/tests/phpunit/tests/rest-api/rest-request.php b/tests/phpunit/tests/rest-api/rest-request.php index 7ad78d81aa..d2f5021ce2 100644 --- a/tests/phpunit/tests/rest-api/rest-request.php +++ b/tests/phpunit/tests/rest-api/rest-request.php @@ -779,4 +779,72 @@ class Tests_REST_Request extends WP_UnitTestCase { $this->assertTrue( $request->has_param( 'param' ) ); $this->assertSame( 'value', $request->get_param( 'param' ) ); } + + /** + * @ticket 51255 + */ + public function test_route_level_validate_callback() { + $request = new WP_REST_Request(); + $request->set_query_params( array( 'test' => 'value' ) ); + + $error = new WP_Error( 'error_code', __( 'Error Message' ), array( 'status' => 400 ) ); + $callback = $this->createPartialMock( 'stdClass', array( '__invoke' ) ); + $callback->expects( self::once() )->method( '__invoke' )->with( self::identicalTo( $request ) )->willReturn( $error ); + $request->set_attributes( + array( + 'args' => array( + 'test' => array( + 'validate_callback' => '__return_true', + ), + ), + 'validate_callback' => $callback, + ) + ); + + $this->assertSame( $error, $request->has_valid_params() ); + } + + /** + * @ticket 51255 + */ + public function test_route_level_validate_callback_no_parameter_callbacks() { + $request = new WP_REST_Request(); + $request->set_query_params( array( 'test' => 'value' ) ); + + $error = new WP_Error( 'error_code', __( 'Error Message' ), array( 'status' => 400 ) ); + $callback = $this->createPartialMock( 'stdClass', array( '__invoke' ) ); + $callback->expects( self::once() )->method( '__invoke' )->with( self::identicalTo( $request ) )->willReturn( $error ); + $request->set_attributes( + array( + 'validate_callback' => $callback, + ) + ); + + $this->assertSame( $error, $request->has_valid_params() ); + } + + /** + * @ticket 51255 + */ + public function test_route_level_validate_callback_is_not_executed_if_parameter_validation_fails() { + $request = new WP_REST_Request(); + $request->set_query_params( array( 'test' => 'value' ) ); + + $callback = $this->createPartialMock( 'stdClass', array( '__invoke' ) ); + $callback->expects( self::never() )->method( '__invoke' ); + $request->set_attributes( + array( + 'validate_callback' => $callback, + 'args' => array( + 'test' => array( + 'validate_callback' => '__return_false', + ), + ), + ) + ); + + $valid = $request->has_valid_params(); + $this->assertWPError( $valid ); + $this->assertEquals( 'rest_invalid_param', $valid->get_error_code() ); + } }