diff -r c7c34916027a -r 177826044cd9 wp/wp-includes/rest-api.php --- a/wp/wp-includes/rest-api.php Mon Oct 14 18:06:33 2019 +0200 +++ b/wp/wp-includes/rest-api.php Mon Oct 14 18:28:13 2019 +0200 @@ -17,7 +17,10 @@ /** * Registers a REST API route. * + * Note: Do not use before the {@see 'rest_api_init'} hook. + * * @since 4.4.0 + * @since 5.1.0 Added a _doing_it_wrong() notice when not called on or after the rest_api_init hook. * * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin. * @param string $route The base URL for route you are adding. @@ -36,11 +39,23 @@ */ _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' ); return false; - } else if ( empty( $route ) ) { + } elseif ( empty( $route ) ) { _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); return false; } + if ( ! did_action( 'rest_api_init' ) ) { + _doing_it_wrong( + 'register_rest_route', + sprintf( + /* translators: %s: rest_api_init */ + __( 'REST API routes must be registered on the %s action.' ), + 'rest_api_init' + ), + '5.1.0' + ); + } + if ( isset( $args['args'] ) ) { $common_args = $args['args']; unset( $args['args'] ); @@ -54,9 +69,9 @@ } $defaults = array( - 'methods' => 'GET', - 'callback' => null, - 'args' => array(), + 'methods' => 'GET', + 'callback' => null, + 'args' => array(), ); foreach ( $args as $key => &$arg_group ) { if ( ! is_numeric( $key ) ) { @@ -64,7 +79,7 @@ continue; } - $arg_group = array_merge( $defaults, $arg_group ); + $arg_group = array_merge( $defaults, $arg_group ); $arg_group['args'] = array_merge( $common_args, $arg_group['args'] ); } @@ -141,10 +156,10 @@ function rest_api_register_rewrites() { global $wp_rewrite; - add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' ); - add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' ); - add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' ); - add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' ); + add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); + add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); + add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); + add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); } /** @@ -193,6 +208,11 @@ $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); $revisions_controller->register_routes(); } + + if ( 'attachment' !== $post_type->name ) { + $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name ); + $autosaves_controller->register_routes(); + } } // Post types. @@ -230,9 +250,32 @@ $controller = new WP_REST_Comments_Controller; $controller->register_routes(); + /** + * Filters the search handlers to use in the REST search controller. + * + * @since 5.0.0 + * + * @param array $search_handlers List of search handlers to use in the controller. Each search + * handler instance must extend the `WP_REST_Search_Handler` class. + * Default is only a handler for posts. + */ + $search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) ); + + $controller = new WP_REST_Search_Controller( $search_handlers ); + $controller->register_routes(); + + // Block Renderer. + $controller = new WP_REST_Block_Renderer_Controller; + $controller->register_routes(); + // Settings. $controller = new WP_REST_Settings_Controller; $controller->register_routes(); + + // Themes. + $controller = new WP_REST_Themes_Controller; + $controller->register_routes(); + } /** @@ -307,6 +350,8 @@ $path = '/'; } + $path = '/' . ltrim( $path, '/' ); + if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) { global $wp_rewrite; @@ -316,7 +361,7 @@ $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme ); } - $url .= '/' . ltrim( $path, '/' ); + $url .= $path; } else { $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); // nginx only allows HTTP/1.0 methods when redirecting from / to /index.php @@ -325,8 +370,6 @@ $url .= 'index.php'; } - $path = '/' . ltrim( $path, '/' ); - $url = add_query_arg( 'rest_route', $path, $url ); } @@ -417,7 +460,7 @@ * @param string $class_name The name of the server class. Default 'WP_REST_Server'. */ $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); - $wp_rest_server = new $wp_rest_server_class; + $wp_rest_server = new $wp_rest_server_class; /** * Fires when preparing to serve an API request. @@ -569,15 +612,30 @@ } $response = new WP_REST_Response(); - $data = array(); + $data = array(); foreach ( $handler->get_routes() as $route => $endpoints ) { - $match = preg_match( '@^' . $route . '$@i', $request->get_route() ); + $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches ); if ( ! $match ) { continue; } + $args = array(); + foreach ( $matches as $param => $value ) { + if ( ! is_int( $param ) ) { + $args[ $param ] = $value; + } + } + + foreach ( $endpoints as $endpoint ) { + // Remove the redundant preg_match argument. + unset( $args[0] ); + + $request->set_url_params( $args ); + $request->set_attributes( $endpoint ); + } + $data = $handler->get_data_for_route( $route, $endpoints, 'help' ); $response->set_matched_route( $route ); break; @@ -651,7 +709,7 @@ $data = $response->get_data(); - $fields = is_array( $request['_fields'] ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] ); + $fields = wp_parse_list( $request['_fields'] ); if ( 0 === count( $fields ) ) { return $response; @@ -867,9 +925,9 @@ // cases. if ( ! $is_utc && ! $has_timezone ) { $local = date( 'Y-m-d H:i:s', $date ); - $utc = get_gmt_from_date( $local ); + $utc = get_gmt_from_date( $local ); } else { - $utc = date( 'Y-m-d H:i:s', $date ); + $utc = date( 'Y-m-d H:i:s', $date ); $local = get_date_from_gmt( $utc ); } @@ -982,7 +1040,7 @@ */ function rest_sanitize_boolean( $value ) { // String values are translated to `true`; make sure 'false' is false. - if ( is_string( $value ) ) { + if ( is_string( $value ) ) { $value = strtolower( $value ); if ( in_array( $value, array( 'false', '0' ), true ) ) { $value = false; @@ -990,7 +1048,7 @@ } // Everything else will map nicely to boolean. - return (boolean) $value; + return (bool) $value; } /** @@ -1081,8 +1139,8 @@ */ function rest_validate_value_from_schema( $value, $args, $param = '' ) { if ( 'array' === $args['type'] ) { - if ( ! is_array( $value ) ) { - $value = preg_split( '/[\s,]+/', $value ); + if ( ! is_null( $value ) ) { + $value = wp_parse_list( $value ); } if ( ! wp_is_numeric_array( $value ) ) { /* translators: 1: parameter, 2: type name */ @@ -1146,18 +1204,18 @@ if ( isset( $args['format'] ) ) { switch ( $args['format'] ) { - case 'date-time' : + case 'date-time': if ( ! rest_parse_date( $value ) ) { return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); } break; - case 'email' : + case 'email': if ( ! is_email( $value ) ) { return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); } break; - case 'ip' : + case 'ip': if ( ! rest_is_ip_address( $value ) ) { /* translators: %s: IP address */ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); @@ -1225,9 +1283,7 @@ if ( empty( $args['items'] ) ) { return (array) $value; } - if ( ! is_array( $value ) ) { - $value = preg_split( '/[\s,]+/', $value ); - } + $value = wp_parse_list( $value ); foreach ( $value as $index => $v ) { $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] ); } @@ -1270,19 +1326,19 @@ if ( isset( $args['format'] ) ) { switch ( $args['format'] ) { - case 'date-time' : + case 'date-time': return sanitize_text_field( $value ); - case 'email' : + case 'email': /* * sanitize_email() validates, which would be unexpected. */ return sanitize_text_field( $value ); - case 'uri' : + case 'uri': return esc_url_raw( $value ); - case 'ip' : + case 'ip': return sanitize_text_field( $value ); } } @@ -1293,3 +1349,71 @@ return $value; } + +/** + * Append result of internal request to REST API for purpose of preloading data to be attached to a page. + * Expected to be called in the context of `array_reduce`. + * + * @since 5.0.0 + * + * @param array $memo Reduce accumulator. + * @param string $path REST API path to preload. + * @return array Modified reduce accumulator. + */ +function rest_preload_api_request( $memo, $path ) { + // array_reduce() doesn't support passing an array in PHP 5.2, so we need to make sure we start with one. + if ( ! is_array( $memo ) ) { + $memo = array(); + } + + if ( empty( $path ) ) { + return $memo; + } + + $method = 'GET'; + if ( is_array( $path ) && 2 === count( $path ) ) { + $method = end( $path ); + $path = reset( $path ); + + if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) { + $method = 'GET'; + } + } + + $path_parts = parse_url( $path ); + if ( false === $path_parts ) { + return $memo; + } + + $request = new WP_REST_Request( $method, $path_parts['path'] ); + if ( ! empty( $path_parts['query'] ) ) { + parse_str( $path_parts['query'], $query_params ); + $request->set_query_params( $query_params ); + } + + $response = rest_do_request( $request ); + if ( 200 === $response->status ) { + $server = rest_get_server(); + $data = (array) $response->get_data(); + $links = $server->get_compact_response_links( $response ); + if ( ! empty( $links ) ) { + $data['_links'] = $links; + } + + if ( 'OPTIONS' === $method ) { + $response = rest_send_allow_header( $response, $server, $request ); + + $memo[ $method ][ $path ] = array( + 'body' => $data, + 'headers' => $response->headers, + ); + } else { + $memo[ $path ] = array( + 'body' => $data, + 'headers' => $response->headers, + ); + } + } + + return $memo; +}