WordPress 4.5
[autoinstalls/wordpress.git] / wp-includes / rest-api / class-wp-rest-request.php
1 <?php
2 /**
3  * REST API: WP_REST_Request class
4  *
5  * @package WordPress
6  * @subpackage REST_API
7  * @since 4.4.0
8  */
9
10 /**
11  * Core class used to implement a REST request object.
12  *
13  * Contains data from the request, to be passed to the callback.
14  *
15  * Note: This implements ArrayAccess, and acts as an array of parameters when
16  * used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
17  * so be aware it may have non-array behaviour in some cases.
18  *
19  * Note: When using features provided by ArrayAccess, be aware that WordPress deliberately
20  * does not distinguish between arguments of the same name for different request methods.
21  * For instance, in a request with `GET id=1` and `POST id=2`, `$request['id']` will equal
22  * 2 (`POST`) not 1 (`GET`). For more precision between request methods, use
23  * {@see WP_REST_Request::get_body_params()}, {@see WP_REST_Request::get_url_params()},
24  * etc.
25  *
26  * @since 4.4.0
27  *
28  * @see ArrayAccess
29  */
30 class WP_REST_Request implements ArrayAccess {
31
32         /**
33          * HTTP method.
34          *
35          * @since 4.4.0
36          * @access protected
37          * @var string
38          */
39         protected $method = '';
40
41         /**
42          * Parameters passed to the request.
43          *
44          * These typically come from the `$_GET`, `$_POST` and `$_FILES`
45          * superglobals when being created from the global scope.
46          *
47          * @since 4.4.0
48          * @access protected
49          * @var array Contains GET, POST and FILES keys mapping to arrays of data.
50          */
51         protected $params;
52
53         /**
54          * HTTP headers for the request.
55          *
56          * @since 4.4.0
57          * @access protected
58          * @var array Map of key to value. Key is always lowercase, as per HTTP specification.
59          */
60         protected $headers = array();
61
62         /**
63          * Body data.
64          *
65          * @since 4.4.0
66          * @access protected
67          * @var string Binary data from the request.
68          */
69         protected $body = null;
70
71         /**
72          * Route matched for the request.
73          *
74          * @since 4.4.0
75          * @access protected
76          * @var string
77          */
78         protected $route;
79
80         /**
81          * Attributes (options) for the route that was matched.
82          *
83          * This is the options array used when the route was registered, typically
84          * containing the callback as well as the valid methods for the route.
85          *
86          * @since 4.4.0
87          * @access protected
88          * @var array Attributes for the request.
89          */
90         protected $attributes = array();
91
92         /**
93          * Used to determine if the JSON data has been parsed yet.
94          *
95          * Allows lazy-parsing of JSON data where possible.
96          *
97          * @since 4.4.0
98          * @access protected
99          * @var bool
100          */
101         protected $parsed_json = false;
102
103         /**
104          * Used to determine if the body data has been parsed yet.
105          *
106          * @since 4.4.0
107          * @access protected
108          * @var bool
109          */
110         protected $parsed_body = false;
111
112         /**
113          * Constructor.
114          *
115          * @since 4.4.0
116          * @access public
117          *
118          * @param string $method     Optional. Request method. Default empty.
119          * @param string $route      Optional. Request route. Default empty.
120          * @param array  $attributes Optional. Request attributes. Default empty array.
121          */
122         public function __construct( $method = '', $route = '', $attributes = array() ) {
123                 $this->params = array(
124                         'URL'   => array(),
125                         'GET'   => array(),
126                         'POST'  => array(),
127                         'FILES' => array(),
128
129                         // See parse_json_params.
130                         'JSON'  => null,
131
132                         'defaults' => array(),
133                 );
134
135                 $this->set_method( $method );
136                 $this->set_route( $route );
137                 $this->set_attributes( $attributes );
138         }
139
140         /**
141          * Retrieves the HTTP method for the request.
142          *
143          * @since 4.4.0
144          * @access public
145          *
146          * @return string HTTP method.
147          */
148         public function get_method() {
149                 return $this->method;
150         }
151
152         /**
153          * Sets HTTP method for the request.
154          *
155          * @since 4.4.0
156          * @access public
157          *
158          * @param string $method HTTP method.
159          */
160         public function set_method( $method ) {
161                 $this->method = strtoupper( $method );
162         }
163
164         /**
165          * Retrieves all headers from the request.
166          *
167          * @since 4.4.0
168          * @access public
169          *
170          * @return array Map of key to value. Key is always lowercase, as per HTTP specification.
171          */
172         public function get_headers() {
173                 return $this->headers;
174         }
175
176         /**
177          * Canonicalizes the header name.
178          *
179          * Ensures that header names are always treated the same regardless of
180          * source. Header names are always case insensitive.
181          *
182          * Note that we treat `-` (dashes) and `_` (underscores) as the same
183          * character, as per header parsing rules in both Apache and nginx.
184          *
185          * @link http://stackoverflow.com/q/18185366
186          * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
187          * @link http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
188          *
189          * @since 4.4.0
190          * @access public
191          * @static
192          *
193          * @param string $key Header name.
194          * @return string Canonicalized name.
195          */
196         public static function canonicalize_header_name( $key ) {
197                 $key = strtolower( $key );
198                 $key = str_replace( '-', '_', $key );
199
200                 return $key;
201         }
202
203         /**
204          * Retrieves the given header from the request.
205          *
206          * If the header has multiple values, they will be concatenated with a comma
207          * as per the HTTP specification. Be aware that some non-compliant headers
208          * (notably cookie headers) cannot be joined this way.
209          *
210          * @since 4.4.0
211          * @access public
212          *
213          * @param string $key Header name, will be canonicalized to lowercase.
214          * @return string|null String value if set, null otherwise.
215          */
216         public function get_header( $key ) {
217                 $key = $this->canonicalize_header_name( $key );
218
219                 if ( ! isset( $this->headers[ $key ] ) ) {
220                         return null;
221                 }
222
223                 return implode( ',', $this->headers[ $key ] );
224         }
225
226         /**
227          * Retrieves header values from the request.
228          *
229          * @since 4.4.0
230          * @access public
231          *
232          * @param string $key Header name, will be canonicalized to lowercase.
233          * @return array|null List of string values if set, null otherwise.
234          */
235         public function get_header_as_array( $key ) {
236                 $key = $this->canonicalize_header_name( $key );
237
238                 if ( ! isset( $this->headers[ $key ] ) ) {
239                         return null;
240                 }
241
242                 return $this->headers[ $key ];
243         }
244
245         /**
246          * Sets the header on request.
247          *
248          * @since 4.4.0
249          * @access public
250          *
251          * @param string $key   Header name.
252          * @param string $value Header value, or list of values.
253          */
254         public function set_header( $key, $value ) {
255                 $key = $this->canonicalize_header_name( $key );
256                 $value = (array) $value;
257
258                 $this->headers[ $key ] = $value;
259         }
260
261         /**
262          * Appends a header value for the given header.
263          *
264          * @since 4.4.0
265          * @access public
266          *
267          * @param string $key   Header name.
268          * @param string $value Header value, or list of values.
269          */
270         public function add_header( $key, $value ) {
271                 $key = $this->canonicalize_header_name( $key );
272                 $value = (array) $value;
273
274                 if ( ! isset( $this->headers[ $key ] ) ) {
275                         $this->headers[ $key ] = array();
276                 }
277
278                 $this->headers[ $key ] = array_merge( $this->headers[ $key ], $value );
279         }
280
281         /**
282          * Removes all values for a header.
283          *
284          * @since 4.4.0
285          * @access public
286          *
287          * @param string $key Header name.
288          */
289         public function remove_header( $key ) {
290                 unset( $this->headers[ $key ] );
291         }
292
293         /**
294          * Sets headers on the request.
295          *
296          * @since 4.4.0
297          * @access public
298          *
299          * @param array $headers  Map of header name to value.
300          * @param bool  $override If true, replace the request's headers. Otherwise, merge with existing.
301          */
302         public function set_headers( $headers, $override = true ) {
303                 if ( true === $override ) {
304                         $this->headers = array();
305                 }
306
307                 foreach ( $headers as $key => $value ) {
308                         $this->set_header( $key, $value );
309                 }
310         }
311
312         /**
313          * Retrieves the content-type of the request.
314          *
315          * @since 4.4.0
316          * @access public
317          *
318          * @return array Map containing 'value' and 'parameters' keys.
319          */
320         public function get_content_type() {
321                 $value = $this->get_header( 'content-type' );
322                 if ( empty( $value ) ) {
323                         return null;
324                 }
325
326                 $parameters = '';
327                 if ( strpos( $value, ';' ) ) {
328                         list( $value, $parameters ) = explode( ';', $value, 2 );
329                 }
330
331                 $value = strtolower( $value );
332                 if ( strpos( $value, '/' ) === false ) {
333                         return null;
334                 }
335
336                 // Parse type and subtype out.
337                 list( $type, $subtype ) = explode( '/', $value, 2 );
338
339                 $data = compact( 'value', 'type', 'subtype', 'parameters' );
340                 $data = array_map( 'trim', $data );
341
342                 return $data;
343         }
344
345         /**
346          * Retrieves the parameter priority order.
347          *
348          * Used when checking parameters in get_param().
349          *
350          * @since 4.4.0
351          * @access protected
352          *
353          * @return array List of types to check, in order of priority.
354          */
355         protected function get_parameter_order() {
356                 $order = array();
357                 $order[] = 'JSON';
358
359                 $this->parse_json_params();
360
361                 // Ensure we parse the body data.
362                 $body = $this->get_body();
363                 if ( $this->method !== 'POST' && ! empty( $body ) ) {
364                         $this->parse_body_params();
365                 }
366
367                 $accepts_body_data = array( 'POST', 'PUT', 'PATCH' );
368                 if ( in_array( $this->method, $accepts_body_data ) ) {
369                         $order[] = 'POST';
370                 }
371
372                 $order[] = 'GET';
373                 $order[] = 'URL';
374                 $order[] = 'defaults';
375
376                 /**
377                  * Filter the parameter order.
378                  *
379                  * The order affects which parameters are checked when using get_param() and family.
380                  * This acts similarly to PHP's `request_order` setting.
381                  *
382                  * @since 4.4.0
383                  *
384                  * @param array           $order {
385                  *    An array of types to check, in order of priority.
386                  *
387                  *    @param string $type The type to check.
388                  * }
389                  * @param WP_REST_Request $this The request object.
390                  */
391                 return apply_filters( 'rest_request_parameter_order', $order, $this );
392         }
393
394         /**
395          * Retrieves a parameter from the request.
396          *
397          * @since 4.4.0
398          * @access public
399          *
400          * @param string $key Parameter name.
401          * @return mixed|null Value if set, null otherwise.
402          */
403         public function get_param( $key ) {
404                 $order = $this->get_parameter_order();
405
406                 foreach ( $order as $type ) {
407                         // Determine if we have the parameter for this type.
408                         if ( isset( $this->params[ $type ][ $key ] ) ) {
409                                 return $this->params[ $type ][ $key ];
410                         }
411                 }
412
413                 return null;
414         }
415
416         /**
417          * Sets a parameter on the request.
418          *
419          * @since 4.4.0
420          * @access public
421          *
422          * @param string $key   Parameter name.
423          * @param mixed  $value Parameter value.
424          */
425         public function set_param( $key, $value ) {
426                 switch ( $this->method ) {
427                         case 'POST':
428                                 $this->params['POST'][ $key ] = $value;
429                                 break;
430
431                         default:
432                                 $this->params['GET'][ $key ] = $value;
433                                 break;
434                 }
435         }
436
437         /**
438          * Retrieves merged parameters from the request.
439          *
440          * The equivalent of get_param(), but returns all parameters for the request.
441          * Handles merging all the available values into a single array.
442          *
443          * @since 4.4.0
444          * @access public
445          *
446          * @return array Map of key to value.
447          */
448         public function get_params() {
449                 $order = $this->get_parameter_order();
450                 $order = array_reverse( $order, true );
451
452                 $params = array();
453                 foreach ( $order as $type ) {
454                         $params = array_merge( $params, (array) $this->params[ $type ] );
455                 }
456
457                 return $params;
458         }
459
460         /**
461          * Retrieves parameters from the route itself.
462          *
463          * These are parsed from the URL using the regex.
464          *
465          * @since 4.4.0
466          * @access public
467          *
468          * @return array Parameter map of key to value.
469          */
470         public function get_url_params() {
471                 return $this->params['URL'];
472         }
473
474         /**
475          * Sets parameters from the route.
476          *
477          * Typically, this is set after parsing the URL.
478          *
479          * @since 4.4.0
480          * @access public
481          *
482          * @param array $params Parameter map of key to value.
483          */
484         public function set_url_params( $params ) {
485                 $this->params['URL'] = $params;
486         }
487
488         /**
489          * Retrieves parameters from the query string.
490          *
491          * These are the parameters you'd typically find in `$_GET`.
492          *
493          * @since 4.4.0
494          * @access public
495          *
496          * @return array Parameter map of key to value
497          */
498         public function get_query_params() {
499                 return $this->params['GET'];
500         }
501
502         /**
503          * Sets parameters from the query string.
504          *
505          * Typically, this is set from `$_GET`.
506          *
507          * @since 4.4.0
508          * @access public
509          *
510          * @param array $params Parameter map of key to value.
511          */
512         public function set_query_params( $params ) {
513                 $this->params['GET'] = $params;
514         }
515
516         /**
517          * Retrieves parameters from the body.
518          *
519          * These are the parameters you'd typically find in `$_POST`.
520          *
521          * @since 4.4.0
522          * @access public
523          *
524          * @return array Parameter map of key to value.
525          */
526         public function get_body_params() {
527                 return $this->params['POST'];
528         }
529
530         /**
531          * Sets parameters from the body.
532          *
533          * Typically, this is set from `$_POST`.
534          *
535          * @since 4.4.0
536          * @access public
537          *
538          * @param array $params Parameter map of key to value.
539          */
540         public function set_body_params( $params ) {
541                 $this->params['POST'] = $params;
542         }
543
544         /**
545          * Retrieves multipart file parameters from the body.
546          *
547          * These are the parameters you'd typically find in `$_FILES`.
548          *
549          * @since 4.4.0
550          * @access public
551          *
552          * @return array Parameter map of key to value
553          */
554         public function get_file_params() {
555                 return $this->params['FILES'];
556         }
557
558         /**
559          * Sets multipart file parameters from the body.
560          *
561          * Typically, this is set from `$_FILES`.
562          *
563          * @since 4.4.0
564          * @access public
565          *
566          * @param array $params Parameter map of key to value.
567          */
568         public function set_file_params( $params ) {
569                 $this->params['FILES'] = $params;
570         }
571
572         /**
573          * Retrieves the default parameters.
574          *
575          * These are the parameters set in the route registration.
576          *
577          * @since 4.4.0
578          * @access public
579          *
580          * @return array Parameter map of key to value
581          */
582         public function get_default_params() {
583                 return $this->params['defaults'];
584         }
585
586         /**
587          * Sets default parameters.
588          *
589          * These are the parameters set in the route registration.
590          *
591          * @since 4.4.0
592          * @access public
593          *
594          * @param array $params Parameter map of key to value.
595          */
596         public function set_default_params( $params ) {
597                 $this->params['defaults'] = $params;
598         }
599
600         /**
601          * Retrieves the request body content.
602          *
603          * @since 4.4.0
604          * @access public
605          *
606          * @return string Binary data from the request body.
607          */
608         public function get_body() {
609                 return $this->body;
610         }
611
612         /**
613          * Sets body content.
614          *
615          * @since 4.4.0
616          * @access public
617          *
618          * @param string $data Binary data from the request body.
619          */
620         public function set_body( $data ) {
621                 $this->body = $data;
622
623                 // Enable lazy parsing.
624                 $this->parsed_json = false;
625                 $this->parsed_body = false;
626                 $this->params['JSON'] = null;
627         }
628
629         /**
630          * Retrieves the parameters from a JSON-formatted body.
631          *
632          * @since 4.4.0
633          * @access public
634          *
635          * @return array Parameter map of key to value.
636          */
637         public function get_json_params() {
638                 // Ensure the parameters have been parsed out.
639                 $this->parse_json_params();
640
641                 return $this->params['JSON'];
642         }
643
644         /**
645          * Parses the JSON parameters.
646          *
647          * Avoids parsing the JSON data until we need to access it.
648          *
649          * @since 4.4.0
650          * @access protected
651          */
652         protected function parse_json_params() {
653                 if ( $this->parsed_json ) {
654                         return;
655                 }
656
657                 $this->parsed_json = true;
658
659                 // Check that we actually got JSON.
660                 $content_type = $this->get_content_type();
661
662                 if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
663                         return;
664                 }
665
666                 $params = json_decode( $this->get_body(), true );
667
668                 /*
669                  * Check for a parsing error.
670                  *
671                  * Note that due to WP's JSON compatibility functions, json_last_error
672                  * might not be defined: https://core.trac.wordpress.org/ticket/27799
673                  */
674                 if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
675                         return;
676                 }
677
678                 $this->params['JSON'] = $params;
679         }
680
681         /**
682          * Parses the request body parameters.
683          *
684          * Parses out URL-encoded bodies for request methods that aren't supported
685          * natively by PHP. In PHP 5.x, only POST has these parsed automatically.
686          *
687          * @since 4.4.0
688          * @access protected
689          */
690         protected function parse_body_params() {
691                 if ( $this->parsed_body ) {
692                         return;
693                 }
694
695                 $this->parsed_body = true;
696
697                 /*
698                  * Check that we got URL-encoded. Treat a missing content-type as
699                  * URL-encoded for maximum compatibility.
700                  */
701                 $content_type = $this->get_content_type();
702
703                 if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
704                         return;
705                 }
706
707                 parse_str( $this->get_body(), $params );
708
709                 /*
710                  * Amazingly, parse_str follows magic quote rules. Sigh.
711                  *
712                  * NOTE: Do not refactor to use `wp_unslash`.
713                  */
714                 if ( get_magic_quotes_gpc() ) {
715                         $params = stripslashes_deep( $params );
716                 }
717
718                 /*
719                  * Add to the POST parameters stored internally. If a user has already
720                  * set these manually (via `set_body_params`), don't override them.
721                  */
722                 $this->params['POST'] = array_merge( $params, $this->params['POST'] );
723         }
724
725         /**
726          * Retrieves the route that matched the request.
727          *
728          * @since 4.4.0
729          * @access public
730          *
731          * @return string Route matching regex.
732          */
733         public function get_route() {
734                 return $this->route;
735         }
736
737         /**
738          * Sets the route that matched the request.
739          *
740          * @since 4.4.0
741          * @access public
742          *
743          * @param string $route Route matching regex.
744          */
745         public function set_route( $route ) {
746                 $this->route = $route;
747         }
748
749         /**
750          * Retrieves the attributes for the request.
751          *
752          * These are the options for the route that was matched.
753          *
754          * @since 4.4.0
755          * @access public
756          *
757          * @return array Attributes for the request.
758          */
759         public function get_attributes() {
760                 return $this->attributes;
761         }
762
763         /**
764          * Sets the attributes for the request.
765          *
766          * @since 4.4.0
767          * @access public
768          *
769          * @param array $attributes Attributes for the request.
770          */
771         public function set_attributes( $attributes ) {
772                 $this->attributes = $attributes;
773         }
774
775         /**
776          * Sanitizes (where possible) the params on the request.
777          *
778          * This is primarily based off the sanitize_callback param on each registered
779          * argument.
780          *
781          * @since 4.4.0
782          * @access public
783          *
784          * @return true|null True if there are no parameters to sanitize, null otherwise.
785          */
786         public function sanitize_params() {
787
788                 $attributes = $this->get_attributes();
789
790                 // No arguments set, skip sanitizing.
791                 if ( empty( $attributes['args'] ) ) {
792                         return true;
793                 }
794
795                 $order = $this->get_parameter_order();
796
797                 foreach ( $order as $type ) {
798                         if ( empty( $this->params[ $type ] ) ) {
799                                 continue;
800                         }
801                         foreach ( $this->params[ $type ] as $key => $value ) {
802                                 // Check if this param has a sanitize_callback added.
803                                 if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
804                                         $this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
805                                 }
806                         }
807                 }
808                 return null;
809         }
810
811         /**
812          * Checks whether this request is valid according to its attributes.
813          *
814          * @since 4.4.0
815          * @access public
816          *
817          * @return bool|WP_Error True if there are no parameters to validate or if all pass validation,
818          *                       WP_Error if required parameters are missing.
819          */
820         public function has_valid_params() {
821
822                 $attributes = $this->get_attributes();
823                 $required = array();
824
825                 // No arguments set, skip validation.
826                 if ( empty( $attributes['args'] ) ) {
827                         return true;
828                 }
829
830                 foreach ( $attributes['args'] as $key => $arg ) {
831
832                         $param = $this->get_param( $key );
833                         if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
834                                 $required[] = $key;
835                         }
836                 }
837
838                 if ( ! empty( $required ) ) {
839                         return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
840                 }
841
842                 /*
843                  * Check the validation callbacks for each registered arg.
844                  *
845                  * This is done after required checking as required checking is cheaper.
846                  */
847                 $invalid_params = array();
848
849                 foreach ( $attributes['args'] as $key => $arg ) {
850
851                         $param = $this->get_param( $key );
852
853                         if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
854                                 $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
855
856                                 if ( false === $valid_check ) {
857                                         $invalid_params[ $key ] = __( 'Invalid parameter.' );
858                                 }
859
860                                 if ( is_wp_error( $valid_check ) ) {
861                                         $invalid_params[ $key ] = $valid_check->get_error_message();
862                                 }
863                         }
864                 }
865
866                 if ( $invalid_params ) {
867                         return new WP_Error( 'rest_invalid_param', sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), array( 'status' => 400, 'params' => $invalid_params ) );
868                 }
869
870                 return true;
871
872         }
873
874         /**
875          * Checks if a parameter is set.
876          *
877          * @since 4.4.0
878          * @access public
879          *
880          * @param string $offset Parameter name.
881          * @return bool Whether the parameter is set.
882          */
883         public function offsetExists( $offset ) {
884                 $order = $this->get_parameter_order();
885
886                 foreach ( $order as $type ) {
887                         if ( isset( $this->params[ $type ][ $offset ] ) ) {
888                                 return true;
889                         }
890                 }
891
892                 return false;
893         }
894
895         /**
896          * Retrieves a parameter from the request.
897          *
898          * @since 4.4.0
899          * @access public
900          *
901          * @param string $offset Parameter name.
902          * @return mixed|null Value if set, null otherwise.
903          */
904         public function offsetGet( $offset ) {
905                 return $this->get_param( $offset );
906         }
907
908         /**
909          * Sets a parameter on the request.
910          *
911          * @since 4.4.0
912          * @access public
913          *
914          * @param string $offset Parameter name.
915          * @param mixed  $value  Parameter value.
916          */
917         public function offsetSet( $offset, $value ) {
918                 $this->set_param( $offset, $value );
919         }
920
921         /**
922          * Removes a parameter from the request.
923          *
924          * @since 4.4.0
925          * @access public
926          *
927          * @param string $offset Parameter name.
928          */
929         public function offsetUnset( $offset ) {
930                 $order = $this->get_parameter_order();
931
932                 // Remove the offset from every group.
933                 foreach ( $order as $type ) {
934                         unset( $this->params[ $type ][ $offset ] );
935                 }
936         }
937
938         /**
939          * Retrieves a WP_REST_Request object from a full URL.
940          *
941          * @static
942          * @since 4.5.0
943          * @access public
944          *
945          * @param string $url URL with protocol, domain, path and query args.
946          * @return WP_REST_Request|false WP_REST_Request object on success, false on failure.
947          */
948         public static function from_url( $url ) {
949                 $bits = parse_url( $url );
950                 $query_params = array();
951
952                 if ( ! empty( $bits['query'] ) ) {
953                         wp_parse_str( $bits['query'], $query_params );
954                 }
955
956                 $api_root = rest_url();
957                 if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) {
958                         // Pretty permalinks on, and URL is under the API root
959                         $api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) );
960                         $route = parse_url( $api_url_part, PHP_URL_PATH );
961                 } elseif ( ! empty( $query_params['rest_route'] ) ) {
962                         // ?rest_route=... set directly
963                         $route = $query_params['rest_route'];
964                         unset( $query_params['rest_route'] );
965                 }
966
967                 $request = false;
968                 if ( ! empty( $route ) ) {
969                         $request = new WP_REST_Request( 'GET', $route );
970                         $request->set_query_params( $query_params );
971                 }
972
973                 /**
974                  * Filter the request generated from a URL.
975                  *
976                  * @since 4.5.0
977                  *
978                  * @param WP_REST_Request|false $request Generated request object, or false if URL
979                  *                                       could not be parsed.
980                  * @param string                $url     URL the request was generated from.
981                  */
982                 return apply_filters( 'rest_request_from_url', $request, $url );
983         }
984 }