PluginProbe
Gutenberg / 10.4.0
Gutenberg v10.4.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / class-wp-rest-batch-controller.php

class-wp-rest-batch-controller.php in Gutenberg 10.4.0, at lib/class-wp-rest-batch-controller.php

309 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API: WP_REST_Batch_Controller class
4 *
5 * @package Gutenberg
6 * @subpackage REST_API
7 */
8
9 /**
10 * Core class used to perform batch requests.
11 *
12 * @see WP_REST_Controller
13 */
14 class WP_REST_Batch_Controller {
15
16 /**
17 * Registers the REST API route.
18 *
19 * @since 9.2.0
20 */
21 public function register_routes() {
22 register_rest_route(
23 'v1',
24 'batch',
25 array(
26 'callback' => array( $this, 'serve_batch_request' ),
27 'permission_callback' => '__return_true',
28 'methods' => 'POST',
29 'args' => array(
30 'validation' => array(
31 'type' => 'string',
32 'enum' => array( 'require-all-validate', 'normal' ),
33 'default' => 'normal',
34 ),
35 'requests' => array(
36 'required' => true,
37 'type' => 'array',
38 'maxItems' => 25,
39 'items' => array(
40 'type' => 'object',
41 'properties' => array(
42 'method' => array(
43 'type' => 'string',
44 'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
45 'default' => 'POST',
46 ),
47 'path' => array(
48 'type' => 'string',
49 'required' => true,
50 ),
51 'body' => array(
52 'type' => 'object',
53 'properties' => array(),
54 'additionalProperties' => true,
55 ),
56 'headers' => array(
57 'type' => 'object',
58 'properties' => array(),
59 'additionalProperties' => array(
60 'type' => array( 'string', 'array' ),
61 'items' => array(
62 'type' => 'string',
63 ),
64 ),
65 ),
66 ),
67 ),
68 ),
69 ),
70 )
71 );
72 }
73
74 /**
75 * Serves the batch request.
76 *
77 * @since 9.2.0
78 *
79 * @param WP_REST_Request $batch_request The batch request object.
80 * @return WP_REST_Response
81 */
82 public function serve_batch_request( WP_REST_Request $batch_request ) {
83 $requests = array();
84
85 foreach ( $batch_request['requests'] as $args ) {
86 $parsed_url = wp_parse_url( $args['path'] );
87
88 if ( false === $parsed_url ) {
89 $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.', 'gutenberg' ), array( 'status' => 400 ) );
90
91 continue;
92 }
93
94 $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] );
95
96 if ( ! empty( $parsed_url['query'] ) ) {
97 $query_args = null; // Satisfy linter.
98 wp_parse_str( $parsed_url['query'], $query_args );
99 $single_request->set_query_params( $query_args );
100 }
101
102 if ( ! empty( $args['body'] ) ) {
103 $single_request->set_body_params( $args['body'] );
104 }
105
106 if ( ! empty( $args['headers'] ) ) {
107 $single_request->set_headers( $args['headers'] );
108 }
109
110 $requests[] = $single_request;
111 }
112
113 if ( ! is_callable( array( rest_get_server(), 'match_request_to_handler' ) ) ) {
114 return $this->polyfill_batching( $requests );
115 }
116
117 $matches = array();
118 $validation = array();
119 $has_error = false;
120
121 foreach ( $requests as $single_request ) {
122 $match = rest_get_server()->match_request_to_handler( $single_request );
123 $matches[] = $match;
124 $error = null;
125
126 if ( is_wp_error( $match ) ) {
127 $error = $match;
128 }
129
130 if ( ! $error ) {
131 list( $route, $handler ) = $match;
132
133 if ( isset( $handler['allow_batch'] ) ) {
134 $allow_batch = $handler['allow_batch'];
135 } else {
136 $route_options = rest_get_server()->get_route_options( $route );
137 $allow_batch = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false;
138 }
139
140 if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) {
141 $error = new WP_Error(
142 'rest_batch_not_allowed',
143 __( 'The requested route does not support batch requests.', 'gutenberg' ),
144 array( 'status' => 400 )
145 );
146 }
147 }
148
149 if ( ! $error ) {
150 $check_required = $single_request->has_valid_params();
151 if ( is_wp_error( $check_required ) ) {
152 $error = $check_required;
153 }
154 }
155
156 if ( ! $error ) {
157 $check_sanitized = $single_request->sanitize_params();
158 if ( is_wp_error( $check_sanitized ) ) {
159 $error = $check_sanitized;
160 }
161 }
162
163 if ( $error ) {
164 $has_error = true;
165 $validation[] = $error;
166 } else {
167 $validation[] = true;
168 }
169 }
170
171 $responses = array();
172
173 if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) {
174 foreach ( $validation as $valid ) {
175 if ( is_wp_error( $valid ) ) {
176 $responses[] = rest_get_server()->envelope_response( $this->error_to_response( $valid ), false )->get_data();
177 } else {
178 $responses[] = null;
179 }
180 }
181
182 return new WP_REST_Response(
183 array(
184 'failed' => 'validation',
185 'responses' => $responses,
186 ),
187 WP_Http::MULTI_STATUS
188 );
189 }
190
191 foreach ( $requests as $i => $single_request ) {
192 $clean_request = clone $single_request;
193 $clean_request->set_url_params( array() );
194 $clean_request->set_attributes( array() );
195 $clean_request->set_default_params( array() );
196
197 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
198 $result = apply_filters( 'rest_pre_dispatch', null, rest_get_server(), $clean_request );
199
200 if ( empty( $result ) ) {
201 $match = $matches[ $i ];
202 $error = null;
203
204 if ( is_wp_error( $validation[ $i ] ) ) {
205 $error = $validation[ $i ];
206 }
207
208 if ( is_wp_error( $match ) ) {
209 $result = $this->error_to_response( $match );
210 } else {
211 list( $route, $handler ) = $match;
212
213 if ( ! $error && ! is_callable( $handler['callback'] ) ) {
214 $error = new WP_Error(
215 'rest_invalid_handler',
216 __( 'The handler for the route is invalid', 'gutenberg' ),
217 array( 'status' => 500 )
218 );
219 }
220
221 $result = rest_get_server()->respond_to_request( $single_request, $route, $handler, $error );
222 }
223 }
224
225 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
226 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $single_request );
227
228 $responses[] = rest_get_server()->envelope_response( $result, false )->get_data();
229 }
230
231 return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
232 }
233
234 /**
235 * Polyfills a simple form of batching for compatibility for non-trunk installs.
236 *
237 * @since 9.2.0
238 *
239 * @param WP_REST_Request[] $requests The list of requests to perform.
240 * @return WP_REST_Response The response object.
241 */
242 protected function polyfill_batching( $requests ) {
243 $responses = array();
244
245 foreach ( $requests as $request ) {
246 if ( 0 !== strpos( $request->get_route(), '/__experimental' ) && 0 !== strpos( $request->get_route(), '/wp/v2/widgets' ) ) {
247 $error = new WP_Error(
248 'rest_batch_not_allowed',
249 __( 'The requested route does not support batch requests.', 'gutenberg' ),
250 array( 'status' => 400 )
251 );
252 $responses[] = rest_get_server()->envelope_response( $this->error_to_response( $error ), false )->get_data();
253 continue;
254 }
255
256 $result = rest_get_server()->dispatch( $request );
257 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
258 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), rest_get_server(), $request );
259
260 $responses[] = rest_get_server()->envelope_response( $result, false )->get_data();
261 }
262
263 return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
264 }
265
266 /**
267 * Converts an error to a response object.
268 *
269 * @see WP_REST_Server::error_to_response() This is a temporary copy of that method due to visibility.
270 *
271 * @since 9.2.0
272 *
273 * @param WP_Error $error WP_Error instance.
274 * @return WP_REST_Response List of associative arrays with code and message keys.
275 */
276 protected function error_to_response( $error ) {
277 $error_data = $error->get_error_data();
278
279 if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
280 $status = $error_data['status'];
281 } else {
282 $status = 500;
283 }
284
285 $errors = array();
286
287 foreach ( (array) $error->errors as $code => $messages ) {
288 foreach ( (array) $messages as $message ) {
289 $errors[] = array(
290 'code' => $code,
291 'message' => $message,
292 'data' => $error->get_error_data( $code ),
293 );
294 }
295 }
296
297 $data = $errors[0];
298 if ( count( $errors ) > 1 ) {
299 // Remove the primary error.
300 array_shift( $errors );
301 $data['additional_errors'] = $errors;
302 }
303
304 $response = new WP_REST_Response( $data, $status );
305
306 return $response;
307 }
308 }
309