Changeset 49252
- Timestamp:
- 10/20/2020 07:08:48 PM (4 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/rest-api/class-wp-rest-server.php
r49109 r49252 95 95 $this->endpoints = array( 96 96 // Meta endpoints. 97 '/' => array(97 '/' => array( 98 98 'callback' => array( $this, 'get_index' ), 99 99 'methods' => 'GET', … … 101 101 'context' => array( 102 102 'default' => 'view', 103 ), 104 ), 105 ), 106 '/batch/v1' => array( 107 'callback' => array( $this, 'serve_batch_request_v1' ), 108 'methods' => 'POST', 109 'args' => array( 110 'validation' => array( 111 'type' => 'string', 112 'enum' => array( 'require-all-validate', 'normal' ), 113 'default' => 'normal', 114 ), 115 'requests' => array( 116 'required' => true, 117 'type' => 'array', 118 'maxItems' => $this->get_max_batch_size(), 119 'items' => array( 120 'type' => 'object', 121 'properties' => array( 122 'method' => array( 123 'type' => 'string', 124 'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ), 125 'default' => 'POST', 126 ), 127 'path' => array( 128 'type' => 'string', 129 'required' => true, 130 ), 131 'body' => array( 132 'type' => 'object', 133 'properties' => array(), 134 'additionalProperties' => true, 135 ), 136 'headers' => array( 137 'type' => 'object', 138 'properties' => array(), 139 'additionalProperties' => array( 140 'type' => array( 'string', 'array' ), 141 'items' => array( 142 'type' => 'string', 143 ), 144 ), 145 ), 146 ), 147 ), 103 148 ), 104 149 ), … … 972 1017 * @return array|WP_Error The route and request handler on success or a WP_Error instance if no handler was found. 973 1018 */ 974 p ublicfunction match_request_to_handler( $request ) {1019 protected function match_request_to_handler( $request ) { 975 1020 $method = $request->get_method(); 976 1021 $path = $request->get_route(); … … 1059 1104 * @return WP_REST_Response 1060 1105 */ 1061 p ublicfunction respond_to_request( $request, $route, $handler, $response ) {1106 protected function respond_to_request( $request, $route, $handler, $response ) { 1062 1107 /** 1063 1108 * Filters the response before executing any REST API callbacks. … … 1398 1443 1399 1444 /** 1445 * Gets the maximum number of requests that can be included in a batch. 1446 * 1447 * @since 5.6.0 1448 * 1449 * @return int The maximum requests. 1450 */ 1451 protected function get_max_batch_size() { 1452 /** 1453 * Filters the maximum number of requests that can be included in a batch. 1454 * 1455 * @param int $max_size The maximum size. 1456 */ 1457 return apply_filters( 'rest_get_max_batch_size', 25 ); 1458 } 1459 1460 /** 1461 * Serves the batch/v1 request. 1462 * 1463 * @since 5.6.0 1464 * 1465 * @param WP_REST_Request $batch_request The batch request object. 1466 * @return WP_REST_Response The generated response object. 1467 */ 1468 public function serve_batch_request_v1( WP_REST_Request $batch_request ) { 1469 $requests = array(); 1470 1471 foreach ( $batch_request['requests'] as $args ) { 1472 $parsed_url = wp_parse_url( $args['path'] ); 1473 1474 if ( false === $parsed_url ) { 1475 $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.' ), array( 'status' => 400 ) ); 1476 1477 continue; 1478 } 1479 1480 $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] ); 1481 1482 if ( ! empty( $parsed_url['query'] ) ) { 1483 $query_args = null; // Satisfy linter. 1484 wp_parse_str( $parsed_url['query'], $query_args ); 1485 $single_request->set_query_params( $query_args ); 1486 } 1487 1488 if ( ! empty( $args['body'] ) ) { 1489 $single_request->set_body_params( $args['body'] ); 1490 } 1491 1492 if ( ! empty( $args['headers'] ) ) { 1493 $single_request->set_headers( $args['headers'] ); 1494 } 1495 1496 $requests[] = $single_request; 1497 } 1498 1499 $matches = array(); 1500 $validation = array(); 1501 $has_error = false; 1502 1503 foreach ( $requests as $single_request ) { 1504 $match = $this->match_request_to_handler( $single_request ); 1505 $matches[] = $match; 1506 $error = null; 1507 1508 if ( is_wp_error( $match ) ) { 1509 $error = $match; 1510 } 1511 1512 if ( ! $error ) { 1513 list( $route, $handler ) = $match; 1514 1515 if ( isset( $handler['allow_batch'] ) ) { 1516 $allow_batch = $handler['allow_batch']; 1517 } else { 1518 $route_options = $this->get_route_options( $route ); 1519 $allow_batch = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false; 1520 } 1521 1522 if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) { 1523 $error = new WP_Error( 1524 'rest_batch_not_allowed', 1525 __( 'The requested route does not support batch requests.' ), 1526 array( 'status' => 400 ) 1527 ); 1528 } 1529 } 1530 1531 if ( ! $error ) { 1532 $check_required = $single_request->has_valid_params(); 1533 if ( is_wp_error( $check_required ) ) { 1534 $error = $check_required; 1535 } 1536 } 1537 1538 if ( ! $error ) { 1539 $check_sanitized = $single_request->sanitize_params(); 1540 if ( is_wp_error( $check_sanitized ) ) { 1541 $error = $check_sanitized; 1542 } 1543 } 1544 1545 if ( $error ) { 1546 $has_error = true; 1547 $validation[] = $error; 1548 } else { 1549 $validation[] = true; 1550 } 1551 } 1552 1553 $responses = array(); 1554 1555 if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) { 1556 foreach ( $validation as $valid ) { 1557 if ( is_wp_error( $valid ) ) { 1558 $responses[] = $this->envelope_response( $this->error_to_response( $valid ), false )->get_data(); 1559 } else { 1560 $responses[] = null; 1561 } 1562 } 1563 1564 return new WP_REST_Response( 1565 array( 1566 'failed' => 'validation', 1567 'responses' => $responses, 1568 ), 1569 WP_Http::MULTI_STATUS 1570 ); 1571 } 1572 1573 foreach ( $requests as $i => $single_request ) { 1574 $clean_request = clone $single_request; 1575 $clean_request->set_url_params( array() ); 1576 $clean_request->set_attributes( array() ); 1577 $clean_request->set_default_params( array() ); 1578 1579 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ 1580 $result = apply_filters( 'rest_pre_dispatch', null, $this, $clean_request ); 1581 1582 if ( empty( $result ) ) { 1583 $match = $matches[ $i ]; 1584 $error = null; 1585 1586 if ( is_wp_error( $validation[ $i ] ) ) { 1587 $error = $validation[ $i ]; 1588 } 1589 1590 if ( is_wp_error( $match ) ) { 1591 $result = $this->error_to_response( $match ); 1592 } else { 1593 list( $route, $handler ) = $match; 1594 1595 if ( ! $error && ! is_callable( $handler['callback'] ) ) { 1596 $error = new WP_Error( 1597 'rest_invalid_handler', 1598 __( 'The handler for the route is invalid' ), 1599 array( 'status' => 500 ) 1600 ); 1601 } 1602 1603 $result = $this->respond_to_request( $single_request, $route, $handler, $error ); 1604 } 1605 } 1606 1607 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ 1608 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $single_request ); 1609 1610 $responses[] = $this->envelope_response( $result, false )->get_data(); 1611 } 1612 1613 return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS ); 1614 } 1615 1616 /** 1400 1617 * Sends an HTTP status code. 1401 1618 * -
trunk/tests/phpunit/tests/rest-api/rest-server.php
r48947 r49252 1618 1618 } 1619 1619 1620 /** 1621 * @ticket 50244 1622 * @dataProvider data_batch_v1_optin 1623 */ 1624 public function test_batch_v1_optin( $allow_batch, $allowed ) { 1625 $args = array( 1626 'methods' => 'POST', 1627 'callback' => static function () { 1628 return new WP_REST_Response( 'data' ); 1629 }, 1630 'permission_callback' => '__return_true', 1631 ); 1632 1633 if ( null !== $allow_batch ) { 1634 $args['allow_batch'] = $allow_batch; 1635 } 1636 1637 register_rest_route( 1638 'test-ns/v1', 1639 '/test', 1640 $args 1641 ); 1642 1643 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1644 $request->set_body_params( 1645 array( 1646 'requests' => array( 1647 array( 1648 'path' => '/test-ns/v1/test', 1649 ), 1650 ), 1651 ) 1652 ); 1653 1654 $response = rest_do_request( $request ); 1655 1656 $this->assertEquals( 207, $response->get_status() ); 1657 1658 if ( $allowed ) { 1659 $this->assertEquals( 'data', $response->get_data()['responses'][0]['body'] ); 1660 } else { 1661 $this->assertEquals( 'rest_batch_not_allowed', $response->get_data()['responses'][0]['body']['code'] ); 1662 } 1663 } 1664 1665 public function data_batch_v1_optin() { 1666 return array( 1667 'missing' => array( null, false ), 1668 'invalid type' => array( true, false ), 1669 'invalid type string' => array( 'v1', false ), 1670 'wrong version' => array( array( 'version1' => true ), false ), 1671 'false version' => array( array( 'v1' => false ), false ), 1672 'valid' => array( array( 'v1' => true ), true ), 1673 ); 1674 } 1675 1676 /** 1677 * @ticket 50244 1678 */ 1679 public function test_batch_v1_pre_validation() { 1680 register_rest_route( 1681 'test-ns/v1', 1682 '/test', 1683 array( 1684 'methods' => 'POST', 1685 'callback' => static function ( $request ) { 1686 $project = $request['project']; 1687 update_option( 'test_project', $project ); 1688 1689 return new WP_REST_Response( $project ); 1690 }, 1691 'permission_callback' => '__return_true', 1692 'allow_batch' => array( 'v1' => true ), 1693 'args' => array( 1694 'project' => array( 1695 'type' => 'string', 1696 'enum' => array( 'gutenberg', 'WordPress' ), 1697 ), 1698 ), 1699 ) 1700 ); 1701 1702 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1703 $request->set_body_params( 1704 array( 1705 'validation' => 'require-all-validate', 1706 'requests' => array( 1707 array( 1708 'path' => '/test-ns/v1/test', 1709 'body' => array( 1710 'project' => 'gutenberg', 1711 ), 1712 ), 1713 array( 1714 'path' => '/test-ns/v1/test', 1715 'body' => array( 1716 'project' => 'buddypress', 1717 ), 1718 ), 1719 ), 1720 ) 1721 ); 1722 1723 $response = rest_get_server()->dispatch( $request ); 1724 $data = $response->get_data(); 1725 1726 $this->assertEquals( 207, $response->get_status() ); 1727 $this->assertArrayHasKey( 'failed', $data ); 1728 $this->assertEquals( 'validation', $data['failed'] ); 1729 $this->assertCount( 2, $data['responses'] ); 1730 $this->assertNull( $data['responses'][0] ); 1731 $this->assertEquals( 400, $data['responses'][1]['status'] ); 1732 $this->assertFalse( get_option( 'test_project' ) ); 1733 } 1734 1735 /** 1736 * @ticket 50244 1737 */ 1738 public function test_batch_v1_pre_validation_all_successful() { 1739 register_rest_route( 1740 'test-ns/v1', 1741 '/test', 1742 array( 1743 'methods' => 'POST', 1744 'callback' => static function ( $request ) { 1745 return new WP_REST_Response( $request['project'] ); 1746 }, 1747 'permission_callback' => '__return_true', 1748 'allow_batch' => array( 'v1' => true ), 1749 'args' => array( 1750 'project' => array( 1751 'type' => 'string', 1752 'enum' => array( 'gutenberg', 'WordPress' ), 1753 ), 1754 ), 1755 ) 1756 ); 1757 1758 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1759 $request->set_body_params( 1760 array( 1761 'validation' => 'require-all-validate', 1762 'requests' => array( 1763 array( 1764 'path' => '/test-ns/v1/test', 1765 'body' => array( 1766 'project' => 'gutenberg', 1767 ), 1768 ), 1769 array( 1770 'path' => '/test-ns/v1/test', 1771 'body' => array( 1772 'project' => 'WordPress', 1773 ), 1774 ), 1775 ), 1776 ) 1777 ); 1778 1779 $response = rest_get_server()->dispatch( $request ); 1780 $data = $response->get_data(); 1781 1782 $this->assertEquals( 207, $response->get_status() ); 1783 $this->assertArrayNotHasKey( 'failed', $data ); 1784 $this->assertCount( 2, $data['responses'] ); 1785 $this->assertEquals( 'gutenberg', $data['responses'][0]['body'] ); 1786 $this->assertEquals( 'WordPress', $data['responses'][1]['body'] ); 1787 } 1788 1789 /** 1790 * @ticket 50244 1791 */ 1792 public function test_batch_v1() { 1793 register_rest_route( 1794 'test-ns/v1', 1795 '/test/(?P<id>[\d+])', 1796 array( 1797 'methods' => array( 'POST', 'DELETE' ), 1798 'callback' => function ( WP_REST_Request $request ) { 1799 $this->assertEquals( 'DELETE', $request->get_method() ); 1800 $this->assertEquals( '/test-ns/v1/test/5', $request->get_route() ); 1801 $this->assertEquals( array( 'id' => '5' ), $request->get_url_params() ); 1802 $this->assertEquals( array( 'query' => 'param' ), $request->get_query_params() ); 1803 $this->assertEquals( array( 'project' => 'gutenberg' ), $request->get_body_params() ); 1804 $this->assertEquals( array( 'my_header' => array( 'my-value' ) ), $request->get_headers() ); 1805 1806 return new WP_REST_Response( 'test' ); 1807 }, 1808 'permission_callback' => '__return_true', 1809 'allow_batch' => array( 'v1' => true ), 1810 ) 1811 ); 1812 1813 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1814 $request->set_body_params( 1815 array( 1816 'requests' => array( 1817 array( 1818 'method' => 'DELETE', 1819 'path' => '/test-ns/v1/test/5?query=param', 1820 'headers' => array( 1821 'My-Header' => 'my-value', 1822 ), 1823 'body' => array( 1824 'project' => 'gutenberg', 1825 ), 1826 ), 1827 ), 1828 ) 1829 ); 1830 1831 $response = rest_get_server()->dispatch( $request ); 1832 1833 $this->assertEquals( 207, $response->get_status() ); 1834 $this->assertEquals( 'test', $response->get_data()['responses'][0]['body'] ); 1835 } 1836 1837 /** 1838 * @ticket 50244 1839 */ 1840 public function test_batch_v1_partial_error() { 1841 register_rest_route( 1842 'test-ns/v1', 1843 '/test', 1844 array( 1845 'methods' => 'POST', 1846 'callback' => static function ( $request ) { 1847 $project = $request['project']; 1848 update_option( 'test_project', $project ); 1849 1850 return new WP_REST_Response( $project ); 1851 }, 1852 'permission_callback' => '__return_true', 1853 'allow_batch' => array( 'v1' => true ), 1854 'args' => array( 1855 'project' => array( 1856 'type' => 'string', 1857 'enum' => array( 'gutenberg', 'WordPress' ), 1858 ), 1859 ), 1860 ) 1861 ); 1862 1863 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1864 $request->set_body_params( 1865 array( 1866 'requests' => array( 1867 array( 1868 'path' => '/test-ns/v1/test', 1869 'body' => array( 1870 'project' => 'gutenberg', 1871 ), 1872 ), 1873 array( 1874 'path' => '/test-ns/v1/test', 1875 'body' => array( 1876 'project' => 'buddypress', 1877 ), 1878 ), 1879 ), 1880 ) 1881 ); 1882 1883 $response = rest_get_server()->dispatch( $request ); 1884 $data = $response->get_data(); 1885 1886 $this->assertEquals( 207, $response->get_status() ); 1887 $this->assertArrayNotHasKey( 'failed', $data ); 1888 $this->assertCount( 2, $data['responses'] ); 1889 $this->assertEquals( 'gutenberg', $data['responses'][0]['body'] ); 1890 $this->assertEquals( 400, $data['responses'][1]['status'] ); 1891 $this->assertEquals( 'gutenberg', get_option( 'test_project' ) ); 1892 } 1893 1894 1895 /** 1896 * @ticket 50244 1897 */ 1898 public function test_batch_v1_max_requests() { 1899 add_filter( 1900 'rest_get_max_batch_size', 1901 static function() { 1902 return 5; 1903 } 1904 ); 1905 1906 $GLOBALS['wp_rest_server'] = null; 1907 add_filter( 'wp_rest_server_class', array( $this, 'filter_wp_rest_server_class' ) ); 1908 $GLOBALS['wp_rest_server'] = rest_get_server(); 1909 1910 register_rest_route( 1911 'test-ns/v1', 1912 '/test/(?P<id>[\d+])', 1913 array( 1914 'methods' => array( 'POST', 'DELETE' ), 1915 'callback' => function ( WP_REST_Request $request ) { 1916 return new WP_REST_Response( 'test' ); 1917 }, 1918 'permission_callback' => '__return_true', 1919 'allow_batch' => array( 'v1' => true ), 1920 ) 1921 ); 1922 1923 $request = new WP_REST_Request( 'POST', '/batch/v1' ); 1924 $request->set_body_params( 1925 array( 1926 'requests' => array_fill( 0, 6, array( 'path' => '/test-ns/v1/test/5' ) ), 1927 ) 1928 ); 1929 1930 $response = rest_get_server()->dispatch( $request ); 1931 $this->assertEquals( 400, $response->get_status() ); 1932 } 1933 1620 1934 public function _validate_as_integer_123( $value, $request, $key ) { 1621 1935 if ( ! is_int( $value ) ) { -
trunk/tests/qunit/fixtures/wp-api-generated.js
r49154 r49252 40 40 "_links": { 41 41 "self": "http://example.org/index.php?rest_route=/" 42 } 43 }, 44 "/batch/v1": { 45 "namespace": "", 46 "methods": [ 47 "POST" 48 ], 49 "endpoints": [ 50 { 51 "methods": [ 52 "POST" 53 ], 54 "args": { 55 "validation": { 56 "required": false, 57 "default": "normal", 58 "enum": [ 59 "require-all-validate", 60 "normal" 61 ], 62 "type": "string" 63 }, 64 "requests": { 65 "required": true, 66 "type": "array", 67 "items": { 68 "type": "object", 69 "properties": { 70 "method": { 71 "type": "string", 72 "enum": [ 73 "POST", 74 "PUT", 75 "PATCH", 76 "DELETE" 77 ], 78 "default": "POST" 79 }, 80 "path": { 81 "type": "string", 82 "required": true 83 }, 84 "body": { 85 "type": "object", 86 "properties": [], 87 "additionalProperties": true 88 }, 89 "headers": { 90 "type": "object", 91 "properties": [], 92 "additionalProperties": { 93 "type": [ 94 "string", 95 "array" 96 ], 97 "items": { 98 "type": "string" 99 } 100 } 101 } 102 } 103 } 104 } 105 } 106 } 107 ], 108 "_links": { 109 "self": [ 110 { 111 "href": "http://example.org/index.php?rest_route=/batch/v1" 112 } 113 ] 42 114 } 43 115 },
Note: See TracChangeset
for help on using the changeset viewer.