Make WordPress Core

source: trunk/src/wp-includes/rest-api/class-wp-rest-request.php

Last change on this file was 61139, checked in by SergeyBiryukov, 6 weeks ago

Docs: Add some missing full stops in various DocBlocks.

Follow-up to [8196], [32846], [34928].

Props sujansarkar, shailu25, dhruvang21, westonruter, SergeyBiryukov.
Fixes #64181.

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