|
1 <?php |
|
2 /** |
|
3 * REST API functions. |
|
4 * |
|
5 * @package WordPress |
|
6 * @subpackage REST_API |
|
7 * @since 4.4.0 |
|
8 */ |
|
9 |
|
10 /** |
|
11 * Version number for our API. |
|
12 * |
|
13 * @var string |
|
14 */ |
|
15 define( 'REST_API_VERSION', '2.0' ); |
|
16 |
|
17 /** |
|
18 * Registers a REST API route. |
|
19 * |
|
20 * @since 4.4.0 |
|
21 * |
|
22 * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin. |
|
23 * @param string $route The base URL for route you are adding. |
|
24 * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for |
|
25 * multiple methods. Default empty array. |
|
26 * @param bool $override Optional. If the route already exists, should we override it? True overrides, |
|
27 * false merges (with newer overriding if duplicate keys exist). Default false. |
|
28 * @return bool True on success, false on error. |
|
29 */ |
|
30 function register_rest_route( $namespace, $route, $args = array(), $override = false ) { |
|
31 if ( empty( $namespace ) ) { |
|
32 /* |
|
33 * Non-namespaced routes are not allowed, with the exception of the main |
|
34 * and namespace indexes. If you really need to register a |
|
35 * non-namespaced route, call `WP_REST_Server::register_route` directly. |
|
36 */ |
|
37 _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' ); |
|
38 return false; |
|
39 } else if ( empty( $route ) ) { |
|
40 _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); |
|
41 return false; |
|
42 } |
|
43 |
|
44 if ( isset( $args['args'] ) ) { |
|
45 $common_args = $args['args']; |
|
46 unset( $args['args'] ); |
|
47 } else { |
|
48 $common_args = array(); |
|
49 } |
|
50 |
|
51 if ( isset( $args['callback'] ) ) { |
|
52 // Upgrade a single set to multiple. |
|
53 $args = array( $args ); |
|
54 } |
|
55 |
|
56 $defaults = array( |
|
57 'methods' => 'GET', |
|
58 'callback' => null, |
|
59 'args' => array(), |
|
60 ); |
|
61 foreach ( $args as $key => &$arg_group ) { |
|
62 if ( ! is_numeric( $key ) ) { |
|
63 // Route option, skip here. |
|
64 continue; |
|
65 } |
|
66 |
|
67 $arg_group = array_merge( $defaults, $arg_group ); |
|
68 $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); |
|
69 } |
|
70 |
|
71 $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' ); |
|
72 rest_get_server()->register_route( $namespace, $full_route, $args, $override ); |
|
73 return true; |
|
74 } |
|
75 |
|
76 /** |
|
77 * Registers a new field on an existing WordPress object type. |
|
78 * |
|
79 * @since 4.7.0 |
|
80 * |
|
81 * @global array $wp_rest_additional_fields Holds registered fields, organized |
|
82 * by object type. |
|
83 * |
|
84 * @param string|array $object_type Object(s) the field is being registered |
|
85 * to, "post"|"term"|"comment" etc. |
|
86 * @param string $attribute The attribute name. |
|
87 * @param array $args { |
|
88 * Optional. An array of arguments used to handle the registered field. |
|
89 * |
|
90 * @type string|array|null $get_callback Optional. The callback function used to retrieve the field |
|
91 * value. Default is 'null', the field will not be returned in |
|
92 * the response. |
|
93 * @type string|array|null $update_callback Optional. The callback function used to set and update the |
|
94 * field value. Default is 'null', the value cannot be set or |
|
95 * updated. |
|
96 * @type string|array|null $schema Optional. The callback function used to create the schema for |
|
97 * this field. Default is 'null', no schema entry will be returned. |
|
98 * } |
|
99 */ |
|
100 function register_rest_field( $object_type, $attribute, $args = array() ) { |
|
101 $defaults = array( |
|
102 'get_callback' => null, |
|
103 'update_callback' => null, |
|
104 'schema' => null, |
|
105 ); |
|
106 |
|
107 $args = wp_parse_args( $args, $defaults ); |
|
108 |
|
109 global $wp_rest_additional_fields; |
|
110 |
|
111 $object_types = (array) $object_type; |
|
112 |
|
113 foreach ( $object_types as $object_type ) { |
|
114 $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args; |
|
115 } |
|
116 } |
|
117 |
|
118 /** |
|
119 * Registers rewrite rules for the API. |
|
120 * |
|
121 * @since 4.4.0 |
|
122 * |
|
123 * @see rest_api_register_rewrites() |
|
124 * @global WP $wp Current WordPress environment instance. |
|
125 */ |
|
126 function rest_api_init() { |
|
127 rest_api_register_rewrites(); |
|
128 |
|
129 global $wp; |
|
130 $wp->add_query_var( 'rest_route' ); |
|
131 } |
|
132 |
|
133 /** |
|
134 * Adds REST rewrite rules. |
|
135 * |
|
136 * @since 4.4.0 |
|
137 * |
|
138 * @see add_rewrite_rule() |
|
139 * @global WP_Rewrite $wp_rewrite |
|
140 */ |
|
141 function rest_api_register_rewrites() { |
|
142 global $wp_rewrite; |
|
143 |
|
144 add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' ); |
|
145 add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' ); |
|
146 add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' ); |
|
147 add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' ); |
|
148 } |
|
149 |
|
150 /** |
|
151 * Registers the default REST API filters. |
|
152 * |
|
153 * Attached to the {@see 'rest_api_init'} action |
|
154 * to make testing and disabling these filters easier. |
|
155 * |
|
156 * @since 4.4.0 |
|
157 */ |
|
158 function rest_api_default_filters() { |
|
159 // Deprecated reporting. |
|
160 add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); |
|
161 add_filter( 'deprecated_function_trigger_error', '__return_false' ); |
|
162 add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); |
|
163 add_filter( 'deprecated_argument_trigger_error', '__return_false' ); |
|
164 |
|
165 // Default serving. |
|
166 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); |
|
167 add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); |
|
168 add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); |
|
169 |
|
170 add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 ); |
|
171 } |
|
172 |
|
173 /** |
|
174 * Registers default REST API routes. |
|
175 * |
|
176 * @since 4.7.0 |
|
177 */ |
|
178 function create_initial_rest_routes() { |
|
179 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { |
|
180 $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller'; |
|
181 |
|
182 if ( ! class_exists( $class ) ) { |
|
183 continue; |
|
184 } |
|
185 $controller = new $class( $post_type->name ); |
|
186 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { |
|
187 continue; |
|
188 } |
|
189 |
|
190 $controller->register_routes(); |
|
191 |
|
192 if ( post_type_supports( $post_type->name, 'revisions' ) ) { |
|
193 $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); |
|
194 $revisions_controller->register_routes(); |
|
195 } |
|
196 } |
|
197 |
|
198 // Post types. |
|
199 $controller = new WP_REST_Post_Types_Controller; |
|
200 $controller->register_routes(); |
|
201 |
|
202 // Post statuses. |
|
203 $controller = new WP_REST_Post_Statuses_Controller; |
|
204 $controller->register_routes(); |
|
205 |
|
206 // Taxonomies. |
|
207 $controller = new WP_REST_Taxonomies_Controller; |
|
208 $controller->register_routes(); |
|
209 |
|
210 // Terms. |
|
211 foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { |
|
212 $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller'; |
|
213 |
|
214 if ( ! class_exists( $class ) ) { |
|
215 continue; |
|
216 } |
|
217 $controller = new $class( $taxonomy->name ); |
|
218 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { |
|
219 continue; |
|
220 } |
|
221 |
|
222 $controller->register_routes(); |
|
223 } |
|
224 |
|
225 // Users. |
|
226 $controller = new WP_REST_Users_Controller; |
|
227 $controller->register_routes(); |
|
228 |
|
229 // Comments. |
|
230 $controller = new WP_REST_Comments_Controller; |
|
231 $controller->register_routes(); |
|
232 |
|
233 // Settings. |
|
234 $controller = new WP_REST_Settings_Controller; |
|
235 $controller->register_routes(); |
|
236 } |
|
237 |
|
238 /** |
|
239 * Loads the REST API. |
|
240 * |
|
241 * @since 4.4.0 |
|
242 * |
|
243 * @global WP $wp Current WordPress environment instance. |
|
244 */ |
|
245 function rest_api_loaded() { |
|
246 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { |
|
247 return; |
|
248 } |
|
249 |
|
250 /** |
|
251 * Whether this is a REST Request. |
|
252 * |
|
253 * @since 4.4.0 |
|
254 * @var bool |
|
255 */ |
|
256 define( 'REST_REQUEST', true ); |
|
257 |
|
258 // Initialize the server. |
|
259 $server = rest_get_server(); |
|
260 |
|
261 // Fire off the request. |
|
262 $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] ); |
|
263 if ( empty( $route ) ) { |
|
264 $route = '/'; |
|
265 } |
|
266 $server->serve_request( $route ); |
|
267 |
|
268 // We're done. |
|
269 die(); |
|
270 } |
|
271 |
|
272 /** |
|
273 * Retrieves the URL prefix for any API resource. |
|
274 * |
|
275 * @since 4.4.0 |
|
276 * |
|
277 * @return string Prefix. |
|
278 */ |
|
279 function rest_get_url_prefix() { |
|
280 /** |
|
281 * Filters the REST URL prefix. |
|
282 * |
|
283 * @since 4.4.0 |
|
284 * |
|
285 * @param string $prefix URL prefix. Default 'wp-json'. |
|
286 */ |
|
287 return apply_filters( 'rest_url_prefix', 'wp-json' ); |
|
288 } |
|
289 |
|
290 /** |
|
291 * Retrieves the URL to a REST endpoint on a site. |
|
292 * |
|
293 * Note: The returned URL is NOT escaped. |
|
294 * |
|
295 * @since 4.4.0 |
|
296 * |
|
297 * @todo Check if this is even necessary |
|
298 * @global WP_Rewrite $wp_rewrite |
|
299 * |
|
300 * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog. |
|
301 * @param string $path Optional. REST route. Default '/'. |
|
302 * @param string $scheme Optional. Sanitization scheme. Default 'rest'. |
|
303 * @return string Full URL to the endpoint. |
|
304 */ |
|
305 function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) { |
|
306 if ( empty( $path ) ) { |
|
307 $path = '/'; |
|
308 } |
|
309 |
|
310 if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) { |
|
311 global $wp_rewrite; |
|
312 |
|
313 if ( $wp_rewrite->using_index_permalinks() ) { |
|
314 $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme ); |
|
315 } else { |
|
316 $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme ); |
|
317 } |
|
318 |
|
319 $url .= '/' . ltrim( $path, '/' ); |
|
320 } else { |
|
321 $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); |
|
322 // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php |
|
323 // To work around this, we manually add index.php to the URL, avoiding the redirect. |
|
324 if ( 'index.php' !== substr( $url, 9 ) ) { |
|
325 $url .= 'index.php'; |
|
326 } |
|
327 |
|
328 $path = '/' . ltrim( $path, '/' ); |
|
329 |
|
330 $url = add_query_arg( 'rest_route', $path, $url ); |
|
331 } |
|
332 |
|
333 if ( is_ssl() ) { |
|
334 // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. |
|
335 if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) { |
|
336 $url = set_url_scheme( $url, 'https' ); |
|
337 } |
|
338 } |
|
339 |
|
340 if ( is_admin() && force_ssl_admin() ) { |
|
341 // In this situation the home URL may be http:, and `is_ssl()` may be |
|
342 // false, but the admin is served over https: (one way or another), so |
|
343 // REST API usage will be blocked by browsers unless it is also served |
|
344 // over HTTPS. |
|
345 $url = set_url_scheme( $url, 'https' ); |
|
346 } |
|
347 |
|
348 /** |
|
349 * Filters the REST URL. |
|
350 * |
|
351 * Use this filter to adjust the url returned by the get_rest_url() function. |
|
352 * |
|
353 * @since 4.4.0 |
|
354 * |
|
355 * @param string $url REST URL. |
|
356 * @param string $path REST route. |
|
357 * @param int $blog_id Blog ID. |
|
358 * @param string $scheme Sanitization scheme. |
|
359 */ |
|
360 return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme ); |
|
361 } |
|
362 |
|
363 /** |
|
364 * Retrieves the URL to a REST endpoint. |
|
365 * |
|
366 * Note: The returned URL is NOT escaped. |
|
367 * |
|
368 * @since 4.4.0 |
|
369 * |
|
370 * @param string $path Optional. REST route. Default empty. |
|
371 * @param string $scheme Optional. Sanitization scheme. Default 'json'. |
|
372 * @return string Full URL to the endpoint. |
|
373 */ |
|
374 function rest_url( $path = '', $scheme = 'json' ) { |
|
375 return get_rest_url( null, $path, $scheme ); |
|
376 } |
|
377 |
|
378 /** |
|
379 * Do a REST request. |
|
380 * |
|
381 * Used primarily to route internal requests through WP_REST_Server. |
|
382 * |
|
383 * @since 4.4.0 |
|
384 * |
|
385 * @param WP_REST_Request|string $request Request. |
|
386 * @return WP_REST_Response REST response. |
|
387 */ |
|
388 function rest_do_request( $request ) { |
|
389 $request = rest_ensure_request( $request ); |
|
390 return rest_get_server()->dispatch( $request ); |
|
391 } |
|
392 |
|
393 /** |
|
394 * Retrieves the current REST server instance. |
|
395 * |
|
396 * Instantiates a new instance if none exists already. |
|
397 * |
|
398 * @since 4.5.0 |
|
399 * |
|
400 * @global WP_REST_Server $wp_rest_server REST server instance. |
|
401 * |
|
402 * @return WP_REST_Server REST server instance. |
|
403 */ |
|
404 function rest_get_server() { |
|
405 /* @var WP_REST_Server $wp_rest_server */ |
|
406 global $wp_rest_server; |
|
407 |
|
408 if ( empty( $wp_rest_server ) ) { |
|
409 /** |
|
410 * Filters the REST Server Class. |
|
411 * |
|
412 * This filter allows you to adjust the server class used by the API, using a |
|
413 * different class to handle requests. |
|
414 * |
|
415 * @since 4.4.0 |
|
416 * |
|
417 * @param string $class_name The name of the server class. Default 'WP_REST_Server'. |
|
418 */ |
|
419 $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); |
|
420 $wp_rest_server = new $wp_rest_server_class; |
|
421 |
|
422 /** |
|
423 * Fires when preparing to serve an API request. |
|
424 * |
|
425 * Endpoint objects should be created and register their hooks on this action rather |
|
426 * than another action to ensure they're only loaded when needed. |
|
427 * |
|
428 * @since 4.4.0 |
|
429 * |
|
430 * @param WP_REST_Server $wp_rest_server Server object. |
|
431 */ |
|
432 do_action( 'rest_api_init', $wp_rest_server ); |
|
433 } |
|
434 |
|
435 return $wp_rest_server; |
|
436 } |
|
437 |
|
438 /** |
|
439 * Ensures request arguments are a request object (for consistency). |
|
440 * |
|
441 * @since 4.4.0 |
|
442 * |
|
443 * @param array|WP_REST_Request $request Request to check. |
|
444 * @return WP_REST_Request REST request instance. |
|
445 */ |
|
446 function rest_ensure_request( $request ) { |
|
447 if ( $request instanceof WP_REST_Request ) { |
|
448 return $request; |
|
449 } |
|
450 |
|
451 return new WP_REST_Request( 'GET', '', $request ); |
|
452 } |
|
453 |
|
454 /** |
|
455 * Ensures a REST response is a response object (for consistency). |
|
456 * |
|
457 * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc |
|
458 * without needing to double-check the object. Will also allow WP_Error to indicate error |
|
459 * responses, so users should immediately check for this value. |
|
460 * |
|
461 * @since 4.4.0 |
|
462 * |
|
463 * @param WP_Error|WP_HTTP_Response|mixed $response Response to check. |
|
464 * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response |
|
465 * is already an instance, WP_HTTP_Response, otherwise |
|
466 * returns a new WP_REST_Response instance. |
|
467 */ |
|
468 function rest_ensure_response( $response ) { |
|
469 if ( is_wp_error( $response ) ) { |
|
470 return $response; |
|
471 } |
|
472 |
|
473 if ( $response instanceof WP_HTTP_Response ) { |
|
474 return $response; |
|
475 } |
|
476 |
|
477 return new WP_REST_Response( $response ); |
|
478 } |
|
479 |
|
480 /** |
|
481 * Handles _deprecated_function() errors. |
|
482 * |
|
483 * @since 4.4.0 |
|
484 * |
|
485 * @param string $function The function that was called. |
|
486 * @param string $replacement The function that should have been called. |
|
487 * @param string $version Version. |
|
488 */ |
|
489 function rest_handle_deprecated_function( $function, $replacement, $version ) { |
|
490 if ( ! WP_DEBUG || headers_sent() ) { |
|
491 return; |
|
492 } |
|
493 if ( ! empty( $replacement ) ) { |
|
494 /* translators: 1: function name, 2: WordPress version number, 3: new function name */ |
|
495 $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement ); |
|
496 } else { |
|
497 /* translators: 1: function name, 2: WordPress version number */ |
|
498 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
|
499 } |
|
500 |
|
501 header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); |
|
502 } |
|
503 |
|
504 /** |
|
505 * Handles _deprecated_argument() errors. |
|
506 * |
|
507 * @since 4.4.0 |
|
508 * |
|
509 * @param string $function The function that was called. |
|
510 * @param string $message A message regarding the change. |
|
511 * @param string $version Version. |
|
512 */ |
|
513 function rest_handle_deprecated_argument( $function, $message, $version ) { |
|
514 if ( ! WP_DEBUG || headers_sent() ) { |
|
515 return; |
|
516 } |
|
517 if ( ! empty( $message ) ) { |
|
518 /* translators: 1: function name, 2: WordPress version number, 3: error message */ |
|
519 $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message ); |
|
520 } else { |
|
521 /* translators: 1: function name, 2: WordPress version number */ |
|
522 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
|
523 } |
|
524 |
|
525 header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); |
|
526 } |
|
527 |
|
528 /** |
|
529 * Sends Cross-Origin Resource Sharing headers with API requests. |
|
530 * |
|
531 * @since 4.4.0 |
|
532 * |
|
533 * @param mixed $value Response data. |
|
534 * @return mixed Response data. |
|
535 */ |
|
536 function rest_send_cors_headers( $value ) { |
|
537 $origin = get_http_origin(); |
|
538 |
|
539 if ( $origin ) { |
|
540 // Requests from file:// and data: URLs send "Origin: null" |
|
541 if ( 'null' !== $origin ) { |
|
542 $origin = esc_url_raw( $origin ); |
|
543 } |
|
544 header( 'Access-Control-Allow-Origin: ' . $origin ); |
|
545 header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); |
|
546 header( 'Access-Control-Allow-Credentials: true' ); |
|
547 header( 'Vary: Origin' ); |
|
548 } |
|
549 |
|
550 return $value; |
|
551 } |
|
552 |
|
553 /** |
|
554 * Handles OPTIONS requests for the server. |
|
555 * |
|
556 * This is handled outside of the server code, as it doesn't obey normal route |
|
557 * mapping. |
|
558 * |
|
559 * @since 4.4.0 |
|
560 * |
|
561 * @param mixed $response Current response, either response or `null` to indicate pass-through. |
|
562 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). |
|
563 * @param WP_REST_Request $request The request that was used to make current response. |
|
564 * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through. |
|
565 */ |
|
566 function rest_handle_options_request( $response, $handler, $request ) { |
|
567 if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) { |
|
568 return $response; |
|
569 } |
|
570 |
|
571 $response = new WP_REST_Response(); |
|
572 $data = array(); |
|
573 |
|
574 foreach ( $handler->get_routes() as $route => $endpoints ) { |
|
575 $match = preg_match( '@^' . $route . '$@i', $request->get_route() ); |
|
576 |
|
577 if ( ! $match ) { |
|
578 continue; |
|
579 } |
|
580 |
|
581 $data = $handler->get_data_for_route( $route, $endpoints, 'help' ); |
|
582 $response->set_matched_route( $route ); |
|
583 break; |
|
584 } |
|
585 |
|
586 $response->set_data( $data ); |
|
587 return $response; |
|
588 } |
|
589 |
|
590 /** |
|
591 * Sends the "Allow" header to state all methods that can be sent to the current route. |
|
592 * |
|
593 * @since 4.4.0 |
|
594 * |
|
595 * @param WP_REST_Response $response Current response being served. |
|
596 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). |
|
597 * @param WP_REST_Request $request The request that was used to make current response. |
|
598 * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods. |
|
599 */ |
|
600 function rest_send_allow_header( $response, $server, $request ) { |
|
601 $matched_route = $response->get_matched_route(); |
|
602 |
|
603 if ( ! $matched_route ) { |
|
604 return $response; |
|
605 } |
|
606 |
|
607 $routes = $server->get_routes(); |
|
608 |
|
609 $allowed_methods = array(); |
|
610 |
|
611 // Get the allowed methods across the routes. |
|
612 foreach ( $routes[ $matched_route ] as $_handler ) { |
|
613 foreach ( $_handler['methods'] as $handler_method => $value ) { |
|
614 |
|
615 if ( ! empty( $_handler['permission_callback'] ) ) { |
|
616 |
|
617 $permission = call_user_func( $_handler['permission_callback'], $request ); |
|
618 |
|
619 $allowed_methods[ $handler_method ] = true === $permission; |
|
620 } else { |
|
621 $allowed_methods[ $handler_method ] = true; |
|
622 } |
|
623 } |
|
624 } |
|
625 |
|
626 // Strip out all the methods that are not allowed (false values). |
|
627 $allowed_methods = array_filter( $allowed_methods ); |
|
628 |
|
629 if ( $allowed_methods ) { |
|
630 $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) ); |
|
631 } |
|
632 |
|
633 return $response; |
|
634 } |
|
635 |
|
636 /** |
|
637 * Filter the API response to include only a white-listed set of response object fields. |
|
638 * |
|
639 * @since 4.8.0 |
|
640 * |
|
641 * @param WP_REST_Response $response Current response being served. |
|
642 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). |
|
643 * @param WP_REST_Request $request The request that was used to make current response. |
|
644 * |
|
645 * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields. |
|
646 */ |
|
647 function rest_filter_response_fields( $response, $server, $request ) { |
|
648 if ( ! isset( $request['_fields'] ) || $response->is_error() ) { |
|
649 return $response; |
|
650 } |
|
651 |
|
652 $data = $response->get_data(); |
|
653 |
|
654 $fields = is_array( $request['_fields'] ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] ); |
|
655 |
|
656 if ( 0 === count( $fields ) ) { |
|
657 return $response; |
|
658 } |
|
659 |
|
660 // Trim off outside whitespace from the comma delimited list. |
|
661 $fields = array_map( 'trim', $fields ); |
|
662 |
|
663 $fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) ); |
|
664 |
|
665 if ( wp_is_numeric_array( $data ) ) { |
|
666 $new_data = array(); |
|
667 foreach ( $data as $item ) { |
|
668 $new_data[] = array_intersect_key( $item, $fields_as_keyed ); |
|
669 } |
|
670 } else { |
|
671 $new_data = array_intersect_key( $data, $fields_as_keyed ); |
|
672 } |
|
673 |
|
674 $response->set_data( $new_data ); |
|
675 |
|
676 return $response; |
|
677 } |
|
678 |
|
679 /** |
|
680 * Adds the REST API URL to the WP RSD endpoint. |
|
681 * |
|
682 * @since 4.4.0 |
|
683 * |
|
684 * @see get_rest_url() |
|
685 */ |
|
686 function rest_output_rsd() { |
|
687 $api_root = get_rest_url(); |
|
688 |
|
689 if ( empty( $api_root ) ) { |
|
690 return; |
|
691 } |
|
692 ?> |
|
693 <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" /> |
|
694 <?php |
|
695 } |
|
696 |
|
697 /** |
|
698 * Outputs the REST API link tag into page header. |
|
699 * |
|
700 * @since 4.4.0 |
|
701 * |
|
702 * @see get_rest_url() |
|
703 */ |
|
704 function rest_output_link_wp_head() { |
|
705 $api_root = get_rest_url(); |
|
706 |
|
707 if ( empty( $api_root ) ) { |
|
708 return; |
|
709 } |
|
710 |
|
711 echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n"; |
|
712 } |
|
713 |
|
714 /** |
|
715 * Sends a Link header for the REST API. |
|
716 * |
|
717 * @since 4.4.0 |
|
718 */ |
|
719 function rest_output_link_header() { |
|
720 if ( headers_sent() ) { |
|
721 return; |
|
722 } |
|
723 |
|
724 $api_root = get_rest_url(); |
|
725 |
|
726 if ( empty( $api_root ) ) { |
|
727 return; |
|
728 } |
|
729 |
|
730 header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false ); |
|
731 } |
|
732 |
|
733 /** |
|
734 * Checks for errors when using cookie-based authentication. |
|
735 * |
|
736 * WordPress' built-in cookie authentication is always active |
|
737 * for logged in users. However, the API has to check nonces |
|
738 * for each request to ensure users are not vulnerable to CSRF. |
|
739 * |
|
740 * @since 4.4.0 |
|
741 * |
|
742 * @global mixed $wp_rest_auth_cookie |
|
743 * |
|
744 * @param WP_Error|mixed $result Error from another authentication handler, |
|
745 * null if we should handle it, or another value |
|
746 * if not. |
|
747 * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. |
|
748 */ |
|
749 function rest_cookie_check_errors( $result ) { |
|
750 if ( ! empty( $result ) ) { |
|
751 return $result; |
|
752 } |
|
753 |
|
754 global $wp_rest_auth_cookie; |
|
755 |
|
756 /* |
|
757 * Is cookie authentication being used? (If we get an auth |
|
758 * error, but we're still logged in, another authentication |
|
759 * must have been used). |
|
760 */ |
|
761 if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) { |
|
762 return $result; |
|
763 } |
|
764 |
|
765 // Determine if there is a nonce. |
|
766 $nonce = null; |
|
767 |
|
768 if ( isset( $_REQUEST['_wpnonce'] ) ) { |
|
769 $nonce = $_REQUEST['_wpnonce']; |
|
770 } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) { |
|
771 $nonce = $_SERVER['HTTP_X_WP_NONCE']; |
|
772 } |
|
773 |
|
774 if ( null === $nonce ) { |
|
775 // No nonce at all, so act as if it's an unauthenticated request. |
|
776 wp_set_current_user( 0 ); |
|
777 return true; |
|
778 } |
|
779 |
|
780 // Check the nonce. |
|
781 $result = wp_verify_nonce( $nonce, 'wp_rest' ); |
|
782 |
|
783 if ( ! $result ) { |
|
784 return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) ); |
|
785 } |
|
786 |
|
787 // Send a refreshed nonce in header. |
|
788 rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) ); |
|
789 |
|
790 return true; |
|
791 } |
|
792 |
|
793 /** |
|
794 * Collects cookie authentication status. |
|
795 * |
|
796 * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors. |
|
797 * |
|
798 * @since 4.4.0 |
|
799 * |
|
800 * @see current_action() |
|
801 * @global mixed $wp_rest_auth_cookie |
|
802 */ |
|
803 function rest_cookie_collect_status() { |
|
804 global $wp_rest_auth_cookie; |
|
805 |
|
806 $status_type = current_action(); |
|
807 |
|
808 if ( 'auth_cookie_valid' !== $status_type ) { |
|
809 $wp_rest_auth_cookie = substr( $status_type, 12 ); |
|
810 return; |
|
811 } |
|
812 |
|
813 $wp_rest_auth_cookie = true; |
|
814 } |
|
815 |
|
816 /** |
|
817 * Parses an RFC3339 time into a Unix timestamp. |
|
818 * |
|
819 * @since 4.4.0 |
|
820 * |
|
821 * @param string $date RFC3339 timestamp. |
|
822 * @param bool $force_utc Optional. Whether to force UTC timezone instead of using |
|
823 * the timestamp's timezone. Default false. |
|
824 * @return int Unix timestamp. |
|
825 */ |
|
826 function rest_parse_date( $date, $force_utc = false ) { |
|
827 if ( $force_utc ) { |
|
828 $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); |
|
829 } |
|
830 |
|
831 $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; |
|
832 |
|
833 if ( ! preg_match( $regex, $date, $matches ) ) { |
|
834 return false; |
|
835 } |
|
836 |
|
837 return strtotime( $date ); |
|
838 } |
|
839 |
|
840 /** |
|
841 * Parses a date into both its local and UTC equivalent, in MySQL datetime format. |
|
842 * |
|
843 * @since 4.4.0 |
|
844 * |
|
845 * @see rest_parse_date() |
|
846 * |
|
847 * @param string $date RFC3339 timestamp. |
|
848 * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false. |
|
849 * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s), |
|
850 * null on failure. |
|
851 */ |
|
852 function rest_get_date_with_gmt( $date, $is_utc = false ) { |
|
853 // Whether or not the original date actually has a timezone string |
|
854 // changes the way we need to do timezone conversion. Store this info |
|
855 // before parsing the date, and use it later. |
|
856 $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date ); |
|
857 |
|
858 $date = rest_parse_date( $date ); |
|
859 |
|
860 if ( empty( $date ) ) { |
|
861 return null; |
|
862 } |
|
863 |
|
864 // At this point $date could either be a local date (if we were passed a |
|
865 // *local* date without a timezone offset) or a UTC date (otherwise). |
|
866 // Timezone conversion needs to be handled differently between these two |
|
867 // cases. |
|
868 if ( ! $is_utc && ! $has_timezone ) { |
|
869 $local = date( 'Y-m-d H:i:s', $date ); |
|
870 $utc = get_gmt_from_date( $local ); |
|
871 } else { |
|
872 $utc = date( 'Y-m-d H:i:s', $date ); |
|
873 $local = get_date_from_gmt( $utc ); |
|
874 } |
|
875 |
|
876 return array( $local, $utc ); |
|
877 } |
|
878 |
|
879 /** |
|
880 * Returns a contextual HTTP error code for authorization failure. |
|
881 * |
|
882 * @since 4.7.0 |
|
883 * |
|
884 * @return integer 401 if the user is not logged in, 403 if the user is logged in. |
|
885 */ |
|
886 function rest_authorization_required_code() { |
|
887 return is_user_logged_in() ? 403 : 401; |
|
888 } |
|
889 |
|
890 /** |
|
891 * Validate a request argument based on details registered to the route. |
|
892 * |
|
893 * @since 4.7.0 |
|
894 * |
|
895 * @param mixed $value |
|
896 * @param WP_REST_Request $request |
|
897 * @param string $param |
|
898 * @return WP_Error|boolean |
|
899 */ |
|
900 function rest_validate_request_arg( $value, $request, $param ) { |
|
901 $attributes = $request->get_attributes(); |
|
902 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
903 return true; |
|
904 } |
|
905 $args = $attributes['args'][ $param ]; |
|
906 |
|
907 return rest_validate_value_from_schema( $value, $args, $param ); |
|
908 } |
|
909 |
|
910 /** |
|
911 * Sanitize a request argument based on details registered to the route. |
|
912 * |
|
913 * @since 4.7.0 |
|
914 * |
|
915 * @param mixed $value |
|
916 * @param WP_REST_Request $request |
|
917 * @param string $param |
|
918 * @return mixed |
|
919 */ |
|
920 function rest_sanitize_request_arg( $value, $request, $param ) { |
|
921 $attributes = $request->get_attributes(); |
|
922 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
923 return $value; |
|
924 } |
|
925 $args = $attributes['args'][ $param ]; |
|
926 |
|
927 return rest_sanitize_value_from_schema( $value, $args ); |
|
928 } |
|
929 |
|
930 /** |
|
931 * Parse a request argument based on details registered to the route. |
|
932 * |
|
933 * Runs a validation check and sanitizes the value, primarily to be used via |
|
934 * the `sanitize_callback` arguments in the endpoint args registration. |
|
935 * |
|
936 * @since 4.7.0 |
|
937 * |
|
938 * @param mixed $value |
|
939 * @param WP_REST_Request $request |
|
940 * @param string $param |
|
941 * @return mixed |
|
942 */ |
|
943 function rest_parse_request_arg( $value, $request, $param ) { |
|
944 $is_valid = rest_validate_request_arg( $value, $request, $param ); |
|
945 |
|
946 if ( is_wp_error( $is_valid ) ) { |
|
947 return $is_valid; |
|
948 } |
|
949 |
|
950 $value = rest_sanitize_request_arg( $value, $request, $param ); |
|
951 |
|
952 return $value; |
|
953 } |
|
954 |
|
955 /** |
|
956 * Determines if an IP address is valid. |
|
957 * |
|
958 * Handles both IPv4 and IPv6 addresses. |
|
959 * |
|
960 * @since 4.7.0 |
|
961 * |
|
962 * @param string $ip IP address. |
|
963 * @return string|false The valid IP address, otherwise false. |
|
964 */ |
|
965 function rest_is_ip_address( $ip ) { |
|
966 $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; |
|
967 |
|
968 if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) { |
|
969 return false; |
|
970 } |
|
971 |
|
972 return $ip; |
|
973 } |
|
974 |
|
975 /** |
|
976 * Changes a boolean-like value into the proper boolean value. |
|
977 * |
|
978 * @since 4.7.0 |
|
979 * |
|
980 * @param bool|string|int $value The value being evaluated. |
|
981 * @return boolean Returns the proper associated boolean value. |
|
982 */ |
|
983 function rest_sanitize_boolean( $value ) { |
|
984 // String values are translated to `true`; make sure 'false' is false. |
|
985 if ( is_string( $value ) ) { |
|
986 $value = strtolower( $value ); |
|
987 if ( in_array( $value, array( 'false', '0' ), true ) ) { |
|
988 $value = false; |
|
989 } |
|
990 } |
|
991 |
|
992 // Everything else will map nicely to boolean. |
|
993 return (boolean) $value; |
|
994 } |
|
995 |
|
996 /** |
|
997 * Determines if a given value is boolean-like. |
|
998 * |
|
999 * @since 4.7.0 |
|
1000 * |
|
1001 * @param bool|string $maybe_bool The value being evaluated. |
|
1002 * @return boolean True if a boolean, otherwise false. |
|
1003 */ |
|
1004 function rest_is_boolean( $maybe_bool ) { |
|
1005 if ( is_bool( $maybe_bool ) ) { |
|
1006 return true; |
|
1007 } |
|
1008 |
|
1009 if ( is_string( $maybe_bool ) ) { |
|
1010 $maybe_bool = strtolower( $maybe_bool ); |
|
1011 |
|
1012 $valid_boolean_values = array( |
|
1013 'false', |
|
1014 'true', |
|
1015 '0', |
|
1016 '1', |
|
1017 ); |
|
1018 |
|
1019 return in_array( $maybe_bool, $valid_boolean_values, true ); |
|
1020 } |
|
1021 |
|
1022 if ( is_int( $maybe_bool ) ) { |
|
1023 return in_array( $maybe_bool, array( 0, 1 ), true ); |
|
1024 } |
|
1025 |
|
1026 return false; |
|
1027 } |
|
1028 |
|
1029 /** |
|
1030 * Retrieves the avatar urls in various sizes based on a given email address. |
|
1031 * |
|
1032 * @since 4.7.0 |
|
1033 * |
|
1034 * @see get_avatar_url() |
|
1035 * |
|
1036 * @param string $email Email address. |
|
1037 * @return array $urls Gravatar url for each size. |
|
1038 */ |
|
1039 function rest_get_avatar_urls( $email ) { |
|
1040 $avatar_sizes = rest_get_avatar_sizes(); |
|
1041 |
|
1042 $urls = array(); |
|
1043 foreach ( $avatar_sizes as $size ) { |
|
1044 $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) ); |
|
1045 } |
|
1046 |
|
1047 return $urls; |
|
1048 } |
|
1049 |
|
1050 /** |
|
1051 * Retrieves the pixel sizes for avatars. |
|
1052 * |
|
1053 * @since 4.7.0 |
|
1054 * |
|
1055 * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. |
|
1056 */ |
|
1057 function rest_get_avatar_sizes() { |
|
1058 /** |
|
1059 * Filters the REST avatar sizes. |
|
1060 * |
|
1061 * Use this filter to adjust the array of sizes returned by the |
|
1062 * `rest_get_avatar_sizes` function. |
|
1063 * |
|
1064 * @since 4.4.0 |
|
1065 * |
|
1066 * @param array $sizes An array of int values that are the pixel sizes for avatars. |
|
1067 * Default `[ 24, 48, 96 ]`. |
|
1068 */ |
|
1069 return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); |
|
1070 } |
|
1071 |
|
1072 /** |
|
1073 * Validate a value based on a schema. |
|
1074 * |
|
1075 * @since 4.7.0 |
|
1076 * |
|
1077 * @param mixed $value The value to validate. |
|
1078 * @param array $args Schema array to use for validation. |
|
1079 * @param string $param The parameter name, used in error messages. |
|
1080 * @return true|WP_Error |
|
1081 */ |
|
1082 function rest_validate_value_from_schema( $value, $args, $param = '' ) { |
|
1083 if ( 'array' === $args['type'] ) { |
|
1084 if ( ! is_array( $value ) ) { |
|
1085 $value = preg_split( '/[\s,]+/', $value ); |
|
1086 } |
|
1087 if ( ! wp_is_numeric_array( $value ) ) { |
|
1088 /* translators: 1: parameter, 2: type name */ |
|
1089 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) ); |
|
1090 } |
|
1091 foreach ( $value as $index => $v ) { |
|
1092 $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); |
|
1093 if ( is_wp_error( $is_valid ) ) { |
|
1094 return $is_valid; |
|
1095 } |
|
1096 } |
|
1097 } |
|
1098 |
|
1099 if ( 'object' === $args['type'] ) { |
|
1100 if ( $value instanceof stdClass ) { |
|
1101 $value = (array) $value; |
|
1102 } |
|
1103 if ( ! is_array( $value ) ) { |
|
1104 /* translators: 1: parameter, 2: type name */ |
|
1105 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) ); |
|
1106 } |
|
1107 |
|
1108 foreach ( $value as $property => $v ) { |
|
1109 if ( isset( $args['properties'][ $property ] ) ) { |
|
1110 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
|
1111 if ( is_wp_error( $is_valid ) ) { |
|
1112 return $is_valid; |
|
1113 } |
|
1114 } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
|
1115 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); |
|
1116 } |
|
1117 } |
|
1118 } |
|
1119 |
|
1120 if ( ! empty( $args['enum'] ) ) { |
|
1121 if ( ! in_array( $value, $args['enum'], true ) ) { |
|
1122 /* translators: 1: parameter, 2: list of valid values */ |
|
1123 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); |
|
1124 } |
|
1125 } |
|
1126 |
|
1127 if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) { |
|
1128 /* translators: 1: parameter, 2: type name */ |
|
1129 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) ); |
|
1130 } |
|
1131 |
|
1132 if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) { |
|
1133 /* translators: 1: parameter, 2: type name */ |
|
1134 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); |
|
1135 } |
|
1136 |
|
1137 if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { |
|
1138 /* translators: 1: parameter, 2: type name */ |
|
1139 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); |
|
1140 } |
|
1141 |
|
1142 if ( 'string' === $args['type'] && ! is_string( $value ) ) { |
|
1143 /* translators: 1: parameter, 2: type name */ |
|
1144 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); |
|
1145 } |
|
1146 |
|
1147 if ( isset( $args['format'] ) ) { |
|
1148 switch ( $args['format'] ) { |
|
1149 case 'date-time' : |
|
1150 if ( ! rest_parse_date( $value ) ) { |
|
1151 return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); |
|
1152 } |
|
1153 break; |
|
1154 |
|
1155 case 'email' : |
|
1156 if ( ! is_email( $value ) ) { |
|
1157 return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); |
|
1158 } |
|
1159 break; |
|
1160 case 'ip' : |
|
1161 if ( ! rest_is_ip_address( $value ) ) { |
|
1162 /* translators: %s: IP address */ |
|
1163 return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); |
|
1164 } |
|
1165 break; |
|
1166 } |
|
1167 } |
|
1168 |
|
1169 if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { |
|
1170 if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { |
|
1171 if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { |
|
1172 /* translators: 1: parameter, 2: minimum number */ |
|
1173 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) ); |
|
1174 } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { |
|
1175 /* translators: 1: parameter, 2: minimum number */ |
|
1176 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) ); |
|
1177 } |
|
1178 } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { |
|
1179 if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { |
|
1180 /* translators: 1: parameter, 2: maximum number */ |
|
1181 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) ); |
|
1182 } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { |
|
1183 /* translators: 1: parameter, 2: maximum number */ |
|
1184 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) ); |
|
1185 } |
|
1186 } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { |
|
1187 if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
|
1188 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { |
|
1189 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
|
1190 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); |
|
1191 } |
|
1192 } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
|
1193 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { |
|
1194 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
|
1195 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); |
|
1196 } |
|
1197 } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
|
1198 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { |
|
1199 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
|
1200 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); |
|
1201 } |
|
1202 } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
|
1203 if ( $value > $args['maximum'] || $value < $args['minimum'] ) { |
|
1204 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
|
1205 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); |
|
1206 } |
|
1207 } |
|
1208 } |
|
1209 } |
|
1210 |
|
1211 return true; |
|
1212 } |
|
1213 |
|
1214 /** |
|
1215 * Sanitize a value based on a schema. |
|
1216 * |
|
1217 * @since 4.7.0 |
|
1218 * |
|
1219 * @param mixed $value The value to sanitize. |
|
1220 * @param array $args Schema array to use for sanitization. |
|
1221 * @return true|WP_Error |
|
1222 */ |
|
1223 function rest_sanitize_value_from_schema( $value, $args ) { |
|
1224 if ( 'array' === $args['type'] ) { |
|
1225 if ( empty( $args['items'] ) ) { |
|
1226 return (array) $value; |
|
1227 } |
|
1228 if ( ! is_array( $value ) ) { |
|
1229 $value = preg_split( '/[\s,]+/', $value ); |
|
1230 } |
|
1231 foreach ( $value as $index => $v ) { |
|
1232 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] ); |
|
1233 } |
|
1234 // Normalize to numeric array so nothing unexpected |
|
1235 // is in the keys. |
|
1236 $value = array_values( $value ); |
|
1237 return $value; |
|
1238 } |
|
1239 |
|
1240 if ( 'object' === $args['type'] ) { |
|
1241 if ( $value instanceof stdClass ) { |
|
1242 $value = (array) $value; |
|
1243 } |
|
1244 if ( ! is_array( $value ) ) { |
|
1245 return array(); |
|
1246 } |
|
1247 |
|
1248 foreach ( $value as $property => $v ) { |
|
1249 if ( isset( $args['properties'][ $property ] ) ) { |
|
1250 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] ); |
|
1251 } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
|
1252 unset( $value[ $property ] ); |
|
1253 } |
|
1254 } |
|
1255 |
|
1256 return $value; |
|
1257 } |
|
1258 |
|
1259 if ( 'integer' === $args['type'] ) { |
|
1260 return (int) $value; |
|
1261 } |
|
1262 |
|
1263 if ( 'number' === $args['type'] ) { |
|
1264 return (float) $value; |
|
1265 } |
|
1266 |
|
1267 if ( 'boolean' === $args['type'] ) { |
|
1268 return rest_sanitize_boolean( $value ); |
|
1269 } |
|
1270 |
|
1271 if ( isset( $args['format'] ) ) { |
|
1272 switch ( $args['format'] ) { |
|
1273 case 'date-time' : |
|
1274 return sanitize_text_field( $value ); |
|
1275 |
|
1276 case 'email' : |
|
1277 /* |
|
1278 * sanitize_email() validates, which would be unexpected. |
|
1279 */ |
|
1280 return sanitize_text_field( $value ); |
|
1281 |
|
1282 case 'uri' : |
|
1283 return esc_url_raw( $value ); |
|
1284 |
|
1285 case 'ip' : |
|
1286 return sanitize_text_field( $value ); |
|
1287 } |
|
1288 } |
|
1289 |
|
1290 if ( 'string' === $args['type'] ) { |
|
1291 return strval( $value ); |
|
1292 } |
|
1293 |
|
1294 return $value; |
|
1295 } |