From 62743fcf1843aa2bc4150b20a82f6dccb7c2dabe Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sat, 5 Sep 2020 17:50:57 -0400 Subject: [PATCH 01/10] First pass at a batch controller --- lib/class-wp-rest-batch-controller.php | 309 +++++++++++++++++++ lib/class-wp-rest-menu-items-controller.php | 77 +++++ lib/load.php | 3 + lib/rest-api.php | 9 + phpunit/class-rest-batch-controller-test.php | 217 +++++++++++++ 5 files changed, 615 insertions(+) create mode 100644 lib/class-wp-rest-batch-controller.php create mode 100644 phpunit/class-rest-batch-controller-test.php diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php new file mode 100644 index 00000000000000..297d475b4582a1 --- /dev/null +++ b/lib/class-wp-rest-batch-controller.php @@ -0,0 +1,309 @@ +server = rest_get_server(); + } + + /** + * Registers the REST API route. + * + * @since 9.0.0 + */ + public function register_routes() { + register_rest_route( + '__experimental', + 'batch', + array( + 'callback' => array( $this, 'serve_batch_request' ), + 'permission_callback' => '__return_true', + 'methods' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ), + 'args' => array( + 'validation' => array( + 'type' => 'string', + 'enum' => array( 'require-all-validate', 'normal' ), + 'default' => 'normal', + ), + 'requests' => array( + 'required' => true, + 'type' => 'array', + 'maxItems' => 25, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'path' => array( + 'type' => 'string', + 'required' => true, + ), + 'body' => array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => true, + ), + 'headers' => array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => array( + 'type' => array( 'string', 'array' ), + 'items' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + ), + ) + ); + } + + /** + * Serves the batch request. + * + * @since 9.0.0 + * + * @param WP_REST_Request $batch_request The batch request object. + * @return WP_REST_Response + */ + public function serve_batch_request( WP_REST_Request $batch_request ) { + $requests = array(); + + foreach ( $batch_request['requests'] as $args ) { + $parsed_url = wp_parse_url( $args['path'] ); + + if ( false === $parsed_url ) { + $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.', 'gutenberg' ), array( 'status' => 400 ) ); + + continue; + } + + $single_request = new WP_REST_Request( $batch_request->get_method(), $parsed_url['path'] ); + + if ( ! empty( $parsed_url['query'] ) ) { + wp_parse_str( $parsed_url['query'], $query_args ); + $single_request->set_query_params( $query_args ); + } + + if ( ! empty( $args['body'] ) ) { + $single_request->set_body_params( $args['body'] ); + } + + $requests[] = $single_request; + } + + if ( ! method_exists( $this->server, 'match_request_to_handler' ) ) { + return $this->polyfill_batching( $requests ); + } + + $matches = array(); + $validation = array(); + $has_error = false; + + foreach ( $requests as $single_request ) { + $match = $this->server->match_request_to_handler( $single_request ); + $matches[] = $match; + $error = null; + + if ( is_wp_error( $match ) ) { + $error = $match; + } + + if ( ! $error ) { + list( $route, $handler ) = $match; + + if ( isset( $handler['allow_batch'] ) ) { + $allow_batch = $handler['allow_batch']; + } else { + $allow_batch = ! empty( $this->server->get_route_options( $route )['allow_batch'] ); + } + + if ( ! $allow_batch ) { + $error = new WP_Error( + 'rest_batch_not_allowed', + __( 'The requested route does not support batch requests.', 'gutenberg' ), + array( 'status' => 400 ) + ); + } + } + + if ( ! $error ) { + $check_required = $single_request->has_valid_params(); + if ( is_wp_error( $check_required ) ) { + $error = $check_required; + } + } + + if ( ! $error ) { + $check_sanitized = $single_request->sanitize_params(); + if ( is_wp_error( $check_sanitized ) ) { + $error = $check_sanitized; + } + } + + if ( $error ) { + $has_error = true; + $validation[] = $error; + } else { + $validation[] = true; + } + } + + $responses = array(); + + if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) { + foreach ( $validation as $valid ) { + if ( is_wp_error( $valid ) ) { + $responses[] = $this->server->envelope_response( $this->error_to_response( $valid ), false )->get_data(); + } else { + $responses[] = null; + } + } + + return new WP_REST_Response( + array( + 'failed' => 'validation', + 'responses' => $responses, + ), + WP_Http::MULTI_STATUS + ); + } + + foreach ( $requests as $i => $batch_request ) { + $clean_request = clone $batch_request; + $clean_request->set_url_params( array() ); + $clean_request->set_attributes( array() ); + $clean_request->set_default_params( array() ); + + /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ + $result = apply_filters( 'rest_pre_dispatch', null, $this->server, $clean_request ); + + if ( empty( $result ) ) { + $match = $matches[ $i ]; + $error = null; + + if ( is_wp_error( $validation[ $i ] ) ) { + $error = $validation[ $i ]; + } + + if ( is_wp_error( $match ) ) { + $result = $this->error_to_response( $match ); + } else { + list( $route, $handler ) = $match; + + if ( ! $error && ! is_callable( $handler['callback'] ) ) { + $error = new WP_Error( + 'rest_invalid_handler', + __( 'The handler for the route is invalid' ), + array( 'status' => 500 ) + ); + } + + $result = $this->server->respond_to_request( $batch_request, $route, $handler, $error ); + } + } + + /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ + $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this->server, $batch_request ); + + $responses[] = $this->server->envelope_response( $result, false )->get_data(); + } + + return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS ); + } + + /** + * Polyfills a simple form of batching for compatibility for non-trunk installs. + * + * @since 9.0.0 + * + * @param WP_REST_Request[] $requests The list of requests to perform. + * @return WP_REST_Response The response object. + */ + protected function polyfill_batching( $requests ) { + $responses = array(); + + foreach ( $requests as $request ) { + if ( 0 !== strpos( $request->get_route(), '/__experimental' ) ) { + $error = new WP_Error( + 'rest_batch_not_allowed', + __( 'The requested route does not support batch requests.', 'gutenberg' ), + array( 'status' => 400 ) + ); + $responses[] = $this->server->envelope_response( $this->error_to_response( $error ), false )->get_data(); + continue; + } + + $result = $this->server->dispatch( $request ); + /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ + $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this->server, $request ); + + $responses[] = $this->server->envelope_response( $result, false )->get_data(); + } + + return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS ); + } + + /** + * Converts an error to a response object. + * + * @see WP_REST_Server::error_to_response() This is a temporary copy of that method due to visibility. + * + * @since 9.0.0 + * + * @param WP_Error $error WP_Error instance. + * @return WP_REST_Response List of associative arrays with code and message keys. + */ + protected function error_to_response( $error ) { + $error_data = $error->get_error_data(); + + if ( is_array( $error_data ) && isset( $error_data['status'] ) ) { + $status = $error_data['status']; + } else { + $status = 500; + } + + $errors = array(); + + foreach ( (array) $error->errors as $code => $messages ) { + foreach ( (array) $messages as $message ) { + $errors[] = array( + 'code' => $code, + 'message' => $message, + 'data' => $error->get_error_data( $code ), + ); + } + } + + $data = $errors[0]; + if ( count( $errors ) > 1 ) { + // Remove the primary error. + array_shift( $errors ); + $data['additional_errors'] = $errors; + } + + $response = new WP_REST_Response( $data, $status ); + + return $response; + } +} diff --git a/lib/class-wp-rest-menu-items-controller.php b/lib/class-wp-rest-menu-items-controller.php index f6cf192a80722c..1c80849534e831 100644 --- a/lib/class-wp-rest-menu-items-controller.php +++ b/lib/class-wp-rest-menu-items-controller.php @@ -22,6 +22,83 @@ public function __construct( $post_type ) { $this->namespace = '__experimental'; } + /** + * Overrides the route registration to support "allow_batch". + * + * @since 9.0.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'allow_batch' => true, + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + $schema = $this->get_item_schema(); + $get_item_args = array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ); + if ( isset( $schema['properties']['password'] ) ) { + $get_item_args['password'] = array( + 'description' => __( 'The password for the post if it is password protected.' ), + 'type' => 'string', + ); + } + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the object.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => $get_item_args, + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Whether to bypass Trash and force deletion.' ), + ), + ), + ), + 'allow_batch' => true, + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + /** * Get the post, if the ID is valid. * diff --git a/lib/load.php b/lib/load.php index 540539f56ba59a..e117ac2425c03c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -65,6 +65,9 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_REST_Term_Search_Handler' ) ) { require_once dirname( __FILE__ ) . '/class-wp-rest-term-search-handler.php'; } + if ( ! class_exists( 'WP_REST_Batch_Controller' ) ) { + require_once dirname( __FILE__ ) . '/class-wp-rest-batch-controller.php'; + } /** * End: Include for phase 2 */ diff --git a/lib/rest-api.php b/lib/rest-api.php index e10f52805bcb43..64dbaff83dc90a 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -197,6 +197,15 @@ function gutenberg_register_sidebars_endpoint() { } add_action( 'rest_api_init', 'gutenberg_register_sidebars_endpoint' ); +/** + * Registers the Sidebars REST API routes. + */ +function gutenberg_register_batch_endpoint() { + $batch = new WP_REST_Batch_Controller(); + $batch->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_batch_endpoint' ); + /** * Hook in to the nav menu item post type and enable a post type rest endpoint. * diff --git a/phpunit/class-rest-batch-controller-test.php b/phpunit/class-rest-batch-controller-test.php new file mode 100644 index 00000000000000..7cf7618cbba9cb --- /dev/null +++ b/phpunit/class-rest-batch-controller-test.php @@ -0,0 +1,217 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + } + + /** + * Delete test data after our tests run. + * + * @since 9.0.0 + */ + public static function wpTearDownAfterClass() { + self::delete_user( self::$administrator_id ); + } + + /** + * @since 9.0.0 + */ + public function setUp() { + parent::setUp(); + + $this->tag_id = self::factory()->tag->create(); + $this->menu_id = wp_create_nav_menu( rand_str() ); + $this->menu_item_id = wp_update_nav_menu_item( + $this->menu_id, + 0, + array( + 'menu-item-type' => 'taxonomy', + 'menu-item-object' => 'post_tag', + 'menu-item-object-id' => $this->tag_id, + 'menu-item-status' => 'publish', + ) + ); + } + + /** + * @ticket 50244 + */ + public function test_batch_requires_allow_batch_opt_in() { + register_rest_route( + 'test-ns/v1', + 'test', + array( + 'methods' => 'POST', + 'callback' => static function () { + return new WP_REST_Response( 'data' ); + }, + 'permission_callback' => '__return_true', + ) + ); + + $request = new WP_REST_Request( 'POST', '/__experimental/batch' ); + $request->set_body_params( + array( + 'requests' => array( + array( + 'path' => '/test-ns/v1/test', + ), + ), + ) + ); + + $response = rest_do_request( $request ); + + $this->assertEquals( 207, $response->get_status() ); + $this->assertEquals( 'rest_batch_not_allowed', $response->get_data()['responses'][0]['body']['code'] ); + } + + /** + * @ticket 50244 + */ + public function test_batch_pre_validation() { + wp_set_current_user( self::$administrator_id ); + + $request = new WP_REST_Request( 'POST', '/__experimental/batch' ); + $request->set_body_params( + array( + 'validation' => 'require-all-validate', + 'requests' => array( + array( + 'path' => '/__experimental/menu-items', + 'body' => array( + 'title' => 'Hello World', + 'content' => 'From the moon.', + 'type' => 'custom', + 'url' => '#', + 'menus' => $this->menu_id, + ), + ), + array( + 'path' => '/__experimental/menu-items', + 'body' => array( + 'title' => 'Hello Moon', + 'content' => 'From the world.', + 'status' => 'garbage', + 'type' => 'custom', + 'url' => '#', + 'menus' => $this->menu_id, + ), + ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 207, $response->get_status() ); + $this->assertArrayHasKey( 'failed', $data ); + $this->assertEquals( 'validation', $data['failed'] ); + $this->assertCount( 2, $data['responses'] ); + $this->assertNull( $data['responses'][0] ); + $this->assertEquals( 400, $data['responses'][1]['status'] ); + } + + /** + * @ticket 50244 + */ + public function test_batch_create() { + wp_set_current_user( self::$administrator_id ); + + $request = new WP_REST_Request( 'POST', '/__experimental/batch' ); + $request->set_body_params( + array( + 'requests' => array( + array( + 'path' => '/__experimental/menu-items', + 'body' => array( + 'title' => 'Hello World', + 'content' => 'From the moon.', + 'type' => 'custom', + 'url' => '#', + 'menus' => $this->menu_id, + ), + ), + array( + 'path' => '/__experimental/menu-items', + 'body' => array( + 'title' => 'Hello Moon', + 'status' => 'draft', + 'content' => 'From the world.', + 'type' => 'custom', + 'url' => '#', + 'menus' => $this->menu_id, + ), + ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 207, $response->get_status() ); + $this->assertArrayHasKey( 'responses', $data ); + $this->assertCount( 2, $data['responses'] ); + $this->assertEquals( 'Hello World', $data['responses'][0]['body']['title']['rendered'] ); + $this->assertEquals( 'Hello Moon', $data['responses'][1]['body']['title']['rendered'] ); + } +} From 86897a8dd7f1a2281bb360d2a8240cfabe5e603a Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sat, 5 Sep 2020 18:02:17 -0400 Subject: [PATCH 02/10] Fix docblock comment --- lib/rest-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rest-api.php b/lib/rest-api.php index 64dbaff83dc90a..b4bca6d9af37d2 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -198,7 +198,7 @@ function gutenberg_register_sidebars_endpoint() { add_action( 'rest_api_init', 'gutenberg_register_sidebars_endpoint' ); /** - * Registers the Sidebars REST API routes. + * Registers the Batch REST API routes. */ function gutenberg_register_batch_endpoint() { $batch = new WP_REST_Batch_Controller(); From d5404bee3da172e1284dc3725394600371fee28a Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sat, 5 Sep 2020 18:18:12 -0400 Subject: [PATCH 03/10] Fix lint issues --- lib/class-wp-rest-batch-controller.php | 39 ++++++++------------- lib/class-wp-rest-menu-items-controller.php | 6 ++-- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index 297d475b4582a1..9722de798a31e0 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -13,18 +13,6 @@ */ class WP_REST_Batch_Controller { - /** @var WP_REST_Server */ - private $server; - - /** - * Constructor. - * - * @since 9.0.0 - */ - public function __construct() { - $this->server = rest_get_server(); - } - /** * Registers the REST API route. * @@ -101,6 +89,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $single_request = new WP_REST_Request( $batch_request->get_method(), $parsed_url['path'] ); if ( ! empty( $parsed_url['query'] ) ) { + $query_args = null; // Satisfy linter. wp_parse_str( $parsed_url['query'], $query_args ); $single_request->set_query_params( $query_args ); } @@ -112,7 +101,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $requests[] = $single_request; } - if ( ! method_exists( $this->server, 'match_request_to_handler' ) ) { + if ( ! method_exists( rest_get_server(), 'match_request_to_handler' ) ) { return $this->polyfill_batching( $requests ); } @@ -121,7 +110,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $has_error = false; foreach ( $requests as $single_request ) { - $match = $this->server->match_request_to_handler( $single_request ); + $match = rest_get_server()->match_request_to_handler( $single_request ); $matches[] = $match; $error = null; @@ -135,7 +124,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { if ( isset( $handler['allow_batch'] ) ) { $allow_batch = $handler['allow_batch']; } else { - $allow_batch = ! empty( $this->server->get_route_options( $route )['allow_batch'] ); + $allow_batch = ! empty( rest_get_server()->get_route_options( $route )['allow_batch'] ); } if ( ! $allow_batch ) { @@ -174,7 +163,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) { foreach ( $validation as $valid ) { if ( is_wp_error( $valid ) ) { - $responses[] = $this->server->envelope_response( $this->error_to_response( $valid ), false )->get_data(); + $responses[] = rest_get_server()->envelope_response( $this->error_to_response( $valid ), false )->get_data(); } else { $responses[] = null; } @@ -196,7 +185,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $clean_request->set_default_params( array() ); /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - $result = apply_filters( 'rest_pre_dispatch', null, $this->server, $clean_request ); + $result = apply_filters( 'rest_pre_dispatch', null, rest_get_server(), $clean_request ); if ( empty( $result ) ) { $match = $matches[ $i ]; @@ -214,19 +203,19 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { if ( ! $error && ! is_callable( $handler['callback'] ) ) { $error = new WP_Error( 'rest_invalid_handler', - __( 'The handler for the route is invalid' ), + __( 'The handler for the route is invalid', 'gutenberg' ), array( 'status' => 500 ) ); } - $result = $this->server->respond_to_request( $batch_request, $route, $handler, $error ); + $result = rest_get_server()->respond_to_request( $batch_request, $route, $handler, $error ); } } /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this->server, $batch_request ); + $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $batch_request ); - $responses[] = $this->server->envelope_response( $result, false )->get_data(); + $responses[] = rest_get_server()->envelope_response( $result, false )->get_data(); } return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS ); @@ -250,15 +239,15 @@ protected function polyfill_batching( $requests ) { __( 'The requested route does not support batch requests.', 'gutenberg' ), array( 'status' => 400 ) ); - $responses[] = $this->server->envelope_response( $this->error_to_response( $error ), false )->get_data(); + $responses[] = rest_get_server()->envelope_response( $this->error_to_response( $error ), false )->get_data(); continue; } - $result = $this->server->dispatch( $request ); + $result = rest_get_server()->dispatch( $request ); /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this->server, $request ); + $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $request ); - $responses[] = $this->server->envelope_response( $result, false )->get_data(); + $responses[] = rest_get_server()->envelope_response( $result, false )->get_data(); } return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS ); diff --git a/lib/class-wp-rest-menu-items-controller.php b/lib/class-wp-rest-menu-items-controller.php index 1c80849534e831..bc4dcc536d966c 100644 --- a/lib/class-wp-rest-menu-items-controller.php +++ b/lib/class-wp-rest-menu-items-controller.php @@ -55,7 +55,7 @@ public function register_routes() { ); if ( isset( $schema['properties']['password'] ) ) { $get_item_args['password'] = array( - 'description' => __( 'The password for the post if it is password protected.' ), + 'description' => __( 'The password for the post if it is password protected.', 'gutenberg' ), 'type' => 'string', ); } @@ -65,7 +65,7 @@ public function register_routes() { array( 'args' => array( 'id' => array( - 'description' => __( 'Unique identifier for the object.' ), + 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), 'type' => 'integer', ), ), @@ -89,7 +89,7 @@ public function register_routes() { 'force' => array( 'type' => 'boolean', 'default' => false, - 'description' => __( 'Whether to bypass Trash and force deletion.' ), + 'description' => __( 'Whether to bypass Trash and force deletion.', 'gutenberg' ), ), ), ), From 1ef444f08a992a0d483eeb1bcc844d6f74c9380b Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sat, 5 Sep 2020 18:55:37 -0400 Subject: [PATCH 04/10] Manually fire rest_api_init since this isn't using the controller test case --- phpunit/class-rest-batch-controller-test.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/phpunit/class-rest-batch-controller-test.php b/phpunit/class-rest-batch-controller-test.php index 7cf7618cbba9cb..c5151bffad332a 100644 --- a/phpunit/class-rest-batch-controller-test.php +++ b/phpunit/class-rest-batch-controller-test.php @@ -88,6 +88,21 @@ public function setUp() { 'menu-item-status' => 'publish', ) ); + + /** @var WP_REST_Server $wp_rest_server */ + global $wp_rest_server; + $wp_rest_server = new Spy_REST_Server; + do_action( 'rest_api_init', $wp_rest_server ); + } + + /** + * @since 9.0.0 + */ + public function tearDown() { + parent::tearDown(); + /** @var WP_REST_Server $wp_rest_server */ + global $wp_rest_server; + $wp_rest_server = null; } /** @@ -96,7 +111,7 @@ public function setUp() { public function test_batch_requires_allow_batch_opt_in() { register_rest_route( 'test-ns/v1', - 'test', + '/test', array( 'methods' => 'POST', 'callback' => static function () { From 7471d6ddee1b7b02fe94496d66ceb77544fd666a Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Tue, 15 Sep 2020 14:56:05 -0400 Subject: [PATCH 05/10] Populate headers based on the batch request data --- lib/class-wp-rest-batch-controller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index 9722de798a31e0..eec77b8e8e3fb7 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -98,6 +98,10 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { $single_request->set_body_params( $args['body'] ); } + if ( ! empty( $args['headers'] ) ) { + $single_request->set_headers( $args['headers'] ); + } + $requests[] = $single_request; } From 47edcf69bfc14027c3050931a5e03c23f884d786 Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Tue, 15 Sep 2020 14:57:11 -0400 Subject: [PATCH 06/10] Consistently refer to the inner request as --- lib/class-wp-rest-batch-controller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index eec77b8e8e3fb7..c5ba46f0c3d2fc 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -182,8 +182,8 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { ); } - foreach ( $requests as $i => $batch_request ) { - $clean_request = clone $batch_request; + foreach ( $requests as $i => $single_request ) { + $clean_request = clone $single_request; $clean_request->set_url_params( array() ); $clean_request->set_attributes( array() ); $clean_request->set_default_params( array() ); @@ -212,12 +212,12 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { ); } - $result = rest_get_server()->respond_to_request( $batch_request, $route, $handler, $error ); + $result = rest_get_server()->respond_to_request( $single_request, $route, $handler, $error ); } } /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $batch_request ); + $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $single_request ); $responses[] = rest_get_server()->envelope_response( $result, false )->get_data(); } From 3f0751948838cd55cc58074d0425bf9ddd491af4 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 7 Oct 2020 15:05:29 +0200 Subject: [PATCH 07/10] Bump @since to 9.2.0 --- lib/class-wp-rest-batch-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index c5ba46f0c3d2fc..2bbda68fdcea71 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -16,7 +16,7 @@ class WP_REST_Batch_Controller { /** * Registers the REST API route. * - * @since 9.0.0 + * @since 9.2.0 */ public function register_routes() { register_rest_route( From 899d04ecebeb0e597ab57ba05860101f1f150bd0 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 7 Oct 2020 15:06:05 +0200 Subject: [PATCH 08/10] Bump @since to 9.2.0 --- lib/class-wp-rest-batch-controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-rest-batch-controller.php b/lib/class-wp-rest-batch-controller.php index 2bbda68fdcea71..b07262f9fc2354 100644 --- a/lib/class-wp-rest-batch-controller.php +++ b/lib/class-wp-rest-batch-controller.php @@ -69,7 +69,7 @@ public function register_routes() { /** * Serves the batch request. * - * @since 9.0.0 + * @since 9.2.0 * * @param WP_REST_Request $batch_request The batch request object. * @return WP_REST_Response @@ -228,7 +228,7 @@ public function serve_batch_request( WP_REST_Request $batch_request ) { /** * Polyfills a simple form of batching for compatibility for non-trunk installs. * - * @since 9.0.0 + * @since 9.2.0 * * @param WP_REST_Request[] $requests The list of requests to perform. * @return WP_REST_Response The response object. @@ -262,7 +262,7 @@ protected function polyfill_batching( $requests ) { * * @see WP_REST_Server::error_to_response() This is a temporary copy of that method due to visibility. * - * @since 9.0.0 + * @since 9.2.0 * * @param WP_Error $error WP_Error instance. * @return WP_REST_Response List of associative arrays with code and message keys. From a4dcdf11e7a79acfe58048645a19b89c1c3d22a0 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 7 Oct 2020 15:06:30 +0200 Subject: [PATCH 09/10] Bump @since to 9.2.0 --- lib/class-wp-rest-menu-items-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-rest-menu-items-controller.php b/lib/class-wp-rest-menu-items-controller.php index bc4dcc536d966c..6bf3ac474f552e 100644 --- a/lib/class-wp-rest-menu-items-controller.php +++ b/lib/class-wp-rest-menu-items-controller.php @@ -25,7 +25,7 @@ public function __construct( $post_type ) { /** * Overrides the route registration to support "allow_batch". * - * @since 9.0.0 + * @since 9.2.0 */ public function register_routes() { register_rest_route( From 01d3aaf6a173fff3ca4d0446421a712197bb3fbc Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 7 Oct 2020 15:07:14 +0200 Subject: [PATCH 10/10] Bump @since to 9.2.0 --- phpunit/class-rest-batch-controller-test.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/phpunit/class-rest-batch-controller-test.php b/phpunit/class-rest-batch-controller-test.php index c5151bffad332a..af1664034c859e 100644 --- a/phpunit/class-rest-batch-controller-test.php +++ b/phpunit/class-rest-batch-controller-test.php @@ -13,7 +13,7 @@ class REST_Batch_Controller_Test extends WP_Test_REST_TestCase { /** * Administrator user ID. * - * @since 9.0.0 + * @since 9.2.0 * * @var int */ @@ -22,7 +22,7 @@ class REST_Batch_Controller_Test extends WP_Test_REST_TestCase { /** * Tag id. * - * @since 9.0.0 + * @since 9.2.0 * * @var int */ @@ -31,7 +31,7 @@ class REST_Batch_Controller_Test extends WP_Test_REST_TestCase { /** * Menu id. * - * @since 9.0.0 + * @since 9.2.0 * * @var int */ @@ -40,7 +40,7 @@ class REST_Batch_Controller_Test extends WP_Test_REST_TestCase { /** * Menu item id. * - * @since 9.0.0 + * @since 9.2.0 * * @var int */ @@ -49,7 +49,7 @@ class REST_Batch_Controller_Test extends WP_Test_REST_TestCase { /** * Create test data before the tests run. * - * @since 9.0.0 + * @since 9.2.0 * * @param WP_UnitTest_Factory $factory Helper that lets us create fake data. */ @@ -64,14 +64,14 @@ public static function wpSetUpBeforeClass( $factory ) { /** * Delete test data after our tests run. * - * @since 9.0.0 + * @since 9.2.0 */ public static function wpTearDownAfterClass() { self::delete_user( self::$administrator_id ); } /** - * @since 9.0.0 + * @since 9.2.0 */ public function setUp() { parent::setUp(); @@ -96,7 +96,7 @@ public function setUp() { } /** - * @since 9.0.0 + * @since 9.2.0 */ public function tearDown() { parent::tearDown();