diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index bad90ae336..5add20bc60 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -1571,3 +1571,25 @@ function rest_preload_api_request( $memo, $path ) { return $memo; } + +/** + * Parses the "_embed" parameter into the list of resources to embed. + * + * @since 5.4.0 + * + * @param string|array $embed Raw "_embed" parameter value. + * @return true|string[] Either true to embed all embeds, or a list of relations to embed. + */ +function rest_parse_embed_param( $embed ) { + if ( ! $embed || 'true' === $embed || '1' === $embed ) { + return true; + } + + $rels = wp_parse_list( $embed ); + + if ( ! $rels ) { + return true; + } + + return $rels; +} diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index a14de5c225..d11318916e 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -398,7 +398,8 @@ class WP_REST_Server { } // Embed links inside the request. - $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) ); + $embed = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false; + $result = $this->response_to_data( $result, $embed ); /** * Filters the API response. @@ -450,9 +451,10 @@ class WP_REST_Server { * Converts a response to data to send. * * @since 4.4.0 + * @since 5.4.0 The $embed parameter can now contain a list of link relations to include. * * @param WP_REST_Response $response Response object. - * @param bool $embed Whether links should be embedded. + * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links. * @return array { * Data with sub-requests embedded. * @@ -473,9 +475,11 @@ class WP_REST_Server { $this->embed_cache = array(); // Determine if this is a numeric array. if ( wp_is_numeric_array( $data ) ) { - $data = array_map( array( $this, 'embed_links' ), $data ); + foreach ( $data as $key => $item ) { + $data[ $key ] = $this->embed_links( $item, $embed ); + } } else { - $data = $this->embed_links( $data ); + $data = $this->embed_links( $data, $embed ); } $this->embed_cache = array(); } @@ -571,8 +575,10 @@ class WP_REST_Server { * Embeds the links from the data into the request. * * @since 4.4.0 + * @since 5.4.0 The $embed parameter can now contain a list of link relations to include. * - * @param array $data Data from the request. + * @param array $data Data from the request. + * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations. * @return array { * Data with sub-requests embedded. * @@ -580,7 +586,7 @@ class WP_REST_Server { * @type array [$_embedded] Embeddeds. * } */ - protected function embed_links( $data ) { + protected function embed_links( $data, $embed = true ) { if ( empty( $data['_links'] ) ) { return $data; } @@ -588,6 +594,11 @@ class WP_REST_Server { $embedded = array(); foreach ( $data['_links'] as $rel => $links ) { + // If a list of relations was specified, and the link relation is not in the whitelist, don't process the link. + if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) { + continue; + } + $embeds = array(); foreach ( $links as $item ) { diff --git a/tests/phpunit/tests/rest-api.php b/tests/phpunit/tests/rest-api.php index 38b4fe8aa0..c0eea9804f 100644 --- a/tests/phpunit/tests/rest-api.php +++ b/tests/phpunit/tests/rest-api.php @@ -906,4 +906,31 @@ class Tests_REST_API extends WP_UnitTestCase { $this->assertEquals( '/wp/v2/posts', $request->get_route() ); $this->assertEquals( 'GET', $request->get_method() ); } + + /** + * @dataProvider _dp_rest_parse_embed_param + */ + public function test_rest_parse_embed_param( $expected, $embed ) { + $this->assertEquals( $expected, rest_parse_embed_param( $embed ) ); + } + + public function _dp_rest_parse_embed_param() { + return array( + array( true, '' ), + array( true, null ), + array( true, '1' ), + array( true, 'true' ), + array( true, array() ), + array( array( 'author' ), 'author' ), + array( array( 'author', 'replies' ), 'author,replies' ), + array( array( 'author', 'replies' ), 'author,replies ' ), + array( array( 'wp:term' ), 'wp:term' ), + array( array( 'wp:term', 'wp:attachment' ), 'wp:term,wp:attachment' ), + array( array( 'author' ), array( 'author' ) ), + array( array( 'author', 'replies' ), array( 'author', 'replies' ) ), + array( array( 'https://api.w.org/term' ), 'https://api.w.org/term' ), + array( array( 'https://api.w.org/term', 'https://api.w.org/attachment' ), 'https://api.w.org/term,https://api.w.org/attachment' ), + array( array( 'https://api.w.org/term', 'https://api.w.org/attachment' ), array( 'https://api.w.org/term', 'https://api.w.org/attachment' ) ), + ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index 671a860f07..4d450f7e17 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -845,6 +845,66 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { $this->assertEquals( $self_not_filtered, $data['_links']['self'][0] ); } + /** + * @dataProvider _dp_response_to_data_embedding + */ + public function test_response_to_data_embedding( $expected, $embed ) { + $response = new WP_REST_Response(); + $response->add_link( 'author', rest_url( '404' ), array( 'embeddable' => true ) ); + $response->add_link( 'https://api.w.org/term', rest_url( '404' ), array( 'embeddable' => true ) ); + $response->add_link( 'https://wordpress.org', rest_url( '404' ), array( 'embeddable' => true ) ); + $response->add_link( 'no-embed', rest_url( '404' ) ); + + $data = rest_get_server()->response_to_data( $response, $embed ); + + if ( false === $expected ) { + $this->assertArrayNotHasKey( '_embedded', $data ); + } else { + $this->assertEqualSets( $expected, array_keys( $data['_embedded'] ) ); + } + } + + public function _dp_response_to_data_embedding() { + return array( + array( + array( 'author', 'wp:term', 'https://wordpress.org' ), + true, + ), + array( + array( 'author', 'wp:term', 'https://wordpress.org' ), + array( 'author', 'wp:term', 'https://wordpress.org' ), + ), + array( + array( 'author' ), + array( 'author' ), + ), + array( + array( 'wp:term' ), + array( 'wp:term' ), + ), + array( + array( 'https://wordpress.org' ), + array( 'https://wordpress.org' ), + ), + array( + array( 'author', 'wp:term' ), + array( 'author', 'wp:term' ), + ), + array( + false, + false, + ), + array( + false, + array( 'no-embed' ), + ), + array( + array( 'author' ), + array( 'author', 'no-embed' ), + ), + ); + } + public function test_get_index() { $server = new WP_REST_Server(); $server->register_route(