Ticket #42094: 42094.2.diff
File 42094.2.diff, 16.0 KB (added by , 6 years ago) |
---|
-
src/wp-includes/rest-api.php
diff --git src/wp-includes/rest-api.php src/wp-includes/rest-api.php index 1c73b97824..8e8e3718f9 100644
function rest_send_allow_header( $response, $server, $request ) { 691 691 return $response; 692 692 } 693 693 694 /** 695 * Recursively computes the intersection of arrays using keys for comparison. 696 * 697 * @param array $array1 The array with master keys to check. 698 * @param array $array2 An array to compare keys against. 699 * 700 * @return array An associative array containing all the entries of array1 which have keys that are present in all arguments. 701 */ 702 function _rest_array_intersect_key_recursive( $array1, $array2 ) { 703 $array1 = array_intersect_key( $array1, $array2 ); 704 foreach ( $array1 as $key => $value ) { 705 if ( is_array( $value ) && is_array( $array2[ $key ] ) ) { 706 $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] ); 707 } 708 } 709 return $array1; 710 } 711 694 712 /** 695 713 * Filter the API response to include only a white-listed set of response object fields. 696 714 * … … function rest_filter_response_fields( $response, $server, $request ) { 718 736 // Trim off outside whitespace from the comma delimited list. 719 737 $fields = array_map( 'trim', $fields ); 720 738 721 $fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) ); 739 // Create nested array of accepted field hierarchy. 740 $fields_as_keyed = array(); 741 foreach ( $fields as $field ) { 742 $parts = explode( '.', $field ); 743 $ref = &$fields_as_keyed; 744 while ( count( $parts ) > 1 ) { 745 $next = array_shift( $parts ); 746 $ref[ $next ] = array(); 747 $ref = &$ref[ $next ]; 748 } 749 $last = array_shift( $parts ); 750 $ref[ $last ] = true; 751 } 722 752 723 753 if ( wp_is_numeric_array( $data ) ) { 724 754 $new_data = array(); 725 755 foreach ( $data as $item ) { 726 $new_data[] = array_intersect_key( $item, $fields_as_keyed );756 $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed ); 727 757 } 728 758 } else { 729 $new_data = array_intersect_key( $data, $fields_as_keyed );759 $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed ); 730 760 } 731 761 732 762 $response->set_data( $new_data ); -
src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php index d610504692..46b5b85a48 100644
abstract class WP_REST_Controller { 542 542 if ( in_array( 'id', $fields, true ) ) { 543 543 $requested_fields[] = 'id'; 544 544 } 545 return array_intersect( $fields, $requested_fields ); 545 // Return the list of all requested fields which appear in the schema. 546 return array_reduce( 547 $requested_fields, 548 function( $response_fields, $field ) use ( $fields ) { 549 if ( in_array( $field, $fields, true ) ) { 550 $response_fields[] = $field; 551 return $response_fields; 552 } 553 // Check for nested fields if $field is not a direct match. 554 $nested_fields = explode( '.', $field ); 555 // A nested field is included so long as its top-level property is 556 // present in the schema. 557 if ( in_array( $nested_fields[0], $fields, true ) ) { 558 $response_fields[] = $field; 559 } 560 return $response_fields; 561 }, 562 array() 563 ); 564 } 565 566 /** 567 * Given an array of fields to include in the response, some of which may be 568 * `nested.fields`, determine whether a provided field should be included in 569 * the response body. 570 * 571 * If a parent field is passed in, the presence of any nested field within 572 * that parent will cause the method to return `true`. For example "title" 573 * will return true if any of `title`, `title.raw` or `title.rendered` is 574 * provided. 575 * 576 * @since 5.3.0 577 * 578 * @param string $field A field to test for inclusion in the response body. 579 * @param array $fields An array of string fields supported by the endpoint. 580 * @return bool Whether to include the field or not. 581 */ 582 public function is_field_included( $field, $fields ) { 583 if ( in_array( $field, $fields, true ) ) { 584 return true; 585 } 586 foreach ( $fields as $accepted_field ) { 587 // Check to see if $field is the parent of any item in $fields. 588 // A field "parent" should be accepted if "parent.child" is accepted. 589 if ( strpos( $accepted_field, "$field." ) === 0 ) { 590 return true; 591 } 592 // Conversely, if "parent" is accepted, all "parent.child" fields should 593 // also be accepted. 594 if ( strpos( $field, "$accepted_field." ) === 0 ) { 595 return true; 596 } 597 } 598 return false; 546 599 } 547 600 548 601 /** -
src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index f24bc068c8..c8eeae4e32 100644
class WP_REST_Posts_Controller extends WP_REST_Controller { 1439 1439 // Base fields for every post. 1440 1440 $data = array(); 1441 1441 1442 if ( in_array( 'id', $fields, true) ) {1442 if ( $this->is_field_included( 'id', $fields ) ) { 1443 1443 $data['id'] = $post->ID; 1444 1444 } 1445 1445 1446 if ( in_array( 'date', $fields, true) ) {1446 if ( $this->is_field_included( 'date', $fields ) ) { 1447 1447 $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 1448 1448 } 1449 1449 1450 if ( in_array( 'date_gmt', $fields, true) ) {1450 if ( $this->is_field_included( 'date_gmt', $fields ) ) { 1451 1451 // For drafts, `post_date_gmt` may not be set, indicating that the 1452 1452 // date of the draft should be updated each time it is saved (see 1453 1453 // #38883). In this case, shim the value based on the `post_date` … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1460 1460 $data['date_gmt'] = $this->prepare_date_response( $post_date_gmt ); 1461 1461 } 1462 1462 1463 if ( in_array( 'guid', $fields, true) ) {1463 if ( $this->is_field_included( 'guid', $fields ) ) { 1464 1464 $data['guid'] = array( 1465 1465 /** This filter is documented in wp-includes/post-template.php */ 1466 1466 'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ), … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1468 1468 ); 1469 1469 } 1470 1470 1471 if ( in_array( 'modified', $fields, true) ) {1471 if ( $this->is_field_included( 'modified', $fields ) ) { 1472 1472 $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 1473 1473 } 1474 1474 1475 if ( in_array( 'modified_gmt', $fields, true) ) {1475 if ( $this->is_field_included( 'modified_gmt', $fields ) ) { 1476 1476 // For drafts, `post_modified_gmt` may not be set (see 1477 1477 // `post_date_gmt` comments above). In this case, shim the value 1478 1478 // based on the `post_modified` field with the site's timezone … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1485 1485 $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); 1486 1486 } 1487 1487 1488 if ( in_array( 'password', $fields, true) ) {1488 if ( $this->is_field_included( 'password', $fields ) ) { 1489 1489 $data['password'] = $post->post_password; 1490 1490 } 1491 1491 1492 if ( in_array( 'slug', $fields, true) ) {1492 if ( $this->is_field_included( 'slug', $fields ) ) { 1493 1493 $data['slug'] = $post->post_name; 1494 1494 } 1495 1495 1496 if ( in_array( 'status', $fields, true) ) {1496 if ( $this->is_field_included( 'status', $fields ) ) { 1497 1497 $data['status'] = $post->post_status; 1498 1498 } 1499 1499 1500 if ( in_array( 'type', $fields, true) ) {1500 if ( $this->is_field_included( 'type', $fields ) ) { 1501 1501 $data['type'] = $post->post_type; 1502 1502 } 1503 1503 1504 if ( in_array( 'link', $fields, true) ) {1504 if ( $this->is_field_included( 'link', $fields ) ) { 1505 1505 $data['link'] = get_permalink( $post->ID ); 1506 1506 } 1507 1507 1508 if ( in_array( 'title', $fields, true ) ) { 1508 if ( $this->is_field_included( 'title', $fields ) ) { 1509 $data['title'] = array(); 1510 } 1511 if ( $this->is_field_included( 'title.raw', $fields ) ) { 1512 $data['title']['raw'] = $post->post_title; 1513 } 1514 if ( $this->is_field_included( 'title.rendered', $fields ) ) { 1509 1515 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1510 1516 1511 $data['title'] = array( 1512 'raw' => $post->post_title, 1513 'rendered' => get_the_title( $post->ID ), 1514 ); 1517 $data['title']['rendered'] = get_the_title( $post->ID ); 1515 1518 1516 1519 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1517 1520 } … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1525 1528 $has_password_filter = true; 1526 1529 } 1527 1530 1528 if ( in_array( 'content', $fields, true ) ) { 1529 $data['content'] = array( 1530 'raw' => $post->post_content, 1531 /** This filter is documented in wp-includes/post-template.php */ 1532 'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ), 1533 'protected' => (bool) $post->post_password, 1534 'block_version' => block_version( $post->post_content ), 1535 ); 1531 if ( $this->is_field_included( 'content', $fields ) ) { 1532 $data['content'] = array(); 1533 } 1534 if ( $this->is_field_included( 'content.raw', $fields ) ) { 1535 $data['content']['raw'] = $post->post_content; 1536 } 1537 if ( $this->is_field_included( 'content.rendered', $fields ) ) { 1538 /** This filter is documented in wp-includes/post-template.php */ 1539 $data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ); 1540 } 1541 if ( $this->is_field_included( 'content.protected', $fields ) ) { 1542 $data['content']['protected'] = (bool) $post->post_password; 1543 } 1544 if ( $this->is_field_included( 'content.block_version', $fields ) ) { 1545 $data['content']['block_version'] = block_version( $post->post_content ); 1536 1546 } 1537 1547 1538 if ( in_array( 'excerpt', $fields, true) ) {1548 if ( $this->is_field_included( 'excerpt', $fields ) ) { 1539 1549 /** This filter is documented in wp-includes/post-template.php */ 1540 1550 $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) ); 1541 1551 $data['excerpt'] = array( … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1550 1560 remove_filter( 'post_password_required', '__return_false' ); 1551 1561 } 1552 1562 1553 if ( in_array( 'author', $fields, true) ) {1563 if ( $this->is_field_included( 'author', $fields ) ) { 1554 1564 $data['author'] = (int) $post->post_author; 1555 1565 } 1556 1566 1557 if ( in_array( 'featured_media', $fields, true) ) {1567 if ( $this->is_field_included( 'featured_media', $fields ) ) { 1558 1568 $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID ); 1559 1569 } 1560 1570 1561 if ( in_array( 'parent', $fields, true) ) {1571 if ( $this->is_field_included( 'parent', $fields ) ) { 1562 1572 $data['parent'] = (int) $post->post_parent; 1563 1573 } 1564 1574 1565 if ( in_array( 'menu_order', $fields, true) ) {1575 if ( $this->is_field_included( 'menu_order', $fields ) ) { 1566 1576 $data['menu_order'] = (int) $post->menu_order; 1567 1577 } 1568 1578 1569 if ( in_array( 'comment_status', $fields, true) ) {1579 if ( $this->is_field_included( 'comment_status', $fields ) ) { 1570 1580 $data['comment_status'] = $post->comment_status; 1571 1581 } 1572 1582 1573 if ( in_array( 'ping_status', $fields, true) ) {1583 if ( $this->is_field_included( 'ping_status', $fields ) ) { 1574 1584 $data['ping_status'] = $post->ping_status; 1575 1585 } 1576 1586 1577 if ( in_array( 'sticky', $fields, true) ) {1587 if ( $this->is_field_included( 'sticky', $fields ) ) { 1578 1588 $data['sticky'] = is_sticky( $post->ID ); 1579 1589 } 1580 1590 1581 if ( in_array( 'template', $fields, true) ) {1591 if ( $this->is_field_included( 'template', $fields ) ) { 1582 1592 if ( $template = get_page_template_slug( $post->ID ) ) { 1583 1593 $data['template'] = $template; 1584 1594 } else { … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1586 1596 } 1587 1597 } 1588 1598 1589 if ( in_array( 'format', $fields, true) ) {1599 if ( $this->is_field_included( 'format', $fields ) ) { 1590 1600 $data['format'] = get_post_format( $post->ID ); 1591 1601 1592 1602 // Fill in blank post format. … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1595 1605 } 1596 1606 } 1597 1607 1598 if ( in_array( 'meta', $fields, true) ) {1608 if ( $this->is_field_included( 'meta', $fields ) ) { 1599 1609 $data['meta'] = $this->meta->get_value( $post->ID, $request ); 1600 1610 } 1601 1611 … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1604 1614 foreach ( $taxonomies as $taxonomy ) { 1605 1615 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1606 1616 1607 if ( in_array( $base, $fields, true) ) {1617 if ( $this->is_field_included( $base, $fields ) ) { 1608 1618 $terms = get_the_terms( $post, $taxonomy->name ); 1609 1619 $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); 1610 1620 } … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1619 1629 1620 1630 $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' ); 1621 1631 1622 if ( in_array( 'permalink_template', $fields, true) ) {1632 if ( $this->is_field_included( 'permalink_template', $fields ) ) { 1623 1633 $data['permalink_template'] = $sample_permalink[0]; 1624 1634 } 1625 if ( in_array( 'generated_slug', $fields, true) ) {1635 if ( $this->is_field_included( 'generated_slug', $fields ) ) { 1626 1636 $data['generated_slug'] = $sample_permalink[1]; 1627 1637 } 1628 1638 } -
tests/phpunit/tests/rest-api.php
diff --git tests/phpunit/tests/rest-api.php tests/phpunit/tests/rest-api.php index 7e72708d43..e3bf21974f 100644
class Tests_REST_API extends WP_UnitTestCase { 519 519 ); 520 520 } 521 521 522 /** 523 * Ensure that nested fields may be whitelisted with request['_fields']. 524 */ 525 public function test_rest_filter_response_fields_nested_field_filter() { 526 $response = new WP_REST_Response(); 527 528 $response->set_data( 529 array( 530 'a' => 0, 531 'b' => array( 532 '1' => 1, 533 '2' => 2, 534 ), 535 'c' => 3, 536 'd' => array( 537 '4' => 4, 538 '5' => 5, 539 ), 540 ) 541 ); 542 $request = array( 543 '_fields' => 'b.1,c,d.5', 544 ); 545 546 $response = rest_filter_response_fields( $response, null, $request ); 547 $this->assertEquals( 548 array( 549 'b' => array( 550 '1' => 1, 551 ), 552 'c' => 3, 553 'd' => array( 554 '5' => 5, 555 ), 556 ), 557 $response->get_data() 558 ); 559 } 560 522 561 /** 523 562 * The get_rest_url function should return a URL consistently terminated with a "/", 524 563 * whether the blog is configured with pretty permalink support or not. -
tests/phpunit/tests/rest-api/rest-controller.php
diff --git tests/phpunit/tests/rest-api/rest-controller.php tests/phpunit/tests/rest-api/rest-controller.php index 6301e64d9d..a9dc094447 100644
class WP_Test_REST_Controller extends WP_Test_REST_TestCase { 232 232 public function data_get_fields_for_response() { 233 233 return array( 234 234 array( 235 'somestring,someinteger ',235 'somestring,someinteger,someinvalidkey', 236 236 array( 237 237 'somestring', 238 238 'someinteger', … … class WP_Test_REST_Controller extends WP_Test_REST_TestCase { 255 255 ); 256 256 } 257 257 258 public function test_is_field_included() { 259 $controller = new WP_REST_Test_Controller(); 260 261 $fields = array( 262 'id', 263 'title', 264 'content.raw', 265 'custom.property', 266 ); 267 268 $this->assertTrue( $controller->is_field_included( 'id', $fields ) ); 269 $this->assertTrue( $controller->is_field_included( 'title', $fields ) ); 270 $this->assertTrue( $controller->is_field_included( 'title.raw', $fields ) ); 271 $this->assertTrue( $controller->is_field_included( 'title.rendered', $fields ) ); 272 $this->assertTrue( $controller->is_field_included( 'content', $fields ) ); 273 $this->assertTrue( $controller->is_field_included( 'content.raw', $fields ) ); 274 $this->assertTrue( $controller->is_field_included( 'custom.property', $fields ) ); 275 $this->assertFalse( $controller->is_field_included( 'content.rendered', $fields ) ); 276 $this->assertFalse( $controller->is_field_included( 'type', $fields ) ); 277 $this->assertFalse( $controller->is_field_included( 'meta', $fields ) ); 278 $this->assertFalse( $controller->is_field_included( 'meta.value', $fields ) ); 279 } 280 258 281 public function test_add_additional_fields_to_object_respects_fields_param() { 259 282 $controller = new WP_REST_Test_Controller(); 260 283 $request = new WP_REST_Request( 'GET', '/wp/v2/testroute' );