3 * REST API: WP_REST_Request class
11 * Core class used to implement a REST request object.
13 * Contains data from the request, to be passed to the callback.
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.
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()},
30 class WP_REST_Request implements ArrayAccess {
39 protected $method = '';
42 * Parameters passed to the request.
44 * These typically come from the `$_GET`, `$_POST` and `$_FILES`
45 * superglobals when being created from the global scope.
49 * @var array Contains GET, POST and FILES keys mapping to arrays of data.
54 * HTTP headers for the request.
58 * @var array Map of key to value. Key is always lowercase, as per HTTP specification.
60 protected $headers = array();
67 * @var string Binary data from the request.
69 protected $body = null;
72 * Route matched for the request.
81 * Attributes (options) for the route that was matched.
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.
88 * @var array Attributes for the request.
90 protected $attributes = array();
93 * Used to determine if the JSON data has been parsed yet.
95 * Allows lazy-parsing of JSON data where possible.
101 protected $parsed_json = false;
104 * Used to determine if the body data has been parsed yet.
110 protected $parsed_body = false;
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.
122 public function __construct( $method = '', $route = '', $attributes = array() ) {
123 $this->params = array(
129 // See parse_json_params.
132 'defaults' => array(),
135 $this->set_method( $method );
136 $this->set_route( $route );
137 $this->set_attributes( $attributes );
141 * Retrieves the HTTP method for the request.
146 * @return string HTTP method.
148 public function get_method() {
149 return $this->method;
153 * Sets HTTP method for the request.
158 * @param string $method HTTP method.
160 public function set_method( $method ) {
161 $this->method = strtoupper( $method );
165 * Retrieves all headers from the request.
170 * @return array Map of key to value. Key is always lowercase, as per HTTP specification.
172 public function get_headers() {
173 return $this->headers;
177 * Canonicalizes the header name.
179 * Ensures that header names are always treated the same regardless of
180 * source. Header names are always case insensitive.
182 * Note that we treat `-` (dashes) and `_` (underscores) as the same
183 * character, as per header parsing rules in both Apache and nginx.
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
193 * @param string $key Header name.
194 * @return string Canonicalized name.
196 public static function canonicalize_header_name( $key ) {
197 $key = strtolower( $key );
198 $key = str_replace( '-', '_', $key );
204 * Retrieves the given header from the request.
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.
213 * @param string $key Header name, will be canonicalized to lowercase.
214 * @return string|null String value if set, null otherwise.
216 public function get_header( $key ) {
217 $key = $this->canonicalize_header_name( $key );
219 if ( ! isset( $this->headers[ $key ] ) ) {
223 return implode( ',', $this->headers[ $key ] );
227 * Retrieves header values from the request.
232 * @param string $key Header name, will be canonicalized to lowercase.
233 * @return array|null List of string values if set, null otherwise.
235 public function get_header_as_array( $key ) {
236 $key = $this->canonicalize_header_name( $key );
238 if ( ! isset( $this->headers[ $key ] ) ) {
242 return $this->headers[ $key ];
246 * Sets the header on request.
251 * @param string $key Header name.
252 * @param string $value Header value, or list of values.
254 public function set_header( $key, $value ) {
255 $key = $this->canonicalize_header_name( $key );
256 $value = (array) $value;
258 $this->headers[ $key ] = $value;
262 * Appends a header value for the given header.
267 * @param string $key Header name.
268 * @param string $value Header value, or list of values.
270 public function add_header( $key, $value ) {
271 $key = $this->canonicalize_header_name( $key );
272 $value = (array) $value;
274 if ( ! isset( $this->headers[ $key ] ) ) {
275 $this->headers[ $key ] = array();
278 $this->headers[ $key ] = array_merge( $this->headers[ $key ], $value );
282 * Removes all values for a header.
287 * @param string $key Header name.
289 public function remove_header( $key ) {
290 unset( $this->headers[ $key ] );
294 * Sets headers on the request.
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.
302 public function set_headers( $headers, $override = true ) {
303 if ( true === $override ) {
304 $this->headers = array();
307 foreach ( $headers as $key => $value ) {
308 $this->set_header( $key, $value );
313 * Retrieves the content-type of the request.
318 * @return array Map containing 'value' and 'parameters' keys.
320 public function get_content_type() {
321 $value = $this->get_header( 'content-type' );
322 if ( empty( $value ) ) {
327 if ( strpos( $value, ';' ) ) {
328 list( $value, $parameters ) = explode( ';', $value, 2 );
331 $value = strtolower( $value );
332 if ( strpos( $value, '/' ) === false ) {
336 // Parse type and subtype out.
337 list( $type, $subtype ) = explode( '/', $value, 2 );
339 $data = compact( 'value', 'type', 'subtype', 'parameters' );
340 $data = array_map( 'trim', $data );
346 * Retrieves the parameter priority order.
348 * Used when checking parameters in get_param().
353 * @return array List of types to check, in order of priority.
355 protected function get_parameter_order() {
359 $this->parse_json_params();
361 // Ensure we parse the body data.
362 $body = $this->get_body();
363 if ( $this->method !== 'POST' && ! empty( $body ) ) {
364 $this->parse_body_params();
367 $accepts_body_data = array( 'POST', 'PUT', 'PATCH' );
368 if ( in_array( $this->method, $accepts_body_data ) ) {
374 $order[] = 'defaults';
377 * Filter the parameter order.
379 * The order affects which parameters are checked when using get_param() and family.
380 * This acts similarly to PHP's `request_order` setting.
384 * @param array $order {
385 * An array of types to check, in order of priority.
387 * @param string $type The type to check.
389 * @param WP_REST_Request $this The request object.
391 return apply_filters( 'rest_request_parameter_order', $order, $this );
395 * Retrieves a parameter from the request.
400 * @param string $key Parameter name.
401 * @return mixed|null Value if set, null otherwise.
403 public function get_param( $key ) {
404 $order = $this->get_parameter_order();
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 ];
417 * Sets a parameter on the request.
422 * @param string $key Parameter name.
423 * @param mixed $value Parameter value.
425 public function set_param( $key, $value ) {
426 switch ( $this->method ) {
428 $this->params['POST'][ $key ] = $value;
432 $this->params['GET'][ $key ] = $value;
438 * Retrieves merged parameters from the request.
440 * The equivalent of get_param(), but returns all parameters for the request.
441 * Handles merging all the available values into a single array.
446 * @return array Map of key to value.
448 public function get_params() {
449 $order = $this->get_parameter_order();
450 $order = array_reverse( $order, true );
453 foreach ( $order as $type ) {
454 $params = array_merge( $params, (array) $this->params[ $type ] );
461 * Retrieves parameters from the route itself.
463 * These are parsed from the URL using the regex.
468 * @return array Parameter map of key to value.
470 public function get_url_params() {
471 return $this->params['URL'];
475 * Sets parameters from the route.
477 * Typically, this is set after parsing the URL.
482 * @param array $params Parameter map of key to value.
484 public function set_url_params( $params ) {
485 $this->params['URL'] = $params;
489 * Retrieves parameters from the query string.
491 * These are the parameters you'd typically find in `$_GET`.
496 * @return array Parameter map of key to value
498 public function get_query_params() {
499 return $this->params['GET'];
503 * Sets parameters from the query string.
505 * Typically, this is set from `$_GET`.
510 * @param array $params Parameter map of key to value.
512 public function set_query_params( $params ) {
513 $this->params['GET'] = $params;
517 * Retrieves parameters from the body.
519 * These are the parameters you'd typically find in `$_POST`.
524 * @return array Parameter map of key to value.
526 public function get_body_params() {
527 return $this->params['POST'];
531 * Sets parameters from the body.
533 * Typically, this is set from `$_POST`.
538 * @param array $params Parameter map of key to value.
540 public function set_body_params( $params ) {
541 $this->params['POST'] = $params;
545 * Retrieves multipart file parameters from the body.
547 * These are the parameters you'd typically find in `$_FILES`.
552 * @return array Parameter map of key to value
554 public function get_file_params() {
555 return $this->params['FILES'];
559 * Sets multipart file parameters from the body.
561 * Typically, this is set from `$_FILES`.
566 * @param array $params Parameter map of key to value.
568 public function set_file_params( $params ) {
569 $this->params['FILES'] = $params;
573 * Retrieves the default parameters.
575 * These are the parameters set in the route registration.
580 * @return array Parameter map of key to value
582 public function get_default_params() {
583 return $this->params['defaults'];
587 * Sets default parameters.
589 * These are the parameters set in the route registration.
594 * @param array $params Parameter map of key to value.
596 public function set_default_params( $params ) {
597 $this->params['defaults'] = $params;
601 * Retrieves the request body content.
606 * @return string Binary data from the request body.
608 public function get_body() {
618 * @param string $data Binary data from the request body.
620 public function set_body( $data ) {
623 // Enable lazy parsing.
624 $this->parsed_json = false;
625 $this->parsed_body = false;
626 $this->params['JSON'] = null;
630 * Retrieves the parameters from a JSON-formatted body.
635 * @return array Parameter map of key to value.
637 public function get_json_params() {
638 // Ensure the parameters have been parsed out.
639 $this->parse_json_params();
641 return $this->params['JSON'];
645 * Parses the JSON parameters.
647 * Avoids parsing the JSON data until we need to access it.
652 protected function parse_json_params() {
653 if ( $this->parsed_json ) {
657 $this->parsed_json = true;
659 // Check that we actually got JSON.
660 $content_type = $this->get_content_type();
662 if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
666 $params = json_decode( $this->get_body(), true );
669 * Check for a parsing error.
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
674 if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
678 $this->params['JSON'] = $params;
682 * Parses the request body parameters.
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.
690 protected function parse_body_params() {
691 if ( $this->parsed_body ) {
695 $this->parsed_body = true;
698 * Check that we got URL-encoded. Treat a missing content-type as
699 * URL-encoded for maximum compatibility.
701 $content_type = $this->get_content_type();
703 if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
707 parse_str( $this->get_body(), $params );
710 * Amazingly, parse_str follows magic quote rules. Sigh.
712 * NOTE: Do not refactor to use `wp_unslash`.
714 if ( get_magic_quotes_gpc() ) {
715 $params = stripslashes_deep( $params );
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.
722 $this->params['POST'] = array_merge( $params, $this->params['POST'] );
726 * Retrieves the route that matched the request.
731 * @return string Route matching regex.
733 public function get_route() {
738 * Sets the route that matched the request.
743 * @param string $route Route matching regex.
745 public function set_route( $route ) {
746 $this->route = $route;
750 * Retrieves the attributes for the request.
752 * These are the options for the route that was matched.
757 * @return array Attributes for the request.
759 public function get_attributes() {
760 return $this->attributes;
764 * Sets the attributes for the request.
769 * @param array $attributes Attributes for the request.
771 public function set_attributes( $attributes ) {
772 $this->attributes = $attributes;
776 * Sanitizes (where possible) the params on the request.
778 * This is primarily based off the sanitize_callback param on each registered
784 * @return true|null True if there are no parameters to sanitize, null otherwise.
786 public function sanitize_params() {
788 $attributes = $this->get_attributes();
790 // No arguments set, skip sanitizing.
791 if ( empty( $attributes['args'] ) ) {
795 $order = $this->get_parameter_order();
797 foreach ( $order as $type ) {
798 if ( empty( $this->params[ $type ] ) ) {
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 );
812 * Checks whether this request is valid according to its attributes.
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.
820 public function has_valid_params() {
822 $attributes = $this->get_attributes();
825 // No arguments set, skip validation.
826 if ( empty( $attributes['args'] ) ) {
830 foreach ( $attributes['args'] as $key => $arg ) {
832 $param = $this->get_param( $key );
833 if ( isset( $arg['required'] ) && true === $arg['required'] && null === $param ) {
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 ) );
843 * Check the validation callbacks for each registered arg.
845 * This is done after required checking as required checking is cheaper.
847 $invalid_params = array();
849 foreach ( $attributes['args'] as $key => $arg ) {
851 $param = $this->get_param( $key );
853 if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
854 $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
856 if ( false === $valid_check ) {
857 $invalid_params[ $key ] = __( 'Invalid parameter.' );
860 if ( is_wp_error( $valid_check ) ) {
861 $invalid_params[ $key ] = $valid_check->get_error_message();
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 ) );
875 * Checks if a parameter is set.
880 * @param string $offset Parameter name.
881 * @return bool Whether the parameter is set.
883 public function offsetExists( $offset ) {
884 $order = $this->get_parameter_order();
886 foreach ( $order as $type ) {
887 if ( isset( $this->params[ $type ][ $offset ] ) ) {
896 * Retrieves a parameter from the request.
901 * @param string $offset Parameter name.
902 * @return mixed|null Value if set, null otherwise.
904 public function offsetGet( $offset ) {
905 return $this->get_param( $offset );
909 * Sets a parameter on the request.
914 * @param string $offset Parameter name.
915 * @param mixed $value Parameter value.
917 public function offsetSet( $offset, $value ) {
918 $this->set_param( $offset, $value );
922 * Removes a parameter from the request.
927 * @param string $offset Parameter name.
929 public function offsetUnset( $offset ) {
930 $order = $this->get_parameter_order();
932 // Remove the offset from every group.
933 foreach ( $order as $type ) {
934 unset( $this->params[ $type ][ $offset ] );
939 * Retrieves a WP_REST_Request object from a full URL.
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.
948 public static function from_url( $url ) {
949 $bits = parse_url( $url );
950 $query_params = array();
952 if ( ! empty( $bits['query'] ) ) {
953 wp_parse_str( $bits['query'], $query_params );
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'] );
968 if ( ! empty( $route ) ) {
969 $request = new WP_REST_Request( 'GET', $route );
970 $request->set_query_params( $query_params );
974 * Filter the request generated from a URL.
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.
982 return apply_filters( 'rest_request_from_url', $request, $url );