REST API: Support dot.nested hierarchical properties in _fields query parameter.
Enable clients to opt-in to receipt of one or more specific sub-properties within a response, and not other sub-properties. Skip potentially expensive filtering and processing for post resources which were explicitly not requested. Props kadamwhite, TimothyBlynJacobs, dlh. Fixes #42094. Built from https://develop.svn.wordpress.org/trunk@46184 git-svn-id: http://core.svn.wordpress.org/trunk@45996 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
7c9d4950b7
commit
66f907b2eb
|
@ -696,6 +696,24 @@ function rest_send_allow_header( $response, $server, $request ) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively computes the intersection of arrays using keys for comparison.
|
||||||
|
*
|
||||||
|
* @param array $array1 The array with master keys to check.
|
||||||
|
* @param array $array2 An array to compare keys against.
|
||||||
|
*
|
||||||
|
* @return array An associative array containing all the entries of array1 which have keys that are present in all arguments.
|
||||||
|
*/
|
||||||
|
function _rest_array_intersect_key_recursive( $array1, $array2 ) {
|
||||||
|
$array1 = array_intersect_key( $array1, $array2 );
|
||||||
|
foreach ( $array1 as $key => $value ) {
|
||||||
|
if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
|
||||||
|
$array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $array1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the API response to include only a white-listed set of response object fields.
|
* Filter the API response to include only a white-listed set of response object fields.
|
||||||
*
|
*
|
||||||
|
@ -723,15 +741,27 @@ function rest_filter_response_fields( $response, $server, $request ) {
|
||||||
// Trim off outside whitespace from the comma delimited list.
|
// Trim off outside whitespace from the comma delimited list.
|
||||||
$fields = array_map( 'trim', $fields );
|
$fields = array_map( 'trim', $fields );
|
||||||
|
|
||||||
$fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) );
|
// Create nested array of accepted field hierarchy.
|
||||||
|
$fields_as_keyed = array();
|
||||||
|
foreach ( $fields as $field ) {
|
||||||
|
$parts = explode( '.', $field );
|
||||||
|
$ref = &$fields_as_keyed;
|
||||||
|
while ( count( $parts ) > 1 ) {
|
||||||
|
$next = array_shift( $parts );
|
||||||
|
$ref[ $next ] = array();
|
||||||
|
$ref = &$ref[ $next ];
|
||||||
|
}
|
||||||
|
$last = array_shift( $parts );
|
||||||
|
$ref[ $last ] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ( wp_is_numeric_array( $data ) ) {
|
if ( wp_is_numeric_array( $data ) ) {
|
||||||
$new_data = array();
|
$new_data = array();
|
||||||
foreach ( $data as $item ) {
|
foreach ( $data as $item ) {
|
||||||
$new_data[] = array_intersect_key( $item, $fields_as_keyed );
|
$new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$new_data = array_intersect_key( $data, $fields_as_keyed );
|
$new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->set_data( $new_data );
|
$response->set_data( $new_data );
|
||||||
|
@ -739,6 +769,41 @@ function rest_filter_response_fields( $response, $server, $request ) {
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of fields to include in a response, some of which may be
|
||||||
|
* `nested.fields`, determine whether the provided field should be included
|
||||||
|
* in the response body.
|
||||||
|
*
|
||||||
|
* If a parent field is passed in, the presence of any nested field within
|
||||||
|
* that parent will cause the method to return `true`. For example "title"
|
||||||
|
* will return true if any of `title`, `title.raw` or `title.rendered` is
|
||||||
|
* provided.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*
|
||||||
|
* @param string $field A field to test for inclusion in the response body.
|
||||||
|
* @param array $fields An array of string fields supported by the endpoint.
|
||||||
|
* @return bool Whether to include the field or not.
|
||||||
|
*/
|
||||||
|
function rest_is_field_included( $field, $fields ) {
|
||||||
|
if ( in_array( $field, $fields, true ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foreach ( $fields as $accepted_field ) {
|
||||||
|
// Check to see if $field is the parent of any item in $fields.
|
||||||
|
// A field "parent" should be accepted if "parent.child" is accepted.
|
||||||
|
if ( strpos( $accepted_field, "$field." ) === 0 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Conversely, if "parent" is accepted, all "parent.child" fields should
|
||||||
|
// also be accepted.
|
||||||
|
if ( strpos( $field, "$accepted_field." ) === 0 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the REST API URL to the WP RSD endpoint.
|
* Adds the REST API URL to the WP RSD endpoint.
|
||||||
*
|
*
|
||||||
|
|
|
@ -562,7 +562,25 @@ abstract class WP_REST_Controller {
|
||||||
if ( in_array( 'id', $fields, true ) ) {
|
if ( in_array( 'id', $fields, true ) ) {
|
||||||
$requested_fields[] = 'id';
|
$requested_fields[] = 'id';
|
||||||
}
|
}
|
||||||
return array_intersect( $fields, $requested_fields );
|
// Return the list of all requested fields which appear in the schema.
|
||||||
|
return array_reduce(
|
||||||
|
$requested_fields,
|
||||||
|
function( $response_fields, $field ) use ( $fields ) {
|
||||||
|
if ( in_array( $field, $fields, true ) ) {
|
||||||
|
$response_fields[] = $field;
|
||||||
|
return $response_fields;
|
||||||
|
}
|
||||||
|
// Check for nested fields if $field is not a direct match.
|
||||||
|
$nested_fields = explode( '.', $field );
|
||||||
|
// A nested field is included so long as its top-level property is
|
||||||
|
// present in the schema.
|
||||||
|
if ( in_array( $nested_fields[0], $fields, true ) ) {
|
||||||
|
$response_fields[] = $field;
|
||||||
|
}
|
||||||
|
return $response_fields;
|
||||||
|
},
|
||||||
|
array()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1439,15 +1439,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
// Base fields for every post.
|
// Base fields for every post.
|
||||||
$data = array();
|
$data = array();
|
||||||
|
|
||||||
if ( in_array( 'id', $fields, true ) ) {
|
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||||
$data['id'] = $post->ID;
|
$data['id'] = $post->ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'date', $fields, true ) ) {
|
if ( rest_is_field_included( 'date', $fields ) ) {
|
||||||
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
|
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'date_gmt', $fields, true ) ) {
|
if ( rest_is_field_included( 'date_gmt', $fields ) ) {
|
||||||
// For drafts, `post_date_gmt` may not be set, indicating that the
|
// For drafts, `post_date_gmt` may not be set, indicating that the
|
||||||
// date of the draft should be updated each time it is saved (see
|
// date of the draft should be updated each time it is saved (see
|
||||||
// #38883). In this case, shim the value based on the `post_date`
|
// #38883). In this case, shim the value based on the `post_date`
|
||||||
|
@ -1460,7 +1460,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
$data['date_gmt'] = $this->prepare_date_response( $post_date_gmt );
|
$data['date_gmt'] = $this->prepare_date_response( $post_date_gmt );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'guid', $fields, true ) ) {
|
if ( rest_is_field_included( 'guid', $fields ) ) {
|
||||||
$data['guid'] = array(
|
$data['guid'] = array(
|
||||||
/** This filter is documented in wp-includes/post-template.php */
|
/** This filter is documented in wp-includes/post-template.php */
|
||||||
'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
|
'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
|
||||||
|
@ -1468,11 +1468,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'modified', $fields, true ) ) {
|
if ( rest_is_field_included( 'modified', $fields ) ) {
|
||||||
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
|
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'modified_gmt', $fields, true ) ) {
|
if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
|
||||||
// For drafts, `post_modified_gmt` may not be set (see
|
// For drafts, `post_modified_gmt` may not be set (see
|
||||||
// `post_date_gmt` comments above). In this case, shim the value
|
// `post_date_gmt` comments above). In this case, shim the value
|
||||||
// based on the `post_modified` field with the site's timezone
|
// based on the `post_modified` field with the site's timezone
|
||||||
|
@ -1485,33 +1485,36 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
$data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt );
|
$data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'password', $fields, true ) ) {
|
if ( rest_is_field_included( 'password', $fields ) ) {
|
||||||
$data['password'] = $post->post_password;
|
$data['password'] = $post->post_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'slug', $fields, true ) ) {
|
if ( rest_is_field_included( 'slug', $fields ) ) {
|
||||||
$data['slug'] = $post->post_name;
|
$data['slug'] = $post->post_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'status', $fields, true ) ) {
|
if ( rest_is_field_included( 'status', $fields ) ) {
|
||||||
$data['status'] = $post->post_status;
|
$data['status'] = $post->post_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'type', $fields, true ) ) {
|
if ( rest_is_field_included( 'type', $fields ) ) {
|
||||||
$data['type'] = $post->post_type;
|
$data['type'] = $post->post_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'link', $fields, true ) ) {
|
if ( rest_is_field_included( 'link', $fields ) ) {
|
||||||
$data['link'] = get_permalink( $post->ID );
|
$data['link'] = get_permalink( $post->ID );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'title', $fields, true ) ) {
|
if ( rest_is_field_included( 'title', $fields ) ) {
|
||||||
|
$data['title'] = array();
|
||||||
|
}
|
||||||
|
if ( rest_is_field_included( 'title.raw', $fields ) ) {
|
||||||
|
$data['title']['raw'] = $post->post_title;
|
||||||
|
}
|
||||||
|
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
|
||||||
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||||
|
|
||||||
$data['title'] = array(
|
$data['title']['rendered'] = get_the_title( $post->ID );
|
||||||
'raw' => $post->post_title,
|
|
||||||
'rendered' => get_the_title( $post->ID ),
|
|
||||||
);
|
|
||||||
|
|
||||||
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||||
}
|
}
|
||||||
|
@ -1525,17 +1528,24 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
$has_password_filter = true;
|
$has_password_filter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'content', $fields, true ) ) {
|
if ( rest_is_field_included( 'content', $fields ) ) {
|
||||||
$data['content'] = array(
|
$data['content'] = array();
|
||||||
'raw' => $post->post_content,
|
}
|
||||||
|
if ( rest_is_field_included( 'content.raw', $fields ) ) {
|
||||||
|
$data['content']['raw'] = $post->post_content;
|
||||||
|
}
|
||||||
|
if ( rest_is_field_included( 'content.rendered', $fields ) ) {
|
||||||
/** This filter is documented in wp-includes/post-template.php */
|
/** This filter is documented in wp-includes/post-template.php */
|
||||||
'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),
|
$data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content );
|
||||||
'protected' => (bool) $post->post_password,
|
}
|
||||||
'block_version' => block_version( $post->post_content ),
|
if ( rest_is_field_included( 'content.protected', $fields ) ) {
|
||||||
);
|
$data['content']['protected'] = (bool) $post->post_password;
|
||||||
|
}
|
||||||
|
if ( rest_is_field_included( 'content.block_version', $fields ) ) {
|
||||||
|
$data['content']['block_version'] = block_version( $post->post_content );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'excerpt', $fields, true ) ) {
|
if ( rest_is_field_included( 'excerpt', $fields ) ) {
|
||||||
/** This filter is documented in wp-includes/post-template.php */
|
/** This filter is documented in wp-includes/post-template.php */
|
||||||
$excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
|
$excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
|
||||||
$data['excerpt'] = array(
|
$data['excerpt'] = array(
|
||||||
|
@ -1550,35 +1560,35 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
remove_filter( 'post_password_required', '__return_false' );
|
remove_filter( 'post_password_required', '__return_false' );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'author', $fields, true ) ) {
|
if ( rest_is_field_included( 'author', $fields ) ) {
|
||||||
$data['author'] = (int) $post->post_author;
|
$data['author'] = (int) $post->post_author;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'featured_media', $fields, true ) ) {
|
if ( rest_is_field_included( 'featured_media', $fields ) ) {
|
||||||
$data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
|
$data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'parent', $fields, true ) ) {
|
if ( rest_is_field_included( 'parent', $fields ) ) {
|
||||||
$data['parent'] = (int) $post->post_parent;
|
$data['parent'] = (int) $post->post_parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'menu_order', $fields, true ) ) {
|
if ( rest_is_field_included( 'menu_order', $fields ) ) {
|
||||||
$data['menu_order'] = (int) $post->menu_order;
|
$data['menu_order'] = (int) $post->menu_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'comment_status', $fields, true ) ) {
|
if ( rest_is_field_included( 'comment_status', $fields ) ) {
|
||||||
$data['comment_status'] = $post->comment_status;
|
$data['comment_status'] = $post->comment_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'ping_status', $fields, true ) ) {
|
if ( rest_is_field_included( 'ping_status', $fields ) ) {
|
||||||
$data['ping_status'] = $post->ping_status;
|
$data['ping_status'] = $post->ping_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'sticky', $fields, true ) ) {
|
if ( rest_is_field_included( 'sticky', $fields ) ) {
|
||||||
$data['sticky'] = is_sticky( $post->ID );
|
$data['sticky'] = is_sticky( $post->ID );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'template', $fields, true ) ) {
|
if ( rest_is_field_included( 'template', $fields ) ) {
|
||||||
$template = get_page_template_slug( $post->ID );
|
$template = get_page_template_slug( $post->ID );
|
||||||
if ( $template ) {
|
if ( $template ) {
|
||||||
$data['template'] = $template;
|
$data['template'] = $template;
|
||||||
|
@ -1587,7 +1597,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'format', $fields, true ) ) {
|
if ( rest_is_field_included( 'format', $fields ) ) {
|
||||||
$data['format'] = get_post_format( $post->ID );
|
$data['format'] = get_post_format( $post->ID );
|
||||||
|
|
||||||
// Fill in blank post format.
|
// Fill in blank post format.
|
||||||
|
@ -1596,7 +1606,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( in_array( 'meta', $fields, true ) ) {
|
if ( rest_is_field_included( 'meta', $fields ) ) {
|
||||||
$data['meta'] = $this->meta->get_value( $post->ID, $request );
|
$data['meta'] = $this->meta->get_value( $post->ID, $request );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1605,7 +1615,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
foreach ( $taxonomies as $taxonomy ) {
|
foreach ( $taxonomies as $taxonomy ) {
|
||||||
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
||||||
|
|
||||||
if ( in_array( $base, $fields, true ) ) {
|
if ( rest_is_field_included( $base, $fields ) ) {
|
||||||
$terms = get_the_terms( $post, $taxonomy->name );
|
$terms = get_the_terms( $post, $taxonomy->name );
|
||||||
$data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
|
$data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
|
||||||
}
|
}
|
||||||
|
@ -1613,8 +1623,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||||
|
|
||||||
$post_type_obj = get_post_type_object( $post->post_type );
|
$post_type_obj = get_post_type_object( $post->post_type );
|
||||||
if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
|
if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
|
||||||
$permalink_template_requested = in_array( 'permalink_template', $fields, true );
|
$permalink_template_requested = rest_is_field_included( 'permalink_template', $fields );
|
||||||
$generated_slug_requested = in_array( 'generated_slug', $fields, true );
|
$generated_slug_requested = rest_is_field_included( 'generated_slug', $fields );
|
||||||
|
|
||||||
if ( $permalink_template_requested || $generated_slug_requested ) {
|
if ( $permalink_template_requested || $generated_slug_requested ) {
|
||||||
if ( ! function_exists( 'get_sample_permalink' ) ) {
|
if ( ! function_exists( 'get_sample_permalink' ) ) {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '5.3-alpha-46183';
|
$wp_version = '5.3-alpha-46184';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|
Loading…
Reference in New Issue