Ticket #42094: 42094.diff
File 42094.diff, 18.3 KB (added by , 5 years ago) |
---|
-
src/wp-includes/rest-api.php
diff --git src/wp-includes/rest-api.php src/wp-includes/rest-api.php index 4b49ebfdcb..68f13c2ac1 100644
function rest_send_allow_header( $response, $server, $request ) { 696 696 return $response; 697 697 } 698 698 699 /** 700 * Recursively computes the intersection of arrays using keys for comparison. 701 * 702 * @param array $array1 The array with master keys to check. 703 * @param array $array2 An array to compare keys against. 704 * 705 * @return array An associative array containing all the entries of array1 which have keys that are present in all arguments. 706 */ 707 function _rest_array_intersect_key_recursive( $array1, $array2 ) { 708 $array1 = array_intersect_key( $array1, $array2 ); 709 foreach ( $array1 as $key => $value ) { 710 if ( is_array( $value ) && is_array( $array2[ $key ] ) ) { 711 $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] ); 712 } 713 } 714 return $array1; 715 } 716 699 717 /** 700 718 * Filter the API response to include only a white-listed set of response object fields. 701 719 * … … function rest_filter_response_fields( $response, $server, $request ) { 723 741 // Trim off outside whitespace from the comma delimited list. 724 742 $fields = array_map( 'trim', $fields ); 725 743 726 $fields_as_keyed = array_combine( $fields, array_fill( 0, count( $fields ), true ) ); 744 // Create nested array of accepted field hierarchy. 745 $fields_as_keyed = array(); 746 foreach ( $fields as $field ) { 747 $parts = explode( '.', $field ); 748 $ref = &$fields_as_keyed; 749 while ( count( $parts ) > 1 ) { 750 $next = array_shift( $parts ); 751 $ref[ $next ] = array(); 752 $ref = &$ref[ $next ]; 753 } 754 $last = array_shift( $parts ); 755 $ref[ $last ] = true; 756 } 727 757 728 758 if ( wp_is_numeric_array( $data ) ) { 729 759 $new_data = array(); 730 760 foreach ( $data as $item ) { 731 $new_data[] = array_intersect_key( $item, $fields_as_keyed );761 $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed ); 732 762 } 733 763 } else { 734 $new_data = array_intersect_key( $data, $fields_as_keyed );764 $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed ); 735 765 } 736 766 737 767 $response->set_data( $new_data ); … … function rest_filter_response_fields( $response, $server, $request ) { 739 769 return $response; 740 770 } 741 771 772 /** 773 * Given an array of fields to include in a response, some of which may be 774 * `nested.fields`, determine whether the provided field should be included 775 * in the response body. 776 * 777 * If a parent field is passed in, the presence of any nested field within 778 * that parent will cause the method to return `true`. For example "title" 779 * will return true if any of `title`, `title.raw` or `title.rendered` is 780 * provided. 781 * 782 * @since 5.3.0 783 * 784 * @param string $field A field to test for inclusion in the response body. 785 * @param array $fields An array of string fields supported by the endpoint. 786 * @return bool Whether to include the field or not. 787 */ 788 function rest_is_field_included( $field, $fields ) { 789 if ( in_array( $field, $fields, true ) ) { 790 return true; 791 } 792 foreach ( $fields as $accepted_field ) { 793 // Check to see if $field is the parent of any item in $fields. 794 // A field "parent" should be accepted if "parent.child" is accepted. 795 if ( strpos( $accepted_field, "$field." ) === 0 ) { 796 return true; 797 } 798 // Conversely, if "parent" is accepted, all "parent.child" fields should 799 // also be accepted. 800 if ( strpos( $field, "$accepted_field." ) === 0 ) { 801 return true; 802 } 803 } 804 return false; 805 } 806 742 807 /** 743 808 * Adds the REST API URL to the WP RSD endpoint. 744 809 * -
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 7adc5ed616..1bc38124e9 100644
abstract class WP_REST_Controller { 562 562 if ( in_array( 'id', $fields, true ) ) { 563 563 $requested_fields[] = 'id'; 564 564 } 565 return array_intersect( $fields, $requested_fields ); 565 // Return the list of all requested fields which appear in the schema. 566 return array_reduce( 567 $requested_fields, 568 function( $response_fields, $field ) use ( $fields ) { 569 if ( in_array( $field, $fields, true ) ) { 570 $response_fields[] = $field; 571 return $response_fields; 572 } 573 // Check for nested fields if $field is not a direct match. 574 $nested_fields = explode( '.', $field ); 575 // A nested field is included so long as its top-level property is 576 // present in the schema. 577 if ( in_array( $nested_fields[0], $fields, true ) ) { 578 $response_fields[] = $field; 579 } 580 return $response_fields; 581 }, 582 array() 583 ); 566 584 } 567 585 568 586 /** -
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 e53ff4b910..883e9b31fe 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 ( rest_is_field_included( 'id', $fields ) ) { 1443 1443 $data['id'] = $post->ID; 1444 1444 } 1445 1445 1446 if ( in_array( 'date', $fields, true) ) {1446 if ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_is_field_included( 'title', $fields ) ) { 1509 $data['title'] = array(); 1510 } 1511 if ( rest_is_field_included( 'title.raw', $fields ) ) { 1512 $data['title']['raw'] = $post->post_title; 1513 } 1514 if ( rest_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 ( rest_is_field_included( 'content', $fields ) ) { 1532 $data['content'] = array(); 1533 } 1534 if ( rest_is_field_included( 'content.raw', $fields ) ) { 1535 $data['content']['raw'] = $post->post_content; 1536 } 1537 if ( rest_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 ( rest_is_field_included( 'content.protected', $fields ) ) { 1542 $data['content']['protected'] = (bool) $post->post_password; 1543 } 1544 if ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_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 ( rest_is_field_included( 'template', $fields ) ) { 1582 1592 $template = get_page_template_slug( $post->ID ); 1583 1593 if ( $template ) { 1584 1594 $data['template'] = $template; … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1587 1597 } 1588 1598 } 1589 1599 1590 if ( in_array( 'format', $fields, true) ) {1600 if ( rest_is_field_included( 'format', $fields ) ) { 1591 1601 $data['format'] = get_post_format( $post->ID ); 1592 1602 1593 1603 // Fill in blank post format. … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1596 1606 } 1597 1607 } 1598 1608 1599 if ( in_array( 'meta', $fields, true) ) {1609 if ( rest_is_field_included( 'meta', $fields ) ) { 1600 1610 $data['meta'] = $this->meta->get_value( $post->ID, $request ); 1601 1611 } 1602 1612 … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1605 1615 foreach ( $taxonomies as $taxonomy ) { 1606 1616 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1607 1617 1608 if ( in_array( $base, $fields, true) ) {1618 if ( rest_is_field_included( $base, $fields ) ) { 1609 1619 $terms = get_the_terms( $post, $taxonomy->name ); 1610 1620 $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); 1611 1621 } … … class WP_REST_Posts_Controller extends WP_REST_Controller { 1613 1623 1614 1624 $post_type_obj = get_post_type_object( $post->post_type ); 1615 1625 if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { 1616 $permalink_template_requested = in_array( 'permalink_template', $fields, true);1617 $generated_slug_requested = in_array( 'generated_slug', $fields, true);1626 $permalink_template_requested = rest_is_field_included( 'permalink_template', $fields ); 1627 $generated_slug_requested = rest_is_field_included( 'generated_slug', $fields ); 1618 1628 1619 1629 if ( $permalink_template_requested || $generated_slug_requested ) { 1620 1630 if ( ! function_exists( 'get_sample_permalink' ) ) { -
tests/phpunit/tests/rest-api.php
diff --git tests/phpunit/tests/rest-api.php tests/phpunit/tests/rest-api.php index c81efd67b6..512aa61b6d 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 * @ticket 42094 526 */ 527 public function test_rest_filter_response_fields_nested_field_filter() { 528 $response = new WP_REST_Response(); 529 530 $response->set_data( 531 array( 532 'a' => 0, 533 'b' => array( 534 '1' => 1, 535 '2' => 2, 536 ), 537 'c' => 3, 538 'd' => array( 539 '4' => 4, 540 '5' => 5, 541 ), 542 ) 543 ); 544 $request = array( 545 '_fields' => 'b.1,c,d.5', 546 ); 547 548 $response = rest_filter_response_fields( $response, null, $request ); 549 $this->assertEquals( 550 array( 551 'b' => array( 552 '1' => 1, 553 ), 554 'c' => 3, 555 'd' => array( 556 '5' => 5, 557 ), 558 ), 559 $response->get_data() 560 ); 561 } 562 563 /** 564 * @ticket 42094 565 */ 566 public function test_rest_is_field_included() { 567 $fields = array( 568 'id', 569 'title', 570 'content.raw', 571 'custom.property', 572 ); 573 574 $this->assertTrue( rest_is_field_included( 'id', $fields ) ); 575 $this->assertTrue( rest_is_field_included( 'title', $fields ) ); 576 $this->assertTrue( rest_is_field_included( 'title.raw', $fields ) ); 577 $this->assertTrue( rest_is_field_included( 'title.rendered', $fields ) ); 578 $this->assertTrue( rest_is_field_included( 'content', $fields ) ); 579 $this->assertTrue( rest_is_field_included( 'content.raw', $fields ) ); 580 $this->assertTrue( rest_is_field_included( 'custom.property', $fields ) ); 581 $this->assertFalse( rest_is_field_included( 'content.rendered', $fields ) ); 582 $this->assertFalse( rest_is_field_included( 'type', $fields ) ); 583 $this->assertFalse( rest_is_field_included( 'meta', $fields ) ); 584 $this->assertFalse( rest_is_field_included( 'meta.value', $fields ) ); 585 } 586 522 587 /** 523 588 * The get_rest_url function should return a URL consistently terminated with a "/", 524 589 * 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 f130abc62d..91e1d0c5b7 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', -
tests/phpunit/tests/rest-api/rest-posts-controller.php
diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php index ba174c448e..95e8ee11c2 100644
class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te 1689 1689 ); 1690 1690 } 1691 1691 1692 /** 1693 * @ticket 42094 1694 */ 1695 public function test_prepare_item_filters_content_when_needed() { 1696 $filter_count = 0; 1697 $filter_content = function() use ( &$filter_count ) { 1698 $filter_count++; 1699 return '<p>Filtered content.</p>'; 1700 }; 1701 add_filter( 'the_content', $filter_content ); 1702 1703 wp_set_current_user( self::$editor_id ); 1704 $endpoint = new WP_REST_Posts_Controller( 'post' ); 1705 $request = new WP_REST_REQUEST( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); 1706 1707 $request->set_param( 'context', 'edit' ); 1708 $request->set_param( '_fields', 'content.rendered' ); 1709 1710 $post = get_post( self::$post_id ); 1711 $response = $endpoint->prepare_item_for_response( $post, $request ); 1712 1713 remove_filter( 'the_content', $filter_content ); 1714 1715 $this->assertEquals( 1716 array( 1717 'id' => self::$post_id, 1718 'content' => array( 1719 'rendered' => '<p>Filtered content.</p>', 1720 ), 1721 ), 1722 $response->get_data() 1723 ); 1724 $this->assertSame( 1, $filter_count ); 1725 } 1726 1727 /** 1728 * @ticket 42094 1729 */ 1730 public function test_prepare_item_skips_content_filter_if_not_needed() { 1731 $filter_count = 0; 1732 $filter_content = function() use ( &$filter_count ) { 1733 $filter_count++; 1734 return '<p>Filtered content.</p>'; 1735 }; 1736 add_filter( 'the_content', $filter_content ); 1737 1738 wp_set_current_user( self::$editor_id ); 1739 $endpoint = new WP_REST_Posts_Controller( 'post' ); 1740 $request = new WP_REST_REQUEST( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); 1741 1742 $request->set_param( 'context', 'edit' ); 1743 $request->set_param( '_fields', 'content.raw' ); 1744 1745 $post = get_post( self::$post_id ); 1746 $response = $endpoint->prepare_item_for_response( $post, $request ); 1747 1748 remove_filter( 'the_content', $filter_content ); 1749 1750 $this->assertEquals( 1751 array( 1752 'id' => $post->ID, 1753 'content' => array( 1754 'raw' => $post->post_content, 1755 ), 1756 ), 1757 $response->get_data() 1758 ); 1759 $this->assertSame( 0, $filter_count ); 1760 } 1761 1692 1762 public function test_create_item() { 1693 1763 wp_set_current_user( self::$editor_id ); 1694 1764