22 * 2 (`POST`) not 1 (`GET`). For more precision between request methods, use |
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. |
23 * WP_REST_Request::get_body_params(), WP_REST_Request::get_url_params(), etc. |
24 * |
24 * |
25 * @since 4.4.0 |
25 * @since 4.4.0 |
26 * |
26 * |
27 * @see ArrayAccess |
27 * @link https://www.php.net/manual/en/class.arrayaccess.php |
28 */ |
28 */ |
29 class WP_REST_Request implements ArrayAccess { |
29 class WP_REST_Request implements ArrayAccess { |
30 |
30 |
31 /** |
31 /** |
32 * HTTP method. |
32 * HTTP method. |
292 /** |
292 /** |
293 * Retrieves the content-type of the request. |
293 * Retrieves the content-type of the request. |
294 * |
294 * |
295 * @since 4.4.0 |
295 * @since 4.4.0 |
296 * |
296 * |
297 * @return array Map containing 'value' and 'parameters' keys. |
297 * @return array|null Map containing 'value' and 'parameters' keys |
|
298 * or null when no valid content-type header was |
|
299 * available. |
298 */ |
300 */ |
299 public function get_content_type() { |
301 public function get_content_type() { |
300 $value = $this->get_header( 'content-type' ); |
302 $value = $this->get_header( 'content-type' ); |
301 if ( empty( $value ) ) { |
303 if ( empty( $value ) ) { |
302 return null; |
304 return null; |
306 if ( strpos( $value, ';' ) ) { |
308 if ( strpos( $value, ';' ) ) { |
307 list( $value, $parameters ) = explode( ';', $value, 2 ); |
309 list( $value, $parameters ) = explode( ';', $value, 2 ); |
308 } |
310 } |
309 |
311 |
310 $value = strtolower( $value ); |
312 $value = strtolower( $value ); |
311 if ( strpos( $value, '/' ) === false ) { |
313 if ( false === strpos( $value, '/' ) ) { |
312 return null; |
314 return null; |
313 } |
315 } |
314 |
316 |
315 // Parse type and subtype out. |
317 // Parse type and subtype out. |
316 list( $type, $subtype ) = explode( '/', $value, 2 ); |
318 list( $type, $subtype ) = explode( '/', $value, 2 ); |
326 * |
328 * |
327 * Used when checking parameters in get_param(). |
329 * Used when checking parameters in get_param(). |
328 * |
330 * |
329 * @since 4.4.0 |
331 * @since 4.4.0 |
330 * |
332 * |
331 * @return array List of types to check, in order of priority. |
333 * @return string[] Array of types to check, in order of priority. |
332 */ |
334 */ |
333 protected function get_parameter_order() { |
335 protected function get_parameter_order() { |
334 $order = array(); |
336 $order = array(); |
335 |
337 |
336 $content_type = $this->get_content_type(); |
338 $content_type = $this->get_content_type(); |
337 if ( $content_type['value'] === 'application/json' ) { |
339 if ( isset( $content_type['value'] ) && 'application/json' === $content_type['value'] ) { |
338 $order[] = 'JSON'; |
340 $order[] = 'JSON'; |
339 } |
341 } |
340 |
342 |
341 $this->parse_json_params(); |
343 $this->parse_json_params(); |
342 |
344 |
346 if ( 'POST' !== $this->method && ! empty( $body ) ) { |
348 if ( 'POST' !== $this->method && ! empty( $body ) ) { |
347 $this->parse_body_params(); |
349 $this->parse_body_params(); |
348 } |
350 } |
349 |
351 |
350 $accepts_body_data = array( 'POST', 'PUT', 'PATCH', 'DELETE' ); |
352 $accepts_body_data = array( 'POST', 'PUT', 'PATCH', 'DELETE' ); |
351 if ( in_array( $this->method, $accepts_body_data ) ) { |
353 if ( in_array( $this->method, $accepts_body_data, true ) ) { |
352 $order[] = 'POST'; |
354 $order[] = 'POST'; |
353 } |
355 } |
354 |
356 |
355 $order[] = 'GET'; |
357 $order[] = 'GET'; |
356 $order[] = 'URL'; |
358 $order[] = 'URL'; |
362 * The order affects which parameters are checked when using get_param() and family. |
364 * The order affects which parameters are checked when using get_param() and family. |
363 * This acts similarly to PHP's `request_order` setting. |
365 * This acts similarly to PHP's `request_order` setting. |
364 * |
366 * |
365 * @since 4.4.0 |
367 * @since 4.4.0 |
366 * |
368 * |
367 * @param array $order { |
369 * @param string[] $order Array of types to check, in order of priority. |
368 * An array of types to check, in order of priority. |
370 * @param WP_REST_Request $this The request object. |
369 * |
|
370 * @param string $type The type to check. |
|
371 * } |
|
372 * @param WP_REST_Request $this The request object. |
|
373 */ |
371 */ |
374 return apply_filters( 'rest_request_parameter_order', $order, $this ); |
372 return apply_filters( 'rest_request_parameter_order', $order, $this ); |
375 } |
373 } |
376 |
374 |
377 /** |
375 /** |
394 |
392 |
395 return null; |
393 return null; |
396 } |
394 } |
397 |
395 |
398 /** |
396 /** |
|
397 * Checks if a parameter exists in the request. |
|
398 * |
|
399 * This allows distinguishing between an omitted parameter, |
|
400 * and a parameter specifically set to null. |
|
401 * |
|
402 * @since 5.3.0 |
|
403 * |
|
404 * @param string $key Parameter name. |
|
405 * @return bool True if a param exists for the given key. |
|
406 */ |
|
407 public function has_param( $key ) { |
|
408 $order = $this->get_parameter_order(); |
|
409 |
|
410 foreach ( $order as $type ) { |
|
411 if ( is_array( $this->params[ $type ] ) && array_key_exists( $key, $this->params[ $type ] ) ) { |
|
412 return true; |
|
413 } |
|
414 } |
|
415 |
|
416 return false; |
|
417 } |
|
418 |
|
419 /** |
399 * Sets a parameter on the request. |
420 * Sets a parameter on the request. |
|
421 * |
|
422 * If the given parameter key exists in any parameter type an update will take place, |
|
423 * otherwise a new param will be created in the first parameter type (respecting |
|
424 * get_parameter_order()). |
400 * |
425 * |
401 * @since 4.4.0 |
426 * @since 4.4.0 |
402 * |
427 * |
403 * @param string $key Parameter name. |
428 * @param string $key Parameter name. |
404 * @param mixed $value Parameter value. |
429 * @param mixed $value Parameter value. |
405 */ |
430 */ |
406 public function set_param( $key, $value ) { |
431 public function set_param( $key, $value ) { |
407 $order = $this->get_parameter_order(); |
432 $order = $this->get_parameter_order(); |
408 $this->params[ $order[0] ][ $key ] = $value; |
433 $found_key = false; |
|
434 |
|
435 foreach ( $order as $type ) { |
|
436 if ( 'defaults' !== $type && is_array( $this->params[ $type ] ) && array_key_exists( $key, $this->params[ $type ] ) ) { |
|
437 $this->params[ $type ][ $key ] = $value; |
|
438 $found_key = true; |
|
439 } |
|
440 } |
|
441 |
|
442 if ( ! $found_key ) { |
|
443 $this->params[ $order[0] ][ $key ] = $value; |
|
444 } |
409 } |
445 } |
410 |
446 |
411 /** |
447 /** |
412 * Retrieves merged parameters from the request. |
448 * Retrieves merged parameters from the request. |
413 * |
449 * |
422 $order = $this->get_parameter_order(); |
458 $order = $this->get_parameter_order(); |
423 $order = array_reverse( $order, true ); |
459 $order = array_reverse( $order, true ); |
424 |
460 |
425 $params = array(); |
461 $params = array(); |
426 foreach ( $order as $type ) { |
462 foreach ( $order as $type ) { |
427 // array_merge / the "+" operator will mess up |
463 // array_merge() / the "+" operator will mess up |
428 // numeric keys, so instead do a manual foreach. |
464 // numeric keys, so instead do a manual foreach. |
429 foreach ( (array) $this->params[ $type ] as $key => $value ) { |
465 foreach ( (array) $this->params[ $type ] as $key => $value ) { |
430 $params[ $key ] = $value; |
466 $params[ $key ] = $value; |
431 } |
467 } |
432 } |
468 } |
635 |
671 |
636 $params = json_decode( $body, true ); |
672 $params = json_decode( $body, true ); |
637 |
673 |
638 /* |
674 /* |
639 * Check for a parsing error. |
675 * Check for a parsing error. |
640 * |
|
641 * Note that due to WP's JSON compatibility functions, json_last_error |
|
642 * might not be defined: https://core.trac.wordpress.org/ticket/27799 |
|
643 */ |
676 */ |
644 if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) { |
677 if ( null === $params && JSON_ERROR_NONE !== json_last_error() ) { |
645 // Ensure subsequent calls receive error instance. |
678 // Ensure subsequent calls receive error instance. |
646 $this->parsed_json = false; |
679 $this->parsed_json = false; |
647 |
680 |
648 $error_data = array( |
681 $error_data = array( |
649 'status' => WP_Http::BAD_REQUEST, |
682 'status' => WP_Http::BAD_REQUEST, |
|
683 'json_error_code' => json_last_error(), |
|
684 'json_error_message' => json_last_error_msg(), |
650 ); |
685 ); |
651 if ( function_exists( 'json_last_error' ) ) { |
|
652 $error_data['json_error_code'] = json_last_error(); |
|
653 $error_data['json_error_message'] = json_last_error_msg(); |
|
654 } |
|
655 |
686 |
656 return new WP_Error( 'rest_invalid_json', __( 'Invalid JSON body passed.' ), $error_data ); |
687 return new WP_Error( 'rest_invalid_json', __( 'Invalid JSON body passed.' ), $error_data ); |
657 } |
688 } |
658 |
689 |
659 $this->params['JSON'] = $params; |
690 $this->params['JSON'] = $params; |
|
691 |
660 return true; |
692 return true; |
661 } |
693 } |
662 |
694 |
663 /** |
695 /** |
664 * Parses the request body parameters. |
696 * Parses the request body parameters. |
686 } |
718 } |
687 |
719 |
688 parse_str( $this->get_body(), $params ); |
720 parse_str( $this->get_body(), $params ); |
689 |
721 |
690 /* |
722 /* |
691 * Amazingly, parse_str follows magic quote rules. Sigh. |
|
692 * |
|
693 * NOTE: Do not refactor to use `wp_unslash`. |
|
694 */ |
|
695 if ( get_magic_quotes_gpc() ) { |
|
696 $params = stripslashes_deep( $params ); |
|
697 } |
|
698 |
|
699 /* |
|
700 * Add to the POST parameters stored internally. If a user has already |
723 * Add to the POST parameters stored internally. If a user has already |
701 * set these manually (via `set_body_params`), don't override them. |
724 * set these manually (via `set_body_params`), don't override them. |
702 */ |
725 */ |
703 $this->params['POST'] = array_merge( $params, $this->params['POST'] ); |
726 $this->params['POST'] = array_merge( $params, $this->params['POST'] ); |
704 } |
727 } |
773 |
796 |
774 foreach ( $order as $type ) { |
797 foreach ( $order as $type ) { |
775 if ( empty( $this->params[ $type ] ) ) { |
798 if ( empty( $this->params[ $type ] ) ) { |
776 continue; |
799 continue; |
777 } |
800 } |
|
801 |
778 foreach ( $this->params[ $type ] as $key => $value ) { |
802 foreach ( $this->params[ $type ] as $key => $value ) { |
779 if ( ! isset( $attributes['args'][ $key ] ) ) { |
803 if ( ! isset( $attributes['args'][ $key ] ) ) { |
780 continue; |
804 continue; |
781 } |
805 } |
|
806 |
782 $param_args = $attributes['args'][ $key ]; |
807 $param_args = $attributes['args'][ $key ]; |
783 |
808 |
784 // If the arg has a type but no sanitize_callback attribute, default to rest_parse_request_arg. |
809 // If the arg has a type but no sanitize_callback attribute, default to rest_parse_request_arg. |
785 if ( ! array_key_exists( 'sanitize_callback', $param_args ) && ! empty( $param_args['type'] ) ) { |
810 if ( ! array_key_exists( 'sanitize_callback', $param_args ) && ! empty( $param_args['type'] ) ) { |
786 $param_args['sanitize_callback'] = 'rest_parse_request_arg'; |
811 $param_args['sanitize_callback'] = 'rest_parse_request_arg'; |
801 } |
826 } |
802 |
827 |
803 if ( $invalid_params ) { |
828 if ( $invalid_params ) { |
804 return new WP_Error( |
829 return new WP_Error( |
805 'rest_invalid_param', |
830 'rest_invalid_param', |
|
831 /* translators: %s: List of invalid parameters. */ |
806 sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), |
832 sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), |
807 array( |
833 array( |
808 'status' => 400, |
834 'status' => 400, |
809 'params' => $invalid_params, |
835 'params' => $invalid_params, |
810 ) |
836 ) |
846 } |
872 } |
847 |
873 |
848 if ( ! empty( $required ) ) { |
874 if ( ! empty( $required ) ) { |
849 return new WP_Error( |
875 return new WP_Error( |
850 'rest_missing_callback_param', |
876 'rest_missing_callback_param', |
|
877 /* translators: %s: List of required parameters. */ |
851 sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), |
878 sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), |
852 array( |
879 array( |
853 'status' => 400, |
880 'status' => 400, |
854 'params' => $required, |
881 'params' => $required, |
855 ) |
882 ) |
881 } |
908 } |
882 |
909 |
883 if ( $invalid_params ) { |
910 if ( $invalid_params ) { |
884 return new WP_Error( |
911 return new WP_Error( |
885 'rest_invalid_param', |
912 'rest_invalid_param', |
|
913 /* translators: %s: List of invalid parameters. */ |
886 sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), |
914 sprintf( __( 'Invalid parameter(s): %s' ), implode( ', ', array_keys( $invalid_params ) ) ), |
887 array( |
915 array( |
888 'status' => 400, |
916 'status' => 400, |
889 'params' => $invalid_params, |
917 'params' => $invalid_params, |
890 ) |
918 ) |
975 if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) { |
1003 if ( get_option( 'permalink_structure' ) && 0 === strpos( $url, $api_root ) ) { |
976 // Pretty permalinks on, and URL is under the API root. |
1004 // Pretty permalinks on, and URL is under the API root. |
977 $api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) ); |
1005 $api_url_part = substr( $url, strlen( untrailingslashit( $api_root ) ) ); |
978 $route = parse_url( $api_url_part, PHP_URL_PATH ); |
1006 $route = parse_url( $api_url_part, PHP_URL_PATH ); |
979 } elseif ( ! empty( $query_params['rest_route'] ) ) { |
1007 } elseif ( ! empty( $query_params['rest_route'] ) ) { |
980 // ?rest_route=... set directly |
1008 // ?rest_route=... set directly. |
981 $route = $query_params['rest_route']; |
1009 $route = $query_params['rest_route']; |
982 unset( $query_params['rest_route'] ); |
1010 unset( $query_params['rest_route'] ); |
983 } |
1011 } |
984 |
1012 |
985 $request = false; |
1013 $request = false; |