155 /** |
164 /** |
156 * Checks the authentication headers if supplied. |
165 * Checks the authentication headers if supplied. |
157 * |
166 * |
158 * @since 4.4.0 |
167 * @since 4.4.0 |
159 * |
168 * |
160 * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful |
169 * @return WP_Error|null|true WP_Error indicates unsuccessful login, null indicates successful |
161 * or no authentication provided |
170 * or no authentication provided |
162 */ |
171 */ |
163 public function check_authentication() { |
172 public function check_authentication() { |
164 /** |
173 /** |
165 * Filters REST API authentication errors. |
174 * Filters REST API authentication errors. |
166 * |
175 * |
190 |
199 |
191 /** |
200 /** |
192 * Converts an error to a response object. |
201 * Converts an error to a response object. |
193 * |
202 * |
194 * This iterates over all error codes and messages to change it into a flat |
203 * This iterates over all error codes and messages to change it into a flat |
195 * array. This enables simpler client behaviour, as it is represented as a |
204 * array. This enables simpler client behavior, as it is represented as a |
196 * list in JSON rather than an object/map. |
205 * list in JSON rather than an object/map. |
197 * |
206 * |
198 * @since 4.4.0 |
207 * @since 4.4.0 |
199 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}. |
208 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}. |
200 * |
209 * |
226 } |
235 } |
227 |
236 |
228 $error = compact( 'code', 'message' ); |
237 $error = compact( 'code', 'message' ); |
229 |
238 |
230 return wp_json_encode( $error ); |
239 return wp_json_encode( $error ); |
|
240 } |
|
241 |
|
242 /** |
|
243 * Gets the encoding options passed to {@see wp_json_encode}. |
|
244 * |
|
245 * @since 6.1.0 |
|
246 * |
|
247 * @param \WP_REST_Request $request The current request object. |
|
248 * |
|
249 * @return int The JSON encode options. |
|
250 */ |
|
251 protected function get_json_encode_options( WP_REST_Request $request ) { |
|
252 $options = 0; |
|
253 |
|
254 if ( $request->has_param( '_pretty' ) ) { |
|
255 $options |= JSON_PRETTY_PRINT; |
|
256 } |
|
257 |
|
258 /** |
|
259 * Filters the JSON encoding options used to send the REST API response. |
|
260 * |
|
261 * @since 6.1.0 |
|
262 * |
|
263 * @param int $options JSON encoding options {@see json_encode()}. |
|
264 * @param WP_REST_Request $request Current request object. |
|
265 */ |
|
266 return apply_filters( 'rest_json_encode_options', $options, $request ); |
231 } |
267 } |
232 |
268 |
233 /** |
269 /** |
234 * Handles serving a REST API request. |
270 * Handles serving a REST API request. |
235 * |
271 * |
282 $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) ); |
318 $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) ); |
283 $this->send_header( 'X-Robots-Tag', 'noindex' ); |
319 $this->send_header( 'X-Robots-Tag', 'noindex' ); |
284 |
320 |
285 $api_root = get_rest_url(); |
321 $api_root = get_rest_url(); |
286 if ( ! empty( $api_root ) ) { |
322 if ( ! empty( $api_root ) ) { |
287 $this->send_header( 'Link', '<' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"' ); |
323 $this->send_header( 'Link', '<' . sanitize_url( $api_root ) . '>; rel="https://api.w.org/"' ); |
288 } |
324 } |
289 |
325 |
290 /* |
326 /* |
291 * Mitigate possible JSONP Flash attacks. |
327 * Mitigate possible JSONP Flash attacks. |
292 * |
328 * |
293 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
329 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
294 */ |
330 */ |
295 $this->send_header( 'X-Content-Type-Options', 'nosniff' ); |
331 $this->send_header( 'X-Content-Type-Options', 'nosniff' ); |
296 $expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' ); |
|
297 |
|
298 /** |
|
299 * Filters the list of response headers that are exposed to REST API CORS requests. |
|
300 * |
|
301 * @since 5.5.0 |
|
302 * |
|
303 * @param string[] $expose_headers The list of response headers to expose. |
|
304 */ |
|
305 $expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers ); |
|
306 |
|
307 $this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) ); |
|
308 |
|
309 $allow_headers = array( |
|
310 'Authorization', |
|
311 'X-WP-Nonce', |
|
312 'Content-Disposition', |
|
313 'Content-MD5', |
|
314 'Content-Type', |
|
315 ); |
|
316 |
|
317 /** |
|
318 * Filters the list of request headers that are allowed for REST API CORS requests. |
|
319 * |
|
320 * The allowed headers are passed to the browser to specify which |
|
321 * headers can be passed to the REST API. By default, we allow the |
|
322 * Content-* headers needed to upload files to the media endpoints. |
|
323 * As well as the Authorization and Nonce headers for allowing authentication. |
|
324 * |
|
325 * @since 5.5.0 |
|
326 * |
|
327 * @param string[] $allow_headers The list of request headers to allow. |
|
328 */ |
|
329 $allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers ); |
|
330 |
|
331 $this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) ); |
|
332 |
|
333 /** |
|
334 * Filters whether to send nocache headers on a REST API request. |
|
335 * |
|
336 * @since 4.4.0 |
|
337 * |
|
338 * @param bool $rest_send_nocache_headers Whether to send no-cache headers. |
|
339 */ |
|
340 $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() ); |
|
341 if ( $send_no_cache_headers ) { |
|
342 foreach ( wp_get_nocache_headers() as $header => $header_value ) { |
|
343 if ( empty( $header_value ) ) { |
|
344 $this->remove_header( $header ); |
|
345 } else { |
|
346 $this->send_header( $header, $header_value ); |
|
347 } |
|
348 } |
|
349 } |
|
350 |
332 |
351 /** |
333 /** |
352 * Filters whether the REST API is enabled. |
334 * Filters whether the REST API is enabled. |
353 * |
335 * |
354 * @since 4.4.0 |
336 * @since 4.4.0 |
400 /* |
382 /* |
401 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check |
383 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check |
402 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE |
384 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE |
403 * header. |
385 * header. |
404 */ |
386 */ |
|
387 $method_overridden = false; |
405 if ( isset( $_GET['_method'] ) ) { |
388 if ( isset( $_GET['_method'] ) ) { |
406 $request->set_method( $_GET['_method'] ); |
389 $request->set_method( $_GET['_method'] ); |
407 } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { |
390 } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { |
408 $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ); |
391 $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ); |
409 } |
392 $method_overridden = true; |
|
393 } |
|
394 |
|
395 $expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' ); |
|
396 |
|
397 /** |
|
398 * Filters the list of response headers that are exposed to REST API CORS requests. |
|
399 * |
|
400 * @since 5.5.0 |
|
401 * @since 6.3.0 The `$request` parameter was added. |
|
402 * |
|
403 * @param string[] $expose_headers The list of response headers to expose. |
|
404 * @param WP_REST_Request $request The request in context. |
|
405 */ |
|
406 $expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers, $request ); |
|
407 |
|
408 $this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) ); |
|
409 |
|
410 $allow_headers = array( |
|
411 'Authorization', |
|
412 'X-WP-Nonce', |
|
413 'Content-Disposition', |
|
414 'Content-MD5', |
|
415 'Content-Type', |
|
416 ); |
|
417 |
|
418 /** |
|
419 * Filters the list of request headers that are allowed for REST API CORS requests. |
|
420 * |
|
421 * The allowed headers are passed to the browser to specify which |
|
422 * headers can be passed to the REST API. By default, we allow the |
|
423 * Content-* headers needed to upload files to the media endpoints. |
|
424 * As well as the Authorization and Nonce headers for allowing authentication. |
|
425 * |
|
426 * @since 5.5.0 |
|
427 * @since 6.3.0 The `$request` parameter was added. |
|
428 * |
|
429 * @param string[] $allow_headers The list of request headers to allow. |
|
430 * @param WP_REST_Request $request The request in context. |
|
431 */ |
|
432 $allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers, $request ); |
|
433 |
|
434 $this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) ); |
410 |
435 |
411 $result = $this->check_authentication(); |
436 $result = $this->check_authentication(); |
412 |
437 |
413 if ( ! is_wp_error( $result ) ) { |
438 if ( ! is_wp_error( $result ) ) { |
414 $result = $this->dispatch( $request ); |
439 $result = $this->dispatch( $request ); |
446 $headers = $result->get_headers(); |
471 $headers = $result->get_headers(); |
447 $this->send_headers( $headers ); |
472 $this->send_headers( $headers ); |
448 |
473 |
449 $code = $result->get_status(); |
474 $code = $result->get_status(); |
450 $this->set_status( $code ); |
475 $this->set_status( $code ); |
|
476 |
|
477 /** |
|
478 * Filters whether to send no-cache headers on a REST API request. |
|
479 * |
|
480 * @since 4.4.0 |
|
481 * @since 6.3.2 Moved the block to catch the filter added on rest_cookie_check_errors() from wp-includes/rest-api.php. |
|
482 * |
|
483 * @param bool $rest_send_nocache_headers Whether to send no-cache headers. |
|
484 */ |
|
485 $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() ); |
|
486 |
|
487 /* |
|
488 * Send no-cache headers if $send_no_cache_headers is true, |
|
489 * OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code. |
|
490 */ |
|
491 if ( $send_no_cache_headers || ( true === $method_overridden && str_starts_with( $code, '4' ) ) ) { |
|
492 foreach ( wp_get_nocache_headers() as $header => $header_value ) { |
|
493 if ( empty( $header_value ) ) { |
|
494 $this->remove_header( $header ); |
|
495 } else { |
|
496 $this->send_header( $header, $header_value ); |
|
497 } |
|
498 } |
|
499 } |
451 |
500 |
452 /** |
501 /** |
453 * Filters whether the REST API request has already been served. |
502 * Filters whether the REST API request has already been served. |
454 * |
503 * |
455 * Allow sending the request manually - by returning true, the API result |
504 * Allow sending the request manually - by returning true, the API result |
491 // The 204 response shouldn't have a body. |
540 // The 204 response shouldn't have a body. |
492 if ( 204 === $code || null === $result ) { |
541 if ( 204 === $code || null === $result ) { |
493 return null; |
542 return null; |
494 } |
543 } |
495 |
544 |
496 $result = wp_json_encode( $result ); |
545 $result = wp_json_encode( $result, $this->get_json_encode_options( $request ) ); |
497 |
546 |
498 $json_error_message = $this->get_json_last_error(); |
547 $json_error_message = $this->get_json_last_error(); |
499 |
548 |
500 if ( $json_error_message ) { |
549 if ( $json_error_message ) { |
501 $this->set_status( 500 ); |
550 $this->set_status( 500 ); |
504 $json_error_message, |
553 $json_error_message, |
505 array( 'status' => 500 ) |
554 array( 'status' => 500 ) |
506 ); |
555 ); |
507 |
556 |
508 $result = $this->error_to_response( $json_error_obj ); |
557 $result = $this->error_to_response( $json_error_obj ); |
509 $result = wp_json_encode( $result->data ); |
558 $result = wp_json_encode( $result->data, $this->get_json_encode_options( $request ) ); |
510 } |
559 } |
511 |
560 |
512 if ( $jsonp_callback ) { |
561 if ( $jsonp_callback ) { |
513 // Prepend '/**/' to mitigate possible JSONP Flash attacks. |
562 // Prepend '/**/' to mitigate possible JSONP Flash attacks. |
514 // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
563 // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
523 |
572 |
524 /** |
573 /** |
525 * Converts a response to data to send. |
574 * Converts a response to data to send. |
526 * |
575 * |
527 * @since 4.4.0 |
576 * @since 4.4.0 |
528 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include. |
577 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include. |
529 * |
578 * |
530 * @param WP_REST_Response $response Response object. |
579 * @param WP_REST_Response $response Response object. |
531 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links. |
580 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links. |
532 * @return array { |
581 * @return array { |
533 * Data with sub-requests embedded. |
582 * Data with sub-requests embedded. |
618 foreach ( $links as $rel => $items ) { |
667 foreach ( $links as $rel => $items ) { |
619 |
668 |
620 // Convert $rel URIs to their compact versions if they exist. |
669 // Convert $rel URIs to their compact versions if they exist. |
621 foreach ( $curies as $curie ) { |
670 foreach ( $curies as $curie ) { |
622 $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) ); |
671 $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) ); |
623 if ( strpos( $rel, $href_prefix ) !== 0 ) { |
672 if ( ! str_starts_with( $rel, $href_prefix ) ) { |
624 continue; |
673 continue; |
625 } |
674 } |
626 |
675 |
627 // Relation now changes from '$uri' to '$curie:$relation'. |
676 // Relation now changes from '$uri' to '$curie:$relation'. |
628 $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) ); |
677 $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) ); |
647 |
696 |
648 /** |
697 /** |
649 * Embeds the links from the data into the request. |
698 * Embeds the links from the data into the request. |
650 * |
699 * |
651 * @since 4.4.0 |
700 * @since 4.4.0 |
652 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include. |
701 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include. |
653 * |
702 * |
654 * @param array $data Data from the request. |
703 * @param array $data Data from the request. |
655 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations. |
704 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations. |
656 * @return array { |
705 * @return array { |
657 * Data with sub-requests embedded. |
706 * Data with sub-requests embedded. |
666 } |
715 } |
667 |
716 |
668 $embedded = array(); |
717 $embedded = array(); |
669 |
718 |
670 foreach ( $data['_links'] as $rel => $links ) { |
719 foreach ( $data['_links'] as $rel => $links ) { |
671 // If a list of relations was specified, and the link relation |
720 /* |
672 // is not in the list of allowed relations, don't process the link. |
721 * If a list of relations was specified, and the link relation |
|
722 * is not in the list of allowed relations, don't process the link. |
|
723 */ |
673 if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) { |
724 if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) { |
674 continue; |
725 continue; |
675 } |
726 } |
676 |
727 |
677 $embeds = array(); |
728 $embeds = array(); |
695 // Embedded resources get passed context=embed. |
746 // Embedded resources get passed context=embed. |
696 if ( empty( $request['context'] ) ) { |
747 if ( empty( $request['context'] ) ) { |
697 $request['context'] = 'embed'; |
748 $request['context'] = 'embed'; |
698 } |
749 } |
699 |
750 |
|
751 if ( empty( $request['per_page'] ) ) { |
|
752 $matched = $this->match_request_to_handler( $request ); |
|
753 if ( ! is_wp_error( $matched ) && isset( $matched[1]['args']['per_page']['maximum'] ) ) { |
|
754 $request['per_page'] = (int) $matched[1]['args']['per_page']['maximum']; |
|
755 } |
|
756 } |
|
757 |
700 $response = $this->dispatch( $request ); |
758 $response = $this->dispatch( $request ); |
701 |
759 |
702 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ |
760 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ |
703 $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); |
761 $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); |
704 |
762 |
729 * The enveloping technique is used to work around browser/client |
787 * The enveloping technique is used to work around browser/client |
730 * compatibility issues. Essentially, it converts the full HTTP response to |
788 * compatibility issues. Essentially, it converts the full HTTP response to |
731 * data instead. |
789 * data instead. |
732 * |
790 * |
733 * @since 4.4.0 |
791 * @since 4.4.0 |
734 * @since 6.0.0 The $embed parameter can now contain a list of link relations to include |
792 * @since 6.0.0 The `$embed` parameter can now contain a list of link relations to include. |
735 * |
793 * |
736 * @param WP_REST_Response $response Response object. |
794 * @param WP_REST_Response $response Response object. |
737 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links. |
795 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links. |
738 * @return WP_REST_Response New response with wrapped data |
796 * @return WP_REST_Response New response with wrapped data |
739 */ |
797 */ |
767 /** |
825 /** |
768 * Registers a route to the server. |
826 * Registers a route to the server. |
769 * |
827 * |
770 * @since 4.4.0 |
828 * @since 4.4.0 |
771 * |
829 * |
772 * @param string $namespace Namespace. |
830 * @param string $route_namespace Namespace. |
773 * @param string $route The REST route. |
831 * @param string $route The REST route. |
774 * @param array $route_args Route arguments. |
832 * @param array $route_args Route arguments. |
775 * @param bool $override Optional. Whether the route should be overridden if it already exists. |
833 * @param bool $override Optional. Whether the route should be overridden if it already exists. |
776 * Default false. |
834 * Default false. |
777 */ |
835 */ |
778 public function register_route( $namespace, $route, $route_args, $override = false ) { |
836 public function register_route( $route_namespace, $route, $route_args, $override = false ) { |
779 if ( ! isset( $this->namespaces[ $namespace ] ) ) { |
837 if ( ! isset( $this->namespaces[ $route_namespace ] ) ) { |
780 $this->namespaces[ $namespace ] = array(); |
838 $this->namespaces[ $route_namespace ] = array(); |
781 |
839 |
782 $this->register_route( |
840 $this->register_route( |
783 $namespace, |
841 $route_namespace, |
784 '/' . $namespace, |
842 '/' . $route_namespace, |
785 array( |
843 array( |
786 array( |
844 array( |
787 'methods' => self::READABLE, |
845 'methods' => self::READABLE, |
788 'callback' => array( $this, 'get_namespace_index' ), |
846 'callback' => array( $this, 'get_namespace_index' ), |
789 'args' => array( |
847 'args' => array( |
790 'namespace' => array( |
848 'namespace' => array( |
791 'default' => $namespace, |
849 'default' => $route_namespace, |
792 ), |
850 ), |
793 'context' => array( |
851 'context' => array( |
794 'default' => 'view', |
852 'default' => 'view', |
795 ), |
853 ), |
796 ), |
854 ), |
798 ) |
856 ) |
799 ); |
857 ); |
800 } |
858 } |
801 |
859 |
802 // Associative to avoid double-registration. |
860 // Associative to avoid double-registration. |
803 $this->namespaces[ $namespace ][ $route ] = true; |
861 $this->namespaces[ $route_namespace ][ $route ] = true; |
804 $route_args['namespace'] = $namespace; |
862 |
|
863 $route_args['namespace'] = $route_namespace; |
805 |
864 |
806 if ( $override || empty( $this->endpoints[ $route ] ) ) { |
865 if ( $override || empty( $this->endpoints[ $route ] ) ) { |
807 $this->endpoints[ $route ] = $route_args; |
866 $this->endpoints[ $route ] = $route_args; |
808 } else { |
867 } else { |
809 $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args ); |
868 $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args ); |
824 * |
883 * |
825 * Note that the path regexes (array keys) must have @ escaped, as this is |
884 * Note that the path regexes (array keys) must have @ escaped, as this is |
826 * used as the delimiter with preg_match() |
885 * used as the delimiter with preg_match() |
827 * |
886 * |
828 * @since 4.4.0 |
887 * @since 4.4.0 |
829 * @since 5.4.0 Add $namespace parameter. |
888 * @since 5.4.0 Added `$route_namespace` parameter. |
830 * |
889 * |
831 * @param string $namespace Optionally, only return routes in the given namespace. |
890 * @param string $route_namespace Optionally, only return routes in the given namespace. |
832 * @return array `'/path/regex' => array( $callback, $bitmask )` or |
891 * @return array `'/path/regex' => array( $callback, $bitmask )` or |
833 * `'/path/regex' => array( array( $callback, $bitmask ), ...)`. |
892 * `'/path/regex' => array( array( $callback, $bitmask ), ...)`. |
834 */ |
893 */ |
835 public function get_routes( $namespace = '' ) { |
894 public function get_routes( $route_namespace = '' ) { |
836 $endpoints = $this->endpoints; |
895 $endpoints = $this->endpoints; |
837 |
896 |
838 if ( $namespace ) { |
897 if ( $route_namespace ) { |
839 $endpoints = wp_list_filter( $endpoints, array( 'namespace' => $namespace ) ); |
898 $endpoints = wp_list_filter( $endpoints, array( 'namespace' => $route_namespace ) ); |
840 } |
899 } |
841 |
900 |
842 /** |
901 /** |
843 * Filters the array of available REST API endpoints. |
902 * Filters the array of available REST API endpoints. |
844 * |
903 * |
937 * |
996 * |
938 * @param WP_REST_Request $request Request to attempt dispatching. |
997 * @param WP_REST_Request $request Request to attempt dispatching. |
939 * @return WP_REST_Response Response returned by the callback. |
998 * @return WP_REST_Response Response returned by the callback. |
940 */ |
999 */ |
941 public function dispatch( $request ) { |
1000 public function dispatch( $request ) { |
|
1001 $this->dispatching_requests[] = $request; |
|
1002 |
942 /** |
1003 /** |
943 * Filters the pre-calculated result of a REST API dispatch request. |
1004 * Filters the pre-calculated result of a REST API dispatch request. |
944 * |
1005 * |
945 * Allow hijacking the request before dispatching by returning a non-empty. The returned value |
1006 * Allow hijacking the request before dispatching by returning a non-empty. The returned value |
946 * will be used to serve the request instead. |
1007 * will be used to serve the request instead. |
953 * @param WP_REST_Request $request Request used to generate the response. |
1014 * @param WP_REST_Request $request Request used to generate the response. |
954 */ |
1015 */ |
955 $result = apply_filters( 'rest_pre_dispatch', null, $this, $request ); |
1016 $result = apply_filters( 'rest_pre_dispatch', null, $this, $request ); |
956 |
1017 |
957 if ( ! empty( $result ) ) { |
1018 if ( ! empty( $result ) ) { |
|
1019 |
|
1020 // Normalize to either WP_Error or WP_REST_Response... |
|
1021 $result = rest_ensure_response( $result ); |
|
1022 |
|
1023 // ...then convert WP_Error across. |
|
1024 if ( is_wp_error( $result ) ) { |
|
1025 $result = $this->error_to_response( $result ); |
|
1026 } |
|
1027 |
|
1028 array_pop( $this->dispatching_requests ); |
958 return $result; |
1029 return $result; |
959 } |
1030 } |
960 |
1031 |
961 $error = null; |
1032 $error = null; |
962 $matched = $this->match_request_to_handler( $request ); |
1033 $matched = $this->match_request_to_handler( $request ); |
963 |
1034 |
964 if ( is_wp_error( $matched ) ) { |
1035 if ( is_wp_error( $matched ) ) { |
965 return $this->error_to_response( $matched ); |
1036 $response = $this->error_to_response( $matched ); |
|
1037 array_pop( $this->dispatching_requests ); |
|
1038 return $response; |
966 } |
1039 } |
967 |
1040 |
968 list( $route, $handler ) = $matched; |
1041 list( $route, $handler ) = $matched; |
969 |
1042 |
970 if ( ! is_callable( $handler['callback'] ) ) { |
1043 if ( ! is_callable( $handler['callback'] ) ) { |
985 $error = $check_sanitized; |
1058 $error = $check_sanitized; |
986 } |
1059 } |
987 } |
1060 } |
988 } |
1061 } |
989 |
1062 |
990 return $this->respond_to_request( $request, $route, $handler, $error ); |
1063 $response = $this->respond_to_request( $request, $route, $handler, $error ); |
|
1064 array_pop( $this->dispatching_requests ); |
|
1065 return $response; |
|
1066 } |
|
1067 |
|
1068 /** |
|
1069 * Returns whether the REST server is currently dispatching / responding to a request. |
|
1070 * |
|
1071 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. |
|
1072 * |
|
1073 * @since 6.5.0 |
|
1074 * |
|
1075 * @return bool Whether the REST server is currently handling a request. |
|
1076 */ |
|
1077 public function is_dispatching() { |
|
1078 return (bool) $this->dispatching_requests; |
991 } |
1079 } |
992 |
1080 |
993 /** |
1081 /** |
994 * Matches a request object to its handler. |
1082 * Matches a request object to its handler. |
995 * |
1083 * |
1004 $path = $request->get_route(); |
1092 $path = $request->get_route(); |
1005 |
1093 |
1006 $with_namespace = array(); |
1094 $with_namespace = array(); |
1007 |
1095 |
1008 foreach ( $this->get_namespaces() as $namespace ) { |
1096 foreach ( $this->get_namespaces() as $namespace ) { |
1009 if ( 0 === strpos( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) { |
1097 if ( str_starts_with( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) { |
1010 $with_namespace[] = $this->get_routes( $namespace ); |
1098 $with_namespace[] = $this->get_routes( $namespace ); |
1011 } |
1099 } |
1012 } |
1100 } |
1013 |
1101 |
1014 if ( $with_namespace ) { |
1102 if ( $with_namespace ) { |
1032 } |
1120 } |
1033 } |
1121 } |
1034 |
1122 |
1035 foreach ( $handlers as $handler ) { |
1123 foreach ( $handlers as $handler ) { |
1036 $callback = $handler['callback']; |
1124 $callback = $handler['callback']; |
1037 $response = null; |
|
1038 |
1125 |
1039 // Fallback to GET method if no HEAD method is registered. |
1126 // Fallback to GET method if no HEAD method is registered. |
1040 $checked_method = $method; |
1127 $checked_method = $method; |
1041 if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) { |
1128 if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) { |
1042 $checked_method = 'GET'; |
1129 $checked_method = 'GET'; |
1226 'authentication' => array(), |
1313 'authentication' => array(), |
1227 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ), |
1314 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ), |
1228 ); |
1315 ); |
1229 |
1316 |
1230 $response = new WP_REST_Response( $available ); |
1317 $response = new WP_REST_Response( $available ); |
1231 $response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' ); |
1318 |
1232 $this->add_active_theme_link_to_index( $response ); |
1319 $fields = isset( $request['_fields'] ) ? $request['_fields'] : ''; |
1233 $this->add_site_logo_to_index( $response ); |
1320 $fields = wp_parse_list( $fields ); |
1234 $this->add_site_icon_to_index( $response ); |
1321 if ( empty( $fields ) ) { |
|
1322 $fields[] = '_links'; |
|
1323 } |
|
1324 |
|
1325 if ( $request->has_param( '_embed' ) ) { |
|
1326 $fields[] = '_embedded'; |
|
1327 } |
|
1328 |
|
1329 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { |
|
1330 $response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' ); |
|
1331 $this->add_active_theme_link_to_index( $response ); |
|
1332 $this->add_site_logo_to_index( $response ); |
|
1333 $this->add_site_icon_to_index( $response ); |
|
1334 } else { |
|
1335 if ( rest_is_field_included( 'site_logo', $fields ) ) { |
|
1336 $this->add_site_logo_to_index( $response ); |
|
1337 } |
|
1338 if ( rest_is_field_included( 'site_icon', $fields ) || rest_is_field_included( 'site_icon_url', $fields ) ) { |
|
1339 $this->add_site_icon_to_index( $response ); |
|
1340 } |
|
1341 } |
1235 |
1342 |
1236 /** |
1343 /** |
1237 * Filters the REST API root index data. |
1344 * Filters the REST API root index data. |
1238 * |
1345 * |
1239 * This contains the data describing the API. This includes information |
1346 * This contains the data describing the API. This includes information |
1306 */ |
1413 */ |
1307 protected function add_site_icon_to_index( WP_REST_Response $response ) { |
1414 protected function add_site_icon_to_index( WP_REST_Response $response ) { |
1308 $site_icon_id = get_option( 'site_icon', 0 ); |
1415 $site_icon_id = get_option( 'site_icon', 0 ); |
1309 |
1416 |
1310 $this->add_image_to_index( $response, $site_icon_id, 'site_icon' ); |
1417 $this->add_image_to_index( $response, $site_icon_id, 'site_icon' ); |
|
1418 |
|
1419 $response->data['site_icon_url'] = get_site_icon_url(); |
1311 } |
1420 } |
1312 |
1421 |
1313 /** |
1422 /** |
1314 * Exposes an image through the WordPress REST API. |
1423 * Exposes an image through the WordPress REST API. |
1315 * This is used for fetching this information when user has no rights |
1424 * This is used for fetching this information when user has no rights |
1481 |
1590 |
1482 if ( isset( $callback['args'] ) ) { |
1591 if ( isset( $callback['args'] ) ) { |
1483 $endpoint_data['args'] = array(); |
1592 $endpoint_data['args'] = array(); |
1484 |
1593 |
1485 foreach ( $callback['args'] as $key => $opts ) { |
1594 foreach ( $callback['args'] as $key => $opts ) { |
|
1595 if ( is_string( $opts ) ) { |
|
1596 $opts = array( $opts => 0 ); |
|
1597 } elseif ( ! is_array( $opts ) ) { |
|
1598 $opts = array(); |
|
1599 } |
1486 $arg_data = array_intersect_key( $opts, $allowed_schema_keywords ); |
1600 $arg_data = array_intersect_key( $opts, $allowed_schema_keywords ); |
1487 $arg_data['required'] = ! empty( $opts['required'] ); |
1601 $arg_data['required'] = ! empty( $opts['required'] ); |
1488 |
1602 |
1489 $endpoint_data['args'][ $key ] = $arg_data; |
1603 $endpoint_data['args'][ $key ] = $arg_data; |
1490 } |
1604 } |
1491 } |
1605 } |
1492 |
1606 |
1493 $data['endpoints'][] = $endpoint_data; |
1607 $data['endpoints'][] = $endpoint_data; |
1494 |
1608 |
1495 // For non-variable routes, generate links. |
1609 // For non-variable routes, generate links. |
1496 if ( strpos( $route, '{' ) === false ) { |
1610 if ( ! str_contains( $route, '{' ) ) { |
1497 $data['_links'] = array( |
1611 $data['_links'] = array( |
1498 'self' => array( |
1612 'self' => array( |
1499 array( |
1613 array( |
1500 'href' => rest_url( $route ), |
1614 'href' => rest_url( $route ), |
1501 ), |
1615 ), |
1780 'CONTENT_MD5' => true, |
1894 'CONTENT_MD5' => true, |
1781 'CONTENT_TYPE' => true, |
1895 'CONTENT_TYPE' => true, |
1782 ); |
1896 ); |
1783 |
1897 |
1784 foreach ( $server as $key => $value ) { |
1898 foreach ( $server as $key => $value ) { |
1785 if ( strpos( $key, 'HTTP_' ) === 0 ) { |
1899 if ( str_starts_with( $key, 'HTTP_' ) ) { |
1786 $headers[ substr( $key, 5 ) ] = $value; |
1900 $headers[ substr( $key, 5 ) ] = $value; |
1787 } elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) { |
1901 } elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) { |
1788 /* |
1902 /* |
1789 * In some server configurations, the authorization header is passed in this alternate location. |
1903 * In some server configurations, the authorization header is passed in this alternate location. |
1790 * Since it would not be passed in in both places we do not check for both headers and resolve. |
1904 * Since it would not be passed in in both places we do not check for both headers and resolve. |