wp/wp-includes/rest-api/class-wp-rest-request.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    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;