wp/wp-includes/rest-api.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 13 d255fe9cd479
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    15 define( 'REST_API_VERSION', '2.0' );
    15 define( 'REST_API_VERSION', '2.0' );
    16 
    16 
    17 /**
    17 /**
    18  * Registers a REST API route.
    18  * Registers a REST API route.
    19  *
    19  *
    20  * @since 4.4.0
    20  * Note: Do not use before the {@see 'rest_api_init'} hook.
       
    21  *
       
    22  * @since 4.4.0
       
    23  * @since 5.1.0 Added a _doing_it_wrong() notice when not called on or after the rest_api_init hook.
    21  *
    24  *
    22  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
    25  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
    23  * @param string $route     The base URL for route you are adding.
    26  * @param string $route     The base URL for route you are adding.
    24  * @param array  $args      Optional. Either an array of options for the endpoint, or an array of arrays for
    27  * @param array  $args      Optional. Either an array of options for the endpoint, or an array of arrays for
    25  *                          multiple methods. Default empty array.
    28  *                          multiple methods. Default empty array.
    34 		 * and namespace indexes. If you really need to register a
    37 		 * and namespace indexes. If you really need to register a
    35 		 * non-namespaced route, call `WP_REST_Server::register_route` directly.
    38 		 * non-namespaced route, call `WP_REST_Server::register_route` directly.
    36 		 */
    39 		 */
    37 		_doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
    40 		_doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
    38 		return false;
    41 		return false;
    39 	} else if ( empty( $route ) ) {
    42 	} elseif ( empty( $route ) ) {
    40 		_doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
    43 		_doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
    41 		return false;
    44 		return false;
       
    45 	}
       
    46 
       
    47 	if ( ! did_action( 'rest_api_init' ) ) {
       
    48 		_doing_it_wrong(
       
    49 			'register_rest_route',
       
    50 			sprintf(
       
    51 				/* translators: %s: rest_api_init */
       
    52 				__( 'REST API routes must be registered on the %s action.' ),
       
    53 				'<code>rest_api_init</code>'
       
    54 			),
       
    55 			'5.1.0'
       
    56 		);
    42 	}
    57 	}
    43 
    58 
    44 	if ( isset( $args['args'] ) ) {
    59 	if ( isset( $args['args'] ) ) {
    45 		$common_args = $args['args'];
    60 		$common_args = $args['args'];
    46 		unset( $args['args'] );
    61 		unset( $args['args'] );
    52 		// Upgrade a single set to multiple.
    67 		// Upgrade a single set to multiple.
    53 		$args = array( $args );
    68 		$args = array( $args );
    54 	}
    69 	}
    55 
    70 
    56 	$defaults = array(
    71 	$defaults = array(
    57 		'methods'         => 'GET',
    72 		'methods'  => 'GET',
    58 		'callback'        => null,
    73 		'callback' => null,
    59 		'args'            => array(),
    74 		'args'     => array(),
    60 	);
    75 	);
    61 	foreach ( $args as $key => &$arg_group ) {
    76 	foreach ( $args as $key => &$arg_group ) {
    62 		if ( ! is_numeric( $key ) ) {
    77 		if ( ! is_numeric( $key ) ) {
    63 			// Route option, skip here.
    78 			// Route option, skip here.
    64 			continue;
    79 			continue;
    65 		}
    80 		}
    66 
    81 
    67 		$arg_group = array_merge( $defaults, $arg_group );
    82 		$arg_group         = array_merge( $defaults, $arg_group );
    68 		$arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
    83 		$arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
    69 	}
    84 	}
    70 
    85 
    71 	$full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
    86 	$full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
    72 	rest_get_server()->register_route( $namespace, $full_route, $args, $override );
    87 	rest_get_server()->register_route( $namespace, $full_route, $args, $override );
   139  * @global WP_Rewrite $wp_rewrite
   154  * @global WP_Rewrite $wp_rewrite
   140  */
   155  */
   141 function rest_api_register_rewrites() {
   156 function rest_api_register_rewrites() {
   142 	global $wp_rewrite;
   157 	global $wp_rewrite;
   143 
   158 
   144 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
   159 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
   145 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
   160 	add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
   146 	add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
   161 	add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
   147 	add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
   162 	add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
   148 }
   163 }
   149 
   164 
   150 /**
   165 /**
   151  * Registers the default REST API filters.
   166  * Registers the default REST API filters.
   152  *
   167  *
   191 
   206 
   192 		if ( post_type_supports( $post_type->name, 'revisions' ) ) {
   207 		if ( post_type_supports( $post_type->name, 'revisions' ) ) {
   193 			$revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
   208 			$revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
   194 			$revisions_controller->register_routes();
   209 			$revisions_controller->register_routes();
   195 		}
   210 		}
       
   211 
       
   212 		if ( 'attachment' !== $post_type->name ) {
       
   213 			$autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
       
   214 			$autosaves_controller->register_routes();
       
   215 		}
   196 	}
   216 	}
   197 
   217 
   198 	// Post types.
   218 	// Post types.
   199 	$controller = new WP_REST_Post_Types_Controller;
   219 	$controller = new WP_REST_Post_Types_Controller;
   200 	$controller->register_routes();
   220 	$controller->register_routes();
   228 
   248 
   229 	// Comments.
   249 	// Comments.
   230 	$controller = new WP_REST_Comments_Controller;
   250 	$controller = new WP_REST_Comments_Controller;
   231 	$controller->register_routes();
   251 	$controller->register_routes();
   232 
   252 
       
   253 	/**
       
   254 	 * Filters the search handlers to use in the REST search controller.
       
   255 	 *
       
   256 	 * @since 5.0.0
       
   257 	 *
       
   258 	 * @param array $search_handlers List of search handlers to use in the controller. Each search
       
   259 	 *                               handler instance must extend the `WP_REST_Search_Handler` class.
       
   260 	 *                               Default is only a handler for posts.
       
   261 	 */
       
   262 	$search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) );
       
   263 
       
   264 	$controller = new WP_REST_Search_Controller( $search_handlers );
       
   265 	$controller->register_routes();
       
   266 
       
   267 	// Block Renderer.
       
   268 	$controller = new WP_REST_Block_Renderer_Controller;
       
   269 	$controller->register_routes();
       
   270 
   233 	// Settings.
   271 	// Settings.
   234 	$controller = new WP_REST_Settings_Controller;
   272 	$controller = new WP_REST_Settings_Controller;
   235 	$controller->register_routes();
   273 	$controller->register_routes();
       
   274 
       
   275 	// Themes.
       
   276 	$controller = new WP_REST_Themes_Controller;
       
   277 	$controller->register_routes();
       
   278 
   236 }
   279 }
   237 
   280 
   238 /**
   281 /**
   239  * Loads the REST API.
   282  * Loads the REST API.
   240  *
   283  *
   305 function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
   348 function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
   306 	if ( empty( $path ) ) {
   349 	if ( empty( $path ) ) {
   307 		$path = '/';
   350 		$path = '/';
   308 	}
   351 	}
   309 
   352 
       
   353 	$path = '/' . ltrim( $path, '/' );
       
   354 
   310 	if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
   355 	if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
   311 		global $wp_rewrite;
   356 		global $wp_rewrite;
   312 
   357 
   313 		if ( $wp_rewrite->using_index_permalinks() ) {
   358 		if ( $wp_rewrite->using_index_permalinks() ) {
   314 			$url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
   359 			$url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
   315 		} else {
   360 		} else {
   316 			$url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
   361 			$url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
   317 		}
   362 		}
   318 
   363 
   319 		$url .= '/' . ltrim( $path, '/' );
   364 		$url .= $path;
   320 	} else {
   365 	} else {
   321 		$url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
   366 		$url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
   322 		// nginx only allows HTTP/1.0 methods when redirecting from / to /index.php
   367 		// nginx only allows HTTP/1.0 methods when redirecting from / to /index.php
   323 		// To work around this, we manually add index.php to the URL, avoiding the redirect.
   368 		// To work around this, we manually add index.php to the URL, avoiding the redirect.
   324 		if ( 'index.php' !== substr( $url, 9 ) ) {
   369 		if ( 'index.php' !== substr( $url, 9 ) ) {
   325 			$url .= 'index.php';
   370 			$url .= 'index.php';
   326 		}
   371 		}
   327 
       
   328 		$path = '/' . ltrim( $path, '/' );
       
   329 
   372 
   330 		$url = add_query_arg( 'rest_route', $path, $url );
   373 		$url = add_query_arg( 'rest_route', $path, $url );
   331 	}
   374 	}
   332 
   375 
   333 	if ( is_ssl() ) {
   376 	if ( is_ssl() ) {
   415 		 * @since 4.4.0
   458 		 * @since 4.4.0
   416 		 *
   459 		 *
   417 		 * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
   460 		 * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
   418 		 */
   461 		 */
   419 		$wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
   462 		$wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
   420 		$wp_rest_server = new $wp_rest_server_class;
   463 		$wp_rest_server       = new $wp_rest_server_class;
   421 
   464 
   422 		/**
   465 		/**
   423 		 * Fires when preparing to serve an API request.
   466 		 * Fires when preparing to serve an API request.
   424 		 *
   467 		 *
   425 		 * Endpoint objects should be created and register their hooks on this action rather
   468 		 * Endpoint objects should be created and register their hooks on this action rather
   567 	if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
   610 	if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
   568 		return $response;
   611 		return $response;
   569 	}
   612 	}
   570 
   613 
   571 	$response = new WP_REST_Response();
   614 	$response = new WP_REST_Response();
   572 	$data = array();
   615 	$data     = array();
   573 
   616 
   574 	foreach ( $handler->get_routes() as $route => $endpoints ) {
   617 	foreach ( $handler->get_routes() as $route => $endpoints ) {
   575 		$match = preg_match( '@^' . $route . '$@i', $request->get_route() );
   618 		$match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches );
   576 
   619 
   577 		if ( ! $match ) {
   620 		if ( ! $match ) {
   578 			continue;
   621 			continue;
       
   622 		}
       
   623 
       
   624 		$args = array();
       
   625 		foreach ( $matches as $param => $value ) {
       
   626 			if ( ! is_int( $param ) ) {
       
   627 				$args[ $param ] = $value;
       
   628 			}
       
   629 		}
       
   630 
       
   631 		foreach ( $endpoints as $endpoint ) {
       
   632 			// Remove the redundant preg_match argument.
       
   633 			unset( $args[0] );
       
   634 
       
   635 			$request->set_url_params( $args );
       
   636 			$request->set_attributes( $endpoint );
   579 		}
   637 		}
   580 
   638 
   581 		$data = $handler->get_data_for_route( $route, $endpoints, 'help' );
   639 		$data = $handler->get_data_for_route( $route, $endpoints, 'help' );
   582 		$response->set_matched_route( $route );
   640 		$response->set_matched_route( $route );
   583 		break;
   641 		break;
   649 		return $response;
   707 		return $response;
   650 	}
   708 	}
   651 
   709 
   652 	$data = $response->get_data();
   710 	$data = $response->get_data();
   653 
   711 
   654 	$fields = is_array( $request['_fields']  ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] );
   712 	$fields = wp_parse_list( $request['_fields'] );
   655 
   713 
   656 	if ( 0 === count( $fields ) ) {
   714 	if ( 0 === count( $fields ) ) {
   657 		return $response;
   715 		return $response;
   658 	}
   716 	}
   659 
   717 
   865 	// *local* date without a timezone offset) or a UTC date (otherwise).
   923 	// *local* date without a timezone offset) or a UTC date (otherwise).
   866 	// Timezone conversion needs to be handled differently between these two
   924 	// Timezone conversion needs to be handled differently between these two
   867 	// cases.
   925 	// cases.
   868 	if ( ! $is_utc && ! $has_timezone ) {
   926 	if ( ! $is_utc && ! $has_timezone ) {
   869 		$local = date( 'Y-m-d H:i:s', $date );
   927 		$local = date( 'Y-m-d H:i:s', $date );
   870 		$utc = get_gmt_from_date( $local );
   928 		$utc   = get_gmt_from_date( $local );
   871 	} else {
   929 	} else {
   872 		$utc = date( 'Y-m-d H:i:s', $date );
   930 		$utc   = date( 'Y-m-d H:i:s', $date );
   873 		$local = get_date_from_gmt( $utc );
   931 		$local = get_date_from_gmt( $utc );
   874 	}
   932 	}
   875 
   933 
   876 	return array( $local, $utc );
   934 	return array( $local, $utc );
   877 }
   935 }
   980  * @param bool|string|int $value The value being evaluated.
  1038  * @param bool|string|int $value The value being evaluated.
   981  * @return boolean Returns the proper associated boolean value.
  1039  * @return boolean Returns the proper associated boolean value.
   982  */
  1040  */
   983 function rest_sanitize_boolean( $value ) {
  1041 function rest_sanitize_boolean( $value ) {
   984 	// String values are translated to `true`; make sure 'false' is false.
  1042 	// String values are translated to `true`; make sure 'false' is false.
   985 	if ( is_string( $value )  ) {
  1043 	if ( is_string( $value ) ) {
   986 		$value = strtolower( $value );
  1044 		$value = strtolower( $value );
   987 		if ( in_array( $value, array( 'false', '0' ), true ) ) {
  1045 		if ( in_array( $value, array( 'false', '0' ), true ) ) {
   988 			$value = false;
  1046 			$value = false;
   989 		}
  1047 		}
   990 	}
  1048 	}
   991 
  1049 
   992 	// Everything else will map nicely to boolean.
  1050 	// Everything else will map nicely to boolean.
   993 	return (boolean) $value;
  1051 	return (bool) $value;
   994 }
  1052 }
   995 
  1053 
   996 /**
  1054 /**
   997  * Determines if a given value is boolean-like.
  1055  * Determines if a given value is boolean-like.
   998  *
  1056  *
  1079  * @param string $param The parameter name, used in error messages.
  1137  * @param string $param The parameter name, used in error messages.
  1080  * @return true|WP_Error
  1138  * @return true|WP_Error
  1081  */
  1139  */
  1082 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
  1140 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
  1083 	if ( 'array' === $args['type'] ) {
  1141 	if ( 'array' === $args['type'] ) {
  1084 		if ( ! is_array( $value ) ) {
  1142 		if ( ! is_null( $value ) ) {
  1085 			$value = preg_split( '/[\s,]+/', $value );
  1143 			$value = wp_parse_list( $value );
  1086 		}
  1144 		}
  1087 		if ( ! wp_is_numeric_array( $value ) ) {
  1145 		if ( ! wp_is_numeric_array( $value ) ) {
  1088 			/* translators: 1: parameter, 2: type name */
  1146 			/* translators: 1: parameter, 2: type name */
  1089 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  1147 			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
  1090 		}
  1148 		}
  1144 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
  1202 		return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
  1145 	}
  1203 	}
  1146 
  1204 
  1147 	if ( isset( $args['format'] ) ) {
  1205 	if ( isset( $args['format'] ) ) {
  1148 		switch ( $args['format'] ) {
  1206 		switch ( $args['format'] ) {
  1149 			case 'date-time' :
  1207 			case 'date-time':
  1150 				if ( ! rest_parse_date( $value ) ) {
  1208 				if ( ! rest_parse_date( $value ) ) {
  1151 					return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
  1209 					return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
  1152 				}
  1210 				}
  1153 				break;
  1211 				break;
  1154 
  1212 
  1155 			case 'email' :
  1213 			case 'email':
  1156 				if ( ! is_email( $value ) ) {
  1214 				if ( ! is_email( $value ) ) {
  1157 					return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
  1215 					return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
  1158 				}
  1216 				}
  1159 				break;
  1217 				break;
  1160 			case 'ip' :
  1218 			case 'ip':
  1161 				if ( ! rest_is_ip_address( $value ) ) {
  1219 				if ( ! rest_is_ip_address( $value ) ) {
  1162 					/* translators: %s: IP address */
  1220 					/* translators: %s: IP address */
  1163 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
  1221 					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
  1164 				}
  1222 				}
  1165 				break;
  1223 				break;
  1223 function rest_sanitize_value_from_schema( $value, $args ) {
  1281 function rest_sanitize_value_from_schema( $value, $args ) {
  1224 	if ( 'array' === $args['type'] ) {
  1282 	if ( 'array' === $args['type'] ) {
  1225 		if ( empty( $args['items'] ) ) {
  1283 		if ( empty( $args['items'] ) ) {
  1226 			return (array) $value;
  1284 			return (array) $value;
  1227 		}
  1285 		}
  1228 		if ( ! is_array( $value ) ) {
  1286 		$value = wp_parse_list( $value );
  1229 			$value = preg_split( '/[\s,]+/', $value );
       
  1230 		}
       
  1231 		foreach ( $value as $index => $v ) {
  1287 		foreach ( $value as $index => $v ) {
  1232 			$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
  1288 			$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
  1233 		}
  1289 		}
  1234 		// Normalize to numeric array so nothing unexpected
  1290 		// Normalize to numeric array so nothing unexpected
  1235 		// is in the keys.
  1291 		// is in the keys.
  1268 		return rest_sanitize_boolean( $value );
  1324 		return rest_sanitize_boolean( $value );
  1269 	}
  1325 	}
  1270 
  1326 
  1271 	if ( isset( $args['format'] ) ) {
  1327 	if ( isset( $args['format'] ) ) {
  1272 		switch ( $args['format'] ) {
  1328 		switch ( $args['format'] ) {
  1273 			case 'date-time' :
  1329 			case 'date-time':
  1274 				return sanitize_text_field( $value );
  1330 				return sanitize_text_field( $value );
  1275 
  1331 
  1276 			case 'email' :
  1332 			case 'email':
  1277 				/*
  1333 				/*
  1278 				 * sanitize_email() validates, which would be unexpected.
  1334 				 * sanitize_email() validates, which would be unexpected.
  1279 				 */
  1335 				 */
  1280 				return sanitize_text_field( $value );
  1336 				return sanitize_text_field( $value );
  1281 
  1337 
  1282 			case 'uri' :
  1338 			case 'uri':
  1283 				return esc_url_raw( $value );
  1339 				return esc_url_raw( $value );
  1284 
  1340 
  1285 			case 'ip' :
  1341 			case 'ip':
  1286 				return sanitize_text_field( $value );
  1342 				return sanitize_text_field( $value );
  1287 		}
  1343 		}
  1288 	}
  1344 	}
  1289 
  1345 
  1290 	if ( 'string' === $args['type'] ) {
  1346 	if ( 'string' === $args['type'] ) {
  1291 		return strval( $value );
  1347 		return strval( $value );
  1292 	}
  1348 	}
  1293 
  1349 
  1294 	return $value;
  1350 	return $value;
  1295 }
  1351 }
       
  1352 
       
  1353 /**
       
  1354  * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
       
  1355  * Expected to be called in the context of `array_reduce`.
       
  1356  *
       
  1357  * @since 5.0.0
       
  1358  *
       
  1359  * @param  array  $memo Reduce accumulator.
       
  1360  * @param  string $path REST API path to preload.
       
  1361  * @return array        Modified reduce accumulator.
       
  1362  */
       
  1363 function rest_preload_api_request( $memo, $path ) {
       
  1364 	// array_reduce() doesn't support passing an array in PHP 5.2, so we need to make sure we start with one.
       
  1365 	if ( ! is_array( $memo ) ) {
       
  1366 		$memo = array();
       
  1367 	}
       
  1368 
       
  1369 	if ( empty( $path ) ) {
       
  1370 		return $memo;
       
  1371 	}
       
  1372 
       
  1373 	$method = 'GET';
       
  1374 	if ( is_array( $path ) && 2 === count( $path ) ) {
       
  1375 		$method = end( $path );
       
  1376 		$path   = reset( $path );
       
  1377 
       
  1378 		if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
       
  1379 			$method = 'GET';
       
  1380 		}
       
  1381 	}
       
  1382 
       
  1383 	$path_parts = parse_url( $path );
       
  1384 	if ( false === $path_parts ) {
       
  1385 		return $memo;
       
  1386 	}
       
  1387 
       
  1388 	$request = new WP_REST_Request( $method, $path_parts['path'] );
       
  1389 	if ( ! empty( $path_parts['query'] ) ) {
       
  1390 		parse_str( $path_parts['query'], $query_params );
       
  1391 		$request->set_query_params( $query_params );
       
  1392 	}
       
  1393 
       
  1394 	$response = rest_do_request( $request );
       
  1395 	if ( 200 === $response->status ) {
       
  1396 		$server = rest_get_server();
       
  1397 		$data   = (array) $response->get_data();
       
  1398 		$links  = $server->get_compact_response_links( $response );
       
  1399 		if ( ! empty( $links ) ) {
       
  1400 			$data['_links'] = $links;
       
  1401 		}
       
  1402 
       
  1403 		if ( 'OPTIONS' === $method ) {
       
  1404 			$response = rest_send_allow_header( $response, $server, $request );
       
  1405 
       
  1406 			$memo[ $method ][ $path ] = array(
       
  1407 				'body'    => $data,
       
  1408 				'headers' => $response->headers,
       
  1409 			);
       
  1410 		} else {
       
  1411 			$memo[ $path ] = array(
       
  1412 				'body'    => $data,
       
  1413 				'headers' => $response->headers,
       
  1414 			);
       
  1415 		}
       
  1416 	}
       
  1417 
       
  1418 	return $memo;
       
  1419 }