Ticket #43316: 43316.4.diff
File 43316.4.diff, 31.0 KB (added by , 7 years ago) |
---|
-
src/wp-includes/rest-api.php
diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 0cce9fa62f..51e5e6f1a3 100644
a b function create_initial_rest_routes() { 192 192 if ( post_type_supports( $post_type->name, 'revisions' ) ) { 193 193 $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); 194 194 $revisions_controller->register_routes(); 195 $controller = new WP_REST_Autosaves_Controller( $post_type->name ); 196 $controller->register_routes(); 195 197 } 196 198 } 197 199 -
new file src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php new file mode 100644 index 0000000000..1c2b04b940
- + 1 <?php 2 /** 3 * REST API: WP_REST_Autosaves_Controller class. 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.0.0 8 */ 9 10 /** 11 * Core class used to access autosaves via the REST API. 12 * 13 * @since 5.0.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { 18 19 /** 20 * Parent post type. 21 * 22 * @since 5.0.0 23 * @var string 24 */ 25 private $parent_post_type; 26 27 /** 28 * Parent controller. 29 * 30 * @since 5.0.0 31 * @var WP_REST_Controller 32 */ 33 private $parent_controller; 34 35 /** 36 * Parent controller. 37 * 38 * @since 5.0.0 39 * @var WP_REST_Controller 40 */ 41 private $revision_controller; 42 43 /** 44 * The base of the parent controller's route. 45 * 46 * @since 5.0.0 47 * @var string 48 */ 49 private $parent_base; 50 51 /** 52 * Constructor. 53 * 54 * @since 5.0.0 55 * 56 * @param string $parent_post_type Post type of the parent. 57 */ 58 public function __construct( $parent_post_type ) { 59 $this->parent_post_type = $parent_post_type; 60 $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); 61 $this->revision_controller = new WP_REST_Revisions_Controller( $parent_post_type ); 62 $this->rest_namespace = 'wp/v2'; 63 $this->rest_base = 'autosaves'; 64 $post_type_object = get_post_type_object( $parent_post_type ); 65 $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; 66 } 67 68 /**o 69 * Registers routes for autosaves. 70 * 71 * @since 5.0.0 72 * 73 * @see register_rest_route() 74 */ 75 public function register_routes() { 76 register_rest_route( 77 $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( 78 'args' => array( 79 'parent' => array( 80 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), 81 'type' => 'integer', 82 ), 83 ), 84 array( 85 'methods' => WP_REST_Server::READABLE, 86 'callback' => array( $this, 'get_items' ), 87 'permission_callback' => array( $this->revision_controller, 'get_items_permissions_check' ), 88 'args' => $this->get_collection_params(), 89 ), 90 array( 91 'methods' => WP_REST_Server::CREATABLE, 92 'callback' => array( $this, 'create_item' ), 93 'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ), 94 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 95 ), 96 'schema' => array( $this, 'get_public_item_schema' ), 97 ) 98 ); 99 100 register_rest_route( 101 $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( 102 'args' => array( 103 'parent' => array( 104 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), 105 'type' => 'integer', 106 ), 107 'id' => array( 108 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), 109 'type' => 'integer', 110 ), 111 ), 112 array( 113 'methods' => WP_REST_Server::READABLE, 114 'callback' => array( $this, 'get_item' ), 115 'permission_callback' => array( $this->revision_controller, 'get_item_permissions_check' ), 116 'args' => array( 117 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 118 ), 119 ), 120 array( 121 'methods' => WP_REST_Server::DELETABLE, 122 'callback' => array( $this, 'delete_item' ), 123 'permission_callback' => array( $this->revision_controller, 'delete_item_permissions_check' ), 124 'args' => array( 125 'force' => array( 126 'type' => 'boolean', 127 'default' => false, 128 'description' => __( 'Required to be true, as autosaves do not support trashing.', 'gutenberg' ), 129 ), 130 ), 131 ), 132 array( 133 'methods' => WP_REST_Server::CREATABLE, 134 'callback' => array( $this, 'create_item' ), 135 'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ), 136 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 137 ), 138 'schema' => array( $this, 'get_public_item_schema' ), 139 ) 140 ); 141 142 } 143 144 /** 145 * Creates a single autosave. 146 * 147 * @since 5.0.0 148 * 149 * @param WP_REST_Request $request Full details about the request. 150 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 151 */ 152 public function create_item( $request ) { 153 154 // Map new fields onto the existing post data. 155 $parent = $this->revision_controller->get_parent( $request->get_param( 'parent' ) ); 156 $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); 157 $prepared_post->ID = $parent->ID; 158 159 // If the parent post a draft, autosaving updates it and does not create a revision. 160 if ( 'draft' === $parent->post_status ) { 161 162 define( 'DOING_AUTOSAVE', true ); 163 $autosave_id = wp_update_post( (array) $prepared_post, true ); 164 165 if ( ! is_wp_error( $autosave_id ) ) { 166 $post = get_post( $autosave_id ); 167 } 168 } else { 169 170 // Non-draft posts - update the post, creating an autosave. 171 $autosave_id = $this->create_post_autosave( (array) $prepared_post ); 172 $post = get_post( $autosave_id ); 173 } 174 $request->set_param( 'context', 'edit' ); 175 176 $response = $this->prepare_item_for_response( $post, $request ); 177 $response = rest_ensure_response( $response ); 178 179 $response->set_status( 201 ); 180 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->rest_namespace, $this->rest_base, $autosave_id ) ) ); 181 182 return $response; 183 } 184 185 /** 186 * Get the autosave, if the ID is valid. 187 * 188 * @since 5.0.0 189 * 190 * @param WP_REST_Request $request Full data about the request. 191 * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. 192 */ 193 public function get_item( $request ) { 194 $id = $request->get_param( 'id' ); 195 $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.', 'gutenberg' ), array( 'status' => 404 ) ); 196 if ( (int) $id <= 0 ) { 197 return $error; 198 } 199 200 $autosave = get_post( (int) $id ); 201 if ( empty( $autosave ) || empty( $autosave->ID ) || 'revision' !== $autosave->post_type ) { 202 return $error; 203 } 204 205 return $autosave; 206 } 207 208 /** 209 * Gets a collection of autosaves using wp_get_post_autosave. 210 * 211 * Contains the user's autosave, for empty if it doesn't exist. 212 * 213 * @since 5.0.0 214 * 215 * @param WP_REST_Request $request Full data about the request. 216 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 217 */ 218 public function get_items( $request ) { 219 $parent = $this->revision_controller->get_parent( $request->get_param( 'parent' ) ); 220 if ( is_wp_error( $parent ) ) { 221 return $parent; 222 } 223 224 $autosave = wp_get_post_autosave( $request->get_param( 'parent' ) ); 225 226 if ( ! $autosave ) { 227 return array(); 228 } 229 230 $response = array(); 231 $data = $this->prepare_item_for_response( $autosave, $request ); 232 $response[] = $this->prepare_response_for_collection( $data ); 233 234 return rest_ensure_response( $response ); 235 } 236 237 238 /** 239 * Retrieves the autosave's schema, conforming to JSON Schema. 240 * 241 * @since 5.0.0 242 * 243 * @return array Item schema data. 244 */ 245 public function get_item_schema() { 246 return $this->revision_controller->get_item_schema(); 247 } 248 249 /** 250 * Creates autosave data for the specified post from $_POST data. 251 * 252 * From wp-admin/post.php. 253 * 254 * @since 2.6.0 255 * 256 * @param mixed $post_data Associative array containing the post data or int post ID. 257 * @return mixed The autosave revision ID. WP_Error or 0 on error. 258 */ 259 public function create_post_autosave( $post_data ) { 260 261 $post_id = (int) $post_data['ID']; 262 $post_author = get_current_user_id(); 263 264 // Store one autosave per author. If there is already an autosave, overwrite it. 265 $old_autosave = wp_get_post_autosave( $post_id, $post_author ); 266 if ( $old_autosave ) { 267 $new_autosave = _wp_post_revision_data( $post_data, true ); 268 $new_autosave['ID'] = $old_autosave->ID; 269 $new_autosave['post_author'] = $post_author; 270 271 // If the new autosave has the same content as the post, delete the autosave. 272 $post = get_post( $post_id ); 273 $autosave_is_different = false; 274 foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { 275 if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { 276 $autosave_is_different = true; 277 break; 278 } 279 } 280 281 if ( ! $autosave_is_different ) { 282 wp_delete_post_revision( $old_autosave->ID ); 283 return 0; 284 } 285 286 /** 287 * This filter is documented in wp-admin/post.php. 288 */ 289 do_action( 'wp_creating_autosave', $new_autosave ); 290 291 return wp_update_post( $new_autosave ); 292 } 293 294 // _wp_put_post_revision() expects unescaped. 295 $post_data = wp_unslash( $post_data ); 296 297 // Otherwise create the new autosave as a special post revision. 298 return _wp_put_post_revision( $post_data, true ); 299 } 300 } -
src/wp-settings.php
diff --git a/src/wp-settings.php b/src/wp-settings.php index 6edd3c98ca..b2c4189c64 100644
a b require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-contro 230 230 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' ); 231 231 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' ); 232 232 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' ); 233 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php' ); 233 234 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' ); 234 235 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' ); 235 236 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' ); -
tests/phpunit/tests/rest-api/rest-schema-setup.php
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index e5b3460122..e13c0eaff0 100644
a b class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 89 89 '/wp/v2/posts/(?P<id>[\\d]+)', 90 90 '/wp/v2/posts/(?P<parent>[\\d]+)/revisions', 91 91 '/wp/v2/posts/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)', 92 '/wp/v2/posts/(?P<parent>[\\d]+)/autosaves', 93 '/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)', 92 94 '/wp/v2/pages', 93 95 '/wp/v2/pages/(?P<id>[\\d]+)', 94 96 '/wp/v2/pages/(?P<parent>[\\d]+)/revisions', 95 97 '/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)', 98 '/wp/v2/pages/(?P<parent>[\\d]+)/autosaves', 99 '/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)', 96 100 '/wp/v2/media', 97 101 '/wp/v2/media/(?P<id>[\\d]+)', 98 102 '/wp/v2/types', -
tests/qunit/fixtures/wp-api-generated.js
diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 583843fbc1..3ba083c8de 100644
a b mockedApiResponse.Schema = { 783 783 } 784 784 ] 785 785 }, 786 "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves": { 787 "namespace": "wp/v2", 788 "methods": [ 789 "GET", 790 "POST" 791 ], 792 "endpoints": [ 793 { 794 "methods": [ 795 "GET" 796 ], 797 "args": { 798 "parent": { 799 "required": false, 800 "description": "The ID for the parent of the object.", 801 "type": "integer" 802 }, 803 "context": { 804 "required": false, 805 "default": "view", 806 "enum": [ 807 "view", 808 "embed", 809 "edit" 810 ], 811 "description": "Scope under which the request is made; determines fields present in response.", 812 "type": "string" 813 } 814 } 815 }, 816 { 817 "methods": [ 818 "POST" 819 ], 820 "args": { 821 "parent": { 822 "required": false, 823 "description": "The ID for the parent of the object.", 824 "type": "integer" 825 }, 826 "author": { 827 "required": false, 828 "description": "The ID for the author of the object.", 829 "type": "integer" 830 }, 831 "date": { 832 "required": false, 833 "description": "The date the object was published, in the site's timezone.", 834 "type": "string" 835 }, 836 "date_gmt": { 837 "required": false, 838 "description": "The date the object was published, as GMT.", 839 "type": "string" 840 }, 841 "id": { 842 "required": false, 843 "description": "Unique identifier for the object.", 844 "type": "integer" 845 }, 846 "modified": { 847 "required": false, 848 "description": "The date the object was last modified, in the site's timezone.", 849 "type": "string" 850 }, 851 "modified_gmt": { 852 "required": false, 853 "description": "The date the object was last modified, as GMT.", 854 "type": "string" 855 }, 856 "slug": { 857 "required": false, 858 "description": "An alphanumeric identifier for the object unique to its type.", 859 "type": "string" 860 }, 861 "title": { 862 "required": false, 863 "description": "The title for the object.", 864 "type": "object" 865 }, 866 "content": { 867 "required": false, 868 "description": "The content for the object.", 869 "type": "object" 870 }, 871 "excerpt": { 872 "required": false, 873 "description": "The excerpt for the object.", 874 "type": "object" 875 } 876 } 877 } 878 ] 879 }, 880 "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": { 881 "namespace": "wp/v2", 882 "methods": [ 883 "GET", 884 "DELETE", 885 "POST" 886 ], 887 "endpoints": [ 888 { 889 "methods": [ 890 "GET" 891 ], 892 "args": { 893 "parent": { 894 "required": false, 895 "description": "The ID for the parent of the object.", 896 "type": "integer" 897 }, 898 "id": { 899 "required": false, 900 "description": "Unique identifier for the object.", 901 "type": "integer" 902 }, 903 "context": { 904 "required": false, 905 "default": "view", 906 "enum": [ 907 "view", 908 "embed", 909 "edit" 910 ], 911 "description": "Scope under which the request is made; determines fields present in response.", 912 "type": "string" 913 } 914 } 915 }, 916 { 917 "methods": [ 918 "DELETE" 919 ], 920 "args": { 921 "parent": { 922 "required": false, 923 "description": "The ID for the parent of the object.", 924 "type": "integer" 925 }, 926 "id": { 927 "required": false, 928 "description": "Unique identifier for the object.", 929 "type": "integer" 930 }, 931 "force": { 932 "required": false, 933 "default": false, 934 "description": "Required to be true, as autosaves do not support trashing.", 935 "type": "boolean" 936 } 937 } 938 }, 939 { 940 "methods": [ 941 "POST" 942 ], 943 "args": { 944 "parent": { 945 "required": false, 946 "description": "The ID for the parent of the object.", 947 "type": "integer" 948 }, 949 "id": { 950 "required": false, 951 "description": "Unique identifier for the object.", 952 "type": "integer" 953 }, 954 "author": { 955 "required": false, 956 "description": "The ID for the author of the object.", 957 "type": "integer" 958 }, 959 "date": { 960 "required": false, 961 "description": "The date the object was published, in the site's timezone.", 962 "type": "string" 963 }, 964 "date_gmt": { 965 "required": false, 966 "description": "The date the object was published, as GMT.", 967 "type": "string" 968 }, 969 "modified": { 970 "required": false, 971 "description": "The date the object was last modified, in the site's timezone.", 972 "type": "string" 973 }, 974 "modified_gmt": { 975 "required": false, 976 "description": "The date the object was last modified, as GMT.", 977 "type": "string" 978 }, 979 "slug": { 980 "required": false, 981 "description": "An alphanumeric identifier for the object unique to its type.", 982 "type": "string" 983 }, 984 "title": { 985 "required": false, 986 "description": "The title for the object.", 987 "type": "object" 988 }, 989 "content": { 990 "required": false, 991 "description": "The content for the object.", 992 "type": "object" 993 }, 994 "excerpt": { 995 "required": false, 996 "description": "The excerpt for the object.", 997 "type": "object" 998 } 999 } 1000 } 1001 ] 1002 }, 786 1003 "/wp/v2/pages": { 787 1004 "namespace": "wp/v2", 788 1005 "methods": [ … … mockedApiResponse.Schema = { 1321 1538 } 1322 1539 ] 1323 1540 }, 1541 "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves": { 1542 "namespace": "wp/v2", 1543 "methods": [ 1544 "GET", 1545 "POST" 1546 ], 1547 "endpoints": [ 1548 { 1549 "methods": [ 1550 "GET" 1551 ], 1552 "args": { 1553 "parent": { 1554 "required": false, 1555 "description": "The ID for the parent of the object.", 1556 "type": "integer" 1557 }, 1558 "context": { 1559 "required": false, 1560 "default": "view", 1561 "enum": [ 1562 "view", 1563 "embed", 1564 "edit" 1565 ], 1566 "description": "Scope under which the request is made; determines fields present in response.", 1567 "type": "string" 1568 } 1569 } 1570 }, 1571 { 1572 "methods": [ 1573 "POST" 1574 ], 1575 "args": { 1576 "parent": { 1577 "required": false, 1578 "description": "The ID for the parent of the object.", 1579 "type": "integer" 1580 }, 1581 "author": { 1582 "required": false, 1583 "description": "The ID for the author of the object.", 1584 "type": "integer" 1585 }, 1586 "date": { 1587 "required": false, 1588 "description": "The date the object was published, in the site's timezone.", 1589 "type": "string" 1590 }, 1591 "date_gmt": { 1592 "required": false, 1593 "description": "The date the object was published, as GMT.", 1594 "type": "string" 1595 }, 1596 "id": { 1597 "required": false, 1598 "description": "Unique identifier for the object.", 1599 "type": "integer" 1600 }, 1601 "modified": { 1602 "required": false, 1603 "description": "The date the object was last modified, in the site's timezone.", 1604 "type": "string" 1605 }, 1606 "modified_gmt": { 1607 "required": false, 1608 "description": "The date the object was last modified, as GMT.", 1609 "type": "string" 1610 }, 1611 "slug": { 1612 "required": false, 1613 "description": "An alphanumeric identifier for the object unique to its type.", 1614 "type": "string" 1615 }, 1616 "title": { 1617 "required": false, 1618 "description": "The title for the object.", 1619 "type": "object" 1620 }, 1621 "content": { 1622 "required": false, 1623 "description": "The content for the object.", 1624 "type": "object" 1625 }, 1626 "excerpt": { 1627 "required": false, 1628 "description": "The excerpt for the object.", 1629 "type": "object" 1630 } 1631 } 1632 } 1633 ] 1634 }, 1635 "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": { 1636 "namespace": "wp/v2", 1637 "methods": [ 1638 "GET", 1639 "DELETE", 1640 "POST" 1641 ], 1642 "endpoints": [ 1643 { 1644 "methods": [ 1645 "GET" 1646 ], 1647 "args": { 1648 "parent": { 1649 "required": false, 1650 "description": "The ID for the parent of the object.", 1651 "type": "integer" 1652 }, 1653 "id": { 1654 "required": false, 1655 "description": "Unique identifier for the object.", 1656 "type": "integer" 1657 }, 1658 "context": { 1659 "required": false, 1660 "default": "view", 1661 "enum": [ 1662 "view", 1663 "embed", 1664 "edit" 1665 ], 1666 "description": "Scope under which the request is made; determines fields present in response.", 1667 "type": "string" 1668 } 1669 } 1670 }, 1671 { 1672 "methods": [ 1673 "DELETE" 1674 ], 1675 "args": { 1676 "parent": { 1677 "required": false, 1678 "description": "The ID for the parent of the object.", 1679 "type": "integer" 1680 }, 1681 "id": { 1682 "required": false, 1683 "description": "Unique identifier for the object.", 1684 "type": "integer" 1685 }, 1686 "force": { 1687 "required": false, 1688 "default": false, 1689 "description": "Required to be true, as autosaves do not support trashing.", 1690 "type": "boolean" 1691 } 1692 } 1693 }, 1694 { 1695 "methods": [ 1696 "POST" 1697 ], 1698 "args": { 1699 "parent": { 1700 "required": false, 1701 "description": "The ID for the parent of the object.", 1702 "type": "integer" 1703 }, 1704 "id": { 1705 "required": false, 1706 "description": "Unique identifier for the object.", 1707 "type": "integer" 1708 }, 1709 "author": { 1710 "required": false, 1711 "description": "The ID for the author of the object.", 1712 "type": "integer" 1713 }, 1714 "date": { 1715 "required": false, 1716 "description": "The date the object was published, in the site's timezone.", 1717 "type": "string" 1718 }, 1719 "date_gmt": { 1720 "required": false, 1721 "description": "The date the object was published, as GMT.", 1722 "type": "string" 1723 }, 1724 "modified": { 1725 "required": false, 1726 "description": "The date the object was last modified, in the site's timezone.", 1727 "type": "string" 1728 }, 1729 "modified_gmt": { 1730 "required": false, 1731 "description": "The date the object was last modified, as GMT.", 1732 "type": "string" 1733 }, 1734 "slug": { 1735 "required": false, 1736 "description": "An alphanumeric identifier for the object unique to its type.", 1737 "type": "string" 1738 }, 1739 "title": { 1740 "required": false, 1741 "description": "The title for the object.", 1742 "type": "object" 1743 }, 1744 "content": { 1745 "required": false, 1746 "description": "The content for the object.", 1747 "type": "object" 1748 }, 1749 "excerpt": { 1750 "required": false, 1751 "description": "The excerpt for the object.", 1752 "type": "object" 1753 } 1754 } 1755 } 1756 ] 1757 }, 1324 1758 "/wp/v2/media": { 1325 1759 "namespace": "wp/v2", 1326 1760 "methods": [