Changeset 46184
- Timestamp:
- 09/19/2019 02:04:51 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/rest-api.php
r46101 r46184 698 698 699 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 717 /** 700 718 * Filter the API response to include only a white-listed set of response object fields. 701 719 * … … 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 … … 738 768 739 769 return $response; 770 } 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; 740 805 } 741 806 -
trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
r46069 r46184 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 -
trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
r46068 r46184 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 … … 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 */ … … 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 … … 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' ) ); … … 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 ); 1536 } 1537 1538 if ( in_array( 'excerpt', $fields, true ) ) { 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 ); 1546 } 1547 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 ) ); … … 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 ) { … … 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 … … 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 } … … 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(); … … 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 ) { -
trunk/tests/phpunit/tests/rest-api.php
r46099 r46184 518 518 $response->get_data() 519 519 ); 520 } 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 ) ); 520 585 } 521 586 -
trunk/tests/phpunit/tests/rest-api/rest-controller.php
r45706 r46184 233 233 return array( 234 234 array( 235 'somestring,someinteger ',235 'somestring,someinteger,someinvalidkey', 236 236 array( 237 237 'somestring', -
trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php
r45811 r46184 1688 1688 array_keys( $response->get_data() ) 1689 1689 ); 1690 } 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 ); 1690 1760 } 1691 1761
Note: See TracChangeset
for help on using the changeset viewer.