changeset 16 | a86126ab1dd4 |
parent 13 | d255fe9cd479 |
child 18 | be944660c56a |
15:3d4e9c994f10 | 16:a86126ab1dd4 |
---|---|
18 * Registers a REST API route. |
18 * Registers a REST API route. |
19 * |
19 * |
20 * Note: Do not use before the {@see 'rest_api_init'} hook. |
20 * Note: Do not use before the {@see 'rest_api_init'} hook. |
21 * |
21 * |
22 * @since 4.4.0 |
22 * @since 4.4.0 |
23 * @since 5.1.0 Added a _doing_it_wrong() notice when not called on or after the rest_api_init hook. |
23 * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook. |
24 * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set. |
|
24 * |
25 * |
25 * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin. |
26 * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin. |
26 * @param string $route The base URL for route you are adding. |
27 * @param string $route The base URL for route you are adding. |
27 * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for |
28 * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for |
28 * multiple methods. Default empty array. |
29 * multiple methods. Default empty array. |
42 } elseif ( empty( $route ) ) { |
43 } elseif ( empty( $route ) ) { |
43 _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); |
44 _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); |
44 return false; |
45 return false; |
45 } |
46 } |
46 |
47 |
48 $clean_namespace = trim( $namespace, '/' ); |
|
49 |
|
50 if ( $clean_namespace !== $namespace ) { |
|
51 _doing_it_wrong( __FUNCTION__, __( 'Namespace must not start or end with a slash.' ), '5.4.2' ); |
|
52 } |
|
53 |
|
47 if ( ! did_action( 'rest_api_init' ) ) { |
54 if ( ! did_action( 'rest_api_init' ) ) { |
48 _doing_it_wrong( |
55 _doing_it_wrong( |
49 'register_rest_route', |
56 'register_rest_route', |
50 sprintf( |
57 sprintf( |
51 /* translators: %s: rest_api_init */ |
58 /* translators: %s: rest_api_init */ |
71 $defaults = array( |
78 $defaults = array( |
72 'methods' => 'GET', |
79 'methods' => 'GET', |
73 'callback' => null, |
80 'callback' => null, |
74 'args' => array(), |
81 'args' => array(), |
75 ); |
82 ); |
83 |
|
76 foreach ( $args as $key => &$arg_group ) { |
84 foreach ( $args as $key => &$arg_group ) { |
77 if ( ! is_numeric( $key ) ) { |
85 if ( ! is_numeric( $key ) ) { |
78 // Route option, skip here. |
86 // Route option, skip here. |
79 continue; |
87 continue; |
80 } |
88 } |
81 |
89 |
82 $arg_group = array_merge( $defaults, $arg_group ); |
90 $arg_group = array_merge( $defaults, $arg_group ); |
83 $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); |
91 $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); |
84 } |
92 |
85 |
93 if ( ! isset( $arg_group['permission_callback'] ) ) { |
86 $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' ); |
94 _doing_it_wrong( |
87 rest_get_server()->register_route( $namespace, $full_route, $args, $override ); |
95 __FUNCTION__, |
96 sprintf( |
|
97 /* translators: 1. The REST API route being registered. 2. The argument name. 3. The suggested function name. */ |
|
98 __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ), |
|
99 '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>', |
|
100 '<code>permission_callback</code>', |
|
101 '<code>__return_true</code>' |
|
102 ), |
|
103 '5.5.0' |
|
104 ); |
|
105 } |
|
106 } |
|
107 |
|
108 $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' ); |
|
109 rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override ); |
|
88 return true; |
110 return true; |
89 } |
111 } |
90 |
112 |
91 /** |
113 /** |
92 * Registers a new field on an existing WordPress object type. |
114 * Registers a new field on an existing WordPress object type. |
96 * @global array $wp_rest_additional_fields Holds registered fields, organized |
118 * @global array $wp_rest_additional_fields Holds registered fields, organized |
97 * by object type. |
119 * by object type. |
98 * |
120 * |
99 * @param string|array $object_type Object(s) the field is being registered |
121 * @param string|array $object_type Object(s) the field is being registered |
100 * to, "post"|"term"|"comment" etc. |
122 * to, "post"|"term"|"comment" etc. |
101 * @param string $attribute The attribute name. |
123 * @param string $attribute The attribute name. |
102 * @param array $args { |
124 * @param array $args { |
103 * Optional. An array of arguments used to handle the registered field. |
125 * Optional. An array of arguments used to handle the registered field. |
104 * |
126 * |
105 * @type string|array|null $get_callback Optional. The callback function used to retrieve the field |
127 * @type callable|null $get_callback Optional. The callback function used to retrieve the field value. Default is |
106 * value. Default is 'null', the field will not be returned in |
128 * 'null', the field will not be returned in the response. The function will |
107 * the response. |
129 * be passed the prepared object data. |
108 * @type string|array|null $update_callback Optional. The callback function used to set and update the |
130 * @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default |
109 * field value. Default is 'null', the value cannot be set or |
131 * is 'null', the value cannot be set or updated. The function will be passed |
110 * updated. |
132 * the model object, like WP_Post. |
111 * @type string|array|null $schema Optional. The callback function used to create the schema for |
133 * @type array|null $schema Optional. The callback function used to create the schema for this field. |
112 * this field. Default is 'null', no schema entry will be returned. |
134 * Default is 'null', no schema entry will be returned. |
113 * } |
135 * } |
114 */ |
136 */ |
115 function register_rest_field( $object_type, $attribute, $args = array() ) { |
137 function register_rest_field( $object_type, $attribute, $args = array() ) { |
116 $defaults = array( |
138 $defaults = array( |
117 'get_callback' => null, |
139 'get_callback' => null, |
149 * Adds REST rewrite rules. |
171 * Adds REST rewrite rules. |
150 * |
172 * |
151 * @since 4.4.0 |
173 * @since 4.4.0 |
152 * |
174 * |
153 * @see add_rewrite_rule() |
175 * @see add_rewrite_rule() |
154 * @global WP_Rewrite $wp_rewrite |
176 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. |
155 */ |
177 */ |
156 function rest_api_register_rewrites() { |
178 function rest_api_register_rewrites() { |
157 global $wp_rewrite; |
179 global $wp_rewrite; |
158 |
180 |
159 add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); |
181 add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); |
169 * to make testing and disabling these filters easier. |
191 * to make testing and disabling these filters easier. |
170 * |
192 * |
171 * @since 4.4.0 |
193 * @since 4.4.0 |
172 */ |
194 */ |
173 function rest_api_default_filters() { |
195 function rest_api_default_filters() { |
174 // Deprecated reporting. |
196 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { |
175 add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); |
197 // Deprecated reporting. |
176 add_filter( 'deprecated_function_trigger_error', '__return_false' ); |
198 add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); |
177 add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); |
199 add_filter( 'deprecated_function_trigger_error', '__return_false' ); |
178 add_filter( 'deprecated_argument_trigger_error', '__return_false' ); |
200 add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); |
201 add_filter( 'deprecated_argument_trigger_error', '__return_false' ); |
|
202 add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 ); |
|
203 add_filter( 'doing_it_wrong_trigger_error', '__return_false' ); |
|
204 } |
|
179 |
205 |
180 // Default serving. |
206 // Default serving. |
181 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); |
207 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); |
182 add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); |
208 add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); |
183 add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); |
209 add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); |
190 * |
216 * |
191 * @since 4.7.0 |
217 * @since 4.7.0 |
192 */ |
218 */ |
193 function create_initial_rest_routes() { |
219 function create_initial_rest_routes() { |
194 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { |
220 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { |
195 $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller'; |
221 $controller = $post_type->get_rest_controller(); |
196 |
222 |
197 if ( ! class_exists( $class ) ) { |
223 if ( ! $controller ) { |
198 continue; |
|
199 } |
|
200 $controller = new $class( $post_type->name ); |
|
201 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { |
|
202 continue; |
224 continue; |
203 } |
225 } |
204 |
226 |
205 $controller->register_routes(); |
227 $controller->register_routes(); |
206 |
228 |
227 $controller = new WP_REST_Taxonomies_Controller; |
249 $controller = new WP_REST_Taxonomies_Controller; |
228 $controller->register_routes(); |
250 $controller->register_routes(); |
229 |
251 |
230 // Terms. |
252 // Terms. |
231 foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { |
253 foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { |
232 $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller'; |
254 $controller = $taxonomy->get_rest_controller(); |
233 |
255 |
234 if ( ! class_exists( $class ) ) { |
256 if ( ! $controller ) { |
235 continue; |
|
236 } |
|
237 $controller = new $class( $taxonomy->name ); |
|
238 if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { |
|
239 continue; |
257 continue; |
240 } |
258 } |
241 |
259 |
242 $controller->register_routes(); |
260 $controller->register_routes(); |
243 } |
261 } |
266 |
284 |
267 // Block Renderer. |
285 // Block Renderer. |
268 $controller = new WP_REST_Block_Renderer_Controller; |
286 $controller = new WP_REST_Block_Renderer_Controller; |
269 $controller->register_routes(); |
287 $controller->register_routes(); |
270 |
288 |
289 // Block Types. |
|
290 $controller = new WP_REST_Block_Types_Controller(); |
|
291 $controller->register_routes(); |
|
292 |
|
271 // Settings. |
293 // Settings. |
272 $controller = new WP_REST_Settings_Controller; |
294 $controller = new WP_REST_Settings_Controller; |
273 $controller->register_routes(); |
295 $controller->register_routes(); |
274 |
296 |
275 // Themes. |
297 // Themes. |
276 $controller = new WP_REST_Themes_Controller; |
298 $controller = new WP_REST_Themes_Controller; |
277 $controller->register_routes(); |
299 $controller->register_routes(); |
278 |
300 |
301 // Plugins. |
|
302 $controller = new WP_REST_Plugins_Controller(); |
|
303 $controller->register_routes(); |
|
304 |
|
305 // Block Directory. |
|
306 $controller = new WP_REST_Block_Directory_Controller(); |
|
307 $controller->register_routes(); |
|
308 |
|
279 } |
309 } |
280 |
310 |
281 /** |
311 /** |
282 * Loads the REST API. |
312 * Loads the REST API. |
283 * |
313 * |
284 * @since 4.4.0 |
314 * @since 4.4.0 |
285 * |
315 * |
286 * @global WP $wp Current WordPress environment instance. |
316 * @global WP $wp Current WordPress environment instance. |
287 */ |
317 */ |
288 function rest_api_loaded() { |
318 function rest_api_loaded() { |
289 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { |
319 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { |
290 return; |
320 return; |
291 } |
321 } |
336 * Note: The returned URL is NOT escaped. |
366 * Note: The returned URL is NOT escaped. |
337 * |
367 * |
338 * @since 4.4.0 |
368 * @since 4.4.0 |
339 * |
369 * |
340 * @todo Check if this is even necessary |
370 * @todo Check if this is even necessary |
341 * @global WP_Rewrite $wp_rewrite |
371 * @global WP_Rewrite $wp_rewrite WordPress rewrite component. |
342 * |
372 * |
343 * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog. |
373 * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog. |
344 * @param string $path Optional. REST route. Default '/'. |
374 * @param string $path Optional. REST route. Default '/'. |
345 * @param string $scheme Optional. Sanitization scheme. Default 'rest'. |
375 * @param string $scheme Optional. Sanitization scheme. Default 'rest'. |
346 * @return string Full URL to the endpoint. |
376 * @return string Full URL to the endpoint. |
362 } |
392 } |
363 |
393 |
364 $url .= $path; |
394 $url .= $path; |
365 } else { |
395 } else { |
366 $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); |
396 $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); |
367 // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php |
397 // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php. |
368 // To work around this, we manually add index.php to the URL, avoiding the redirect. |
398 // To work around this, we manually add index.php to the URL, avoiding the redirect. |
369 if ( 'index.php' !== substr( $url, 9 ) ) { |
399 if ( 'index.php' !== substr( $url, 9 ) ) { |
370 $url .= 'index.php'; |
400 $url .= 'index.php'; |
371 } |
401 } |
372 |
402 |
373 $url = add_query_arg( 'rest_route', $path, $url ); |
403 $url = add_query_arg( 'rest_route', $path, $url ); |
374 } |
404 } |
375 |
405 |
376 if ( is_ssl() ) { |
406 if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) { |
377 // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. |
407 // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. |
378 if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) { |
408 if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) { |
379 $url = set_url_scheme( $url, 'https' ); |
409 $url = set_url_scheme( $url, 'https' ); |
380 } |
410 } |
381 } |
411 } |
382 |
412 |
383 if ( is_admin() && force_ssl_admin() ) { |
413 if ( is_admin() && force_ssl_admin() ) { |
384 // In this situation the home URL may be http:, and `is_ssl()` may be |
414 /* |
385 // false, but the admin is served over https: (one way or another), so |
415 * In this situation the home URL may be http:, and `is_ssl()` may be false, |
386 // REST API usage will be blocked by browsers unless it is also served |
416 * but the admin is served over https: (one way or another), so REST API usage |
387 // over HTTPS. |
417 * will be blocked by browsers unless it is also served over HTTPS. |
418 */ |
|
388 $url = set_url_scheme( $url, 'https' ); |
419 $url = set_url_scheme( $url, 'https' ); |
389 } |
420 } |
390 |
421 |
391 /** |
422 /** |
392 * Filters the REST URL. |
423 * Filters the REST URL. |
409 * Note: The returned URL is NOT escaped. |
440 * Note: The returned URL is NOT escaped. |
410 * |
441 * |
411 * @since 4.4.0 |
442 * @since 4.4.0 |
412 * |
443 * |
413 * @param string $path Optional. REST route. Default empty. |
444 * @param string $path Optional. REST route. Default empty. |
414 * @param string $scheme Optional. Sanitization scheme. Default 'json'. |
445 * @param string $scheme Optional. Sanitization scheme. Default 'rest'. |
415 * @return string Full URL to the endpoint. |
446 * @return string Full URL to the endpoint. |
416 */ |
447 */ |
417 function rest_url( $path = '', $scheme = 'json' ) { |
448 function rest_url( $path = '', $scheme = 'rest' ) { |
418 return get_rest_url( null, $path, $scheme ); |
449 return get_rest_url( null, $path, $scheme ); |
419 } |
450 } |
420 |
451 |
421 /** |
452 /** |
422 * Do a REST request. |
453 * Do a REST request. |
480 |
511 |
481 /** |
512 /** |
482 * Ensures request arguments are a request object (for consistency). |
513 * Ensures request arguments are a request object (for consistency). |
483 * |
514 * |
484 * @since 4.4.0 |
515 * @since 4.4.0 |
485 * |
516 * @since 5.3.0 Accept string argument for the request path. |
486 * @param array|WP_REST_Request $request Request to check. |
517 * |
518 * @param array|string|WP_REST_Request $request Request to check. |
|
487 * @return WP_REST_Request REST request instance. |
519 * @return WP_REST_Request REST request instance. |
488 */ |
520 */ |
489 function rest_ensure_request( $request ) { |
521 function rest_ensure_request( $request ) { |
490 if ( $request instanceof WP_REST_Request ) { |
522 if ( $request instanceof WP_REST_Request ) { |
491 return $request; |
523 return $request; |
492 } |
524 } |
493 |
525 |
526 if ( is_string( $request ) ) { |
|
527 return new WP_REST_Request( 'GET', $request ); |
|
528 } |
|
529 |
|
494 return new WP_REST_Request( 'GET', '', $request ); |
530 return new WP_REST_Request( 'GET', '', $request ); |
495 } |
531 } |
496 |
532 |
497 /** |
533 /** |
498 * Ensures a REST response is a response object (for consistency). |
534 * Ensures a REST response is a response object (for consistency). |
499 * |
535 * |
500 * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc |
536 * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc |
501 * without needing to double-check the object. Will also allow WP_Error to indicate error |
537 * without needing to double-check the object. Will also allow WP_Error to indicate error |
502 * responses, so users should immediately check for this value. |
538 * responses, so users should immediately check for this value. |
503 * |
539 * |
504 * @since 4.4.0 |
540 * @since 4.4.0 |
505 * |
541 * |
506 * @param WP_Error|WP_HTTP_Response|mixed $response Response to check. |
542 * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check. |
507 * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response |
543 * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response |
508 * is already an instance, WP_HTTP_Response, otherwise |
544 * is already an instance, WP_REST_Response, otherwise |
509 * returns a new WP_REST_Response instance. |
545 * returns a new WP_REST_Response instance. |
510 */ |
546 */ |
511 function rest_ensure_response( $response ) { |
547 function rest_ensure_response( $response ) { |
512 if ( is_wp_error( $response ) ) { |
548 if ( is_wp_error( $response ) ) { |
513 return $response; |
549 return $response; |
514 } |
550 } |
515 |
551 |
552 if ( $response instanceof WP_REST_Response ) { |
|
553 return $response; |
|
554 } |
|
555 |
|
556 // While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide |
|
557 // all the required methods used in WP_REST_Server::dispatch(). |
|
516 if ( $response instanceof WP_HTTP_Response ) { |
558 if ( $response instanceof WP_HTTP_Response ) { |
517 return $response; |
559 return new WP_REST_Response( |
560 $response->get_data(), |
|
561 $response->get_status(), |
|
562 $response->get_headers() |
|
563 ); |
|
518 } |
564 } |
519 |
565 |
520 return new WP_REST_Response( $response ); |
566 return new WP_REST_Response( $response ); |
521 } |
567 } |
522 |
568 |
532 function rest_handle_deprecated_function( $function, $replacement, $version ) { |
578 function rest_handle_deprecated_function( $function, $replacement, $version ) { |
533 if ( ! WP_DEBUG || headers_sent() ) { |
579 if ( ! WP_DEBUG || headers_sent() ) { |
534 return; |
580 return; |
535 } |
581 } |
536 if ( ! empty( $replacement ) ) { |
582 if ( ! empty( $replacement ) ) { |
537 /* translators: 1: function name, 2: WordPress version number, 3: new function name */ |
583 /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */ |
538 $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement ); |
584 $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement ); |
539 } else { |
585 } else { |
540 /* translators: 1: function name, 2: WordPress version number */ |
586 /* translators: 1: Function name, 2: WordPress version number. */ |
541 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
587 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
542 } |
588 } |
543 |
589 |
544 header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); |
590 header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); |
545 } |
591 } |
555 */ |
601 */ |
556 function rest_handle_deprecated_argument( $function, $message, $version ) { |
602 function rest_handle_deprecated_argument( $function, $message, $version ) { |
557 if ( ! WP_DEBUG || headers_sent() ) { |
603 if ( ! WP_DEBUG || headers_sent() ) { |
558 return; |
604 return; |
559 } |
605 } |
560 if ( ! empty( $message ) ) { |
606 if ( $message ) { |
561 /* translators: 1: function name, 2: WordPress version number, 3: error message */ |
607 /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */ |
562 $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message ); |
608 $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message ); |
563 } else { |
609 } else { |
564 /* translators: 1: function name, 2: WordPress version number */ |
610 /* translators: 1: Function name, 2: WordPress version number. */ |
565 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
611 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); |
566 } |
612 } |
567 |
613 |
568 header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); |
614 header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); |
615 } |
|
616 |
|
617 /** |
|
618 * Handles _doing_it_wrong errors. |
|
619 * |
|
620 * @since 5.5.0 |
|
621 * |
|
622 * @param string $function The function that was called. |
|
623 * @param string $message A message explaining what has been done incorrectly. |
|
624 * @param string|null $version The version of WordPress where the message was added. |
|
625 */ |
|
626 function rest_handle_doing_it_wrong( $function, $message, $version ) { |
|
627 if ( ! WP_DEBUG || headers_sent() ) { |
|
628 return; |
|
629 } |
|
630 |
|
631 if ( $version ) { |
|
632 /* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */ |
|
633 $string = __( '%1$s (since %2$s; %3$s)' ); |
|
634 $string = sprintf( $string, $function, $version, $message ); |
|
635 } else { |
|
636 /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */ |
|
637 $string = __( '%1$s (%2$s)' ); |
|
638 $string = sprintf( $string, $function, $message ); |
|
639 } |
|
640 |
|
641 header( sprintf( 'X-WP-DoingItWrong: %s', $string ) ); |
|
569 } |
642 } |
570 |
643 |
571 /** |
644 /** |
572 * Sends Cross-Origin Resource Sharing headers with API requests. |
645 * Sends Cross-Origin Resource Sharing headers with API requests. |
573 * |
646 * |
578 */ |
651 */ |
579 function rest_send_cors_headers( $value ) { |
652 function rest_send_cors_headers( $value ) { |
580 $origin = get_http_origin(); |
653 $origin = get_http_origin(); |
581 |
654 |
582 if ( $origin ) { |
655 if ( $origin ) { |
583 // Requests from file:// and data: URLs send "Origin: null" |
656 // Requests from file:// and data: URLs send "Origin: null". |
584 if ( 'null' !== $origin ) { |
657 if ( 'null' !== $origin ) { |
585 $origin = esc_url_raw( $origin ); |
658 $origin = esc_url_raw( $origin ); |
586 } |
659 } |
587 header( 'Access-Control-Allow-Origin: ' . $origin ); |
660 header( 'Access-Control-Allow-Origin: ' . $origin ); |
588 header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); |
661 header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); |
589 header( 'Access-Control-Allow-Credentials: true' ); |
662 header( 'Access-Control-Allow-Credentials: true' ); |
590 header( 'Vary: Origin', false ); |
663 header( 'Vary: Origin', false ); |
591 } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { |
664 } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { |
592 header( 'Vary: Origin' ); |
665 header( 'Vary: Origin', false ); |
593 } |
666 } |
594 |
667 |
595 return $value; |
668 return $value; |
596 } |
669 } |
597 |
670 |
629 $args[ $param ] = $value; |
702 $args[ $param ] = $value; |
630 } |
703 } |
631 } |
704 } |
632 |
705 |
633 foreach ( $endpoints as $endpoint ) { |
706 foreach ( $endpoints as $endpoint ) { |
634 // Remove the redundant preg_match argument. |
707 // Remove the redundant preg_match() argument. |
635 unset( $args[0] ); |
708 unset( $args[0] ); |
636 |
709 |
637 $request->set_url_params( $args ); |
710 $request->set_url_params( $args ); |
638 $request->set_attributes( $endpoint ); |
711 $request->set_attributes( $endpoint ); |
639 } |
712 } |
692 |
765 |
693 return $response; |
766 return $response; |
694 } |
767 } |
695 |
768 |
696 /** |
769 /** |
770 * Recursively computes the intersection of arrays using keys for comparison. |
|
771 * |
|
772 * @since 5.3.0 |
|
773 * |
|
774 * @param array $array1 The array with master keys to check. |
|
775 * @param array $array2 An array to compare keys against. |
|
776 * @return array An associative array containing all the entries of array1 which have keys |
|
777 * that are present in all arguments. |
|
778 */ |
|
779 function _rest_array_intersect_key_recursive( $array1, $array2 ) { |
|
780 $array1 = array_intersect_key( $array1, $array2 ); |
|
781 foreach ( $array1 as $key => $value ) { |
|
782 if ( is_array( $value ) && is_array( $array2[ $key ] ) ) { |
|
783 $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] ); |
|
784 } |
|
785 } |
|
786 return $array1; |
|
787 } |
|
788 |
|
789 /** |
|
697 * Filter the API response to include only a white-listed set of response object fields. |
790 * Filter the API response to include only a white-listed set of response object fields. |
698 * |
791 * |
699 * @since 4.8.0 |
792 * @since 4.8.0 |
700 * |
793 * |
701 * @param WP_REST_Response $response Current response being served. |
794 * @param WP_REST_Response $response Current response being served. |
702 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). |
795 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). |
703 * @param WP_REST_Request $request The request that was used to make current response. |
796 * @param WP_REST_Request $request The request that was used to make current response. |
704 * |
|
705 * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields. |
797 * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields. |
706 */ |
798 */ |
707 function rest_filter_response_fields( $response, $server, $request ) { |
799 function rest_filter_response_fields( $response, $server, $request ) { |
708 if ( ! isset( $request['_fields'] ) || $response->is_error() ) { |
800 if ( ! isset( $request['_fields'] ) || $response->is_error() ) { |
709 return $response; |
801 return $response; |
718 } |
810 } |
719 |
811 |
720 // Trim off outside whitespace from the comma delimited list. |
812 // Trim off outside whitespace from the comma delimited list. |
721 $fields = array_map( 'trim', $fields ); |
813 $fields = array_map( 'trim', $fields ); |
722 |
814 |
723 $fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) ); |
815 // Create nested array of accepted field hierarchy. |
816 $fields_as_keyed = array(); |
|
817 foreach ( $fields as $field ) { |
|
818 $parts = explode( '.', $field ); |
|
819 $ref = &$fields_as_keyed; |
|
820 while ( count( $parts ) > 1 ) { |
|
821 $next = array_shift( $parts ); |
|
822 if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) { |
|
823 // Skip any sub-properties if their parent prop is already marked for inclusion. |
|
824 break 2; |
|
825 } |
|
826 $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array(); |
|
827 $ref = &$ref[ $next ]; |
|
828 } |
|
829 $last = array_shift( $parts ); |
|
830 $ref[ $last ] = true; |
|
831 } |
|
724 |
832 |
725 if ( wp_is_numeric_array( $data ) ) { |
833 if ( wp_is_numeric_array( $data ) ) { |
726 $new_data = array(); |
834 $new_data = array(); |
727 foreach ( $data as $item ) { |
835 foreach ( $data as $item ) { |
728 $new_data[] = array_intersect_key( $item, $fields_as_keyed ); |
836 $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed ); |
729 } |
837 } |
730 } else { |
838 } else { |
731 $new_data = array_intersect_key( $data, $fields_as_keyed ); |
839 $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed ); |
732 } |
840 } |
733 |
841 |
734 $response->set_data( $new_data ); |
842 $response->set_data( $new_data ); |
735 |
843 |
736 return $response; |
844 return $response; |
845 } |
|
846 |
|
847 /** |
|
848 * Given an array of fields to include in a response, some of which may be |
|
849 * `nested.fields`, determine whether the provided field should be included |
|
850 * in the response body. |
|
851 * |
|
852 * If a parent field is passed in, the presence of any nested field within |
|
853 * that parent will cause the method to return `true`. For example "title" |
|
854 * will return true if any of `title`, `title.raw` or `title.rendered` is |
|
855 * provided. |
|
856 * |
|
857 * @since 5.3.0 |
|
858 * |
|
859 * @param string $field A field to test for inclusion in the response body. |
|
860 * @param array $fields An array of string fields supported by the endpoint. |
|
861 * @return bool Whether to include the field or not. |
|
862 */ |
|
863 function rest_is_field_included( $field, $fields ) { |
|
864 if ( in_array( $field, $fields, true ) ) { |
|
865 return true; |
|
866 } |
|
867 |
|
868 foreach ( $fields as $accepted_field ) { |
|
869 // Check to see if $field is the parent of any item in $fields. |
|
870 // A field "parent" should be accepted if "parent.child" is accepted. |
|
871 if ( strpos( $accepted_field, "$field." ) === 0 ) { |
|
872 return true; |
|
873 } |
|
874 // Conversely, if "parent" is accepted, all "parent.child" fields |
|
875 // should also be accepted. |
|
876 if ( strpos( $field, "$accepted_field." ) === 0 ) { |
|
877 return true; |
|
878 } |
|
879 } |
|
880 |
|
881 return false; |
|
737 } |
882 } |
738 |
883 |
739 /** |
884 /** |
740 * Adds the REST API URL to the WP RSD endpoint. |
885 * Adds the REST API URL to the WP RSD endpoint. |
741 * |
886 * |
766 |
911 |
767 if ( empty( $api_root ) ) { |
912 if ( empty( $api_root ) ) { |
768 return; |
913 return; |
769 } |
914 } |
770 |
915 |
771 echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n"; |
916 printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) ); |
917 |
|
918 $resource = rest_get_queried_resource_route(); |
|
919 |
|
920 if ( $resource ) { |
|
921 printf( '<link rel="alternate" type="application/json" href="%s" />', esc_url( rest_url( $resource ) ) ); |
|
922 } |
|
772 } |
923 } |
773 |
924 |
774 /** |
925 /** |
775 * Sends a Link header for the REST API. |
926 * Sends a Link header for the REST API. |
776 * |
927 * |
785 |
936 |
786 if ( empty( $api_root ) ) { |
937 if ( empty( $api_root ) ) { |
787 return; |
938 return; |
788 } |
939 } |
789 |
940 |
790 header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false ); |
941 header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', esc_url_raw( $api_root ) ), false ); |
942 |
|
943 $resource = rest_get_queried_resource_route(); |
|
944 |
|
945 if ( $resource ) { |
|
946 header( sprintf( 'Link: <%s>; rel="alternate"; type="application/json"', esc_url_raw( rest_url( $resource ) ) ), false ); |
|
947 } |
|
791 } |
948 } |
792 |
949 |
793 /** |
950 /** |
794 * Checks for errors when using cookie-based authentication. |
951 * Checks for errors when using cookie-based authentication. |
795 * |
952 * |
800 * @since 4.4.0 |
957 * @since 4.4.0 |
801 * |
958 * |
802 * @global mixed $wp_rest_auth_cookie |
959 * @global mixed $wp_rest_auth_cookie |
803 * |
960 * |
804 * @param WP_Error|mixed $result Error from another authentication handler, |
961 * @param WP_Error|mixed $result Error from another authentication handler, |
805 * null if we should handle it, or another value |
962 * null if we should handle it, or another value if not. |
806 * if not. |
|
807 * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. |
963 * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. |
808 */ |
964 */ |
809 function rest_cookie_check_errors( $result ) { |
965 function rest_cookie_check_errors( $result ) { |
810 if ( ! empty( $result ) ) { |
966 if ( ! empty( $result ) ) { |
811 return $result; |
967 return $result; |
872 |
1028 |
873 $wp_rest_auth_cookie = true; |
1029 $wp_rest_auth_cookie = true; |
874 } |
1030 } |
875 |
1031 |
876 /** |
1032 /** |
877 * Parses an RFC3339 time into a Unix timestamp. |
1033 * Retrieves the avatar urls in various sizes. |
878 * |
|
879 * @since 4.4.0 |
|
880 * |
|
881 * @param string $date RFC3339 timestamp. |
|
882 * @param bool $force_utc Optional. Whether to force UTC timezone instead of using |
|
883 * the timestamp's timezone. Default false. |
|
884 * @return int Unix timestamp. |
|
885 */ |
|
886 function rest_parse_date( $date, $force_utc = false ) { |
|
887 if ( $force_utc ) { |
|
888 $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); |
|
889 } |
|
890 |
|
891 $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; |
|
892 |
|
893 if ( ! preg_match( $regex, $date, $matches ) ) { |
|
894 return false; |
|
895 } |
|
896 |
|
897 return strtotime( $date ); |
|
898 } |
|
899 |
|
900 /** |
|
901 * Parses a date into both its local and UTC equivalent, in MySQL datetime format. |
|
902 * |
|
903 * @since 4.4.0 |
|
904 * |
|
905 * @see rest_parse_date() |
|
906 * |
|
907 * @param string $date RFC3339 timestamp. |
|
908 * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false. |
|
909 * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s), |
|
910 * null on failure. |
|
911 */ |
|
912 function rest_get_date_with_gmt( $date, $is_utc = false ) { |
|
913 // Whether or not the original date actually has a timezone string |
|
914 // changes the way we need to do timezone conversion. Store this info |
|
915 // before parsing the date, and use it later. |
|
916 $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date ); |
|
917 |
|
918 $date = rest_parse_date( $date ); |
|
919 |
|
920 if ( empty( $date ) ) { |
|
921 return null; |
|
922 } |
|
923 |
|
924 // At this point $date could either be a local date (if we were passed a |
|
925 // *local* date without a timezone offset) or a UTC date (otherwise). |
|
926 // Timezone conversion needs to be handled differently between these two |
|
927 // cases. |
|
928 if ( ! $is_utc && ! $has_timezone ) { |
|
929 $local = date( 'Y-m-d H:i:s', $date ); |
|
930 $utc = get_gmt_from_date( $local ); |
|
931 } else { |
|
932 $utc = date( 'Y-m-d H:i:s', $date ); |
|
933 $local = get_date_from_gmt( $utc ); |
|
934 } |
|
935 |
|
936 return array( $local, $utc ); |
|
937 } |
|
938 |
|
939 /** |
|
940 * Returns a contextual HTTP error code for authorization failure. |
|
941 * |
1034 * |
942 * @since 4.7.0 |
1035 * @since 4.7.0 |
943 * |
1036 * |
944 * @return integer 401 if the user is not logged in, 403 if the user is logged in. |
|
945 */ |
|
946 function rest_authorization_required_code() { |
|
947 return is_user_logged_in() ? 403 : 401; |
|
948 } |
|
949 |
|
950 /** |
|
951 * Validate a request argument based on details registered to the route. |
|
952 * |
|
953 * @since 4.7.0 |
|
954 * |
|
955 * @param mixed $value |
|
956 * @param WP_REST_Request $request |
|
957 * @param string $param |
|
958 * @return WP_Error|boolean |
|
959 */ |
|
960 function rest_validate_request_arg( $value, $request, $param ) { |
|
961 $attributes = $request->get_attributes(); |
|
962 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
963 return true; |
|
964 } |
|
965 $args = $attributes['args'][ $param ]; |
|
966 |
|
967 return rest_validate_value_from_schema( $value, $args, $param ); |
|
968 } |
|
969 |
|
970 /** |
|
971 * Sanitize a request argument based on details registered to the route. |
|
972 * |
|
973 * @since 4.7.0 |
|
974 * |
|
975 * @param mixed $value |
|
976 * @param WP_REST_Request $request |
|
977 * @param string $param |
|
978 * @return mixed |
|
979 */ |
|
980 function rest_sanitize_request_arg( $value, $request, $param ) { |
|
981 $attributes = $request->get_attributes(); |
|
982 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
983 return $value; |
|
984 } |
|
985 $args = $attributes['args'][ $param ]; |
|
986 |
|
987 return rest_sanitize_value_from_schema( $value, $args ); |
|
988 } |
|
989 |
|
990 /** |
|
991 * Parse a request argument based on details registered to the route. |
|
992 * |
|
993 * Runs a validation check and sanitizes the value, primarily to be used via |
|
994 * the `sanitize_callback` arguments in the endpoint args registration. |
|
995 * |
|
996 * @since 4.7.0 |
|
997 * |
|
998 * @param mixed $value |
|
999 * @param WP_REST_Request $request |
|
1000 * @param string $param |
|
1001 * @return mixed |
|
1002 */ |
|
1003 function rest_parse_request_arg( $value, $request, $param ) { |
|
1004 $is_valid = rest_validate_request_arg( $value, $request, $param ); |
|
1005 |
|
1006 if ( is_wp_error( $is_valid ) ) { |
|
1007 return $is_valid; |
|
1008 } |
|
1009 |
|
1010 $value = rest_sanitize_request_arg( $value, $request, $param ); |
|
1011 |
|
1012 return $value; |
|
1013 } |
|
1014 |
|
1015 /** |
|
1016 * Determines if an IP address is valid. |
|
1017 * |
|
1018 * Handles both IPv4 and IPv6 addresses. |
|
1019 * |
|
1020 * @since 4.7.0 |
|
1021 * |
|
1022 * @param string $ip IP address. |
|
1023 * @return string|false The valid IP address, otherwise false. |
|
1024 */ |
|
1025 function rest_is_ip_address( $ip ) { |
|
1026 $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]?)$/'; |
|
1027 |
|
1028 if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) { |
|
1029 return false; |
|
1030 } |
|
1031 |
|
1032 return $ip; |
|
1033 } |
|
1034 |
|
1035 /** |
|
1036 * Changes a boolean-like value into the proper boolean value. |
|
1037 * |
|
1038 * @since 4.7.0 |
|
1039 * |
|
1040 * @param bool|string|int $value The value being evaluated. |
|
1041 * @return boolean Returns the proper associated boolean value. |
|
1042 */ |
|
1043 function rest_sanitize_boolean( $value ) { |
|
1044 // String values are translated to `true`; make sure 'false' is false. |
|
1045 if ( is_string( $value ) ) { |
|
1046 $value = strtolower( $value ); |
|
1047 if ( in_array( $value, array( 'false', '0' ), true ) ) { |
|
1048 $value = false; |
|
1049 } |
|
1050 } |
|
1051 |
|
1052 // Everything else will map nicely to boolean. |
|
1053 return (bool) $value; |
|
1054 } |
|
1055 |
|
1056 /** |
|
1057 * Determines if a given value is boolean-like. |
|
1058 * |
|
1059 * @since 4.7.0 |
|
1060 * |
|
1061 * @param bool|string $maybe_bool The value being evaluated. |
|
1062 * @return boolean True if a boolean, otherwise false. |
|
1063 */ |
|
1064 function rest_is_boolean( $maybe_bool ) { |
|
1065 if ( is_bool( $maybe_bool ) ) { |
|
1066 return true; |
|
1067 } |
|
1068 |
|
1069 if ( is_string( $maybe_bool ) ) { |
|
1070 $maybe_bool = strtolower( $maybe_bool ); |
|
1071 |
|
1072 $valid_boolean_values = array( |
|
1073 'false', |
|
1074 'true', |
|
1075 '0', |
|
1076 '1', |
|
1077 ); |
|
1078 |
|
1079 return in_array( $maybe_bool, $valid_boolean_values, true ); |
|
1080 } |
|
1081 |
|
1082 if ( is_int( $maybe_bool ) ) { |
|
1083 return in_array( $maybe_bool, array( 0, 1 ), true ); |
|
1084 } |
|
1085 |
|
1086 return false; |
|
1087 } |
|
1088 |
|
1089 /** |
|
1090 * Retrieves the avatar urls in various sizes based on a given email address. |
|
1091 * |
|
1092 * @since 4.7.0 |
|
1093 * |
|
1094 * @see get_avatar_url() |
1037 * @see get_avatar_url() |
1095 * |
1038 * |
1096 * @param string $email Email address. |
1039 * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash, |
1097 * @return array $urls Gravatar url for each size. |
1040 * user email, WP_User object, WP_Post object, or WP_Comment object. |
1098 */ |
1041 * @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false. |
1099 function rest_get_avatar_urls( $email ) { |
1042 */ |
1043 function rest_get_avatar_urls( $id_or_email ) { |
|
1100 $avatar_sizes = rest_get_avatar_sizes(); |
1044 $avatar_sizes = rest_get_avatar_sizes(); |
1101 |
1045 |
1102 $urls = array(); |
1046 $urls = array(); |
1103 foreach ( $avatar_sizes as $size ) { |
1047 foreach ( $avatar_sizes as $size ) { |
1104 $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) ); |
1048 $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) ); |
1105 } |
1049 } |
1106 |
1050 |
1107 return $urls; |
1051 return $urls; |
1108 } |
1052 } |
1109 |
1053 |
1110 /** |
1054 /** |
1111 * Retrieves the pixel sizes for avatars. |
1055 * Retrieves the pixel sizes for avatars. |
1112 * |
1056 * |
1113 * @since 4.7.0 |
1057 * @since 4.7.0 |
1114 * |
1058 * |
1115 * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. |
1059 * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. |
1116 */ |
1060 */ |
1117 function rest_get_avatar_sizes() { |
1061 function rest_get_avatar_sizes() { |
1118 /** |
1062 /** |
1119 * Filters the REST avatar sizes. |
1063 * Filters the REST avatar sizes. |
1120 * |
1064 * |
1121 * Use this filter to adjust the array of sizes returned by the |
1065 * Use this filter to adjust the array of sizes returned by the |
1122 * `rest_get_avatar_sizes` function. |
1066 * `rest_get_avatar_sizes` function. |
1123 * |
1067 * |
1124 * @since 4.4.0 |
1068 * @since 4.4.0 |
1125 * |
1069 * |
1126 * @param array $sizes An array of int values that are the pixel sizes for avatars. |
1070 * @param int[] $sizes An array of int values that are the pixel sizes for avatars. |
1127 * Default `[ 24, 48, 96 ]`. |
1071 * Default `[ 24, 48, 96 ]`. |
1128 */ |
1072 */ |
1129 return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); |
1073 return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); |
1130 } |
1074 } |
1131 |
1075 |
1132 /** |
1076 /** |
1077 * Parses an RFC3339 time into a Unix timestamp. |
|
1078 * |
|
1079 * @since 4.4.0 |
|
1080 * |
|
1081 * @param string $date RFC3339 timestamp. |
|
1082 * @param bool $force_utc Optional. Whether to force UTC timezone instead of using |
|
1083 * the timestamp's timezone. Default false. |
|
1084 * @return int Unix timestamp. |
|
1085 */ |
|
1086 function rest_parse_date( $date, $force_utc = false ) { |
|
1087 if ( $force_utc ) { |
|
1088 $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); |
|
1089 } |
|
1090 |
|
1091 $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; |
|
1092 |
|
1093 if ( ! preg_match( $regex, $date, $matches ) ) { |
|
1094 return false; |
|
1095 } |
|
1096 |
|
1097 return strtotime( $date ); |
|
1098 } |
|
1099 |
|
1100 /** |
|
1101 * Parses a 3 or 6 digit hex color (with #). |
|
1102 * |
|
1103 * @since 5.4.0 |
|
1104 * |
|
1105 * @param string $color 3 or 6 digit hex color (with #). |
|
1106 * @return string|false |
|
1107 */ |
|
1108 function rest_parse_hex_color( $color ) { |
|
1109 $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|'; |
|
1110 if ( ! preg_match( $regex, $color, $matches ) ) { |
|
1111 return false; |
|
1112 } |
|
1113 |
|
1114 return $color; |
|
1115 } |
|
1116 |
|
1117 /** |
|
1118 * Parses a date into both its local and UTC equivalent, in MySQL datetime format. |
|
1119 * |
|
1120 * @since 4.4.0 |
|
1121 * |
|
1122 * @see rest_parse_date() |
|
1123 * |
|
1124 * @param string $date RFC3339 timestamp. |
|
1125 * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false. |
|
1126 * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s), |
|
1127 * null on failure. |
|
1128 */ |
|
1129 function rest_get_date_with_gmt( $date, $is_utc = false ) { |
|
1130 /* |
|
1131 * Whether or not the original date actually has a timezone string |
|
1132 * changes the way we need to do timezone conversion. |
|
1133 * Store this info before parsing the date, and use it later. |
|
1134 */ |
|
1135 $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date ); |
|
1136 |
|
1137 $date = rest_parse_date( $date ); |
|
1138 |
|
1139 if ( empty( $date ) ) { |
|
1140 return null; |
|
1141 } |
|
1142 |
|
1143 /* |
|
1144 * At this point $date could either be a local date (if we were passed |
|
1145 * a *local* date without a timezone offset) or a UTC date (otherwise). |
|
1146 * Timezone conversion needs to be handled differently between these two cases. |
|
1147 */ |
|
1148 if ( ! $is_utc && ! $has_timezone ) { |
|
1149 $local = gmdate( 'Y-m-d H:i:s', $date ); |
|
1150 $utc = get_gmt_from_date( $local ); |
|
1151 } else { |
|
1152 $utc = gmdate( 'Y-m-d H:i:s', $date ); |
|
1153 $local = get_date_from_gmt( $utc ); |
|
1154 } |
|
1155 |
|
1156 return array( $local, $utc ); |
|
1157 } |
|
1158 |
|
1159 /** |
|
1160 * Returns a contextual HTTP error code for authorization failure. |
|
1161 * |
|
1162 * @since 4.7.0 |
|
1163 * |
|
1164 * @return integer 401 if the user is not logged in, 403 if the user is logged in. |
|
1165 */ |
|
1166 function rest_authorization_required_code() { |
|
1167 return is_user_logged_in() ? 403 : 401; |
|
1168 } |
|
1169 |
|
1170 /** |
|
1171 * Validate a request argument based on details registered to the route. |
|
1172 * |
|
1173 * @since 4.7.0 |
|
1174 * |
|
1175 * @param mixed $value |
|
1176 * @param WP_REST_Request $request |
|
1177 * @param string $param |
|
1178 * @return true|WP_Error |
|
1179 */ |
|
1180 function rest_validate_request_arg( $value, $request, $param ) { |
|
1181 $attributes = $request->get_attributes(); |
|
1182 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
1183 return true; |
|
1184 } |
|
1185 $args = $attributes['args'][ $param ]; |
|
1186 |
|
1187 return rest_validate_value_from_schema( $value, $args, $param ); |
|
1188 } |
|
1189 |
|
1190 /** |
|
1191 * Sanitize a request argument based on details registered to the route. |
|
1192 * |
|
1193 * @since 4.7.0 |
|
1194 * |
|
1195 * @param mixed $value |
|
1196 * @param WP_REST_Request $request |
|
1197 * @param string $param |
|
1198 * @return mixed |
|
1199 */ |
|
1200 function rest_sanitize_request_arg( $value, $request, $param ) { |
|
1201 $attributes = $request->get_attributes(); |
|
1202 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { |
|
1203 return $value; |
|
1204 } |
|
1205 $args = $attributes['args'][ $param ]; |
|
1206 |
|
1207 return rest_sanitize_value_from_schema( $value, $args, $param ); |
|
1208 } |
|
1209 |
|
1210 /** |
|
1211 * Parse a request argument based on details registered to the route. |
|
1212 * |
|
1213 * Runs a validation check and sanitizes the value, primarily to be used via |
|
1214 * the `sanitize_callback` arguments in the endpoint args registration. |
|
1215 * |
|
1216 * @since 4.7.0 |
|
1217 * |
|
1218 * @param mixed $value |
|
1219 * @param WP_REST_Request $request |
|
1220 * @param string $param |
|
1221 * @return mixed |
|
1222 */ |
|
1223 function rest_parse_request_arg( $value, $request, $param ) { |
|
1224 $is_valid = rest_validate_request_arg( $value, $request, $param ); |
|
1225 |
|
1226 if ( is_wp_error( $is_valid ) ) { |
|
1227 return $is_valid; |
|
1228 } |
|
1229 |
|
1230 $value = rest_sanitize_request_arg( $value, $request, $param ); |
|
1231 |
|
1232 return $value; |
|
1233 } |
|
1234 |
|
1235 /** |
|
1236 * Determines if an IP address is valid. |
|
1237 * |
|
1238 * Handles both IPv4 and IPv6 addresses. |
|
1239 * |
|
1240 * @since 4.7.0 |
|
1241 * |
|
1242 * @param string $ip IP address. |
|
1243 * @return string|false The valid IP address, otherwise false. |
|
1244 */ |
|
1245 function rest_is_ip_address( $ip ) { |
|
1246 $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]?)$/'; |
|
1247 |
|
1248 if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) { |
|
1249 return false; |
|
1250 } |
|
1251 |
|
1252 return $ip; |
|
1253 } |
|
1254 |
|
1255 /** |
|
1256 * Changes a boolean-like value into the proper boolean value. |
|
1257 * |
|
1258 * @since 4.7.0 |
|
1259 * |
|
1260 * @param bool|string|int $value The value being evaluated. |
|
1261 * @return boolean Returns the proper associated boolean value. |
|
1262 */ |
|
1263 function rest_sanitize_boolean( $value ) { |
|
1264 // String values are translated to `true`; make sure 'false' is false. |
|
1265 if ( is_string( $value ) ) { |
|
1266 $value = strtolower( $value ); |
|
1267 if ( in_array( $value, array( 'false', '0' ), true ) ) { |
|
1268 $value = false; |
|
1269 } |
|
1270 } |
|
1271 |
|
1272 // Everything else will map nicely to boolean. |
|
1273 return (bool) $value; |
|
1274 } |
|
1275 |
|
1276 /** |
|
1277 * Determines if a given value is boolean-like. |
|
1278 * |
|
1279 * @since 4.7.0 |
|
1280 * |
|
1281 * @param bool|string $maybe_bool The value being evaluated. |
|
1282 * @return boolean True if a boolean, otherwise false. |
|
1283 */ |
|
1284 function rest_is_boolean( $maybe_bool ) { |
|
1285 if ( is_bool( $maybe_bool ) ) { |
|
1286 return true; |
|
1287 } |
|
1288 |
|
1289 if ( is_string( $maybe_bool ) ) { |
|
1290 $maybe_bool = strtolower( $maybe_bool ); |
|
1291 |
|
1292 $valid_boolean_values = array( |
|
1293 'false', |
|
1294 'true', |
|
1295 '0', |
|
1296 '1', |
|
1297 ); |
|
1298 |
|
1299 return in_array( $maybe_bool, $valid_boolean_values, true ); |
|
1300 } |
|
1301 |
|
1302 if ( is_int( $maybe_bool ) ) { |
|
1303 return in_array( $maybe_bool, array( 0, 1 ), true ); |
|
1304 } |
|
1305 |
|
1306 return false; |
|
1307 } |
|
1308 |
|
1309 /** |
|
1310 * Determines if a given value is integer-like. |
|
1311 * |
|
1312 * @since 5.5.0 |
|
1313 * |
|
1314 * @param mixed $maybe_integer The value being evaluated. |
|
1315 * @return bool True if an integer, otherwise false. |
|
1316 */ |
|
1317 function rest_is_integer( $maybe_integer ) { |
|
1318 return is_numeric( $maybe_integer ) && round( floatval( $maybe_integer ) ) === floatval( $maybe_integer ); |
|
1319 } |
|
1320 |
|
1321 /** |
|
1322 * Determines if a given value is array-like. |
|
1323 * |
|
1324 * @since 5.5.0 |
|
1325 * |
|
1326 * @param mixed $maybe_array The value being evaluated. |
|
1327 * @return bool |
|
1328 */ |
|
1329 function rest_is_array( $maybe_array ) { |
|
1330 if ( is_scalar( $maybe_array ) ) { |
|
1331 $maybe_array = wp_parse_list( $maybe_array ); |
|
1332 } |
|
1333 |
|
1334 return wp_is_numeric_array( $maybe_array ); |
|
1335 } |
|
1336 |
|
1337 /** |
|
1338 * Converts an array-like value to an array. |
|
1339 * |
|
1340 * @since 5.5.0 |
|
1341 * |
|
1342 * @param mixed $maybe_array The value being evaluated. |
|
1343 * @return array Returns the array extracted from the value. |
|
1344 */ |
|
1345 function rest_sanitize_array( $maybe_array ) { |
|
1346 if ( is_scalar( $maybe_array ) ) { |
|
1347 return wp_parse_list( $maybe_array ); |
|
1348 } |
|
1349 |
|
1350 if ( ! is_array( $maybe_array ) ) { |
|
1351 return array(); |
|
1352 } |
|
1353 |
|
1354 // Normalize to numeric array so nothing unexpected is in the keys. |
|
1355 return array_values( $maybe_array ); |
|
1356 } |
|
1357 |
|
1358 /** |
|
1359 * Determines if a given value is object-like. |
|
1360 * |
|
1361 * @since 5.5.0 |
|
1362 * |
|
1363 * @param mixed $maybe_object The value being evaluated. |
|
1364 * @return bool True if object like, otherwise false. |
|
1365 */ |
|
1366 function rest_is_object( $maybe_object ) { |
|
1367 if ( '' === $maybe_object ) { |
|
1368 return true; |
|
1369 } |
|
1370 |
|
1371 if ( $maybe_object instanceof stdClass ) { |
|
1372 return true; |
|
1373 } |
|
1374 |
|
1375 if ( $maybe_object instanceof JsonSerializable ) { |
|
1376 $maybe_object = $maybe_object->jsonSerialize(); |
|
1377 } |
|
1378 |
|
1379 return is_array( $maybe_object ); |
|
1380 } |
|
1381 |
|
1382 /** |
|
1383 * Converts an object-like value to an object. |
|
1384 * |
|
1385 * @since 5.5.0 |
|
1386 * |
|
1387 * @param mixed $maybe_object The value being evaluated. |
|
1388 * @return array Returns the object extracted from the value. |
|
1389 */ |
|
1390 function rest_sanitize_object( $maybe_object ) { |
|
1391 if ( '' === $maybe_object ) { |
|
1392 return array(); |
|
1393 } |
|
1394 |
|
1395 if ( $maybe_object instanceof stdClass ) { |
|
1396 return (array) $maybe_object; |
|
1397 } |
|
1398 |
|
1399 if ( $maybe_object instanceof JsonSerializable ) { |
|
1400 $maybe_object = $maybe_object->jsonSerialize(); |
|
1401 } |
|
1402 |
|
1403 if ( ! is_array( $maybe_object ) ) { |
|
1404 return array(); |
|
1405 } |
|
1406 |
|
1407 return $maybe_object; |
|
1408 } |
|
1409 |
|
1410 /** |
|
1411 * Gets the best type for a value. |
|
1412 * |
|
1413 * @since 5.5.0 |
|
1414 * |
|
1415 * @param mixed $value The value to check. |
|
1416 * @param array $types The list of possible types. |
|
1417 * @return string The best matching type, an empty string if no types match. |
|
1418 */ |
|
1419 function rest_get_best_type_for_value( $value, $types ) { |
|
1420 static $checks = array( |
|
1421 'array' => 'rest_is_array', |
|
1422 'object' => 'rest_is_object', |
|
1423 'integer' => 'rest_is_integer', |
|
1424 'number' => 'is_numeric', |
|
1425 'boolean' => 'rest_is_boolean', |
|
1426 'string' => 'is_string', |
|
1427 'null' => 'is_null', |
|
1428 ); |
|
1429 |
|
1430 // Both arrays and objects allow empty strings to be converted to their types. |
|
1431 // But the best answer for this type is a string. |
|
1432 if ( '' === $value && in_array( 'string', $types, true ) ) { |
|
1433 return 'string'; |
|
1434 } |
|
1435 |
|
1436 foreach ( $types as $type ) { |
|
1437 if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) { |
|
1438 return $type; |
|
1439 } |
|
1440 } |
|
1441 |
|
1442 return ''; |
|
1443 } |
|
1444 |
|
1445 /** |
|
1446 * Handles getting the best type for a multi-type schema. |
|
1447 * |
|
1448 * This is a wrapper for {@see rest_get_best_type_for_value()} that handles |
|
1449 * backward compatibility for schemas that use invalid types. |
|
1450 * |
|
1451 * @since 5.5.0 |
|
1452 * |
|
1453 * @param mixed $value The value to check. |
|
1454 * @param array $args The schema array to use. |
|
1455 * @param string $param The parameter name, used in error messages. |
|
1456 * @return string |
|
1457 */ |
|
1458 function rest_handle_multi_type_schema( $value, $args, $param = '' ) { |
|
1459 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); |
|
1460 $invalid_types = array_diff( $args['type'], $allowed_types ); |
|
1461 |
|
1462 if ( $invalid_types ) { |
|
1463 _doing_it_wrong( |
|
1464 __FUNCTION__, |
|
1465 /* translators: 1. Parameter. 2. List of allowed types. */ |
|
1466 wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ), |
|
1467 '5.5.0' |
|
1468 ); |
|
1469 } |
|
1470 |
|
1471 $best_type = rest_get_best_type_for_value( $value, $args['type'] ); |
|
1472 |
|
1473 if ( ! $best_type ) { |
|
1474 if ( ! $invalid_types ) { |
|
1475 return ''; |
|
1476 } |
|
1477 |
|
1478 // Backward compatibility for previous behavior which allowed the value if there was an invalid type used. |
|
1479 $best_type = reset( $invalid_types ); |
|
1480 } |
|
1481 |
|
1482 return $best_type; |
|
1483 } |
|
1484 |
|
1485 /** |
|
1486 * Checks if an array is made up of unique items. |
|
1487 * |
|
1488 * @since 5.5.0 |
|
1489 * |
|
1490 * @param array $array The array to check. |
|
1491 * @return bool True if the array contains unique items, false otherwise. |
|
1492 */ |
|
1493 function rest_validate_array_contains_unique_items( $array ) { |
|
1494 $seen = array(); |
|
1495 |
|
1496 foreach ( $array as $item ) { |
|
1497 $stabilized = rest_stabilize_value( $item ); |
|
1498 $key = serialize( $stabilized ); |
|
1499 |
|
1500 if ( ! isset( $seen[ $key ] ) ) { |
|
1501 $seen[ $key ] = true; |
|
1502 |
|
1503 continue; |
|
1504 } |
|
1505 |
|
1506 return false; |
|
1507 } |
|
1508 |
|
1509 return true; |
|
1510 } |
|
1511 |
|
1512 /** |
|
1513 * Stabilizes a value following JSON Schema semantics. |
|
1514 * |
|
1515 * For lists, order is preserved. For objects, properties are reordered alphabetically. |
|
1516 * |
|
1517 * @since 5.5.0 |
|
1518 * |
|
1519 * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays. |
|
1520 * @return mixed The stabilized value. |
|
1521 */ |
|
1522 function rest_stabilize_value( $value ) { |
|
1523 if ( is_scalar( $value ) || is_null( $value ) ) { |
|
1524 return $value; |
|
1525 } |
|
1526 |
|
1527 if ( is_object( $value ) ) { |
|
1528 _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' ); |
|
1529 |
|
1530 return $value; |
|
1531 } |
|
1532 |
|
1533 ksort( $value ); |
|
1534 |
|
1535 foreach ( $value as $k => $v ) { |
|
1536 $value[ $k ] = rest_stabilize_value( $v ); |
|
1537 } |
|
1538 |
|
1539 return $value; |
|
1540 } |
|
1541 |
|
1542 /** |
|
1133 * Validate a value based on a schema. |
1543 * Validate a value based on a schema. |
1134 * |
1544 * |
1135 * @since 4.7.0 |
1545 * @since 4.7.0 |
1546 * @since 4.9.0 Support the "object" type. |
|
1547 * @since 5.2.0 Support validating "additionalProperties" against a schema. |
|
1548 * @since 5.3.0 Support multiple types. |
|
1549 * @since 5.4.0 Convert an empty string to an empty object. |
|
1550 * @since 5.5.0 Add the "uuid" and "hex-color" formats. |
|
1551 * Support the "minLength", "maxLength" and "pattern" keywords for strings. |
|
1552 * Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays. |
|
1553 * Validate required properties. |
|
1136 * |
1554 * |
1137 * @param mixed $value The value to validate. |
1555 * @param mixed $value The value to validate. |
1138 * @param array $args Schema array to use for validation. |
1556 * @param array $args Schema array to use for validation. |
1139 * @param string $param The parameter name, used in error messages. |
1557 * @param string $param The parameter name, used in error messages. |
1140 * @return true|WP_Error |
1558 * @return true|WP_Error |
1141 */ |
1559 */ |
1142 function rest_validate_value_from_schema( $value, $args, $param = '' ) { |
1560 function rest_validate_value_from_schema( $value, $args, $param = '' ) { |
1561 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); |
|
1562 |
|
1563 if ( ! isset( $args['type'] ) ) { |
|
1564 /* translators: 1. Parameter */ |
|
1565 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); |
|
1566 } |
|
1567 |
|
1568 if ( is_array( $args['type'] ) ) { |
|
1569 $best_type = rest_handle_multi_type_schema( $value, $args, $param ); |
|
1570 |
|
1571 if ( ! $best_type ) { |
|
1572 /* translators: 1: Parameter, 2: List of types. */ |
|
1573 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) ); |
|
1574 } |
|
1575 |
|
1576 $args['type'] = $best_type; |
|
1577 } |
|
1578 |
|
1579 if ( ! in_array( $args['type'], $allowed_types, true ) ) { |
|
1580 _doing_it_wrong( |
|
1581 __FUNCTION__, |
|
1582 /* translators: 1. Parameter 2. The list of allowed types. */ |
|
1583 wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), |
|
1584 '5.5.0' |
|
1585 ); |
|
1586 } |
|
1587 |
|
1143 if ( 'array' === $args['type'] ) { |
1588 if ( 'array' === $args['type'] ) { |
1144 if ( ! is_null( $value ) ) { |
1589 if ( ! rest_is_array( $value ) ) { |
1145 $value = wp_parse_list( $value ); |
1590 /* translators: 1: Parameter, 2: Type name. */ |
1146 } |
|
1147 if ( ! wp_is_numeric_array( $value ) ) { |
|
1148 /* translators: 1: parameter, 2: type name */ |
|
1149 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) ); |
1591 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) ); |
1150 } |
1592 } |
1151 foreach ( $value as $index => $v ) { |
1593 |
1152 $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); |
1594 $value = rest_sanitize_array( $value ); |
1153 if ( is_wp_error( $is_valid ) ) { |
1595 |
1154 return $is_valid; |
1596 if ( isset( $args['items'] ) ) { |
1597 foreach ( $value as $index => $v ) { |
|
1598 $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); |
|
1599 if ( is_wp_error( $is_valid ) ) { |
|
1600 return $is_valid; |
|
1601 } |
|
1155 } |
1602 } |
1156 } |
1603 } |
1604 |
|
1605 if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) { |
|
1606 /* translators: 1: Parameter, 2: Number. */ |
|
1607 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) ); |
|
1608 } |
|
1609 |
|
1610 if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) { |
|
1611 /* translators: 1: Parameter, 2: Number. */ |
|
1612 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) ); |
|
1613 } |
|
1614 |
|
1615 if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { |
|
1616 /* translators: 1: Parameter */ |
|
1617 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) ); |
|
1618 } |
|
1157 } |
1619 } |
1158 |
1620 |
1159 if ( 'object' === $args['type'] ) { |
1621 if ( 'object' === $args['type'] ) { |
1160 if ( $value instanceof stdClass ) { |
1622 if ( ! rest_is_object( $value ) ) { |
1161 $value = (array) $value; |
1623 /* translators: 1: Parameter, 2: Type name. */ |
1162 } |
|
1163 if ( ! is_array( $value ) ) { |
|
1164 /* translators: 1: parameter, 2: type name */ |
|
1165 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) ); |
1624 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) ); |
1625 } |
|
1626 |
|
1627 $value = rest_sanitize_object( $value ); |
|
1628 |
|
1629 if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4 |
|
1630 foreach ( $args['required'] as $name ) { |
|
1631 if ( ! array_key_exists( $name, $value ) ) { |
|
1632 /* translators: 1: Property of an object, 2: Parameter. */ |
|
1633 return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); |
|
1634 } |
|
1635 } |
|
1636 } elseif ( isset( $args['properties'] ) ) { // schema version 3 |
|
1637 foreach ( $args['properties'] as $name => $property ) { |
|
1638 if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) { |
|
1639 /* translators: 1: Property of an object, 2: Parameter. */ |
|
1640 return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) ); |
|
1641 } |
|
1642 } |
|
1166 } |
1643 } |
1167 |
1644 |
1168 foreach ( $value as $property => $v ) { |
1645 foreach ( $value as $property => $v ) { |
1169 if ( isset( $args['properties'][ $property ] ) ) { |
1646 if ( isset( $args['properties'][ $property ] ) ) { |
1170 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
1647 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
1171 if ( is_wp_error( $is_valid ) ) { |
1648 if ( is_wp_error( $is_valid ) ) { |
1172 return $is_valid; |
1649 return $is_valid; |
1173 } |
1650 } |
1174 } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
1651 } elseif ( isset( $args['additionalProperties'] ) ) { |
1175 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); |
1652 if ( false === $args['additionalProperties'] ) { |
1653 /* translators: %s: Property of an object. */ |
|
1654 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); |
|
1655 } |
|
1656 |
|
1657 if ( is_array( $args['additionalProperties'] ) ) { |
|
1658 $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); |
|
1659 if ( is_wp_error( $is_valid ) ) { |
|
1660 return $is_valid; |
|
1661 } |
|
1662 } |
|
1176 } |
1663 } |
1177 } |
1664 } |
1665 } |
|
1666 |
|
1667 if ( 'null' === $args['type'] ) { |
|
1668 if ( null !== $value ) { |
|
1669 /* translators: 1: Parameter, 2: Type name. */ |
|
1670 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) ); |
|
1671 } |
|
1672 |
|
1673 return true; |
|
1178 } |
1674 } |
1179 |
1675 |
1180 if ( ! empty( $args['enum'] ) ) { |
1676 if ( ! empty( $args['enum'] ) ) { |
1181 if ( ! in_array( $value, $args['enum'], true ) ) { |
1677 if ( ! in_array( $value, $args['enum'], true ) ) { |
1182 /* translators: 1: parameter, 2: list of valid values */ |
1678 /* translators: 1: Parameter, 2: List of valid values. */ |
1183 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); |
1679 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); |
1184 } |
1680 } |
1185 } |
1681 } |
1186 |
1682 |
1187 if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) { |
1683 if ( in_array( $args['type'], array( 'integer', 'number' ), true ) && ! is_numeric( $value ) ) { |
1188 /* translators: 1: parameter, 2: type name */ |
1684 /* translators: 1: Parameter, 2: Type name. */ |
1189 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) ); |
1685 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) ); |
1190 } |
1686 } |
1191 |
1687 |
1192 if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) { |
1688 if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) { |
1193 /* translators: 1: parameter, 2: type name */ |
1689 /* translators: 1: Parameter, 2: Type name. */ |
1194 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); |
1690 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); |
1195 } |
1691 } |
1196 |
1692 |
1197 if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { |
1693 if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { |
1198 /* translators: 1: parameter, 2: type name */ |
1694 /* translators: 1: Parameter, 2: Type name. */ |
1199 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); |
1695 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) ); |
1200 } |
1696 } |
1201 |
1697 |
1202 if ( 'string' === $args['type'] && ! is_string( $value ) ) { |
1698 if ( 'string' === $args['type'] ) { |
1203 /* translators: 1: parameter, 2: type name */ |
1699 if ( ! is_string( $value ) ) { |
1204 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); |
1700 /* translators: 1: Parameter, 2: Type name. */ |
1205 } |
1701 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); |
1206 |
1702 } |
1207 if ( isset( $args['format'] ) ) { |
1703 |
1704 if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) { |
|
1705 return new WP_Error( |
|
1706 'rest_invalid_param', |
|
1707 sprintf( |
|
1708 /* translators: 1: Parameter, 2: Number of characters. */ |
|
1709 _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ), |
|
1710 $param, |
|
1711 number_format_i18n( $args['minLength'] ) |
|
1712 ) |
|
1713 ); |
|
1714 } |
|
1715 |
|
1716 if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) { |
|
1717 return new WP_Error( |
|
1718 'rest_invalid_param', |
|
1719 sprintf( |
|
1720 /* translators: 1: Parameter, 2: Number of characters. */ |
|
1721 _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ), |
|
1722 $param, |
|
1723 number_format_i18n( $args['maxLength'] ) |
|
1724 ) |
|
1725 ); |
|
1726 } |
|
1727 |
|
1728 if ( isset( $args['pattern'] ) ) { |
|
1729 $pattern = str_replace( '#', '\\#', $args['pattern'] ); |
|
1730 if ( ! preg_match( '#' . $pattern . '#u', $value ) ) { |
|
1731 /* translators: 1: Parameter, 2: Pattern. */ |
|
1732 return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) ); |
|
1733 } |
|
1734 } |
|
1735 } |
|
1736 |
|
1737 // The "format" keyword should only be applied to strings. However, for backward compatibility, |
|
1738 // we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value. |
|
1739 if ( isset( $args['format'] ) |
|
1740 && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) |
|
1741 ) { |
|
1208 switch ( $args['format'] ) { |
1742 switch ( $args['format'] ) { |
1743 case 'hex-color': |
|
1744 if ( ! rest_parse_hex_color( $value ) ) { |
|
1745 return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) ); |
|
1746 } |
|
1747 break; |
|
1748 |
|
1209 case 'date-time': |
1749 case 'date-time': |
1210 if ( ! rest_parse_date( $value ) ) { |
1750 if ( ! rest_parse_date( $value ) ) { |
1211 return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); |
1751 return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); |
1212 } |
1752 } |
1213 break; |
1753 break; |
1217 return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); |
1757 return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); |
1218 } |
1758 } |
1219 break; |
1759 break; |
1220 case 'ip': |
1760 case 'ip': |
1221 if ( ! rest_is_ip_address( $value ) ) { |
1761 if ( ! rest_is_ip_address( $value ) ) { |
1222 /* translators: %s: IP address */ |
1762 /* translators: %s: IP address. */ |
1223 return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); |
1763 return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) ); |
1764 } |
|
1765 break; |
|
1766 case 'uuid': |
|
1767 if ( ! wp_is_uuid( $value ) ) { |
|
1768 /* translators: %s is the name of a JSON field expecting a valid uuid. */ |
|
1769 return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) ); |
|
1224 } |
1770 } |
1225 break; |
1771 break; |
1226 } |
1772 } |
1227 } |
1773 } |
1228 |
1774 |
1229 if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { |
1775 if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { |
1230 if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { |
1776 if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { |
1231 if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { |
1777 if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { |
1232 /* translators: 1: parameter, 2: minimum number */ |
1778 /* translators: 1: Parameter, 2: Minimum number. */ |
1233 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) ); |
1779 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) ); |
1234 } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { |
1780 } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { |
1235 /* translators: 1: parameter, 2: minimum number */ |
1781 /* translators: 1: Parameter, 2: Minimum number. */ |
1236 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) ); |
1782 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) ); |
1237 } |
1783 } |
1238 } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { |
1784 } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { |
1239 if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { |
1785 if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { |
1240 /* translators: 1: parameter, 2: maximum number */ |
1786 /* translators: 1: Parameter, 2: Maximum number. */ |
1241 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) ); |
1787 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) ); |
1242 } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { |
1788 } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { |
1243 /* translators: 1: parameter, 2: maximum number */ |
1789 /* translators: 1: Parameter, 2: Maximum number. */ |
1244 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) ); |
1790 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) ); |
1245 } |
1791 } |
1246 } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { |
1792 } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { |
1247 if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
1793 if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
1248 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { |
1794 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { |
1249 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
1795 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ |
1250 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'] ) ); |
1796 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'] ) ); |
1251 } |
1797 } |
1252 } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
1798 } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { |
1253 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { |
1799 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { |
1254 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
1800 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ |
1255 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'] ) ); |
1801 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'] ) ); |
1256 } |
1802 } |
1257 } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
1803 } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
1258 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { |
1804 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { |
1259 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
1805 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ |
1260 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'] ) ); |
1806 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'] ) ); |
1261 } |
1807 } |
1262 } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
1808 } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { |
1263 if ( $value > $args['maximum'] || $value < $args['minimum'] ) { |
1809 if ( $value > $args['maximum'] || $value < $args['minimum'] ) { |
1264 /* translators: 1: parameter, 2: minimum number, 3: maximum number */ |
1810 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */ |
1265 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'] ) ); |
1811 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'] ) ); |
1266 } |
1812 } |
1267 } |
1813 } |
1268 } |
1814 } |
1269 } |
1815 } |
1273 |
1819 |
1274 /** |
1820 /** |
1275 * Sanitize a value based on a schema. |
1821 * Sanitize a value based on a schema. |
1276 * |
1822 * |
1277 * @since 4.7.0 |
1823 * @since 4.7.0 |
1278 * |
1824 * @since 5.5.0 Added the `$param` parameter. |
1279 * @param mixed $value The value to sanitize. |
1825 * |
1280 * @param array $args Schema array to use for sanitization. |
1826 * @param mixed $value The value to sanitize. |
1281 * @return true|WP_Error |
1827 * @param array $args Schema array to use for sanitization. |
1282 */ |
1828 * @param string $param The parameter name, used in error messages. |
1283 function rest_sanitize_value_from_schema( $value, $args ) { |
1829 * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized. |
1830 */ |
|
1831 function rest_sanitize_value_from_schema( $value, $args, $param = '' ) { |
|
1832 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' ); |
|
1833 |
|
1834 if ( ! isset( $args['type'] ) ) { |
|
1835 /* translators: 1. Parameter */ |
|
1836 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' ); |
|
1837 } |
|
1838 |
|
1839 if ( is_array( $args['type'] ) ) { |
|
1840 $best_type = rest_handle_multi_type_schema( $value, $args, $param ); |
|
1841 |
|
1842 if ( ! $best_type ) { |
|
1843 return null; |
|
1844 } |
|
1845 |
|
1846 $args['type'] = $best_type; |
|
1847 } |
|
1848 |
|
1849 if ( ! in_array( $args['type'], $allowed_types, true ) ) { |
|
1850 _doing_it_wrong( |
|
1851 __FUNCTION__, |
|
1852 /* translators: 1. Parameter. 2. The list of allowed types. */ |
|
1853 wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ), |
|
1854 '5.5.0' |
|
1855 ); |
|
1856 } |
|
1857 |
|
1284 if ( 'array' === $args['type'] ) { |
1858 if ( 'array' === $args['type'] ) { |
1285 if ( empty( $args['items'] ) ) { |
1859 $value = rest_sanitize_array( $value ); |
1286 return (array) $value; |
1860 |
1287 } |
1861 if ( ! empty( $args['items'] ) ) { |
1288 $value = wp_parse_list( $value ); |
1862 foreach ( $value as $index => $v ) { |
1289 foreach ( $value as $index => $v ) { |
1863 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); |
1290 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] ); |
1864 } |
1291 } |
1865 } |
1292 // Normalize to numeric array so nothing unexpected |
1866 |
1293 // is in the keys. |
1867 if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) { |
1294 $value = array_values( $value ); |
1868 /* translators: 1: Parameter */ |
1869 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) ); |
|
1870 } |
|
1871 |
|
1295 return $value; |
1872 return $value; |
1296 } |
1873 } |
1297 |
1874 |
1298 if ( 'object' === $args['type'] ) { |
1875 if ( 'object' === $args['type'] ) { |
1299 if ( $value instanceof stdClass ) { |
1876 $value = rest_sanitize_object( $value ); |
1300 $value = (array) $value; |
|
1301 } |
|
1302 if ( ! is_array( $value ) ) { |
|
1303 return array(); |
|
1304 } |
|
1305 |
1877 |
1306 foreach ( $value as $property => $v ) { |
1878 foreach ( $value as $property => $v ) { |
1307 if ( isset( $args['properties'][ $property ] ) ) { |
1879 if ( isset( $args['properties'][ $property ] ) ) { |
1308 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] ); |
1880 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
1309 } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
1881 } elseif ( isset( $args['additionalProperties'] ) ) { |
1310 unset( $value[ $property ] ); |
1882 if ( false === $args['additionalProperties'] ) { |
1883 unset( $value[ $property ] ); |
|
1884 } elseif ( is_array( $args['additionalProperties'] ) ) { |
|
1885 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' ); |
|
1886 } |
|
1311 } |
1887 } |
1312 } |
1888 } |
1313 |
1889 |
1314 return $value; |
1890 return $value; |
1891 } |
|
1892 |
|
1893 if ( 'null' === $args['type'] ) { |
|
1894 return null; |
|
1315 } |
1895 } |
1316 |
1896 |
1317 if ( 'integer' === $args['type'] ) { |
1897 if ( 'integer' === $args['type'] ) { |
1318 return (int) $value; |
1898 return (int) $value; |
1319 } |
1899 } |
1324 |
1904 |
1325 if ( 'boolean' === $args['type'] ) { |
1905 if ( 'boolean' === $args['type'] ) { |
1326 return rest_sanitize_boolean( $value ); |
1906 return rest_sanitize_boolean( $value ); |
1327 } |
1907 } |
1328 |
1908 |
1329 if ( isset( $args['format'] ) ) { |
1909 // This behavior matches rest_validate_value_from_schema(). |
1910 if ( isset( $args['format'] ) |
|
1911 && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) ) |
|
1912 ) { |
|
1330 switch ( $args['format'] ) { |
1913 switch ( $args['format'] ) { |
1914 case 'hex-color': |
|
1915 return (string) sanitize_hex_color( $value ); |
|
1916 |
|
1331 case 'date-time': |
1917 case 'date-time': |
1332 return sanitize_text_field( $value ); |
1918 return sanitize_text_field( $value ); |
1333 |
1919 |
1334 case 'email': |
1920 case 'email': |
1335 /* |
1921 // sanitize_email() validates, which would be unexpected. |
1336 * sanitize_email() validates, which would be unexpected. |
|
1337 */ |
|
1338 return sanitize_text_field( $value ); |
1922 return sanitize_text_field( $value ); |
1339 |
1923 |
1340 case 'uri': |
1924 case 'uri': |
1341 return esc_url_raw( $value ); |
1925 return esc_url_raw( $value ); |
1342 |
1926 |
1343 case 'ip': |
1927 case 'ip': |
1344 return sanitize_text_field( $value ); |
1928 return sanitize_text_field( $value ); |
1929 |
|
1930 case 'uuid': |
|
1931 return sanitize_text_field( $value ); |
|
1345 } |
1932 } |
1346 } |
1933 } |
1347 |
1934 |
1348 if ( 'string' === $args['type'] ) { |
1935 if ( 'string' === $args['type'] ) { |
1349 return strval( $value ); |
1936 return strval( $value ); |
1356 * Append result of internal request to REST API for purpose of preloading data to be attached to a page. |
1943 * Append result of internal request to REST API for purpose of preloading data to be attached to a page. |
1357 * Expected to be called in the context of `array_reduce`. |
1944 * Expected to be called in the context of `array_reduce`. |
1358 * |
1945 * |
1359 * @since 5.0.0 |
1946 * @since 5.0.0 |
1360 * |
1947 * |
1361 * @param array $memo Reduce accumulator. |
1948 * @param array $memo Reduce accumulator. |
1362 * @param string $path REST API path to preload. |
1949 * @param string $path REST API path to preload. |
1363 * @return array Modified reduce accumulator. |
1950 * @return array Modified reduce accumulator. |
1364 */ |
1951 */ |
1365 function rest_preload_api_request( $memo, $path ) { |
1952 function rest_preload_api_request( $memo, $path ) { |
1366 // array_reduce() doesn't support passing an array in PHP 5.2, so we need to make sure we start with one. |
1953 // array_reduce() doesn't support passing an array in PHP 5.2, |
1954 // so we need to make sure we start with one. |
|
1367 if ( ! is_array( $memo ) ) { |
1955 if ( ! is_array( $memo ) ) { |
1368 $memo = array(); |
1956 $memo = array(); |
1369 } |
1957 } |
1370 |
1958 |
1371 if ( empty( $path ) ) { |
1959 if ( empty( $path ) ) { |
1395 |
1983 |
1396 $response = rest_do_request( $request ); |
1984 $response = rest_do_request( $request ); |
1397 if ( 200 === $response->status ) { |
1985 if ( 200 === $response->status ) { |
1398 $server = rest_get_server(); |
1986 $server = rest_get_server(); |
1399 $data = (array) $response->get_data(); |
1987 $data = (array) $response->get_data(); |
1400 $links = $server->get_compact_response_links( $response ); |
1988 $links = $server::get_compact_response_links( $response ); |
1401 if ( ! empty( $links ) ) { |
1989 if ( ! empty( $links ) ) { |
1402 $data['_links'] = $links; |
1990 $data['_links'] = $links; |
1403 } |
1991 } |
1404 |
1992 |
1405 if ( 'OPTIONS' === $method ) { |
1993 if ( 'OPTIONS' === $method ) { |
1417 } |
2005 } |
1418 } |
2006 } |
1419 |
2007 |
1420 return $memo; |
2008 return $memo; |
1421 } |
2009 } |
2010 |
|
2011 /** |
|
2012 * Parses the "_embed" parameter into the list of resources to embed. |
|
2013 * |
|
2014 * @since 5.4.0 |
|
2015 * |
|
2016 * @param string|array $embed Raw "_embed" parameter value. |
|
2017 * @return true|string[] Either true to embed all embeds, or a list of relations to embed. |
|
2018 */ |
|
2019 function rest_parse_embed_param( $embed ) { |
|
2020 if ( ! $embed || 'true' === $embed || '1' === $embed ) { |
|
2021 return true; |
|
2022 } |
|
2023 |
|
2024 $rels = wp_parse_list( $embed ); |
|
2025 |
|
2026 if ( ! $rels ) { |
|
2027 return true; |
|
2028 } |
|
2029 |
|
2030 return $rels; |
|
2031 } |
|
2032 |
|
2033 /** |
|
2034 * Filters the response to remove any fields not available in the given context. |
|
2035 * |
|
2036 * @since 5.5.0 |
|
2037 * |
|
2038 * @param array|object $data The response data to modify. |
|
2039 * @param array $schema The schema for the endpoint used to filter the response. |
|
2040 * @param string $context The requested context. |
|
2041 * @return array|object The filtered response data. |
|
2042 */ |
|
2043 function rest_filter_response_by_context( $data, $schema, $context ) { |
|
2044 if ( ! is_array( $data ) && ! is_object( $data ) ) { |
|
2045 return $data; |
|
2046 } |
|
2047 |
|
2048 if ( isset( $schema['type'] ) ) { |
|
2049 $type = $schema['type']; |
|
2050 } elseif ( isset( $schema['properties'] ) ) { |
|
2051 $type = 'object'; // Back compat if a developer accidentally omitted the type. |
|
2052 } else { |
|
2053 return $data; |
|
2054 } |
|
2055 |
|
2056 $is_array_type = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) ); |
|
2057 $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) ); |
|
2058 |
|
2059 if ( $is_array_type && $is_object_type ) { |
|
2060 if ( rest_is_array( $data ) ) { |
|
2061 $is_object_type = false; |
|
2062 } else { |
|
2063 $is_array_type = false; |
|
2064 } |
|
2065 } |
|
2066 |
|
2067 $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ); |
|
2068 |
|
2069 foreach ( $data as $key => $value ) { |
|
2070 $check = array(); |
|
2071 |
|
2072 if ( $is_array_type ) { |
|
2073 $check = isset( $schema['items'] ) ? $schema['items'] : array(); |
|
2074 } elseif ( $is_object_type ) { |
|
2075 if ( isset( $schema['properties'][ $key ] ) ) { |
|
2076 $check = $schema['properties'][ $key ]; |
|
2077 } elseif ( $has_additional_properties ) { |
|
2078 $check = $schema['additionalProperties']; |
|
2079 } |
|
2080 } |
|
2081 |
|
2082 if ( ! isset( $check['context'] ) ) { |
|
2083 continue; |
|
2084 } |
|
2085 |
|
2086 if ( ! in_array( $context, $check['context'], true ) ) { |
|
2087 if ( $is_array_type ) { |
|
2088 // All array items share schema, so there's no need to check each one. |
|
2089 $data = array(); |
|
2090 break; |
|
2091 } |
|
2092 |
|
2093 if ( is_object( $data ) ) { |
|
2094 unset( $data->$key ); |
|
2095 } else { |
|
2096 unset( $data[ $key ] ); |
|
2097 } |
|
2098 } elseif ( is_array( $value ) || is_object( $value ) ) { |
|
2099 $new_value = rest_filter_response_by_context( $value, $check, $context ); |
|
2100 |
|
2101 if ( is_object( $data ) ) { |
|
2102 $data->$key = $new_value; |
|
2103 } else { |
|
2104 $data[ $key ] = $new_value; |
|
2105 } |
|
2106 } |
|
2107 } |
|
2108 |
|
2109 return $data; |
|
2110 } |
|
2111 |
|
2112 /** |
|
2113 * Sets the "additionalProperties" to false by default for all object definitions in the schema. |
|
2114 * |
|
2115 * @since 5.5.0 |
|
2116 * |
|
2117 * @param array $schema The schema to modify. |
|
2118 * @return array The modified schema. |
|
2119 */ |
|
2120 function rest_default_additional_properties_to_false( $schema ) { |
|
2121 $type = (array) $schema['type']; |
|
2122 |
|
2123 if ( in_array( 'object', $type, true ) ) { |
|
2124 if ( isset( $schema['properties'] ) ) { |
|
2125 foreach ( $schema['properties'] as $key => $child_schema ) { |
|
2126 $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema ); |
|
2127 } |
|
2128 } |
|
2129 |
|
2130 if ( ! isset( $schema['additionalProperties'] ) ) { |
|
2131 $schema['additionalProperties'] = false; |
|
2132 } |
|
2133 } |
|
2134 |
|
2135 if ( in_array( 'array', $type, true ) ) { |
|
2136 if ( isset( $schema['items'] ) ) { |
|
2137 $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] ); |
|
2138 } |
|
2139 } |
|
2140 |
|
2141 return $schema; |
|
2142 } |
|
2143 |
|
2144 /** |
|
2145 * Gets the REST API route for a post. |
|
2146 * |
|
2147 * @since 5.5.0 |
|
2148 * |
|
2149 * @param int|WP_Post $post Post ID or post object. |
|
2150 * @return string The route path with a leading slash for the given post, or an empty string if there is not a route. |
|
2151 */ |
|
2152 function rest_get_route_for_post( $post ) { |
|
2153 $post = get_post( $post ); |
|
2154 |
|
2155 if ( ! $post instanceof WP_Post ) { |
|
2156 return ''; |
|
2157 } |
|
2158 |
|
2159 $post_type = get_post_type_object( $post->post_type ); |
|
2160 if ( ! $post_type ) { |
|
2161 return ''; |
|
2162 } |
|
2163 |
|
2164 $controller = $post_type->get_rest_controller(); |
|
2165 if ( ! $controller ) { |
|
2166 return ''; |
|
2167 } |
|
2168 |
|
2169 $route = ''; |
|
2170 |
|
2171 // The only two controllers that we can detect are the Attachments and Posts controllers. |
|
2172 if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) { |
|
2173 $namespace = 'wp/v2'; |
|
2174 $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name; |
|
2175 $route = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID ); |
|
2176 } |
|
2177 |
|
2178 /** |
|
2179 * Filters the REST API route for a post. |
|
2180 * |
|
2181 * @since 5.5.0 |
|
2182 * |
|
2183 * @param string $route The route path. |
|
2184 * @param WP_Post $post The post object. |
|
2185 */ |
|
2186 return apply_filters( 'rest_route_for_post', $route, $post ); |
|
2187 } |
|
2188 |
|
2189 /** |
|
2190 * Gets the REST API route for a term. |
|
2191 * |
|
2192 * @since 5.5.0 |
|
2193 * |
|
2194 * @param int|WP_Term $term Term ID or term object. |
|
2195 * @return string The route path with a leading slash for the given term, or an empty string if there is not a route. |
|
2196 */ |
|
2197 function rest_get_route_for_term( $term ) { |
|
2198 $term = get_term( $term ); |
|
2199 |
|
2200 if ( ! $term instanceof WP_Term ) { |
|
2201 return ''; |
|
2202 } |
|
2203 |
|
2204 $taxonomy = get_taxonomy( $term->taxonomy ); |
|
2205 if ( ! $taxonomy ) { |
|
2206 return ''; |
|
2207 } |
|
2208 |
|
2209 $controller = $taxonomy->get_rest_controller(); |
|
2210 if ( ! $controller ) { |
|
2211 return ''; |
|
2212 } |
|
2213 |
|
2214 $route = ''; |
|
2215 |
|
2216 // The only controller that works is the Terms controller. |
|
2217 if ( 'WP_REST_Terms_Controller' === get_class( $controller ) ) { |
|
2218 $namespace = 'wp/v2'; |
|
2219 $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; |
|
2220 $route = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id ); |
|
2221 } |
|
2222 |
|
2223 /** |
|
2224 * Filters the REST API route for a term. |
|
2225 * |
|
2226 * @since 5.5.0 |
|
2227 * |
|
2228 * @param string $route The route path. |
|
2229 * @param WP_Term $term The term object. |
|
2230 */ |
|
2231 return apply_filters( 'rest_route_for_term', $route, $term ); |
|
2232 } |
|
2233 |
|
2234 /** |
|
2235 * Gets the REST route for the currently queried object. |
|
2236 * |
|
2237 * @since 5.5.0 |
|
2238 * |
|
2239 * @return string The REST route of the resource, or an empty string if no resource identified. |
|
2240 */ |
|
2241 function rest_get_queried_resource_route() { |
|
2242 if ( is_singular() ) { |
|
2243 $route = rest_get_route_for_post( get_queried_object() ); |
|
2244 } elseif ( is_category() || is_tag() || is_tax() ) { |
|
2245 $route = rest_get_route_for_term( get_queried_object() ); |
|
2246 } elseif ( is_author() ) { |
|
2247 $route = '/wp/v2/users/' . get_queried_object_id(); |
|
2248 } else { |
|
2249 $route = ''; |
|
2250 } |
|
2251 |
|
2252 /** |
|
2253 * Filters the REST route for the currently queried object. |
|
2254 * |
|
2255 * @since 5.5.0 |
|
2256 * |
|
2257 * @param string $link The route with a leading slash, or an empty string. |
|
2258 */ |
|
2259 return apply_filters( 'rest_queried_resource_route', $route ); |
|
2260 } |