WordPress REST API (Version 2) - Version 2.0-beta12

Version Description

Download this release

Release Info

Developer danielbachhuber
Plugin Icon 128x128 WordPress REST API (Version 2)
Version 2.0-beta12
Comparing to
See all releases

Code changes from version 2.0-beta11 to 2.0-beta12

CHANGELOG.md CHANGED
@@ -1,6 +1,142 @@
1
  # Changelog
2
 
3
- ## 2.0 Beta 11.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  - BREAKING CHANGE: Moves Post->Term relations to the Post Resource
6
 
@@ -147,7 +283,7 @@ unnecessary call to `get_item()`.
147
 
148
  (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
149
 
150
- ## 2.0 Beta 10.0
151
 
152
  - SECURITY: Ensure media of private posts are private too.
153
 
@@ -279,7 +415,7 @@ unnecessary call to `get_item()`.
279
 
280
  (props @danielbachhuber, [#1852](https://github.com/WP-API/WP-API/pull/1852))
281
 
282
- ## 2.0 Beta 9.0
283
 
284
  - BREAKING CHANGE: Move tags and categories to top-level endpoints.
285
 
@@ -393,7 +529,7 @@ unnecessary call to `get_item()`.
393
 
394
  (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
395
 
396
- ## 2.0 Beta 8.0
397
 
398
  - Prevent fatals when uploading attachment by including admin utilities.
399
 
@@ -429,7 +565,7 @@ unnecessary call to `get_item()`.
429
 
430
  (props @wpsmith, [#1759](https://github.com/WP-API/WP-API/pull/1759))
431
 
432
- ## 2.0 Beta 7.0
433
 
434
  - Sync infrastructure from WordPress core as of r35691.
435
 
@@ -461,7 +597,7 @@ unnecessary call to `get_item()`.
461
  (props @danielbachhuber, [#1726](https://github.com/WP-API/WP-API/pull/1726))
462
 
463
 
464
- ## 2.0 Beta 6.0
465
 
466
  - Remove global inclusion of wp-admin/includes/admin.php
467
 
@@ -557,7 +693,7 @@ unnecessary call to `get_item()`.
557
 
558
  (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
559
 
560
- ## 2.0 Beta 5.0
561
 
562
  - Load api-core as a compatibility library
563
 
@@ -592,7 +728,7 @@ unnecessary call to `get_item()`.
592
 
593
  (props @joehoyle)
594
 
595
- ## 2.0 Beta 4.0
596
 
597
  - Show public user information through the user controller.
598
 
@@ -825,7 +961,7 @@ unnecessary call to `get_item()`.
825
  [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
826
  [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
827
 
828
- ## 2.0 Beta 3.0
829
 
830
  - Add ability to declare sanitization and default options for schema fields.
831
 
@@ -890,7 +1026,7 @@ unnecessary call to `get_item()`.
890
  [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
891
  [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
892
 
893
- ## 2.0 Beta 2.0
894
 
895
  - Load the WP REST API before the main query runs.
896
 
@@ -1111,7 +1247,7 @@ unnecessary call to `get_item()`.
1111
  Reported by @kacperszurek on 2015-05-16.
1112
 
1113
 
1114
- ## 2.0 Beta 1
1115
 
1116
  - Avoid passing server to the controller each time
1117
 
1
  # Changelog
2
 
3
+ ## 2.0 Beta 12.0 (February 9, 2016)
4
+
5
+ - BREAKING CHANGE: Removes meta endpoints from primary plugin.
6
+
7
+ If your project depends on post meta endpoints, please install [WP REST API Meta Endpoints](https://wordpress.org/plugins/rest-api-meta-endpoints/). For the gory history of meta, read [#1425](https://github.com/WP-API/WP-API/issues/1425) and linked issues. At this time, we recommend using `register_rest_field()` to expose meta ([docs](http://v2.wp-api.org/extending/modifying/)).
8
+
9
+ (props @danielbachhuber, [#2172](https://github.com/WP-API/WP-API/pull/2172))
10
+
11
+ - BREAKING CHANGE: Returns original resource when deleting PTCU.
12
+
13
+ Now that all resources require the `force` param, we don't need to wrap delete responses with the `trash` state.
14
+
15
+ (props @danielbachhuber, [#2163](https://github.com/WP-API/WP-API/pull/2163))
16
+
17
+ - BREAKING CHANGE: Uses `roles` rather than `role` in the Users controller.
18
+
19
+ Building the REST API gives us the opportunity to standardize on `roles`, instead of having both `roles` and `role`.
20
+
21
+ (props @joehoyle, [#2177](https://github.com/WP-API/WP-API/pull/2177))
22
+
23
+ - BREAKING CHANGES: Moves to consistent use of `context` throughout controllers.
24
+
25
+ Contexts limit the data present in the response. Here's how to think of them: `embed` correlates with sidebar representation, `view` represents the primary public view, and `edit` is the data expected for an editor.
26
+
27
+ (props @danielbachhuber, [#2205](https://github.com/WP-API/WP-API/pull/2205), [#2204](https://github.com/WP-API/WP-API/pull/2204), [#2203](https://github.com/WP-API/WP-API/pull/2203), [#2218](https://github.com/WP-API/WP-API/pull/2218), [#2216](https://github.com/WP-API/WP-API/pull/2216), [#2230](https://github.com/WP-API/WP-API/pull/2230), [#2184](https://github.com/WP-API/WP-API/pull/2184), [#2235](https://github.com/WP-API/WP-API/pull/2235))
28
+
29
+ - BREAKING CHANGE: Removes `post_*` query param support for `GET /wp/v2/comments`.
30
+
31
+ The proper pattern is to use `GET /wp/v2/posts` to fetch the post IDs to limit the request to.
32
+
33
+ (props @danielbachhuber, [#2165](https://github.com/WP-API/WP-API/pull/2165))
34
+
35
+ - BREAKING CHANGE: Introduces `rest_validate_request_arg()`/`rest_sanitize_request_arg()`.
36
+
37
+ Dedicated functions means we can use them for validating / sanitizing query args too. Removes `WP_REST_Controller::validate_schema_property()` and `WP_REST_Controller::sanitize_schema_property()`.
38
+
39
+ (props @danielbachhuber, [#2166](https://github.com/WP-API/WP-API/pull/2166), [#2213](https://github.com/WP-API/WP-API/pull/2213))
40
+
41
+ - Requires minimum value of 1 for `page` param.
42
+
43
+ (props @danielbachhuber, [#2241](https://github.com/WP-API/WP-API/pull/2241))
44
+
45
+ - Introduces `media_type` and `mime_type` params for `GET /wp/v2/media`.
46
+
47
+ (props @danielbachhuber, [#2231](https://github.com/WP-API/WP-API/pull/2231))
48
+
49
+ - Uses the term cache for post data.
50
+
51
+ (props @rmccue, [#2234](https://github.com/WP-API/WP-API/pull/2234))
52
+
53
+ - Supports for querying comments where `post=0`.
54
+
55
+ (props @danielbachhuber, [#1865](https://github.com/WP-API/WP-API/pull/1865))
56
+
57
+ - Exposes taxonomy and post type capabilities in `context=edit`.
58
+
59
+ (props @danielbachhuber, [#2216](https://github.com/WP-API/WP-API/pull/2216))
60
+
61
+ - Errors early when user can't GET types or taxonomies when `context=edit`.
62
+
63
+ (props @danielbachhuber, [#2218](https://github.com/WP-API/WP-API/pull/2218))
64
+
65
+ - Passes original $request context to `prepare_items_query`.
66
+
67
+ (props @danielbachhuber, [#2211](https://github.com/WP-API/WP-API/pull/2211))
68
+
69
+ - Adds `parent` and `parent_exclude` params to GET Comments.
70
+
71
+ (props @danielbachhuber, [#2206](https://github.com/WP-API/WP-API/pull/2206))
72
+
73
+ - Enforces minimum 1 and maximum 100 values for `per_page` parameter.
74
+
75
+ (props @danielbachhuber, [#2209](https://github.com/WP-API/WP-API/pull/2209))
76
+
77
+ - Adds `author` and `author_exclude` params to GET Posts and Comments.
78
+
79
+ (props @danielbachhuber, [#2200](https://github.com/WP-API/WP-API/pull/2202), [#2200](https://github.com/WP-API/WP-API/pull/2202))
80
+
81
+ - Adds `menu_order` param for `GET` Pages; support `menu_order` orderby.
82
+
83
+ (props @danielbachhuber, [#2193](https://github.com/WP-API/WP-API/pull/2193))
84
+
85
+ - Only calls `sanitize_text_field()` when sanitizing `type=string,format=email`.
86
+
87
+ (props @danielbachhuber, [#2185](https://github.com/WP-API/WP-API/pull/2185))
88
+
89
+ - Validates `GET /wp/v2/comments` private query params.
90
+
91
+ Returns an error when user doesn't have permission to use them, instead of silently discarding.
92
+
93
+ (props @danielbachhuber, [#2178](https://github.com/WP-API/WP-API/pull/2178))
94
+
95
+ - Explicitly prevents uploading attachments to other attachments or revisions.
96
+
97
+ (props @danielbachhuber, [#2180](https://github.com/WP-API/WP-API/pull/2180))
98
+
99
+ - Permits user urls to be edited through the API.
100
+
101
+ (props @danielbachhuber, [#2182](https://github.com/WP-API/WP-API/pull/2182))
102
+
103
+ - Marks all Status, Type and Taxonomy fields as `readonly`.
104
+
105
+ (props @danielbachhuber, [#2181](https://github.com/WP-API/WP-API/pull/2181))
106
+
107
+ - Adds validation callbacks to collection query params.
108
+
109
+ (props @danielbachhuber, [#2170](https://github.com/WP-API/WP-API/pull/2170), [#2171](https://github.com/WP-API/WP-API/pull/2171), [#2176](https://github.com/WP-API/WP-API/pull/2176), [#2174](https://github.com/WP-API/WP-API/pull/2174), [#2175](https://github.com/WP-API/WP-API/pull/2175))
110
+
111
+ - Links taxonomy terms to the post type collections they support.
112
+
113
+ (props @danielbachhuber, [#2167](https://github.com/WP-API/WP-API/pull/2167))
114
+
115
+ - Returns error when making a `GET` request with invalid context.
116
+
117
+ (props @danielbachhuber, [#2169](https://github.com/WP-API/WP-API/pull/2169))
118
+
119
+ - Adds `trash` status to `GET /wp/v2/statuses`.
120
+
121
+ (props @danielbachhuber, [#2158](https://github.com/WP-API/WP-API/pull/2158))
122
+
123
+ - Indicates when fields have HTML in schema.
124
+
125
+ (props @joehoyle, [#2159](https://github.com/WP-API/WP-API/pull/2159))
126
+
127
+ - Permits viewing of User who has published any Public posts.
128
+
129
+ (props @danielbachhuber, [#2155](https://github.com/WP-API/WP-API/pull/2155))
130
+
131
+ - Respects `show_avatars` option when adding avatars to Users.
132
+
133
+ (props @nullvariable, [#2151](https://github.com/WP-API/WP-API/pull/2151))
134
+
135
+ - Controllers use `$namespace` and `$rest_base` class variables for easier subclassing.
136
+
137
+ (props @danielbachhuber, [#2119](https://github.com/WP-API/WP-API/pull/2119), [#2130](https://github.com/WP-API/WP-API/pull/2130), [#2131](https://github.com/WP-API/WP-API/pull/2131), [#2132](https://github.com/WP-API/WP-API/pull/2132), [#2133](https://github.com/WP-API/WP-API/pull/2133), [#2134](https://github.com/WP-API/WP-API/pull/2134), [#2139](https://github.com/WP-API/WP-API/pull/2139), [#2141](https://github.com/WP-API/WP-API/pull/2141), [#2142](https://github.com/WP-API/WP-API/pull/2142))
138
+
139
+ ## 2.0 Beta 11.0 (January 25, 2016)
140
 
141
  - BREAKING CHANGE: Moves Post->Term relations to the Post Resource
142
 
283
 
284
  (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
285
 
286
+ ## 2.0 Beta 10.0 (January 11, 2016)
287
 
288
  - SECURITY: Ensure media of private posts are private too.
289
 
415
 
416
  (props @danielbachhuber, [#1852](https://github.com/WP-API/WP-API/pull/1852))
417
 
418
+ ## 2.0 Beta 9.0 (December 11, 2015)
419
 
420
  - BREAKING CHANGE: Move tags and categories to top-level endpoints.
421
 
529
 
530
  (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
531
 
532
+ ## 2.0 Beta 8.0 (December 1, 2015)
533
 
534
  - Prevent fatals when uploading attachment by including admin utilities.
535
 
565
 
566
  (props @wpsmith, [#1759](https://github.com/WP-API/WP-API/pull/1759))
567
 
568
+ ## 2.0 Beta 7.0 (November 17, 2015)
569
 
570
  - Sync infrastructure from WordPress core as of r35691.
571
 
597
  (props @danielbachhuber, [#1726](https://github.com/WP-API/WP-API/pull/1726))
598
 
599
 
600
+ ## 2.0 Beta 6.0 (November 12, 2015)
601
 
602
  - Remove global inclusion of wp-admin/includes/admin.php
603
 
693
 
694
  (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
695
 
696
+ ## 2.0 Beta 5.0 (October 23, 2015)
697
 
698
  - Load api-core as a compatibility library
699
 
728
 
729
  (props @joehoyle)
730
 
731
+ ## 2.0 Beta 4.0 (August 14, 2015)
732
 
733
  - Show public user information through the user controller.
734
 
961
  [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
962
  [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
963
 
964
+ ## 2.0 Beta 3.0 (July 1, 2015)
965
 
966
  - Add ability to declare sanitization and default options for schema fields.
967
 
1026
  [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
1027
  [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
1028
 
1029
+ ## 2.0 Beta 2.0 (May 28, 2015)
1030
 
1031
  - Load the WP REST API before the main query runs.
1032
 
1247
  Reported by @kacperszurek on 2015-05-16.
1248
 
1249
 
1250
+ ## 2.0 Beta 1 (April 28, 2015)
1251
 
1252
  - Avoid passing server to the controller each time
1253
 
core-integration.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Integration points with WordPress core that won't ever be committed
4
+ */
5
+
6
+ /**
7
+ * Inject `parent__in` and `parent__not_in` vars to avoid bad cache
8
+ *
9
+ * @see https://core.trac.wordpress.org/ticket/35677
10
+ */
11
+ function wp_api_comment_query_vars( $query ) {
12
+ $query->query_var_defaults['parent__in'] = array();
13
+ $query->query_var_defaults['parent__not_in'] = array();
14
+ }
15
+ add_action( 'pre_get_comments', 'wp_api_comment_query_vars' );
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -6,14 +6,25 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
6
  * Determine the allowed query_vars for a get_items() response and
7
  * prepare for WP_Query.
8
  *
9
- * @param array $prepared_args
10
- * @return array $query_args
 
11
  */
12
- protected function prepare_items_query( $prepared_args = array() ) {
13
- $query_args = parent::prepare_items_query( $prepared_args );
14
  if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ) ) ) {
15
  $query_args['post_status'] = 'inherit';
16
  }
 
 
 
 
 
 
 
 
 
 
17
  return $query_args;
18
  }
19
 
@@ -40,7 +51,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
40
  $parent = get_post( (int) $request['post'] );
41
  $post_parent_type = get_post_type_object( $parent->post_type );
42
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
43
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) );
44
  }
45
  }
46
 
@@ -55,6 +66,10 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
55
  */
56
  public function create_item( $request ) {
57
 
 
 
 
 
58
  // Get the file via $_FILES or raw data
59
  $files = $request->get_file_params();
60
  $headers = $request->get_headers();
@@ -120,7 +135,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
120
  $response = $this->prepare_item_for_response( $attachment, $request );
121
  $response = rest_ensure_response( $response );
122
  $response->set_status( 201 );
123
- $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
124
 
125
  /**
126
  * Fires after a single attachment is created or updated via the REST API.
@@ -142,6 +157,9 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
142
  * @return WP_Error|WP_REST_Response
143
  */
144
  public function update_item( $request ) {
 
 
 
145
  $response = parent::update_item( $request );
146
  if ( is_wp_error( $response ) ) {
147
  return $response;
@@ -204,6 +222,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
204
  $data['caption'] = $post->post_excerpt;
205
  $data['description'] = $post->post_content;
206
  $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
 
207
  $data['media_details'] = wp_get_attachment_metadata( $post->ID );
208
  $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
209
  $data['source_url'] = wp_get_attachment_url( $post->ID );
@@ -275,54 +294,60 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
275
  $schema = parent::get_item_schema();
276
 
277
  $schema['properties']['alt_text'] = array(
278
- 'description' => __( 'Alternative text to display when attachment is not displayed.' ),
279
  'type' => 'string',
280
  'context' => array( 'view', 'edit', 'embed' ),
281
  'arg_options' => array(
282
  'sanitize_callback' => 'sanitize_text_field',
283
  ),
284
- );
285
  $schema['properties']['caption'] = array(
286
- 'description' => __( 'The caption for the attachment.' ),
287
  'type' => 'string',
288
  'context' => array( 'view', 'edit' ),
289
  'arg_options' => array(
290
  'sanitize_callback' => 'wp_filter_post_kses',
291
  ),
292
- );
293
  $schema['properties']['description'] = array(
294
- 'description' => __( 'The description for the attachment.' ),
295
  'type' => 'string',
296
  'context' => array( 'view', 'edit' ),
297
  'arg_options' => array(
298
  'sanitize_callback' => 'wp_filter_post_kses',
299
  ),
300
- );
301
  $schema['properties']['media_type'] = array(
302
- 'description' => __( 'Type of attachment.' ),
303
  'type' => 'string',
304
  'enum' => array( 'image', 'file' ),
305
  'context' => array( 'view', 'edit', 'embed' ),
306
  'readonly' => true,
307
- );
 
 
 
 
 
 
308
  $schema['properties']['media_details'] = array(
309
- 'description' => __( 'Details about the attachment file, specific to its type.' ),
310
  'type' => 'object',
311
  'context' => array( 'view', 'edit', 'embed' ),
312
  'readonly' => true,
313
- );
314
  $schema['properties']['post'] = array(
315
- 'description' => __( 'The id for the associated post of the attachment.' ),
316
  'type' => 'integer',
317
  'context' => array( 'view', 'edit' ),
318
- );
319
  $schema['properties']['source_url'] = array(
320
- 'description' => __( 'URL to the original attachment file.' ),
321
  'type' => 'string',
322
  'format' => 'uri',
323
  'context' => array( 'view', 'edit', 'embed' ),
324
  'readonly' => true,
325
- );
326
  return $schema;
327
  }
328
 
@@ -423,6 +448,19 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
423
  $params = parent::get_collection_params();
424
  $params['status']['default'] = 'inherit';
425
  $params['status']['enum'] = array( 'inherit', 'private', 'trash' );
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  return $params;
427
  }
428
 
@@ -483,4 +521,22 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
483
  return $file;
484
  }
485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  }
6
  * Determine the allowed query_vars for a get_items() response and
7
  * prepare for WP_Query.
8
  *
9
+ * @param array $prepared_args
10
+ * @param WP_REST_Request $request
11
+ * @return array $query_args
12
  */
13
+ protected function prepare_items_query( $prepared_args = array(), $request = null ) {
14
+ $query_args = parent::prepare_items_query( $prepared_args, $request );
15
  if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ) ) ) {
16
  $query_args['post_status'] = 'inherit';
17
  }
18
+ $media_types = $this->get_media_types();
19
+ if ( ! empty( $request['media_type'] ) && in_array( $request['media_type'], array_keys( $media_types ) ) ) {
20
+ $query_args['post_mime_type'] = $media_types[ $request['media_type'] ];
21
+ }
22
+ if ( ! empty( $request['mime_type'] ) ) {
23
+ $parts = explode( '/', $request['mime_type'] );
24
+ if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ] ) ) {
25
+ $query_args['post_mime_type'] = $request['mime_type'];
26
+ }
27
+ }
28
  return $query_args;
29
  }
30
 
51
  $parent = get_post( (int) $request['post'] );
52
  $post_parent_type = get_post_type_object( $parent->post_type );
53
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
54
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this resource.' ), array( 'status' => rest_authorization_required_code() ) );
55
  }
56
  }
57
 
66
  */
67
  public function create_item( $request ) {
68
 
69
+ if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ) ) ) {
70
+ return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
71
+ }
72
+
73
  // Get the file via $_FILES or raw data
74
  $files = $request->get_file_params();
75
  $headers = $request->get_headers();
135
  $response = $this->prepare_item_for_response( $attachment, $request );
136
  $response = rest_ensure_response( $response );
137
  $response->set_status( 201 );
138
+ $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
139
 
140
  /**
141
  * Fires after a single attachment is created or updated via the REST API.
157
  * @return WP_Error|WP_REST_Response
158
  */
159
  public function update_item( $request ) {
160
+ if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ) ) ) {
161
+ return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
162
+ }
163
  $response = parent::update_item( $request );
164
  if ( is_wp_error( $response ) ) {
165
  return $response;
222
  $data['caption'] = $post->post_excerpt;
223
  $data['description'] = $post->post_content;
224
  $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
225
+ $data['mime_type'] = $post->post_mime_type;
226
  $data['media_details'] = wp_get_attachment_metadata( $post->ID );
227
  $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
228
  $data['source_url'] = wp_get_attachment_url( $post->ID );
294
  $schema = parent::get_item_schema();
295
 
296
  $schema['properties']['alt_text'] = array(
297
+ 'description' => __( 'Alternative text to display when resource is not displayed.' ),
298
  'type' => 'string',
299
  'context' => array( 'view', 'edit', 'embed' ),
300
  'arg_options' => array(
301
  'sanitize_callback' => 'sanitize_text_field',
302
  ),
303
+ );
304
  $schema['properties']['caption'] = array(
305
+ 'description' => __( 'The caption for the resource.' ),
306
  'type' => 'string',
307
  'context' => array( 'view', 'edit' ),
308
  'arg_options' => array(
309
  'sanitize_callback' => 'wp_filter_post_kses',
310
  ),
311
+ );
312
  $schema['properties']['description'] = array(
313
+ 'description' => __( 'The description for the resource.' ),
314
  'type' => 'string',
315
  'context' => array( 'view', 'edit' ),
316
  'arg_options' => array(
317
  'sanitize_callback' => 'wp_filter_post_kses',
318
  ),
319
+ );
320
  $schema['properties']['media_type'] = array(
321
+ 'description' => __( 'Type of resource.' ),
322
  'type' => 'string',
323
  'enum' => array( 'image', 'file' ),
324
  'context' => array( 'view', 'edit', 'embed' ),
325
  'readonly' => true,
326
+ );
327
+ $schema['properties']['mime_type'] = array(
328
+ 'description' => __( 'Mime type of resource.' ),
329
+ 'type' => 'string',
330
+ 'context' => array( 'view', 'edit', 'embed' ),
331
+ 'readonly' => true,
332
+ );
333
  $schema['properties']['media_details'] = array(
334
+ 'description' => __( 'Details about the resource file, specific to its type.' ),
335
  'type' => 'object',
336
  'context' => array( 'view', 'edit', 'embed' ),
337
  'readonly' => true,
338
+ );
339
  $schema['properties']['post'] = array(
340
+ 'description' => __( 'The id for the associated post of the resource.' ),
341
  'type' => 'integer',
342
  'context' => array( 'view', 'edit' ),
343
+ );
344
  $schema['properties']['source_url'] = array(
345
+ 'description' => __( 'URL to the original resource file.' ),
346
  'type' => 'string',
347
  'format' => 'uri',
348
  'context' => array( 'view', 'edit', 'embed' ),
349
  'readonly' => true,
350
+ );
351
  return $schema;
352
  }
353
 
448
  $params = parent::get_collection_params();
449
  $params['status']['default'] = 'inherit';
450
  $params['status']['enum'] = array( 'inherit', 'private', 'trash' );
451
+ $media_types = $this->get_media_types();
452
+ $params['media_type'] = array(
453
+ 'default' => null,
454
+ 'description' => __( 'Limit result set to attachments of a particular media type.' ),
455
+ 'type' => 'string',
456
+ 'enum' => array_keys( $media_types ),
457
+ 'validate_callback' => 'rest_validate_request_arg',
458
+ );
459
+ $params['mime_type'] = array(
460
+ 'default' => null,
461
+ 'description' => __( 'Limit result set to attachments of a particular mime type.' ),
462
+ 'type' => 'string',
463
+ );
464
  return $params;
465
  }
466
 
521
  return $file;
522
  }
523
 
524
+ /**
525
+ * Get the supported media types.
526
+ * Media types are considered the mime type category
527
+ *
528
+ * @return array
529
+ */
530
+ protected function get_media_types() {
531
+ $media_types = array();
532
+ foreach ( get_allowed_mime_types() as $mime_type ) {
533
+ $parts = explode( '/', $mime_type );
534
+ if ( ! isset( $media_types[ $parts[0] ] ) ) {
535
+ $media_types[ $parts[0] ] = array();
536
+ }
537
+ $media_types[ $parts[0] ][] = $mime_type;
538
+ }
539
+ return $media_types;
540
+ }
541
+
542
  }
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -5,12 +5,17 @@
5
  */
6
  class WP_REST_Comments_Controller extends WP_REST_Controller {
7
 
 
 
 
 
 
8
  /**
9
  * Register the routes for the objects of the controller.
10
  */
11
  public function register_routes() {
12
 
13
- register_rest_route( 'wp/v2', '/comments', array(
14
  array(
15
  'methods' => WP_REST_Server::READABLE,
16
  'callback' => array( $this, 'get_items' ),
@@ -23,11 +28,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
23
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
24
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
25
  ),
26
-
27
  'schema' => array( $this, 'get_public_item_schema' ),
28
  ) );
29
 
30
- register_rest_route( 'wp/v2', '/comments/(?P<id>[\d]+)', array(
31
  array(
32
  'methods' => WP_REST_Server::READABLE,
33
  'callback' => array( $this, 'get_item' ),
@@ -53,7 +57,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
53
  ),
54
  ),
55
  ),
56
-
57
  'schema' => array( $this, 'get_public_item_schema' ),
58
  ) );
59
  }
@@ -66,12 +69,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
66
  */
67
  public function get_items_permissions_check( $request ) {
68
 
69
- // If the post id is specified, check that we can read the post
70
- if ( isset( $request['post'] ) ) {
71
- $post = get_post( (int) $request['post'] );
72
-
73
- if ( $post && ! $this->check_read_post_permission( $post ) ) {
74
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
 
 
75
  }
76
  }
77
 
@@ -79,6 +84,27 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
79
  return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  return true;
83
  }
84
 
@@ -90,41 +116,29 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
90
  */
91
  public function get_items( $request ) {
92
  $prepared_args = array(
 
93
  'comment__in' => $request['include'],
94
  'comment__not_in' => $request['exclude'],
 
95
  'number' => $request['per_page'],
96
- 'post_id' => $request['post'] ? $request['post'] : '',
97
- 'parent' => isset( $request['parent'] ) ? $request['parent'] : '',
 
98
  'search' => $request['search'],
99
  'offset' => $request['offset'],
100
  'orderby' => $this->normalize_query_param( $request['orderby'] ),
101
  'order' => $request['order'],
102
- 'status' => 'approve',
103
- 'type' => 'comment',
104
  'no_found_rows' => false,
 
 
105
  );
106
 
107
  if ( empty( $request['offset'] ) ) {
108
  $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
109
  }
110
 
111
- if ( current_user_can( 'edit_posts' ) ) {
112
- $protected_args = array(
113
- 'user_id' => $request['author'] ? $request['author'] : '',
114
- 'status' => $request['status'],
115
- 'type' => isset( $request['type'] ) ? $request['type'] : '',
116
- 'author_email' => isset( $request['author_email'] ) ? $request['author_email'] : '',
117
- 'karma' => isset( $request['karma'] ) ? $request['karma'] : '',
118
- 'post_author' => isset( $request['post_author'] ) ? $request['post_author'] : '',
119
- 'post_name' => isset( $request['post_slug'] ) ? $request['post_slug'] : '',
120
- 'post_parent' => isset( $request['post_parent'] ) ? $request['post_parent'] : '',
121
- 'post_status' => isset( $request['post_status'] ) ? $request['post_status'] : '',
122
- 'post_type' => isset( $request['post_type'] ) ? $request['post_type'] : '',
123
- );
124
-
125
- $prepared_args = array_merge( $prepared_args, $protected_args );
126
- }
127
-
128
  /**
129
  * Filter arguments, before passing to WP_Comment_Query, when querying comments via the REST API.
130
  *
@@ -140,8 +154,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
140
 
141
  $comments = array();
142
  foreach ( $query_result as $comment ) {
143
- $post = get_post( $comment->comment_post_ID );
144
- if ( ! $this->check_read_post_permission( $post ) || ! $this->check_read_permission( $comment ) ) {
145
  continue;
146
  }
147
 
@@ -166,7 +179,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
166
  $response->header( 'X-WP-Total', $total_comments );
167
  $response->header( 'X-WP-TotalPages', $max_pages );
168
 
169
- $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/comments' ) );
170
  if ( $request['page'] > 1 ) {
171
  $prev_page = $request['page'] - 1;
172
  if ( $prev_page > $max_pages ) {
@@ -343,19 +356,15 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
343
  $this->handle_status_param( $request['status'], $comment );
344
  }
345
 
346
- $this->update_additional_fields_for_object( get_comment( $comment_id ), $request );
 
347
 
348
  $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
349
- $get_request = new WP_REST_Request;
350
- $get_request->set_param( 'id', $comment_id );
351
- $get_request->set_param( 'context', $context );
352
- $response = $this->get_item( $get_request );
353
  $response = rest_ensure_response( $response );
354
- if ( is_wp_error( $response ) ) {
355
- return $response;
356
- }
357
  $response->set_status( 201 );
358
- $response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment_id ) );
359
 
360
  /**
361
  * Fires after a comment is created or updated via the REST API.
@@ -482,14 +491,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
482
  */
483
  $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
484
 
485
- $get_request = new WP_REST_Request;
486
- $get_request->set_param( 'id', $id );
487
- $get_request->set_param( 'context', 'edit' );
488
- $response = $this->prepare_item_for_response( $comment, $get_request );
489
 
490
  if ( $force ) {
491
  $result = wp_delete_comment( $comment->comment_ID, true );
492
- $status = 'deleted';
493
  } else {
494
  // If we don't support trashing for this type, error out
495
  if ( ! $supports_trash ) {
@@ -501,16 +507,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
501
  }
502
 
503
  $result = wp_trash_comment( $comment->comment_ID );
504
- $status = 'trashed';
505
  }
506
 
507
- $data = $response->get_data();
508
- $data = array(
509
- 'data' => $data,
510
- $status => true,
511
- );
512
- $response->set_data( $data );
513
-
514
  if ( ! $result ) {
515
  return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
516
  }
@@ -518,11 +516,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
518
  /**
519
  * Fires after a comment is deleted via the REST API.
520
  *
521
- * @param object $comment The deleted comment data.
522
- * @param array $data Delete status data.
523
- * @param WP_REST_Request $request The request sent to the API.
524
  */
525
- do_action( 'rest_delete_comment', $comment, $data, $request );
526
 
527
  return $response;
528
  }
@@ -588,10 +586,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
588
  protected function prepare_links( $comment ) {
589
  $links = array(
590
  'self' => array(
591
- 'href' => rest_url( '/wp/v2/comments/' . $comment->comment_ID ),
592
  ),
593
  'collection' => array(
594
- 'href' => rest_url( '/wp/v2/comments' ),
595
  ),
596
  );
597
 
@@ -605,8 +603,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
605
  if ( 0 !== (int) $comment->comment_post_ID ) {
606
  $post = get_post( $comment->comment_post_ID );
607
  if ( ! empty( $post->ID ) ) {
608
- $posts_controller = new WP_REST_Posts_Controller( $post->post_type );
609
- $base = $posts_controller->get_post_type_base( $post->post_type );
610
 
611
  $links['up'] = array(
612
  'href' => rest_url( '/wp/v2/' . $base . '/' . $comment->comment_post_ID ),
@@ -618,7 +616,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
618
 
619
  if ( 0 !== (int) $comment->comment_parent ) {
620
  $links['in-reply-to'] = array(
621
- 'href' => rest_url( sprintf( '/wp/v2/comments/%d', (int) $comment->comment_parent ) ),
622
  'embeddable' => true,
623
  );
624
  }
@@ -832,7 +830,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
832
  'context' => array( 'edit' ),
833
  ),
834
  'rendered' => array(
835
- 'description' => __( 'Content for the object, transformed for display.' ),
836
  'type' => 'string',
837
  'context' => array( 'view', 'edit', 'embed' ),
838
  ),
@@ -914,11 +912,24 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
914
 
915
  $query_params['context']['default'] = 'view';
916
 
 
 
 
 
 
 
 
 
 
 
 
 
917
  $query_params['author_email'] = array(
918
  'default' => null,
919
- 'description' => __( 'Limit result set to that from a specific author email.' ),
920
  'format' => 'email',
921
  'sanitize_callback' => 'sanitize_email',
 
922
  'type' => 'string',
923
  );
924
  $query_params['exclude'] = array(
@@ -926,28 +937,33 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
926
  'type' => 'array',
927
  'default' => array(),
928
  'sanitize_callback' => 'wp_parse_id_list',
 
929
  );
930
  $query_params['include'] = array(
931
  'description' => __( 'Limit result set to specific ids.' ),
932
  'type' => 'array',
933
  'default' => array(),
934
  'sanitize_callback' => 'wp_parse_id_list',
 
935
  );
936
  $query_params['karma'] = array(
937
  'default' => null,
938
- 'description' => __( 'Limit result set to that of a particular comment karma.' ),
939
  'sanitize_callback' => 'absint',
940
  'type' => 'integer',
 
941
  );
942
  $query_params['offset'] = array(
943
  'description' => __( 'Offset the result set by a specific number of comments.' ),
944
  'type' => 'integer',
945
  'sanitize_callback' => 'absint',
 
946
  );
947
  $query_params['order'] = array(
948
  'description' => __( 'Order sort attribute ascending or descending.' ),
949
  'type' => 'string',
950
  'sanitize_callback' => 'sanitize_key',
 
951
  'default' => 'asc',
952
  'enum' => array(
953
  'asc',
@@ -958,6 +974,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
958
  'description' => __( 'Sort collection by object attribute.' ),
959
  'type' => 'string',
960
  'sanitize_callback' => 'sanitize_key',
 
961
  'default' => 'date_gmt',
962
  'enum' => array(
963
  'date',
@@ -970,63 +987,39 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
970
  ),
971
  );
972
  $query_params['parent'] = array(
973
- 'default' => null,
974
- 'description' => __( 'Limit result set to that of a specific comment parent id.' ),
975
- 'sanitize_callback' => 'absint',
976
- 'type' => 'integer',
 
977
  );
978
- $query_params['post'] = array(
979
- 'default' => null,
980
- 'description' => __( 'Limit result set to comments assigned to a specific post id.' ),
981
- 'sanitize_callback' => 'absint',
982
- 'type' => 'integer',
983
- );
984
- $query_params['post_author'] = array(
985
- 'default' => null,
986
- 'description' => __( 'Limit result set to comments associated with posts of a specific post author id.' ),
987
- 'sanitize_callback' => 'absint',
988
- 'type' => 'integer',
989
- );
990
- $query_params['post_slug'] = array(
991
- 'default' => null,
992
- 'description' => __( 'Limit result set to comments associated with posts of a specific post slug.' ),
993
- 'sanitize_callback' => 'sanitize_title',
994
- 'type' => 'string',
995
- );
996
- $query_params['post_parent'] = array(
997
- 'default' => null,
998
- 'description' => __( 'Limit result set to comments associated with posts of a specific post parent id.' ),
999
- 'sanitize_callback' => 'absint',
1000
- 'type' => 'integer',
1001
- );
1002
- $query_params['post_status'] = array(
1003
- 'default' => null,
1004
- 'description' => __( 'Limit result set to comments associated with posts of a specific post status.' ),
1005
- 'sanitize_callback' => 'sanitize_key',
1006
- 'type' => 'string',
1007
  );
1008
- $query_params['post_type'] = array(
1009
- 'default' => null,
1010
- 'description' => __( 'Limit result set to comments associated with posts of a specific post type.' ),
1011
- 'sanitize_callback' => 'sanitize_key',
1012
- 'type' => 'string',
 
1013
  );
1014
  $query_params['status'] = array(
1015
  'default' => 'approve',
1016
- 'description' => __( 'Limit result set to comments assigned a specific status.' ),
1017
  'sanitize_callback' => 'sanitize_key',
1018
  'type' => 'string',
 
1019
  );
1020
  $query_params['type'] = array(
1021
  'default' => 'comment',
1022
- 'description' => __( 'Limit result set to comments assigned a specific type.' ),
1023
  'sanitize_callback' => 'sanitize_key',
1024
  'type' => 'string',
1025
- );
1026
- $query_params['author'] = array(
1027
- 'description' => __( 'Limit result set to comments assigned to a specific user id.' ),
1028
- 'sanitize_callback' => 'absint',
1029
- 'type' => 'integer',
1030
  );
1031
  return $query_params;
1032
  }
@@ -1105,6 +1098,17 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
1105
  return false;
1106
  }
1107
 
 
 
 
 
 
 
 
 
 
 
 
1108
  if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1109
  return true;
1110
  }
5
  */
6
  class WP_REST_Comments_Controller extends WP_REST_Controller {
7
 
8
+ public function __construct() {
9
+ $this->namespace = 'wp/v2';
10
+ $this->rest_base = 'comments';
11
+ }
12
+
13
  /**
14
  * Register the routes for the objects of the controller.
15
  */
16
  public function register_routes() {
17
 
18
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
19
  array(
20
  'methods' => WP_REST_Server::READABLE,
21
  'callback' => array( $this, 'get_items' ),
28
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
29
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
30
  ),
 
31
  'schema' => array( $this, 'get_public_item_schema' ),
32
  ) );
33
 
34
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
35
  array(
36
  'methods' => WP_REST_Server::READABLE,
37
  'callback' => array( $this, 'get_item' ),
57
  ),
58
  ),
59
  ),
 
60
  'schema' => array( $this, 'get_public_item_schema' ),
61
  ) );
62
  }
69
  */
70
  public function get_items_permissions_check( $request ) {
71
 
72
+ if ( ! empty( $request['post'] ) ) {
73
+ foreach ( $request['post'] as $post_id ) {
74
+ $post = get_post( $post_id );
75
+ if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) {
76
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
77
+ } else if ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) {
78
+ return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) );
79
+ }
80
  }
81
  }
82
 
84
  return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
85
  }
86
 
87
+ if ( ! current_user_can( 'edit_posts' ) ) {
88
+ $protected_params = array( 'author', 'author_exclude', 'karma', 'author_email', 'type', 'status' );
89
+ $forbidden_params = array();
90
+ foreach ( $protected_params as $param ) {
91
+ if ( 'status' === $param ) {
92
+ if ( 'approve' !== $request[ $param ] ) {
93
+ $forbidden_params[] = $param;
94
+ }
95
+ } else if ( 'type' === $param ) {
96
+ if ( 'comment' !== $request[ $param ] ) {
97
+ $forbidden_params[] = $param;
98
+ }
99
+ } else if ( ! empty( $request[ $param ] ) ) {
100
+ $forbidden_params[] = $param;
101
+ }
102
+ }
103
+ if ( ! empty( $forbidden_params ) ) {
104
+ return new WP_Error( 'rest_forbidden_param', sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) );
105
+ }
106
+ }
107
+
108
  return true;
109
  }
110
 
116
  */
117
  public function get_items( $request ) {
118
  $prepared_args = array(
119
+ 'author_email' => isset( $request['author_email'] ) ? $request['author_email'] : '',
120
  'comment__in' => $request['include'],
121
  'comment__not_in' => $request['exclude'],
122
+ 'karma' => isset( $request['karma'] ) ? $request['karma'] : '',
123
  'number' => $request['per_page'],
124
+ 'post__in' => $request['post'],
125
+ 'parent__in' => $request['parent'],
126
+ 'parent__not_in' => $request['parent_exclude'],
127
  'search' => $request['search'],
128
  'offset' => $request['offset'],
129
  'orderby' => $this->normalize_query_param( $request['orderby'] ),
130
  'order' => $request['order'],
131
+ 'status' => $request['status'],
132
+ 'type' => $request['type'],
133
  'no_found_rows' => false,
134
+ 'author__in' => $request['author'],
135
+ 'author__not_in' => $request['author_exclude'],
136
  );
137
 
138
  if ( empty( $request['offset'] ) ) {
139
  $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
140
  }
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  /**
143
  * Filter arguments, before passing to WP_Comment_Query, when querying comments via the REST API.
144
  *
154
 
155
  $comments = array();
156
  foreach ( $query_result as $comment ) {
157
+ if ( ! $this->check_read_permission( $comment ) ) {
 
158
  continue;
159
  }
160
 
179
  $response->header( 'X-WP-Total', $total_comments );
180
  $response->header( 'X-WP-TotalPages', $max_pages );
181
 
182
+ $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
183
  if ( $request['page'] > 1 ) {
184
  $prev_page = $request['page'] - 1;
185
  if ( $prev_page > $max_pages ) {
356
  $this->handle_status_param( $request['status'], $comment );
357
  }
358
 
359
+ $comment = get_comment( $comment_id );
360
+ $this->update_additional_fields_for_object( $comment, $request );
361
 
362
  $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
363
+ $request->set_param( 'context', $context );
364
+ $response = $this->prepare_item_for_response( $comment, $request );
 
 
365
  $response = rest_ensure_response( $response );
 
 
 
366
  $response->set_status( 201 );
367
+ $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) );
368
 
369
  /**
370
  * Fires after a comment is created or updated via the REST API.
491
  */
492
  $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
493
 
494
+ $request->set_param( 'context', 'edit' );
495
+ $response = $this->prepare_item_for_response( $comment, $request );
 
 
496
 
497
  if ( $force ) {
498
  $result = wp_delete_comment( $comment->comment_ID, true );
 
499
  } else {
500
  // If we don't support trashing for this type, error out
501
  if ( ! $supports_trash ) {
507
  }
508
 
509
  $result = wp_trash_comment( $comment->comment_ID );
 
510
  }
511
 
 
 
 
 
 
 
 
512
  if ( ! $result ) {
513
  return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
514
  }
516
  /**
517
  * Fires after a comment is deleted via the REST API.
518
  *
519
+ * @param object $comment The deleted comment data.
520
+ * @param WP_REST_Response $response The response returned from the API.
521
+ * @param WP_REST_Request $request The request sent to the API.
522
  */
523
+ do_action( 'rest_delete_comment', $comment, $response, $request );
524
 
525
  return $response;
526
  }
586
  protected function prepare_links( $comment ) {
587
  $links = array(
588
  'self' => array(
589
+ 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ),
590
  ),
591
  'collection' => array(
592
+ 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
593
  ),
594
  );
595
 
603
  if ( 0 !== (int) $comment->comment_post_ID ) {
604
  $post = get_post( $comment->comment_post_ID );
605
  if ( ! empty( $post->ID ) ) {
606
+ $obj = get_post_type_object( $post->post_type );
607
+ $base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
608
 
609
  $links['up'] = array(
610
  'href' => rest_url( '/wp/v2/' . $base . '/' . $comment->comment_post_ID ),
616
 
617
  if ( 0 !== (int) $comment->comment_parent ) {
618
  $links['in-reply-to'] = array(
619
+ 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ),
620
  'embeddable' => true,
621
  );
622
  }
830
  'context' => array( 'edit' ),
831
  ),
832
  'rendered' => array(
833
+ 'description' => __( 'HTML content for the object, transformed for display.' ),
834
  'type' => 'string',
835
  'context' => array( 'view', 'edit', 'embed' ),
836
  ),
912
 
913
  $query_params['context']['default'] = 'view';
914
 
915
+ $query_params['author'] = array(
916
+ 'description' => __( 'Limit result set to comments assigned to specific user ids. Requires authorization.' ),
917
+ 'sanitize_callback' => 'wp_parse_id_list',
918
+ 'type' => 'array',
919
+ 'validate_callback' => 'rest_validate_request_arg',
920
+ );
921
+ $query_params['author_exclude'] = array(
922
+ 'description' => __( 'Ensure result set excludes comments assigned to specific user ids. Requires authorization.' ),
923
+ 'sanitize_callback' => 'wp_parse_id_list',
924
+ 'type' => 'array',
925
+ 'validate_callback' => 'rest_validate_request_arg',
926
+ );
927
  $query_params['author_email'] = array(
928
  'default' => null,
929
+ 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
930
  'format' => 'email',
931
  'sanitize_callback' => 'sanitize_email',
932
+ 'validate_callback' => 'rest_validate_request_arg',
933
  'type' => 'string',
934
  );
935
  $query_params['exclude'] = array(
937
  'type' => 'array',
938
  'default' => array(),
939
  'sanitize_callback' => 'wp_parse_id_list',
940
+ 'validate_callback' => 'rest_validate_request_arg',
941
  );
942
  $query_params['include'] = array(
943
  'description' => __( 'Limit result set to specific ids.' ),
944
  'type' => 'array',
945
  'default' => array(),
946
  'sanitize_callback' => 'wp_parse_id_list',
947
+ 'validate_callback' => 'rest_validate_request_arg',
948
  );
949
  $query_params['karma'] = array(
950
  'default' => null,
951
+ 'description' => __( 'Limit result set to that of a particular comment karma. Requires authorization.' ),
952
  'sanitize_callback' => 'absint',
953
  'type' => 'integer',
954
+ 'validate_callback' => 'rest_validate_request_arg',
955
  );
956
  $query_params['offset'] = array(
957
  'description' => __( 'Offset the result set by a specific number of comments.' ),
958
  'type' => 'integer',
959
  'sanitize_callback' => 'absint',
960
+ 'validate_callback' => 'rest_validate_request_arg',
961
  );
962
  $query_params['order'] = array(
963
  'description' => __( 'Order sort attribute ascending or descending.' ),
964
  'type' => 'string',
965
  'sanitize_callback' => 'sanitize_key',
966
+ 'validate_callback' => 'rest_validate_request_arg',
967
  'default' => 'asc',
968
  'enum' => array(
969
  'asc',
974
  'description' => __( 'Sort collection by object attribute.' ),
975
  'type' => 'string',
976
  'sanitize_callback' => 'sanitize_key',
977
+ 'validate_callback' => 'rest_validate_request_arg',
978
  'default' => 'date_gmt',
979
  'enum' => array(
980
  'date',
987
  ),
988
  );
989
  $query_params['parent'] = array(
990
+ 'default' => array(),
991
+ 'description' => __( 'Limit result set to resources of specific parent ids.' ),
992
+ 'sanitize_callback' => 'wp_parse_id_list',
993
+ 'type' => 'array',
994
+ 'validate_callback' => 'rest_validate_request_arg',
995
  );
996
+ $query_params['parent_exclude'] = array(
997
+ 'default' => array(),
998
+ 'description' => __( 'Ensure result set excludes specific parent ids.' ),
999
+ 'sanitize_callback' => 'wp_parse_id_list',
1000
+ 'type' => 'array',
1001
+ 'validate_callback' => 'rest_validate_request_arg',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1002
  );
1003
+ $query_params['post'] = array(
1004
+ 'default' => array(),
1005
+ 'description' => __( 'Limit result set to resources assigned to specific post ids.' ),
1006
+ 'type' => 'array',
1007
+ 'sanitize_callback' => 'wp_parse_id_list',
1008
+ 'validate_callback' => 'rest_validate_request_arg',
1009
  );
1010
  $query_params['status'] = array(
1011
  'default' => 'approve',
1012
+ 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
1013
  'sanitize_callback' => 'sanitize_key',
1014
  'type' => 'string',
1015
+ 'validate_callback' => 'rest_validate_request_arg',
1016
  );
1017
  $query_params['type'] = array(
1018
  'default' => 'comment',
1019
+ 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
1020
  'sanitize_callback' => 'sanitize_key',
1021
  'type' => 'string',
1022
+ 'validate_callback' => 'rest_validate_request_arg',
 
 
 
 
1023
  );
1024
  return $query_params;
1025
  }
1098
  return false;
1099
  }
1100
 
1101
+ if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
1102
+ return false;
1103
+ }
1104
+
1105
+ $post = get_post( $comment->comment_post_ID );
1106
+ if ( $comment->comment_post_ID && $post ) {
1107
+ if ( ! $this->check_read_post_permission( $post ) ) {
1108
+ return false;
1109
+ }
1110
+ }
1111
+
1112
  if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1113
  return true;
1114
  }
lib/endpoints/class-wp-rest-controller.php CHANGED
@@ -3,6 +3,20 @@
3
 
4
  abstract class WP_REST_Controller {
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  /**
7
  * Register the routes for the objects of the controller.
8
  */
@@ -225,17 +239,23 @@ abstract class WP_REST_Controller {
225
  'type' => 'integer',
226
  'default' => 1,
227
  'sanitize_callback' => 'absint',
 
 
228
  ),
229
  'per_page' => array(
230
  'description' => __( 'Maximum number of items to be returned in result set.' ),
231
  'type' => 'integer',
232
  'default' => 10,
 
 
233
  'sanitize_callback' => 'absint',
 
234
  ),
235
  'search' => array(
236
  'description' => __( 'Limit results to those matching a string.' ),
237
  'type' => 'string',
238
  'sanitize_callback' => 'sanitize_text_field',
 
239
  ),
240
  );
241
  }
@@ -252,6 +272,8 @@ abstract class WP_REST_Controller {
252
  $param_details = array(
253
  'description' => __( 'Scope under which the request is made; determines fields present in response.' ),
254
  'type' => 'string',
 
 
255
  );
256
  $schema = $this->get_item_schema();
257
  if ( empty( $schema['properties'] ) ) {
@@ -412,8 +434,8 @@ abstract class WP_REST_Controller {
412
  }
413
 
414
  $endpoint_args[ $field_id ] = array(
415
- 'validate_callback' => array( $this, 'validate_schema_property' ),
416
- 'sanitize_callback' => array( $this, 'sanitize_schema_property' ),
417
  );
418
 
419
  if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
@@ -424,6 +446,12 @@ abstract class WP_REST_Controller {
424
  $endpoint_args[ $field_id ]['required'] = true;
425
  }
426
 
 
 
 
 
 
 
427
  // Merge in any options provided by the schema property.
428
  if ( isset( $params['arg_options'] ) ) {
429
 
@@ -439,106 +467,4 @@ abstract class WP_REST_Controller {
439
  return $endpoint_args;
440
  }
441
 
442
- /**
443
- * Validate a parameter value that's based on a property from the item schema.
444
- *
445
- * @param mixed $value
446
- * @param WP_REST_Request $request
447
- * @param string $parameter
448
- * @return WP_Error|boolean
449
- */
450
- public function validate_schema_property( $value, $request, $parameter ) {
451
-
452
- /**
453
- * We don't currently validate against empty values, as lots of checks
454
- * can unintentionally fail, as the callback will often handle an empty
455
- * value it's self.
456
- */
457
- if ( ! $value ) {
458
- return true;
459
- }
460
-
461
- $schema = $this->get_item_schema();
462
-
463
- if ( ! isset( $schema['properties'][ $parameter ] ) ) {
464
- return true;
465
- }
466
-
467
- $property = $schema['properties'][ $parameter ];
468
-
469
- if ( ! empty( $property['enum'] ) ) {
470
- if ( ! in_array( $value, $property['enum'] ) ) {
471
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $parameter, implode( ', ', $property['enum'] ) ) );
472
- }
473
- }
474
-
475
- if ( 'integer' === $property['type'] && ! is_numeric( $value ) ) {
476
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'integer' ) );
477
- }
478
-
479
- if ( 'string' === $property['type'] && ! is_string( $value ) ) {
480
- return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'string' ) );
481
- }
482
-
483
- if ( isset( $property['format'] ) ) {
484
- switch ( $property['format'] ) {
485
- case 'date-time' :
486
- if ( ! rest_parse_date( $value ) ) {
487
- return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
488
- }
489
- break;
490
-
491
- case 'email' :
492
- if ( ! is_email( $value ) ) {
493
- return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
494
- }
495
- break;
496
- }
497
- }
498
-
499
- return true;
500
- }
501
-
502
- /**
503
- * Sanitize a parameter value that's based on a property from the item schema.
504
- *
505
- * @param mixed $value
506
- * @param WP_REST_Request $request
507
- * @param string $parameter
508
- * @return mixed
509
- */
510
- public function sanitize_schema_property( $value, $request, $parameter ) {
511
-
512
- $schema = $this->get_item_schema();
513
-
514
- if ( ! isset( $schema['properties'][ $parameter ] ) ) {
515
- return true;
516
- }
517
-
518
- $property = $schema['properties'][ $parameter ];
519
-
520
- if ( 'integer' === $property['type'] ) {
521
- return (int) $value;
522
- }
523
-
524
- if ( isset( $property['format'] ) ) {
525
- switch ( $property['format'] ) {
526
- case 'date-time' :
527
- return sanitize_text_field( $value );
528
-
529
- case 'email' :
530
- // as sanitize_email is very lossy, we just want to
531
- // make sure the string is safe.
532
- if ( sanitize_email( $value ) ) {
533
- return sanitize_email( $value );
534
- }
535
- return sanitize_text_field( $value );
536
-
537
- case 'uri' :
538
- return esc_url_raw( $value );
539
- }
540
- }
541
-
542
- return $value;
543
- }
544
  }
3
 
4
  abstract class WP_REST_Controller {
5
 
6
+ /**
7
+ * The namespace of this controller's route.
8
+ *
9
+ * @var string
10
+ */
11
+ protected $namespace;
12
+
13
+ /**
14
+ * The base of this controller's route.
15
+ *
16
+ * @var string
17
+ */
18
+ protected $rest_base;
19
+
20
  /**
21
  * Register the routes for the objects of the controller.
22
  */
239
  'type' => 'integer',
240
  'default' => 1,
241
  'sanitize_callback' => 'absint',
242
+ 'validate_callback' => 'rest_validate_request_arg',
243
+ 'minimum' => 1,
244
  ),
245
  'per_page' => array(
246
  'description' => __( 'Maximum number of items to be returned in result set.' ),
247
  'type' => 'integer',
248
  'default' => 10,
249
+ 'minimum' => 1,
250
+ 'maximum' => 100,
251
  'sanitize_callback' => 'absint',
252
+ 'validate_callback' => 'rest_validate_request_arg',
253
  ),
254
  'search' => array(
255
  'description' => __( 'Limit results to those matching a string.' ),
256
  'type' => 'string',
257
  'sanitize_callback' => 'sanitize_text_field',
258
+ 'validate_callback' => 'rest_validate_request_arg',
259
  ),
260
  );
261
  }
272
  $param_details = array(
273
  'description' => __( 'Scope under which the request is made; determines fields present in response.' ),
274
  'type' => 'string',
275
+ 'sanitize_callback' => 'sanitize_key',
276
+ 'validate_callback' => 'rest_validate_request_arg',
277
  );
278
  $schema = $this->get_item_schema();
279
  if ( empty( $schema['properties'] ) ) {
434
  }
435
 
436
  $endpoint_args[ $field_id ] = array(
437
+ 'validate_callback' => 'rest_validate_request_arg',
438
+ 'sanitize_callback' => 'rest_sanitize_request_arg',
439
  );
440
 
441
  if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
446
  $endpoint_args[ $field_id ]['required'] = true;
447
  }
448
 
449
+ foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
450
+ if ( isset( $params[ $schema_prop ] ) ) {
451
+ $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
452
+ }
453
+ }
454
+
455
  // Merge in any options provided by the schema property.
456
  if ( isset( $params['arg_options'] ) ) {
457
 
467
  return $endpoint_args;
468
  }
469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  }
lib/endpoints/class-wp-rest-meta-controller.php DELETED
@@ -1,468 +0,0 @@
1
- <?php
2
- /**
3
- * Metadata base class.
4
- */
5
- abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
6
- /**
7
- * Associated object type.
8
- *
9
- * @var string Type slug ("post", "user", or "comment")
10
- */
11
- protected $parent_type = null;
12
-
13
- /**
14
- * Base path for parent meta type endpoints.
15
- *
16
- * @var string
17
- */
18
- protected $parent_base = null;
19
-
20
- /**
21
- * Construct the API handler object.
22
- */
23
- public function __construct() {
24
- if ( empty( $this->parent_type ) ) {
25
- _doing_it_wrong( 'WP_REST_Meta_Controller::__construct', __( 'The object type must be overridden' ), 'WPAPI-2.0' );
26
- return;
27
- }
28
- if ( empty( $this->parent_base ) ) {
29
- _doing_it_wrong( 'WP_REST_Meta_Controller::__construct', __( 'The parent base must be overridden' ), 'WPAPI-2.0' );
30
- return;
31
- }
32
- }
33
-
34
- /**
35
- * Register the meta-related routes.
36
- */
37
- public function register_routes() {
38
- register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta', array(
39
- array(
40
- 'methods' => WP_REST_Server::READABLE,
41
- 'callback' => array( $this, 'get_items' ),
42
- 'permission_callback' => array( $this, 'get_items_permissions_check' ),
43
- 'args' => $this->get_collection_params(),
44
- ),
45
- array(
46
- 'methods' => WP_REST_Server::CREATABLE,
47
- 'callback' => array( $this, 'create_item' ),
48
- 'permission_callback' => array( $this, 'create_item_permissions_check' ),
49
- 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
50
- ),
51
-
52
- 'schema' => array( $this, 'get_public_item_schema' ),
53
- ) );
54
- register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta/(?P<id>[\d]+)', array(
55
- array(
56
- 'methods' => WP_REST_Server::READABLE,
57
- 'callback' => array( $this, 'get_item' ),
58
- 'permission_callback' => array( $this, 'get_item_permissions_check' ),
59
- 'args' => array(
60
- 'context' => $this->get_context_param( array( 'default' => 'edit' ) ),
61
- ),
62
- ),
63
- array(
64
- 'methods' => WP_REST_Server::EDITABLE,
65
- 'callback' => array( $this, 'update_item' ),
66
- 'permission_callback' => array( $this, 'update_item_permissions_check' ),
67
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
68
- ),
69
- array(
70
- 'methods' => WP_REST_Server::DELETABLE,
71
- 'callback' => array( $this, 'delete_item' ),
72
- 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
73
- 'args' => array(
74
- 'force' => array(
75
- 'default' => false,
76
- 'description' => __( 'Required to be true, as resource does not support trashing.' ),
77
- ),
78
- ),
79
- ),
80
-
81
- 'schema' => array( $this, 'get_public_item_schema' ),
82
- ) );
83
- }
84
-
85
- /**
86
- * Get the meta schema, conforming to JSON Schema
87
- *
88
- * @return array
89
- */
90
- public function get_item_schema() {
91
- $schema = array(
92
- '$schema' => 'http://json-schema.org/draft-04/schema#',
93
- 'title' => 'meta',
94
- 'type' => 'object',
95
- /*
96
- * Base properties for every Post
97
- */
98
- 'properties' => array(
99
- 'id' => array(
100
- 'description' => __( 'Unique identifier for the object.' ),
101
- 'type' => 'integer',
102
- 'context' => array( 'edit' ),
103
- 'readonly' => true,
104
- ),
105
- 'key' => array(
106
- 'description' => __( 'The key for the custom field.' ),
107
- 'type' => 'string',
108
- 'context' => array( 'edit' ),
109
- 'required' => true,
110
- 'arg_options' => array(
111
- 'sanitize_callback' => 'sanitize_text_field',
112
- ),
113
- ),
114
- 'value' => array(
115
- 'description' => __( 'The value of the custom field.' ),
116
- 'type' => 'string',
117
- 'context' => array( 'edit' ),
118
- ),
119
- ),
120
- );
121
- return $schema;
122
- }
123
-
124
- /**
125
- * Get the query params for collections
126
- *
127
- * @return array
128
- */
129
- public function get_collection_params() {
130
- $params = parent::get_collection_params();
131
- $new_params = array();
132
- $new_params['context'] = $params['context'];
133
- $new_params['context']['default'] = 'edit';
134
- return $new_params;
135
- }
136
-
137
- /**
138
- * Get the meta ID column for the relevant table.
139
- *
140
- * @return string
141
- */
142
- protected function get_id_column() {
143
- return ( 'user' === $this->parent_type ) ? 'umeta_id' : 'meta_id';
144
- }
145
-
146
- /**
147
- * Get the object (parent) ID column for the relevant table.
148
- *
149
- * @return string
150
- */
151
- protected function get_parent_column() {
152
- return ( 'user' === $this->parent_type ) ? 'user_id' : 'post_id';
153
- }
154
-
155
- /**
156
- * Retrieve custom fields for object.
157
- *
158
- * @param WP_REST_Request $request
159
- * @return WP_REST_Request|WP_Error List of meta object data on success, WP_Error otherwise
160
- */
161
- public function get_items( $request ) {
162
- $parent_id = (int) $request['parent_id'];
163
-
164
- global $wpdb;
165
- $table = _get_meta_table( $this->parent_type );
166
- $parent_column = $this->get_parent_column();
167
- $id_column = $this->get_id_column();
168
-
169
- // @codingStandardsIgnoreStart
170
- $results = $wpdb->get_results( $wpdb->prepare( "SELECT $id_column, $parent_column, meta_key, meta_value FROM $table WHERE $parent_column = %d", $parent_id ) );
171
- // @codingStandardsIgnoreEnd
172
-
173
- $meta = array();
174
-
175
- foreach ( $results as $row ) {
176
- $value = $this->prepare_item_for_response( $row, $request, true );
177
-
178
- if ( is_wp_error( $value ) ) {
179
- continue;
180
- }
181
-
182
- $meta[] = $this->prepare_response_for_collection( $value );
183
- }
184
-
185
- return rest_ensure_response( $meta );
186
- }
187
-
188
- /**
189
- * Retrieve custom field object.
190
- *
191
- * @param WP_REST_Request $request
192
- * @return WP_REST_Request|WP_Error Meta object data on success, WP_Error otherwise
193
- */
194
- public function get_item( $request ) {
195
- $parent_id = (int) $request['parent_id'];
196
- $mid = (int) $request['id'];
197
-
198
- $parent_column = $this->get_parent_column();
199
- $meta = get_metadata_by_mid( $this->parent_type, $mid );
200
-
201
- if ( empty( $meta ) ) {
202
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
203
- }
204
-
205
- if ( absint( $meta->$parent_column ) !== $parent_id ) {
206
- return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
207
- }
208
-
209
- return $this->prepare_item_for_response( $meta, $request );
210
- }
211
-
212
- /**
213
- * Prepares meta data for return as an object.
214
- *
215
- * @param stdClass $data Metadata row from database
216
- * @param WP_REST_Request $request
217
- * @param boolean $is_raw Is the value field still serialized? (False indicates the value has been unserialized)
218
- * @return WP_REST_Response|WP_Error Meta object data on success, WP_Error otherwise
219
- */
220
- public function prepare_item_for_response( $data, $request, $is_raw = false ) {
221
- $id_column = $this->get_id_column();
222
- $id = $data->$id_column;
223
- $key = $data->meta_key;
224
- $value = $data->meta_value;
225
-
226
- // Don't expose protected fields.
227
- if ( is_protected_meta( $key ) ) {
228
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $key ), array( 'status' => 403 ) );
229
- }
230
-
231
- // Normalize serialized strings
232
- if ( $is_raw && is_serialized_string( $value ) ) {
233
- $value = unserialize( $value );
234
- }
235
-
236
- // Don't expose serialized data
237
- if ( is_serialized( $value ) || ! is_string( $value ) ) {
238
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s contains serialized data.' ), $key ), array( 'status' => 403 ) );
239
- }
240
-
241
- $meta = array(
242
- 'id' => (int) $id,
243
- 'key' => $key,
244
- 'value' => $value,
245
- );
246
-
247
- $response = rest_ensure_response( $meta );
248
- $parent_column = $this->get_parent_column();
249
- $response->add_link( 'about', rest_url( 'wp/' . $this->parent_base . '/' . $data->$parent_column ), array( 'embeddable' => true ) );
250
-
251
- /**
252
- * Filter a meta value returned from the API.
253
- *
254
- * Allows modification of the meta value right before it is returned.
255
- *
256
- * @param array $response Key value array of meta data: id, key, value.
257
- * @param WP_REST_Request $request Request used to generate the response.
258
- */
259
- return apply_filters( 'rest_prepare_meta_value', $response, $request );
260
- }
261
-
262
- /**
263
- * Add meta to an object.
264
- *
265
- * @param WP_REST_Request $request
266
- * @return WP_REST_Response|WP_Error
267
- */
268
- public function update_item( $request ) {
269
- $parent_id = (int) $request['parent_id'];
270
- $mid = (int) $request['id'];
271
-
272
- $parent_column = $this->get_parent_column();
273
- $current = get_metadata_by_mid( $this->parent_type, $mid );
274
-
275
- if ( empty( $current ) ) {
276
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
277
- }
278
-
279
- if ( absint( $current->$parent_column ) !== $parent_id ) {
280
- return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
281
- }
282
-
283
- if ( ! isset( $request['key'] ) && ! isset( $request['value'] ) ) {
284
- return new WP_Error( 'rest_meta_data_invalid', __( 'Invalid meta parameters.' ), array( 'status' => 400 ) );
285
- }
286
- if ( isset( $request['key'] ) ) {
287
- $key = $request['key'];
288
- } else {
289
- $key = $current->meta_key;
290
- }
291
-
292
- if ( isset( $request['value'] ) ) {
293
- $value = $request['value'];
294
- } else {
295
- $value = $current->meta_value;
296
- }
297
-
298
- if ( ! $key ) {
299
- return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
300
- }
301
-
302
- // for now let's not allow updating of arrays, objects or serialized values.
303
- if ( ! $this->is_valid_meta_data( $current->meta_value ) ) {
304
- $code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
305
- return new WP_Error( $code, __( 'Invalid existing meta data for action.' ), array( 'status' => 400 ) );
306
- }
307
-
308
- if ( ! $this->is_valid_meta_data( $value ) ) {
309
- $code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
310
- return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
311
- }
312
-
313
- if ( is_protected_meta( $current->meta_key ) ) {
314
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $current->meta_key ), array( 'status' => 403 ) );
315
- }
316
-
317
- if ( is_protected_meta( $key ) ) {
318
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $key ), array( 'status' => 403 ) );
319
- }
320
-
321
- // update_metadata_by_mid will return false if these are equal, so check
322
- // first and pass through
323
- if ( (string) $value === $current->meta_value && (string) $key === $current->meta_key ) {
324
- return $this->get_item( $request );
325
- }
326
-
327
- if ( ! update_metadata_by_mid( $this->parent_type, $mid, $value, $key ) ) {
328
- return new WP_Error( 'rest_meta_could_not_update', __( 'Could not update meta.' ), array( 'status' => 500 ) );
329
- }
330
-
331
- $request = new WP_REST_Request( 'GET' );
332
- $request->set_query_params( array(
333
- 'context' => 'edit',
334
- 'parent_id' => $parent_id,
335
- 'id' => $mid,
336
- ) );
337
- $response = $this->get_item( $request );
338
-
339
- /**
340
- * Fires after meta is added to an object or updated via the REST API.
341
- *
342
- * @param array $value The inserted meta data.
343
- * @param WP_REST_Request $request The request sent to the API.
344
- * @param boolean $creating True when adding meta, false when updating.
345
- */
346
- do_action( 'rest_insert_meta', $value, $request, false );
347
-
348
- return rest_ensure_response( $response );
349
- }
350
-
351
- /**
352
- * Check if the data provided is valid data.
353
- *
354
- * Excludes serialized data from being sent via the API.
355
- *
356
- * @see https://github.com/WP-API/WP-API/pull/68
357
- * @param mixed $data Data to be checked
358
- * @return boolean Whether the data is valid or not
359
- */
360
- protected function is_valid_meta_data( $data ) {
361
- if ( is_array( $data ) || is_object( $data ) || is_serialized( $data ) ) {
362
- return false;
363
- }
364
-
365
- return true;
366
- }
367
-
368
- /**
369
- * Add meta to an object.
370
- *
371
- * @param WP_REST_Request $request
372
- * @return WP_REST_Response|WP_Error
373
- */
374
- public function create_item( $request ) {
375
- $parent_id = (int) $request['parent_id'];
376
-
377
- if ( ! $this->is_valid_meta_data( $request['value'] ) ) {
378
- $code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
379
-
380
- // for now let's not allow updating of arrays, objects or serialized values.
381
- return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
382
- }
383
-
384
- if ( empty( $request['key'] ) ) {
385
- return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
386
- }
387
-
388
- if ( is_protected_meta( $request['key'] ) ) {
389
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $request['key'] ), array( 'status' => 403 ) );
390
- }
391
-
392
- $meta_key = wp_slash( $request['key'] );
393
- $value = wp_slash( $request['value'] );
394
-
395
- $mid = add_metadata( $this->parent_type, $parent_id, $meta_key, $value );
396
- if ( ! $mid ) {
397
- return new WP_Error( 'rest_meta_could_not_add', __( 'Could not add meta.' ), array( 'status' => 400 ) );
398
- }
399
-
400
- $request = new WP_REST_Request( 'GET' );
401
- $request->set_query_params( array(
402
- 'context' => 'edit',
403
- 'parent_id' => $parent_id,
404
- 'id' => $mid,
405
- ) );
406
- $response = rest_ensure_response( $this->get_item( $request ) );
407
-
408
- $response->set_status( 201 );
409
- $data = $response->get_data();
410
- $response->header( 'Location', rest_url( 'wp/v2' . '/' . $this->parent_base . '/' . $parent_id . '/meta/' . $data['id'] ) );
411
-
412
- /* This action is documented in lib/endpoints/class-wp-rest-meta-controller.php */
413
- do_action( 'rest_insert_meta', $data, $request, true );
414
-
415
- return $response;
416
- }
417
-
418
- /**
419
- * Delete meta from an object.
420
- *
421
- * @param WP_REST_Request $request
422
- * @return WP_REST_Response|WP_Error Message on success, WP_Error otherwise
423
- */
424
- public function delete_item( $request ) {
425
- $parent_id = (int) $request['parent_id'];
426
- $mid = (int) $request['id'];
427
- $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
428
-
429
- // We don't support trashing for this type, error out
430
- if ( ! $force ) {
431
- return new WP_Error( 'rest_trash_not_supported', __( 'Meta does not support trashing.' ), array( 'status' => 501 ) );
432
- }
433
-
434
- $parent_column = $this->get_parent_column();
435
- $current = get_metadata_by_mid( $this->parent_type, $mid );
436
-
437
- if ( empty( $current ) ) {
438
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
439
- }
440
-
441
- if ( absint( $current->$parent_column ) !== (int) $parent_id ) {
442
- return new WP_Error( 'rest_meta_' . $this->parent_type . '_mismatch', __( 'Meta does not belong to this object' ), array( 'status' => 400 ) );
443
- }
444
-
445
- // for now let's not allow updating of arrays, objects or serialized values.
446
- if ( ! $this->is_valid_meta_data( $current->meta_value ) ) {
447
- $code = ( $this->parent_type === 'post' ) ? 'rest_post_invalid_action' : 'rest_meta_invalid_action';
448
- return new WP_Error( $code, __( 'Invalid existing meta data for action.' ), array( 'status' => 400 ) );
449
- }
450
-
451
- if ( is_protected_meta( $current->meta_key ) ) {
452
- return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $current->meta_key ), array( 'status' => 403 ) );
453
- }
454
-
455
- if ( ! delete_metadata_by_mid( $this->parent_type, $mid ) ) {
456
- return new WP_Error( 'rest_meta_could_not_delete', __( 'Could not delete meta.' ), array( 'status' => 500 ) );
457
- }
458
-
459
- /**
460
- * Fires after a meta value is deleted via the REST API.
461
- *
462
- * @param WP_REST_Request $request The request sent to the API.
463
- */
464
- do_action( 'rest_delete_meta', $request );
465
-
466
- return rest_ensure_response( array( 'message' => __( 'Deleted meta' ) ) );
467
- }
468
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/endpoints/class-wp-rest-meta-posts-controller.php DELETED
@@ -1,115 +0,0 @@
1
- <?php
2
-
3
- class WP_REST_Meta_Posts_Controller extends WP_REST_Meta_Controller {
4
- /**
5
- * Associated object type.
6
- *
7
- * @var string Type slug ("post" or "user")
8
- */
9
- protected $parent_type = 'post';
10
-
11
- /**
12
- * Associated post type name.
13
- *
14
- * @var string
15
- */
16
- protected $parent_post_type;
17
-
18
- /**
19
- * Associated post type controller class object.
20
- *
21
- * @var WP_REST_Posts_Controller
22
- */
23
- protected $parent_controller;
24
-
25
- /**
26
- * Base path for post type endpoints.
27
- *
28
- * @var string
29
- */
30
- protected $parent_base;
31
-
32
- public function __construct( $parent_post_type ) {
33
- $this->parent_post_type = $parent_post_type;
34
- $this->parent_controller = new WP_REST_Posts_Controller( $this->parent_post_type );
35
- $this->parent_base = $this->parent_controller->get_post_type_base( $this->parent_post_type );
36
- }
37
-
38
- /**
39
- * Check if a given request has access to get meta for a post.
40
- *
41
- * @param WP_REST_Request $request Full data about the request.
42
- * @return WP_Error|boolean
43
- */
44
- public function get_items_permissions_check( $request ) {
45
- $parent = get_post( (int) $request['parent_id'] );
46
-
47
- if ( empty( $parent ) || empty( $parent->ID ) ) {
48
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
49
- }
50
-
51
- if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
52
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
53
- }
54
-
55
- $post_type = get_post_type_object( $parent->post_type );
56
- if ( ! current_user_can( $post_type->cap->edit_post, $parent->ID ) ) {
57
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view the meta for this post.' ), array( 'status' => rest_authorization_required_code() ) );
58
- }
59
- return true;
60
- }
61
-
62
- /**
63
- * Check if a given request has access to get a specific meta entry for a post.
64
- *
65
- * @param WP_REST_Request $request Full data about the request.
66
- * @return WP_Error|boolean
67
- */
68
- public function get_item_permissions_check( $request ) {
69
- return $this->get_items_permissions_check( $request );
70
- }
71
-
72
- /**
73
- * Check if a given request has access to create a meta entry for a post.
74
- *
75
- * @param WP_REST_Request $request Full data about the request.
76
- * @return WP_Error|boolean
77
- */
78
- public function create_item_permissions_check( $request ) {
79
- return $this->get_items_permissions_check( $request );
80
- }
81
-
82
- /**
83
- * Check if a given request has access to update a meta entry for a post.
84
- *
85
- * @param WP_REST_Request $request Full data about the request.
86
- * @return WP_Error|boolean
87
- */
88
- public function update_item_permissions_check( $request ) {
89
- return $this->get_items_permissions_check( $request );
90
- }
91
-
92
- /**
93
- * Check if a given request has access to delete meta for a post.
94
- *
95
- * @param WP_REST_Request $request Full details about the request.
96
- * @return WP_Error|boolean
97
- */
98
- public function delete_item_permissions_check( $request ) {
99
- $parent = get_post( (int) $request['parent_id'] );
100
-
101
- if ( empty( $parent ) || empty( $parent->ID ) ) {
102
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
103
- }
104
-
105
- if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
106
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
107
- }
108
-
109
- $post_type = get_post_type_object( $parent->post_type );
110
- if ( ! current_user_can( $post_type->cap->delete_post, $parent->ID ) ) {
111
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot delete the meta for this post.' ), array( 'status' => rest_authorization_required_code() ) );
112
- }
113
- return true;
114
- }
115
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/endpoints/class-wp-rest-post-statuses-controller.php CHANGED
@@ -2,24 +2,31 @@
2
 
3
  class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
4
 
 
 
 
 
 
5
  /**
6
  * Register the routes for the objects of the controller.
7
  */
8
  public function register_routes() {
9
 
10
- register_rest_route( 'wp/v2', '/statuses', array(
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
 
14
  'args' => $this->get_collection_params(),
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
18
 
19
- register_rest_route( 'wp/v2', '/statuses/(?P<status>[\w-]+)', array(
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
 
23
  'args' => array(
24
  'context' => $this->get_context_param( array( 'default' => 'view' ) ),
25
  ),
@@ -28,6 +35,25 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
28
  ) );
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /**
32
  * Get all post statuses, depending on user context
33
  *
@@ -36,21 +62,58 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
36
  */
37
  public function get_items( $request ) {
38
  $data = array();
39
- if ( is_user_logged_in() ) {
40
- $statuses = get_post_stati( array( 'internal' => false ), 'object' );
41
- } else {
42
- $statuses = get_post_stati( array( 'public' => true ), 'object' );
43
- }
44
- foreach ( $statuses as $obj ) {
45
- $status = $this->prepare_item_for_response( $obj, $request );
46
- if ( is_wp_error( $status ) ) {
47
  continue;
48
  }
 
49
  $data[ $obj->name ] = $this->prepare_response_for_collection( $status );
50
  }
51
  return rest_ensure_response( $data );
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  /**
55
  * Get a specific post status
56
  *
@@ -60,7 +123,7 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
60
  public function get_item( $request ) {
61
  $obj = get_post_status_object( $request['status'] );
62
  if ( empty( $obj ) ) {
63
- return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) );
64
  }
65
  $data = $this->prepare_item_for_response( $obj, $request );
66
  return rest_ensure_response( $data );
@@ -74,9 +137,6 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
74
  * @return WP_REST_Response Post status data
75
  */
76
  public function prepare_item_for_response( $status, $request ) {
77
- if ( ( false === $status->public && ! is_user_logged_in() ) || ( true === $status->internal && is_user_logged_in() ) ) {
78
- return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => rest_authorization_required_code() ) );
79
- }
80
 
81
  $data = array(
82
  'name' => $status->label,
@@ -94,12 +154,10 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
94
 
95
  $response = rest_ensure_response( $data );
96
 
97
- $posts_controller = new WP_REST_Posts_Controller( 'post' );
98
-
99
  if ( 'publish' === $status->name ) {
100
- $response->add_link( 'archives', rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) );
101
  } else {
102
- $response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) ) );
103
  }
104
 
105
  /**
@@ -126,42 +184,49 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
126
  'type' => 'object',
127
  'properties' => array(
128
  'name' => array(
129
- 'description' => __( 'The title for the status.' ),
130
  'type' => 'string',
131
- 'context' => array( 'view' ),
132
- ),
 
133
  'private' => array(
134
- 'description' => __( 'Whether posts with this status should be private.' ),
135
  'type' => 'boolean',
136
- 'context' => array( 'view' ),
137
- ),
 
138
  'protected' => array(
139
- 'description' => __( 'Whether posts with this status should be protected.' ),
140
  'type' => 'boolean',
141
- 'context' => array( 'view' ),
142
- ),
 
143
  'public' => array(
144
- 'description' => __( 'Whether posts of this status should be shown in the front end of the site.' ),
145
  'type' => 'boolean',
146
- 'context' => array( 'view' ),
147
- ),
 
148
  'queryable' => array(
149
- 'description' => __( 'Whether posts with this status should be publicly-queryable.' ),
150
  'type' => 'boolean',
151
- 'context' => array( 'view' ),
152
- ),
 
153
  'show_in_list' => array(
154
  'description' => __( 'Whether to include posts in the edit listing for their post type.' ),
155
  'type' => 'boolean',
156
- 'context' => array( 'view' ),
157
- ),
 
158
  'slug' => array(
159
- 'description' => __( 'An alphanumeric identifier for the status.' ),
160
  'type' => 'string',
161
- 'context' => array( 'view' ),
162
- ),
163
  ),
164
- );
 
165
  return $this->add_additional_fields_schema( $schema );
166
  }
167
 
2
 
3
  class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
4
 
5
+ public function __construct() {
6
+ $this->namespace = 'wp/v2';
7
+ $this->rest_base = 'statuses';
8
+ }
9
+
10
  /**
11
  * Register the routes for the objects of the controller.
12
  */
13
  public function register_routes() {
14
 
15
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
16
  array(
17
  'methods' => WP_REST_Server::READABLE,
18
  'callback' => array( $this, 'get_items' ),
19
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20
  'args' => $this->get_collection_params(),
21
  ),
22
  'schema' => array( $this, 'get_public_item_schema' ),
23
  ) );
24
 
25
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<status>[\w-]+)', array(
26
  array(
27
  'methods' => WP_REST_Server::READABLE,
28
  'callback' => array( $this, 'get_item' ),
29
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
30
  'args' => array(
31
  'context' => $this->get_context_param( array( 'default' => 'view' ) ),
32
  ),
35
  ) );
36
  }
37
 
38
+ /**
39
+ * Check whether a given request has permission to read post statuses.
40
+ *
41
+ * @param WP_REST_Request $request Full details about the request.
42
+ * @return WP_Error|boolean
43
+ */
44
+ public function get_items_permissions_check( $request ) {
45
+ if ( 'edit' === $request['context'] ) {
46
+ $types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
47
+ foreach ( $types as $type ) {
48
+ if ( current_user_can( $type->cap->edit_posts ) ) {
49
+ return true;
50
+ }
51
+ }
52
+ return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
53
+ }
54
+ return true;
55
+ }
56
+
57
  /**
58
  * Get all post statuses, depending on user context
59
  *
62
  */
63
  public function get_items( $request ) {
64
  $data = array();
65
+ $statuses = get_post_stati( array( 'internal' => false ), 'object' );
66
+ $statuses['trash'] = get_post_status_object( 'trash' );
67
+ foreach ( $statuses as $slug => $obj ) {
68
+ $ret = $this->check_read_permission( $obj );
69
+ if ( ! $ret ) {
 
 
 
70
  continue;
71
  }
72
+ $status = $this->prepare_item_for_response( $obj, $request );
73
  $data[ $obj->name ] = $this->prepare_response_for_collection( $status );
74
  }
75
  return rest_ensure_response( $data );
76
  }
77
 
78
+ /**
79
+ * Check if a given request has access to read a post status.
80
+ *
81
+ * @param WP_REST_Request $request Full details about the request.
82
+ * @return WP_Error|boolean
83
+ */
84
+ public function get_item_permissions_check( $request ) {
85
+ $status = get_post_status_object( $request['status'] );
86
+ if ( empty( $status ) ) {
87
+ return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
88
+ }
89
+ $check = $this->check_read_permission( $status );
90
+ if ( ! $check ) {
91
+ return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) );
92
+ }
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Check whether a given post status should be visible
98
+ *
99
+ * @param object $status
100
+ * @return boolean
101
+ */
102
+ protected function check_read_permission( $status ) {
103
+ if ( true === $status->public ) {
104
+ return true;
105
+ }
106
+ if ( false === $status->internal || 'trash' === $status->name ) {
107
+ $types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
108
+ foreach ( $types as $type ) {
109
+ if ( current_user_can( $type->cap->edit_posts ) ) {
110
+ return true;
111
+ }
112
+ }
113
+ }
114
+ return false;
115
+ }
116
+
117
  /**
118
  * Get a specific post status
119
  *
123
  public function get_item( $request ) {
124
  $obj = get_post_status_object( $request['status'] );
125
  if ( empty( $obj ) ) {
126
+ return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
127
  }
128
  $data = $this->prepare_item_for_response( $obj, $request );
129
  return rest_ensure_response( $data );
137
  * @return WP_REST_Response Post status data
138
  */
139
  public function prepare_item_for_response( $status, $request ) {
 
 
 
140
 
141
  $data = array(
142
  'name' => $status->label,
154
 
155
  $response = rest_ensure_response( $data );
156
 
 
 
157
  if ( 'publish' === $status->name ) {
158
+ $response->add_link( 'archives', rest_url( '/wp/v2/posts' ) );
159
  } else {
160
+ $response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( '/wp/v2/posts' ) ) );
161
  }
162
 
163
  /**
184
  'type' => 'object',
185
  'properties' => array(
186
  'name' => array(
187
+ 'description' => __( 'The title for the resource.' ),
188
  'type' => 'string',
189
+ 'context' => array( 'embed', 'view', 'edit' ),
190
+ 'readonly' => true,
191
+ ),
192
  'private' => array(
193
+ 'description' => __( 'Whether posts with this resource should be private.' ),
194
  'type' => 'boolean',
195
+ 'context' => array( 'edit' ),
196
+ 'readonly' => true,
197
+ ),
198
  'protected' => array(
199
+ 'description' => __( 'Whether posts with this resource should be protected.' ),
200
  'type' => 'boolean',
201
+ 'context' => array( 'edit' ),
202
+ 'readonly' => true,
203
+ ),
204
  'public' => array(
205
+ 'description' => __( 'Whether posts of this resource should be shown in the front end of the site.' ),
206
  'type' => 'boolean',
207
+ 'context' => array( 'view', 'edit' ),
208
+ 'readonly' => true,
209
+ ),
210
  'queryable' => array(
211
+ 'description' => __( 'Whether posts with this resource should be publicly-queryable.' ),
212
  'type' => 'boolean',
213
+ 'context' => array( 'view', 'edit' ),
214
+ 'readonly' => true,
215
+ ),
216
  'show_in_list' => array(
217
  'description' => __( 'Whether to include posts in the edit listing for their post type.' ),
218
  'type' => 'boolean',
219
+ 'context' => array( 'edit' ),
220
+ 'readonly' => true,
221
+ ),
222
  'slug' => array(
223
+ 'description' => __( 'An alphanumeric identifier for the resource.' ),
224
  'type' => 'string',
225
+ 'context' => array( 'embed', 'view', 'edit' ),
226
+ 'readonly' => true,
227
  ),
228
+ ),
229
+ );
230
  return $this->add_additional_fields_schema( $schema );
231
  }
232
 
lib/endpoints/class-wp-rest-post-types-controller.php CHANGED
@@ -2,21 +2,27 @@
2
 
3
  class WP_REST_Post_Types_Controller extends WP_REST_Controller {
4
 
 
 
 
 
 
5
  /**
6
  * Register the routes for the objects of the controller.
7
  */
8
  public function register_routes() {
9
 
10
- register_rest_route( 'wp/v2', '/types', array(
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
 
14
  'args' => $this->get_collection_params(),
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
18
 
19
- register_rest_route( 'wp/v2', '/types/(?P<type>[\w-]+)', array(
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
@@ -28,6 +34,24 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
28
  ) );
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /**
32
  * Get all public post types
33
  *
@@ -55,13 +79,13 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
55
  public function get_item( $request ) {
56
  $obj = get_post_type_object( $request['type'] );
57
  if ( empty( $obj ) ) {
58
- return new WP_Error( 'rest_type_invalid', __( 'Invalid type.' ), array( 'status' => 404 ) );
59
  }
60
  if ( empty( $obj->show_in_rest ) ) {
61
- return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view type.' ), array( 'status' => rest_authorization_required_code() ) );
62
  }
63
  if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) {
64
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this type.' ), array( 'status' => rest_authorization_required_code() ) );
65
  }
66
  $data = $this->prepare_item_for_response( $obj, $request );
67
  return rest_ensure_response( $data );
@@ -76,6 +100,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
76
  */
77
  public function prepare_item_for_response( $post_type, $request ) {
78
  $data = array(
 
79
  'description' => $post_type->description,
80
  'hierarchical' => $post_type->hierarchical,
81
  'labels' => $post_type->labels,
@@ -92,7 +117,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
92
  $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
93
  $response->add_links( array(
94
  'collection' => array(
95
- 'href' => rest_url( 'wp/v2/types' ),
96
  ),
97
  'https://api.w.org/items' => array(
98
  'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
@@ -122,33 +147,44 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
122
  'title' => 'type',
123
  'type' => 'object',
124
  'properties' => array(
 
 
 
 
 
 
125
  'description' => array(
126
- 'description' => __( 'A human-readable description of the object.' ),
127
  'type' => 'string',
128
  'context' => array( 'view', 'edit' ),
129
- ),
 
130
  'hierarchical' => array(
131
- 'description' => __( 'Whether or not the type should have children.' ),
132
  'type' => 'boolean',
133
  'context' => array( 'view', 'edit' ),
134
- ),
 
135
  'labels' => array(
136
- 'description' => __( 'Human-readable labels for the type for various contexts.' ),
137
  'type' => 'object',
138
  'context' => array( 'edit' ),
139
- ),
 
140
  'name' => array(
141
- 'description' => __( 'The title for the object.' ),
142
  'type' => 'string',
143
- 'context' => array( 'view', 'edit' ),
144
- ),
 
145
  'slug' => array(
146
- 'description' => __( 'An alphanumeric identifier for the object.' ),
147
  'type' => 'string',
148
- 'context' => array( 'view', 'edit' ),
149
- ),
150
  ),
151
- );
 
152
  return $this->add_additional_fields_schema( $schema );
153
  }
154
 
2
 
3
  class WP_REST_Post_Types_Controller extends WP_REST_Controller {
4
 
5
+ public function __construct() {
6
+ $this->namespace = 'wp/v2';
7
+ $this->rest_base = 'types';
8
+ }
9
+
10
  /**
11
  * Register the routes for the objects of the controller.
12
  */
13
  public function register_routes() {
14
 
15
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
16
  array(
17
  'methods' => WP_REST_Server::READABLE,
18
  'callback' => array( $this, 'get_items' ),
19
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20
  'args' => $this->get_collection_params(),
21
  ),
22
  'schema' => array( $this, 'get_public_item_schema' ),
23
  ) );
24
 
25
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<type>[\w-]+)', array(
26
  array(
27
  'methods' => WP_REST_Server::READABLE,
28
  'callback' => array( $this, 'get_item' ),
34
  ) );
35
  }
36
 
37
+ /**
38
+ * Check whether a given request has permission to read types.
39
+ *
40
+ * @param WP_REST_Request $request Full details about the request.
41
+ * @return WP_Error|boolean
42
+ */
43
+ public function get_items_permissions_check( $request ) {
44
+ if ( 'edit' === $request['context'] ) {
45
+ foreach ( get_post_types( array(), 'object' ) as $post_type ) {
46
+ if ( ! empty( $post_type->show_in_rest ) && current_user_can( $post_type->cap->edit_posts ) ) {
47
+ return true;
48
+ }
49
+ }
50
+ return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
51
+ }
52
+ return true;
53
+ }
54
+
55
  /**
56
  * Get all public post types
57
  *
79
  public function get_item( $request ) {
80
  $obj = get_post_type_object( $request['type'] );
81
  if ( empty( $obj ) ) {
82
+ return new WP_Error( 'rest_type_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
83
  }
84
  if ( empty( $obj->show_in_rest ) ) {
85
+ return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) );
86
  }
87
  if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) {
88
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) );
89
  }
90
  $data = $this->prepare_item_for_response( $obj, $request );
91
  return rest_ensure_response( $data );
100
  */
101
  public function prepare_item_for_response( $post_type, $request ) {
102
  $data = array(
103
+ 'capabilities' => $post_type->cap,
104
  'description' => $post_type->description,
105
  'hierarchical' => $post_type->hierarchical,
106
  'labels' => $post_type->labels,
117
  $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
118
  $response->add_links( array(
119
  'collection' => array(
120
+ 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
121
  ),
122
  'https://api.w.org/items' => array(
123
  'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
147
  'title' => 'type',
148
  'type' => 'object',
149
  'properties' => array(
150
+ 'capabilities' => array(
151
+ 'description' => __( 'All capabilities used by the resource.' ),
152
+ 'type' => 'array',
153
+ 'context' => array( 'edit' ),
154
+ 'readonly' => true,
155
+ ),
156
  'description' => array(
157
+ 'description' => __( 'A human-readable description of the resource.' ),
158
  'type' => 'string',
159
  'context' => array( 'view', 'edit' ),
160
+ 'readonly' => true,
161
+ ),
162
  'hierarchical' => array(
163
+ 'description' => __( 'Whether or not the resource should have children.' ),
164
  'type' => 'boolean',
165
  'context' => array( 'view', 'edit' ),
166
+ 'readonly' => true,
167
+ ),
168
  'labels' => array(
169
+ 'description' => __( 'Human-readable labels for the resource for various contexts.' ),
170
  'type' => 'object',
171
  'context' => array( 'edit' ),
172
+ 'readonly' => true,
173
+ ),
174
  'name' => array(
175
+ 'description' => __( 'The title for the resource.' ),
176
  'type' => 'string',
177
+ 'context' => array( 'view', 'edit', 'embed' ),
178
+ 'readonly' => true,
179
+ ),
180
  'slug' => array(
181
+ 'description' => __( 'An alphanumeric identifier for the resource.' ),
182
  'type' => 'string',
183
+ 'context' => array( 'view', 'edit', 'embed' ),
184
+ 'readonly' => true,
185
  ),
186
+ ),
187
+ );
188
  return $this->add_additional_fields_schema( $schema );
189
  }
190
 
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -6,6 +6,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
6
 
7
  public function __construct( $post_type ) {
8
  $this->post_type = $post_type;
 
 
 
9
  }
10
 
11
  /**
@@ -13,9 +16,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
13
  */
14
  public function register_routes() {
15
 
16
- $base = $this->get_post_type_base( $this->post_type );
17
-
18
- register_rest_route( 'wp/v2', '/' . $base, array(
19
  array(
20
  'methods' => WP_REST_Server::READABLE,
21
  'callback' => array( $this, 'get_items' ),
@@ -28,10 +29,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
28
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
29
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
30
  ),
31
-
32
  'schema' => array( $this, 'get_public_item_schema' ),
33
  ) );
34
- register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
35
  array(
36
  'methods' => WP_REST_Server::READABLE,
37
  'callback' => array( $this, 'get_item' ),
@@ -57,7 +57,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
57
  ),
58
  ),
59
  ),
60
-
61
  'schema' => array( $this, 'get_public_item_schema' ),
62
  ) );
63
  }
@@ -87,7 +86,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
87
  */
88
  public function get_items( $request ) {
89
  $args = array();
90
- $args['author'] = $request['author'];
 
 
91
  $args['offset'] = $request['offset'];
92
  $args['order'] = $request['order'];
93
  $args['orderby'] = $request['orderby'];
@@ -121,7 +122,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
121
  * @param WP_REST_Request $request The request used.
122
  */
123
  $args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
124
- $query_args = $this->prepare_items_query( $args );
125
 
126
  $posts_query = new WP_Query();
127
  $query_result = $posts_query->query( $query_args );
@@ -159,7 +160,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
159
  unset( $request_params['filter']['posts_per_page'] );
160
  unset( $request_params['filter']['paged'] );
161
  }
162
- $base = add_query_arg( $request_params, rest_url( '/wp/v2/' . $this->get_post_type_base( $this->post_type ) ) );
163
 
164
  if ( $page > 1 ) {
165
  $prev_page = $page - 1;
@@ -321,7 +322,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
321
  $response = $this->prepare_item_for_response( $post, $request );
322
  $response = rest_ensure_response( $response );
323
  $response->set_status( 201 );
324
- $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $post->post_type ) . '/' . $post_id ) );
325
 
326
  return $response;
327
  }
@@ -481,7 +482,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
481
  // If we're forcing, then delete permanently.
482
  if ( $force ) {
483
  $result = wp_delete_post( $id, true );
484
- $status = 'deleted';
485
  } else {
486
  // If we don't support trashing for this type, error out.
487
  if ( ! $supports_trash ) {
@@ -496,28 +496,20 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
496
  // (Note that internally this falls through to `wp_delete_post` if
497
  // the trash is disabled.)
498
  $result = wp_trash_post( $id );
499
- $status = 'trashed';
500
  }
501
 
502
  if ( ! $result ) {
503
  return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
504
  }
505
 
506
- $data = $response->get_data();
507
- $data = array(
508
- 'data' => $data,
509
- $status => true,
510
- );
511
- $response->set_data( $data );
512
-
513
  /**
514
  * Fires after a single post is deleted or trashed via the REST API.
515
  *
516
- * @param object $post The deleted or trashed post.
517
- * @param array $data The response data.
518
- * @param WP_REST_Request $request The request sent to the API.
519
  */
520
- do_action( "rest_delete_{$this->post_type}", $post, $data, $request );
521
 
522
  return $response;
523
  }
@@ -526,10 +518,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
526
  * Determine the allowed query_vars for a get_items() response and
527
  * prepare for WP_Query.
528
  *
529
- * @param array $prepared_args
530
- * @return array $query_args
 
531
  */
532
- protected function prepare_items_query( $prepared_args = array() ) {
533
 
534
  $valid_vars = array_flip( $this->get_allowed_query_vars() );
535
  $query_args = array();
@@ -594,7 +587,19 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
594
  $valid_vars = array_merge( $valid_vars, $private );
595
  }
596
  // Define our own in addition to WP's normal vars.
597
- $rest_valid = array( 'offset', 'post__in', 'post__not_in', 'posts_per_page', 'ignore_sticky_posts', 'post_parent', 'post_parent__in', 'post_parent__not_in' );
 
 
 
 
 
 
 
 
 
 
 
 
598
  $valid_vars = array_merge( $valid_vars, $rest_valid );
599
 
600
  /**
@@ -1027,22 +1032,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1027
  return current_user_can( $post_type->cap->delete_post, $post->ID );
1028
  }
1029
 
1030
- /**
1031
- * Get the base path for a post type's endpoints.
1032
- *
1033
- * @param object|string $post_type
1034
- * @return string $base
1035
- */
1036
- public function get_post_type_base( $post_type ) {
1037
- if ( ! is_object( $post_type ) ) {
1038
- $post_type = get_post_type_object( $post_type );
1039
- }
1040
-
1041
- $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
1042
-
1043
- return $base;
1044
- }
1045
-
1046
  /**
1047
  * Prepare a single post output for response.
1048
  *
@@ -1154,7 +1143,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1154
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1155
  foreach ( $taxonomies as $taxonomy ) {
1156
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1157
- $data[ $base ] = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
 
1158
  }
1159
 
1160
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
@@ -1186,7 +1176,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1186
  * @return array Links for the given post.
1187
  */
1188
  protected function prepare_links( $post ) {
1189
- $base = '/wp/v2/' . $this->get_post_type_base( $this->post_type );
1190
 
1191
  // Entity meta
1192
  $links = array(
@@ -1452,7 +1442,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1452
  'context' => array( 'edit' ),
1453
  ),
1454
  'rendered' => array(
1455
- 'description' => __( 'Title for the object, transformed for display.' ),
1456
  'type' => 'string',
1457
  'context' => array( 'view', 'edit', 'embed' ),
1458
  ),
@@ -1472,7 +1462,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1472
  'context' => array( 'edit' ),
1473
  ),
1474
  'rendered' => array(
1475
- 'description' => __( 'Content for the object, transformed for display.' ),
1476
  'type' => 'string',
1477
  'context' => array( 'view', 'edit' ),
1478
  ),
@@ -1500,7 +1490,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1500
  'context' => array( 'edit' ),
1501
  ),
1502
  'rendered' => array(
1503
- 'description' => __( 'Excerpt for the object, transformed for display.' ),
1504
  'type' => 'string',
1505
  'context' => array( 'view', 'edit', 'embed' ),
1506
  ),
@@ -1593,10 +1583,18 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1593
 
1594
  if ( post_type_supports( $this->post_type, 'author' ) ) {
1595
  $params['author'] = array(
1596
- 'description' => __( 'Limit result set to posts assigned to a specific author.' ),
1597
- 'type' => 'integer',
1598
- 'default' => null,
1599
- 'sanitize_callback' => 'absint',
 
 
 
 
 
 
 
 
1600
  );
1601
  }
1602
  $params['exclude'] = array(
@@ -1611,16 +1609,26 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1611
  'default' => array(),
1612
  'sanitize_callback' => 'wp_parse_id_list',
1613
  );
 
 
 
 
 
 
 
 
1614
  $params['offset'] = array(
1615
  'description' => __( 'Offset the result set by a specific number of items.' ),
1616
  'type' => 'integer',
1617
  'sanitize_callback' => 'absint',
 
1618
  );
1619
  $params['order'] = array(
1620
  'description' => __( 'Order sort attribute ascending or descending.' ),
1621
  'type' => 'string',
1622
  'default' => 'desc',
1623
  'enum' => array( 'asc', 'desc' ),
 
1624
  );
1625
  $params['orderby'] = array(
1626
  'description' => __( 'Sort collection by object attribute.' ),
@@ -1633,7 +1641,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1633
  'title',
1634
  'slug',
1635
  ),
 
1636
  );
 
 
 
1637
 
1638
  $post_type_obj = get_post_type_object( $this->post_type );
1639
  if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) {
@@ -1654,6 +1666,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1654
  $params['slug'] = array(
1655
  'description' => __( 'Limit result set to posts with a specific slug.' ),
1656
  'type' => 'string',
 
1657
  );
1658
  $params['status'] = array(
1659
  'default' => 'publish',
6
 
7
  public function __construct( $post_type ) {
8
  $this->post_type = $post_type;
9
+ $this->namespace = 'wp/v2';
10
+ $obj = get_post_type_object( $post_type );
11
+ $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
12
  }
13
 
14
  /**
16
  */
17
  public function register_routes() {
18
 
19
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
 
 
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_items' ),
29
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
30
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
31
  ),
 
32
  'schema' => array( $this, 'get_public_item_schema' ),
33
  ) );
34
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
35
  array(
36
  'methods' => WP_REST_Server::READABLE,
37
  'callback' => array( $this, 'get_item' ),
57
  ),
58
  ),
59
  ),
 
60
  'schema' => array( $this, 'get_public_item_schema' ),
61
  ) );
62
  }
86
  */
87
  public function get_items( $request ) {
88
  $args = array();
89
+ $args['author__in'] = $request['author'];
90
+ $args['author__not_in'] = $request['author_exclude'];
91
+ $args['menu_order'] = $request['menu_order'];
92
  $args['offset'] = $request['offset'];
93
  $args['order'] = $request['order'];
94
  $args['orderby'] = $request['orderby'];
122
  * @param WP_REST_Request $request The request used.
123
  */
124
  $args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
125
+ $query_args = $this->prepare_items_query( $args, $request );
126
 
127
  $posts_query = new WP_Query();
128
  $query_result = $posts_query->query( $query_args );
160
  unset( $request_params['filter']['posts_per_page'] );
161
  unset( $request_params['filter']['paged'] );
162
  }
163
+ $base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
164
 
165
  if ( $page > 1 ) {
166
  $prev_page = $page - 1;
322
  $response = $this->prepare_item_for_response( $post, $request );
323
  $response = rest_ensure_response( $response );
324
  $response->set_status( 201 );
325
+ $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) );
326
 
327
  return $response;
328
  }
482
  // If we're forcing, then delete permanently.
483
  if ( $force ) {
484
  $result = wp_delete_post( $id, true );
 
485
  } else {
486
  // If we don't support trashing for this type, error out.
487
  if ( ! $supports_trash ) {
496
  // (Note that internally this falls through to `wp_delete_post` if
497
  // the trash is disabled.)
498
  $result = wp_trash_post( $id );
 
499
  }
500
 
501
  if ( ! $result ) {
502
  return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
503
  }
504
 
 
 
 
 
 
 
 
505
  /**
506
  * Fires after a single post is deleted or trashed via the REST API.
507
  *
508
+ * @param object $post The deleted or trashed post.
509
+ * @param WP_REST_Response $response The response data.
510
+ * @param WP_REST_Request $request The request sent to the API.
511
  */
512
+ do_action( "rest_delete_{$this->post_type}", $post, $response, $request );
513
 
514
  return $response;
515
  }
518
  * Determine the allowed query_vars for a get_items() response and
519
  * prepare for WP_Query.
520
  *
521
+ * @param array $prepared_args
522
+ * @param WP_REST_Request $request
523
+ * @return array $query_args
524
  */
525
+ protected function prepare_items_query( $prepared_args = array(), $request = null ) {
526
 
527
  $valid_vars = array_flip( $this->get_allowed_query_vars() );
528
  $query_args = array();
587
  $valid_vars = array_merge( $valid_vars, $private );
588
  }
589
  // Define our own in addition to WP's normal vars.
590
+ $rest_valid = array(
591
+ 'author__in',
592
+ 'author__not_in',
593
+ 'ignore_sticky_posts',
594
+ 'menu_order',
595
+ 'offset',
596
+ 'post__in',
597
+ 'post__not_in',
598
+ 'post_parent',
599
+ 'post_parent__in',
600
+ 'post_parent__not_in',
601
+ 'posts_per_page',
602
+ );
603
  $valid_vars = array_merge( $valid_vars, $rest_valid );
604
 
605
  /**
1032
  return current_user_can( $post_type->cap->delete_post, $post->ID );
1033
  }
1034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  /**
1036
  * Prepare a single post output for response.
1037
  *
1143
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1144
  foreach ( $taxonomies as $taxonomy ) {
1145
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1146
+ $terms = get_the_terms( $post, $taxonomy->name );
1147
+ $data[ $base ] = $terms ? wp_list_pluck( $terms, 'term_id' ) : array();
1148
  }
1149
 
1150
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1176
  * @return array Links for the given post.
1177
  */
1178
  protected function prepare_links( $post ) {
1179
+ $base = sprintf( '/%s/%s', $this->namespace, $this->rest_base );
1180
 
1181
  // Entity meta
1182
  $links = array(
1442
  'context' => array( 'edit' ),
1443
  ),
1444
  'rendered' => array(
1445
+ 'description' => __( 'HTML title for the object, transformed for display.' ),
1446
  'type' => 'string',
1447
  'context' => array( 'view', 'edit', 'embed' ),
1448
  ),
1462
  'context' => array( 'edit' ),
1463
  ),
1464
  'rendered' => array(
1465
+ 'description' => __( 'HTML content for the object, transformed for display.' ),
1466
  'type' => 'string',
1467
  'context' => array( 'view', 'edit' ),
1468
  ),
1490
  'context' => array( 'edit' ),
1491
  ),
1492
  'rendered' => array(
1493
+ 'description' => __( 'HTML excerpt for the object, transformed for display.' ),
1494
  'type' => 'string',
1495
  'context' => array( 'view', 'edit', 'embed' ),
1496
  ),
1583
 
1584
  if ( post_type_supports( $this->post_type, 'author' ) ) {
1585
  $params['author'] = array(
1586
+ 'description' => __( 'Limit result set to posts assigned to specific authors.' ),
1587
+ 'type' => 'array',
1588
+ 'default' => array(),
1589
+ 'sanitize_callback' => 'wp_parse_id_list',
1590
+ 'validate_callback' => 'rest_validate_request_arg',
1591
+ );
1592
+ $params['author_exclude'] = array(
1593
+ 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ),
1594
+ 'type' => 'array',
1595
+ 'default' => array(),
1596
+ 'sanitize_callback' => 'wp_parse_id_list',
1597
+ 'validate_callback' => 'rest_validate_request_arg',
1598
  );
1599
  }
1600
  $params['exclude'] = array(
1609
  'default' => array(),
1610
  'sanitize_callback' => 'wp_parse_id_list',
1611
  );
1612
+ if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
1613
+ $params['menu_order'] = array(
1614
+ 'description' => __( 'Limit result set to resources with a specific menu_order value.' ),
1615
+ 'type' => 'integer',
1616
+ 'sanitize_callback' => 'absint',
1617
+ 'validate_callback' => 'rest_validate_request_arg',
1618
+ );
1619
+ }
1620
  $params['offset'] = array(
1621
  'description' => __( 'Offset the result set by a specific number of items.' ),
1622
  'type' => 'integer',
1623
  'sanitize_callback' => 'absint',
1624
+ 'validate_callback' => 'rest_validate_request_arg',
1625
  );
1626
  $params['order'] = array(
1627
  'description' => __( 'Order sort attribute ascending or descending.' ),
1628
  'type' => 'string',
1629
  'default' => 'desc',
1630
  'enum' => array( 'asc', 'desc' ),
1631
+ 'validate_callback' => 'rest_validate_request_arg',
1632
  );
1633
  $params['orderby'] = array(
1634
  'description' => __( 'Sort collection by object attribute.' ),
1641
  'title',
1642
  'slug',
1643
  ),
1644
+ 'validate_callback' => 'rest_validate_request_arg',
1645
  );
1646
+ if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
1647
+ $params['orderby']['enum'][] = 'menu_order';
1648
+ }
1649
 
1650
  $post_type_obj = get_post_type_object( $this->post_type );
1651
  if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) {
1666
  $params['slug'] = array(
1667
  'description' => __( 'Limit result set to posts with a specific slug.' ),
1668
  'type' => 'string',
1669
+ 'validate_callback' => 'rest_validate_request_arg',
1670
  );
1671
  $params['status'] = array(
1672
  'default' => 'publish',
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -9,7 +9,10 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
9
  public function __construct( $parent_post_type ) {
10
  $this->parent_post_type = $parent_post_type;
11
  $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
12
- $this->parent_base = $this->parent_controller->get_post_type_base( $this->parent_post_type );
 
 
 
13
  }
14
 
15
  /**
@@ -17,18 +20,17 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
17
  */
18
  public function register_routes() {
19
 
20
- register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions', array(
21
  array(
22
  'methods' => WP_REST_Server::READABLE,
23
  'callback' => array( $this, 'get_items' ),
24
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
25
  'args' => $this->get_collection_params(),
26
  ),
27
-
28
  'schema' => array( $this, 'get_public_item_schema' ),
29
  ) );
30
 
31
- register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions/(?P<id>[\d]+)', array(
32
  array(
33
  'methods' => WP_REST_Server::READABLE,
34
  'callback' => array( $this, 'get_item' ),
@@ -42,7 +44,6 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
42
  'callback' => array( $this, 'delete_item' ),
43
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
44
  ),
45
-
46
  'schema' => array( $this, 'get_public_item_schema' ),
47
  ));
48
 
@@ -56,7 +57,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
56
  */
57
  public function get_items_permissions_check( $request ) {
58
 
59
- $parent = get_post( $request['parent_id'] );
60
  if ( ! $parent ) {
61
  return true;
62
  }
@@ -76,12 +77,12 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
76
  */
77
  public function get_items( $request ) {
78
 
79
- $parent = get_post( $request['parent_id'] );
80
- if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
81
- return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
82
  }
83
 
84
- $revisions = wp_get_post_revisions( $request['parent_id'] );
85
 
86
  $response = array();
87
  foreach ( $revisions as $revision ) {
@@ -109,9 +110,9 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
109
  */
110
  public function get_item( $request ) {
111
 
112
- $parent = get_post( $request['parent_id'] );
113
- if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
114
- return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
115
  }
116
 
117
  $revision = get_post( $request['id'] );
@@ -209,7 +210,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
209
  $response = rest_ensure_response( $data );
210
 
211
  if ( ! empty( $data['parent'] ) ) {
212
- $response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
213
  }
214
 
215
  /**
@@ -259,53 +260,53 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
259
  */
260
  'properties' => array(
261
  'author' => array(
262
- 'description' => __( 'The id for the author of the object.' ),
263
- 'type' => 'integer',
264
- 'context' => array( 'view' ),
265
- ),
266
  'date' => array(
267
  'description' => __( 'The date the object was published.' ),
268
  'type' => 'string',
269
  'format' => 'date-time',
270
- 'context' => array( 'view' ),
271
  ),
272
  'date_gmt' => array(
273
  'description' => __( 'The date the object was published, as GMT.' ),
274
  'type' => 'string',
275
  'format' => 'date-time',
276
- 'context' => array( 'view' ),
277
  ),
278
  'guid' => array(
279
  'description' => __( 'GUID for the object, as it exists in the database.' ),
280
  'type' => 'string',
281
- 'context' => array( 'view' ),
282
  ),
283
  'id' => array(
284
  'description' => __( 'Unique identifier for the object.' ),
285
  'type' => 'integer',
286
- 'context' => array( 'view' ),
287
  ),
288
  'modified' => array(
289
  'description' => __( 'The date the object was last modified.' ),
290
  'type' => 'string',
291
  'format' => 'date-time',
292
- 'context' => array( 'view' ),
293
  ),
294
  'modified_gmt' => array(
295
  'description' => __( 'The date the object was last modified, as GMT.' ),
296
  'type' => 'string',
297
  'format' => 'date-time',
298
- 'context' => array( 'view' ),
299
  ),
300
  'parent' => array(
301
  'description' => __( 'The id for the parent of the object.' ),
302
  'type' => 'integer',
303
- 'context' => array( 'view' ),
304
  ),
305
  'slug' => array(
306
  'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
307
  'type' => 'string',
308
- 'context' => array( 'view' ),
309
  ),
310
  ),
311
  );
@@ -323,7 +324,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
323
  $schema['properties']['title'] = array(
324
  'description' => __( 'Title for the object, as it exists in the database.' ),
325
  'type' => 'string',
326
- 'context' => array( 'view' ),
327
  );
328
  break;
329
 
@@ -331,7 +332,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
331
  $schema['properties']['content'] = array(
332
  'description' => __( 'Content for the object, as it exists in the database.' ),
333
  'type' => 'string',
334
- 'context' => array( 'view' ),
335
  );
336
  break;
337
 
@@ -339,7 +340,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
339
  $schema['properties']['excerpt'] = array(
340
  'description' => __( 'Excerpt for the object, as it exists in the database.' ),
341
  'type' => 'string',
342
- 'context' => array( 'view' ),
343
  );
344
  break;
345
 
9
  public function __construct( $parent_post_type ) {
10
  $this->parent_post_type = $parent_post_type;
11
  $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
12
+ $this->namespace = 'wp/v2';
13
+ $this->rest_base = 'revisions';
14
+ $post_type_object = get_post_type_object( $parent_post_type );
15
+ $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
16
  }
17
 
18
  /**
20
  */
21
  public function register_routes() {
22
 
23
+ register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
24
  array(
25
  'methods' => WP_REST_Server::READABLE,
26
  'callback' => array( $this, 'get_items' ),
27
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
28
  'args' => $this->get_collection_params(),
29
  ),
 
30
  'schema' => array( $this, 'get_public_item_schema' ),
31
  ) );
32
 
33
+ register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
34
  array(
35
  'methods' => WP_REST_Server::READABLE,
36
  'callback' => array( $this, 'get_item' ),
44
  'callback' => array( $this, 'delete_item' ),
45
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
46
  ),
 
47
  'schema' => array( $this, 'get_public_item_schema' ),
48
  ));
49
 
57
  */
58
  public function get_items_permissions_check( $request ) {
59
 
60
+ $parent = get_post( $request['parent'] );
61
  if ( ! $parent ) {
62
  return true;
63
  }
77
  */
78
  public function get_items( $request ) {
79
 
80
+ $parent = get_post( $request['parent'] );
81
+ if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
82
+ return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
83
  }
84
 
85
+ $revisions = wp_get_post_revisions( $request['parent'] );
86
 
87
  $response = array();
88
  foreach ( $revisions as $revision ) {
110
  */
111
  public function get_item( $request ) {
112
 
113
+ $parent = get_post( $request['parent'] );
114
+ if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
115
+ return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
116
  }
117
 
118
  $revision = get_post( $request['id'] );
210
  $response = rest_ensure_response( $data );
211
 
212
  if ( ! empty( $data['parent'] ) ) {
213
+ $response->add_link( 'parent', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) );
214
  }
215
 
216
  /**
260
  */
261
  'properties' => array(
262
  'author' => array(
263
+ 'description' => __( 'The id for the author of the object.' ),
264
+ 'type' => 'integer',
265
+ 'context' => array( 'view', 'edit', 'embed' ),
266
+ ),
267
  'date' => array(
268
  'description' => __( 'The date the object was published.' ),
269
  'type' => 'string',
270
  'format' => 'date-time',
271
+ 'context' => array( 'view', 'edit', 'embed' ),
272
  ),
273
  'date_gmt' => array(
274
  'description' => __( 'The date the object was published, as GMT.' ),
275
  'type' => 'string',
276
  'format' => 'date-time',
277
+ 'context' => array( 'view', 'edit' ),
278
  ),
279
  'guid' => array(
280
  'description' => __( 'GUID for the object, as it exists in the database.' ),
281
  'type' => 'string',
282
+ 'context' => array( 'view', 'edit' ),
283
  ),
284
  'id' => array(
285
  'description' => __( 'Unique identifier for the object.' ),
286
  'type' => 'integer',
287
+ 'context' => array( 'view', 'edit', 'embed' ),
288
  ),
289
  'modified' => array(
290
  'description' => __( 'The date the object was last modified.' ),
291
  'type' => 'string',
292
  'format' => 'date-time',
293
+ 'context' => array( 'view', 'edit' ),
294
  ),
295
  'modified_gmt' => array(
296
  'description' => __( 'The date the object was last modified, as GMT.' ),
297
  'type' => 'string',
298
  'format' => 'date-time',
299
+ 'context' => array( 'view', 'edit' ),
300
  ),
301
  'parent' => array(
302
  'description' => __( 'The id for the parent of the object.' ),
303
  'type' => 'integer',
304
+ 'context' => array( 'view', 'edit', 'embed' ),
305
  ),
306
  'slug' => array(
307
  'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
308
  'type' => 'string',
309
+ 'context' => array( 'view', 'edit', 'embed' ),
310
  ),
311
  ),
312
  );
324
  $schema['properties']['title'] = array(
325
  'description' => __( 'Title for the object, as it exists in the database.' ),
326
  'type' => 'string',
327
+ 'context' => array( 'view', 'edit', 'embed' ),
328
  );
329
  break;
330
 
332
  $schema['properties']['content'] = array(
333
  'description' => __( 'Content for the object, as it exists in the database.' ),
334
  'type' => 'string',
335
+ 'context' => array( 'view', 'edit' ),
336
  );
337
  break;
338
 
340
  $schema['properties']['excerpt'] = array(
341
  'description' => __( 'Excerpt for the object, as it exists in the database.' ),
342
  'type' => 'string',
343
+ 'context' => array( 'view', 'edit', 'embed' ),
344
  );
345
  break;
346
 
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -2,21 +2,27 @@
2
 
3
  class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
4
 
 
 
 
 
 
5
  /**
6
  * Register the routes for the objects of the controller.
7
  */
8
  public function register_routes() {
9
 
10
- register_rest_route( 'wp/v2', '/taxonomies', array(
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
 
14
  'args' => $this->get_collection_params(),
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
18
 
19
- register_rest_route( 'wp/v2', '/taxonomies/(?P<taxonomy>[\w-]+)', array(
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
@@ -29,6 +35,29 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
29
  ) );
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  /**
33
  * Get all public taxonomies
34
  *
@@ -68,7 +97,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
68
  return false;
69
  }
70
  if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->manage_terms ) ) {
71
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) );
72
  }
73
  }
74
 
@@ -84,7 +113,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
84
  public function get_item( $request ) {
85
  $tax_obj = get_taxonomy( $request['taxonomy'] );
86
  if ( empty( $tax_obj ) ) {
87
- return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) );
88
  }
89
  $data = $this->prepare_item_for_response( $tax_obj, $request );
90
  return rest_ensure_response( $data );
@@ -102,6 +131,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
102
  $data = array(
103
  'name' => $taxonomy->label,
104
  'slug' => $taxonomy->name,
 
105
  'description' => $taxonomy->description,
106
  'labels' => $taxonomy->labels,
107
  'types' => $taxonomy->object_type,
@@ -119,7 +149,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
119
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
120
  $response->add_links( array(
121
  'collection' => array(
122
- 'href' => rest_url( 'wp/v2/taxonomies' ),
123
  ),
124
  'https://api.w.org/items' => array(
125
  'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
@@ -149,43 +179,56 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
149
  'title' => 'taxonomy',
150
  'type' => 'object',
151
  'properties' => array(
 
 
 
 
 
 
152
  'description' => array(
153
- 'description' => __( 'A human-readable description of the object.' ),
154
  'type' => 'string',
155
  'context' => array( 'view', 'edit' ),
156
- ),
 
157
  'hierarchical' => array(
158
- 'description' => __( 'Whether or not the type should have children.' ),
159
  'type' => 'boolean',
160
  'context' => array( 'view', 'edit' ),
161
- ),
 
162
  'labels' => array(
163
- 'description' => __( 'Human-readable labels for the type for various contexts.' ),
164
  'type' => 'object',
165
  'context' => array( 'edit' ),
166
- ),
 
167
  'name' => array(
168
- 'description' => __( 'The title for the object.' ),
169
  'type' => 'string',
170
- 'context' => array( 'view', 'edit' ),
171
- ),
 
172
  'slug' => array(
173
- 'description' => __( 'An alphanumeric identifier for the object.' ),
174
  'type' => 'string',
175
- 'context' => array( 'view', 'edit' ),
176
- ),
 
177
  'show_cloud' => array(
178
  'description' => __( 'Whether or not the term cloud should be displayed.' ),
179
  'type' => 'boolean',
180
  'context' => array( 'edit' ),
181
- ),
 
182
  'types' => array(
183
- 'description' => __( 'Types associated with taxonomy.' ),
184
  'type' => 'array',
185
  'context' => array( 'view', 'edit' ),
186
- ),
187
  ),
188
- );
 
189
  return $this->add_additional_fields_schema( $schema );
190
  }
191
 
@@ -198,8 +241,9 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
198
  $new_params = array();
199
  $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
200
  $new_params['type'] = array(
201
- 'description' => __( 'Limit results to taxonomies associated with a specific post type.' ),
202
  'type' => 'string',
 
203
  );
204
  return $new_params;
205
  }
2
 
3
  class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
4
 
5
+ public function __construct() {
6
+ $this->namespace = 'wp/v2';
7
+ $this->rest_base = 'taxonomies';
8
+ }
9
+
10
  /**
11
  * Register the routes for the objects of the controller.
12
  */
13
  public function register_routes() {
14
 
15
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
16
  array(
17
  'methods' => WP_REST_Server::READABLE,
18
  'callback' => array( $this, 'get_items' ),
19
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20
  'args' => $this->get_collection_params(),
21
  ),
22
  'schema' => array( $this, 'get_public_item_schema' ),
23
  ) );
24
 
25
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<taxonomy>[\w-]+)', array(
26
  array(
27
  'methods' => WP_REST_Server::READABLE,
28
  'callback' => array( $this, 'get_item' ),
35
  ) );
36
  }
37
 
38
+ /**
39
+ * Check whether a given request has permission to read taxonomies.
40
+ *
41
+ * @param WP_REST_Request $request Full details about the request.
42
+ * @return WP_Error|boolean
43
+ */
44
+ public function get_items_permissions_check( $request ) {
45
+ if ( 'edit' === $request['context'] ) {
46
+ if ( ! empty( $request['type'] ) ) {
47
+ $taxonomies = get_object_taxonomies( $request['type'], 'objects' );
48
+ } else {
49
+ $taxonomies = get_taxonomies( '', 'objects' );
50
+ }
51
+ foreach ( $taxonomies as $taxonomy ) {
52
+ if ( ! empty( $taxonomy->show_in_rest ) && current_user_can( $taxonomy->cap->manage_terms ) ) {
53
+ return true;
54
+ }
55
+ }
56
+ return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
57
+ }
58
+ return true;
59
+ }
60
+
61
  /**
62
  * Get all public taxonomies
63
  *
97
  return false;
98
  }
99
  if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->manage_terms ) ) {
100
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) );
101
  }
102
  }
103
 
113
  public function get_item( $request ) {
114
  $tax_obj = get_taxonomy( $request['taxonomy'] );
115
  if ( empty( $tax_obj ) ) {
116
+ return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
117
  }
118
  $data = $this->prepare_item_for_response( $tax_obj, $request );
119
  return rest_ensure_response( $data );
131
  $data = array(
132
  'name' => $taxonomy->label,
133
  'slug' => $taxonomy->name,
134
+ 'capabilities' => $taxonomy->cap,
135
  'description' => $taxonomy->description,
136
  'labels' => $taxonomy->labels,
137
  'types' => $taxonomy->object_type,
149
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
150
  $response->add_links( array(
151
  'collection' => array(
152
+ 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
153
  ),
154
  'https://api.w.org/items' => array(
155
  'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
179
  'title' => 'taxonomy',
180
  'type' => 'object',
181
  'properties' => array(
182
+ 'capabilities' => array(
183
+ 'description' => __( 'All capabilities used by the resource.' ),
184
+ 'type' => 'array',
185
+ 'context' => array( 'edit' ),
186
+ 'readonly' => true,
187
+ ),
188
  'description' => array(
189
+ 'description' => __( 'A human-readable description of the resource.' ),
190
  'type' => 'string',
191
  'context' => array( 'view', 'edit' ),
192
+ 'readonly' => true,
193
+ ),
194
  'hierarchical' => array(
195
+ 'description' => __( 'Whether or not the resource should have children.' ),
196
  'type' => 'boolean',
197
  'context' => array( 'view', 'edit' ),
198
+ 'readonly' => true,
199
+ ),
200
  'labels' => array(
201
+ 'description' => __( 'Human-readable labels for the resource for various contexts.' ),
202
  'type' => 'object',
203
  'context' => array( 'edit' ),
204
+ 'readonly' => true,
205
+ ),
206
  'name' => array(
207
+ 'description' => __( 'The title for the resource.' ),
208
  'type' => 'string',
209
+ 'context' => array( 'view', 'edit', 'embed' ),
210
+ 'readonly' => true,
211
+ ),
212
  'slug' => array(
213
+ 'description' => __( 'An alphanumeric identifier for the resource.' ),
214
  'type' => 'string',
215
+ 'context' => array( 'view', 'edit', 'embed' ),
216
+ 'readonly' => true,
217
+ ),
218
  'show_cloud' => array(
219
  'description' => __( 'Whether or not the term cloud should be displayed.' ),
220
  'type' => 'boolean',
221
  'context' => array( 'edit' ),
222
+ 'readonly' => true,
223
+ ),
224
  'types' => array(
225
+ 'description' => __( 'Types associated with resource.' ),
226
  'type' => 'array',
227
  'context' => array( 'view', 'edit' ),
228
+ 'readonly' => true,
229
  ),
230
+ ),
231
+ );
232
  return $this->add_additional_fields_schema( $schema );
233
  }
234
 
241
  $new_params = array();
242
  $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
243
  $new_params['type'] = array(
244
+ 'description' => __( 'Limit results to resources associated with a specific post type.' ),
245
  'type' => 'string',
246
+ 'validate_callback' => 'rest_validate_request_arg',
247
  );
248
  return $new_params;
249
  }
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -12,6 +12,9 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
12
  */
13
  public function __construct( $taxonomy ) {
14
  $this->taxonomy = $taxonomy;
 
 
 
15
  }
16
 
17
  /**
@@ -19,8 +22,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
19
  */
20
  public function register_routes() {
21
 
22
- $base = $this->get_taxonomy_base( $this->taxonomy );
23
- register_rest_route( 'wp/v2', '/' . $base, array(
24
  array(
25
  'methods' => WP_REST_Server::READABLE,
26
  'callback' => array( $this, 'get_items' ),
@@ -33,10 +35,9 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
33
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
34
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
35
  ),
36
-
37
  'schema' => array( $this, 'get_public_item_schema' ),
38
  ));
39
- register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
40
  array(
41
  'methods' => WP_REST_Server::READABLE,
42
  'callback' => array( $this, 'get_item' ),
@@ -62,7 +63,6 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
62
  ),
63
  ),
64
  ),
65
-
66
  'schema' => array( $this, 'get_public_item_schema' ),
67
  ) );
68
  }
@@ -74,7 +74,14 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
74
  * @return WP_Error|boolean
75
  */
76
  public function get_items_permissions_check( $request ) {
77
- return $this->check_is_taxonomy_allowed( $this->taxonomy );
 
 
 
 
 
 
 
78
  }
79
 
80
  /**
@@ -163,7 +170,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
163
  $max_pages = ceil( $total_terms / $per_page );
164
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
165
 
166
- $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) ) );
167
  if ( $page > 1 ) {
168
  $prev_page = $page - 1;
169
  if ( $prev_page > $max_pages ) {
@@ -188,7 +195,14 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
188
  * @return WP_Error|boolean
189
  */
190
  public function get_item_permissions_check( $request ) {
191
- return $this->check_is_taxonomy_allowed( $this->taxonomy );
 
 
 
 
 
 
 
192
  }
193
 
194
  /**
@@ -201,7 +215,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
201
 
202
  $term = get_term( (int) $request['id'], $this->taxonomy );
203
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
204
- return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
205
  }
206
  if ( is_wp_error( $term ) ) {
207
  return $term;
@@ -226,7 +240,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
226
 
227
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
228
  if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
229
- return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new terms.' ), array( 'status' => rest_authorization_required_code() ) );
230
  }
231
 
232
  return true;
@@ -252,13 +266,13 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
252
 
253
  if ( isset( $request['parent'] ) ) {
254
  if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
255
- return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
256
  }
257
 
258
  $parent = get_term( (int) $request['parent'], $this->taxonomy );
259
 
260
  if ( ! $parent ) {
261
- return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 404 ) );
262
  }
263
 
264
  $args['parent'] = $parent->term_id;
@@ -294,7 +308,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
294
  $response = $this->prepare_item_for_response( $term, $request );
295
  $response = rest_ensure_response( $response );
296
  $response->set_status( 201 );
297
- $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) . '/' . $term->term_id ) );
298
  return $response;
299
  }
300
 
@@ -312,12 +326,12 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
312
 
313
  $term = get_term( (int) $request['id'], $this->taxonomy );
314
  if ( ! $term ) {
315
- return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
316
  }
317
 
318
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
319
  if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
320
- return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update terms.' ), array( 'status' => rest_authorization_required_code() ) );
321
  }
322
 
323
  return true;
@@ -344,13 +358,13 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
344
 
345
  if ( isset( $request['parent'] ) ) {
346
  if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
347
- return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
348
  }
349
 
350
  $parent = get_term( (int) $request['parent'], $this->taxonomy );
351
 
352
  if ( ! $parent ) {
353
- return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 400 ) );
354
  }
355
 
356
  $prepared_args['parent'] = $parent->term_id;
@@ -389,11 +403,11 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
389
  }
390
  $term = get_term( (int) $request['id'], $this->taxonomy );
391
  if ( ! $term ) {
392
- return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
393
  }
394
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
395
  if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
396
- return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete terms.' ), array( 'status' => rest_authorization_required_code() ) );
397
  }
398
  return true;
399
  }
@@ -410,53 +424,30 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
410
 
411
  // We don't support trashing for this type, error out
412
  if ( ! $force ) {
413
- return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
414
  }
415
 
416
  $term = get_term( (int) $request['id'], $this->taxonomy );
417
  $request->set_param( 'context', 'view' );
418
  $response = $this->prepare_item_for_response( $term, $request );
419
 
420
- $data = $response->get_data();
421
- $data = array(
422
- 'data' => $data,
423
- 'deleted' => true,
424
- );
425
- $response->set_data( $data );
426
-
427
  $retval = wp_delete_term( $term->term_id, $term->taxonomy );
428
  if ( ! $retval ) {
429
- return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
430
  }
431
 
432
  /**
433
  * Fires after a single term is deleted via the REST API.
434
  *
435
- * @param WP_Term $term The deleted term.
436
- * @param array $data The response data.
437
- * @param WP_REST_Request $request The request sent to the API.
438
  */
439
- do_action( "rest_delete_{$this->taxonomy}", $term, $data, $request );
440
 
441
  return $response;
442
  }
443
 
444
- /**
445
- * Get the base path for a term's taxonomy endpoints.
446
- *
447
- * @param object|string $taxonomy
448
- * @return string $base
449
- */
450
- public function get_taxonomy_base( $taxonomy ) {
451
- if ( ! is_object( $taxonomy ) ) {
452
- $taxonomy = get_taxonomy( $taxonomy );
453
- }
454
-
455
- $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
456
-
457
- return $base;
458
- }
459
-
460
  /**
461
  * Prepare a single term output for response
462
  *
@@ -507,7 +498,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
507
  * @return array Links for the given term.
508
  */
509
  protected function prepare_links( $term ) {
510
- $base = '/wp/v2/' . $this->get_taxonomy_base( $term->taxonomy );
511
  $links = array(
512
  'self' => array(
513
  'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
@@ -524,12 +515,32 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
524
  $parent_term = get_term( (int) $term->parent, $term->taxonomy );
525
  if ( $parent_term ) {
526
  $links['up'] = array(
527
- 'href' => rest_url( sprintf( 'wp/v2/%s/%d', $this->get_taxonomy_base( $parent_term->taxonomy ), $parent_term->term_id ) ),
528
  'embeddable' => true,
529
  );
530
  }
531
  }
532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  return $links;
534
  }
535
 
@@ -545,54 +556,54 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
545
  'type' => 'object',
546
  'properties' => array(
547
  'id' => array(
548
- 'description' => __( 'Unique identifier for the object.' ),
549
  'type' => 'integer',
550
- 'context' => array( 'view', 'embed' ),
551
  'readonly' => true,
552
  ),
553
  'count' => array(
554
- 'description' => __( 'Number of published posts for the object.' ),
555
  'type' => 'integer',
556
- 'context' => array( 'view' ),
557
  'readonly' => true,
558
  ),
559
  'description' => array(
560
- 'description' => __( 'A human-readable description of the object.' ),
561
  'type' => 'string',
562
- 'context' => array( 'view' ),
563
  'arg_options' => array(
564
  'sanitize_callback' => 'wp_filter_post_kses',
565
  ),
566
  ),
567
  'link' => array(
568
- 'description' => __( 'URL to the object.' ),
569
  'type' => 'string',
570
  'format' => 'uri',
571
- 'context' => array( 'view', 'embed' ),
572
  'readonly' => true,
573
  ),
574
  'name' => array(
575
- 'description' => __( 'The title for the object.' ),
576
  'type' => 'string',
577
- 'context' => array( 'view', 'embed' ),
578
  'arg_options' => array(
579
  'sanitize_callback' => 'sanitize_text_field',
580
  ),
581
  'required' => true,
582
  ),
583
  'slug' => array(
584
- 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
585
  'type' => 'string',
586
- 'context' => array( 'view', 'embed' ),
587
  'arg_options' => array(
588
  'sanitize_callback' => 'sanitize_title',
589
  ),
590
  ),
591
  'taxonomy' => array(
592
- 'description' => __( 'Type attribution for the object.' ),
593
  'type' => 'string',
594
  'enum' => array_keys( get_taxonomies() ),
595
- 'context' => array( 'view', 'embed' ),
596
  'readonly' => true,
597
  ),
598
  ),
@@ -600,10 +611,10 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
600
  $taxonomy = get_taxonomy( $this->taxonomy );
601
  if ( $taxonomy->hierarchical ) {
602
  $schema['properties']['parent'] = array(
603
- 'description' => __( 'The id for the parent of the object.' ),
604
- 'type' => 'integer',
605
- 'context' => array( 'view' ),
606
- );
607
  }
608
  return $this->add_additional_fields_schema( $schema );
609
  }
@@ -636,6 +647,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
636
  'description' => __( 'Offset the result set by a specific number of items.' ),
637
  'type' => 'integer',
638
  'sanitize_callback' => 'absint',
 
639
  );
640
  }
641
  $query_params['order'] = array(
@@ -647,9 +659,10 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
647
  'asc',
648
  'desc',
649
  ),
 
650
  );
651
  $query_params['orderby'] = array(
652
- 'description' => __( 'Sort collection by object attribute.' ),
653
  'type' => 'string',
654
  'sanitize_callback' => 'sanitize_key',
655
  'default' => 'name',
@@ -662,27 +675,32 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
662
  'description',
663
  'count',
664
  ),
 
665
  );
666
  $query_params['hide_empty'] = array(
667
- 'description' => __( 'Whether to hide terms not assigned to any posts.' ),
668
  'type' => 'boolean',
669
  'default' => false,
 
670
  );
671
  if ( $taxonomy->hierarchical ) {
672
  $query_params['parent'] = array(
673
- 'description' => __( 'Limit result set to terms assigned to a specific parent term.' ),
674
  'type' => 'integer',
675
  'sanitize_callback' => 'absint',
 
676
  );
677
  }
678
  $query_params['post'] = array(
679
- 'description' => __( 'Limit result set to terms assigned to a specific post.' ),
680
- 'type' => 'number',
681
- 'default' => false,
 
682
  );
683
  $query_params['slug'] = array(
684
- 'description' => __( 'Limit result set to terms with a specific slug.' ),
685
  'type' => 'string',
 
686
  );
687
  return $query_params;
688
  }
12
  */
13
  public function __construct( $taxonomy ) {
14
  $this->taxonomy = $taxonomy;
15
+ $this->namespace = 'wp/v2';
16
+ $tax_obj = get_taxonomy( $taxonomy );
17
+ $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
18
  }
19
 
20
  /**
22
  */
23
  public function register_routes() {
24
 
25
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
 
26
  array(
27
  'methods' => WP_REST_Server::READABLE,
28
  'callback' => array( $this, 'get_items' ),
35
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
36
  'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
37
  ),
 
38
  'schema' => array( $this, 'get_public_item_schema' ),
39
  ));
40
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
41
  array(
42
  'methods' => WP_REST_Server::READABLE,
43
  'callback' => array( $this, 'get_item' ),
63
  ),
64
  ),
65
  ),
 
66
  'schema' => array( $this, 'get_public_item_schema' ),
67
  ) );
68
  }
74
  * @return WP_Error|boolean
75
  */
76
  public function get_items_permissions_check( $request ) {
77
+ $tax_obj = get_taxonomy( $this->taxonomy );
78
+ if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
79
+ return false;
80
+ }
81
+ if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
82
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
83
+ }
84
+ return true;
85
  }
86
 
87
  /**
170
  $max_pages = ceil( $total_terms / $per_page );
171
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
172
 
173
+ $base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base ) );
174
  if ( $page > 1 ) {
175
  $prev_page = $page - 1;
176
  if ( $prev_page > $max_pages ) {
195
  * @return WP_Error|boolean
196
  */
197
  public function get_item_permissions_check( $request ) {
198
+ $tax_obj = get_taxonomy( $this->taxonomy );
199
+ if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
200
+ return false;
201
+ }
202
+ if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
203
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
204
+ }
205
+ return true;
206
  }
207
 
208
  /**
215
 
216
  $term = get_term( (int) $request['id'], $this->taxonomy );
217
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
218
+ return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
219
  }
220
  if ( is_wp_error( $term ) ) {
221
  return $term;
240
 
241
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
242
  if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
243
+ return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new resource.' ), array( 'status' => rest_authorization_required_code() ) );
244
  }
245
 
246
  return true;
266
 
267
  if ( isset( $request['parent'] ) ) {
268
  if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
269
+ return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
270
  }
271
 
272
  $parent = get_term( (int) $request['parent'], $this->taxonomy );
273
 
274
  if ( ! $parent ) {
275
+ return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 404 ) );
276
  }
277
 
278
  $args['parent'] = $parent->term_id;
308
  $response = $this->prepare_item_for_response( $term, $request );
309
  $response = rest_ensure_response( $response );
310
  $response->set_status( 201 );
311
+ $response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
312
  return $response;
313
  }
314
 
326
 
327
  $term = get_term( (int) $request['id'], $this->taxonomy );
328
  if ( ! $term ) {
329
+ return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
330
  }
331
 
332
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
333
  if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
334
+ return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) );
335
  }
336
 
337
  return true;
358
 
359
  if ( isset( $request['parent'] ) ) {
360
  if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
361
+ return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
362
  }
363
 
364
  $parent = get_term( (int) $request['parent'], $this->taxonomy );
365
 
366
  if ( ! $parent ) {
367
+ return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
368
  }
369
 
370
  $prepared_args['parent'] = $parent->term_id;
403
  }
404
  $term = get_term( (int) $request['id'], $this->taxonomy );
405
  if ( ! $term ) {
406
+ return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
407
  }
408
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
409
  if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
410
+ return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) );
411
  }
412
  return true;
413
  }
424
 
425
  // We don't support trashing for this type, error out
426
  if ( ! $force ) {
427
+ return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) );
428
  }
429
 
430
  $term = get_term( (int) $request['id'], $this->taxonomy );
431
  $request->set_param( 'context', 'view' );
432
  $response = $this->prepare_item_for_response( $term, $request );
433
 
 
 
 
 
 
 
 
434
  $retval = wp_delete_term( $term->term_id, $term->taxonomy );
435
  if ( ! $retval ) {
436
+ return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
437
  }
438
 
439
  /**
440
  * Fires after a single term is deleted via the REST API.
441
  *
442
+ * @param WP_Term $term The deleted term.
443
+ * @param WP_REST_Response $response The response data.
444
+ * @param WP_REST_Request $request The request sent to the API.
445
  */
446
+ do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
447
 
448
  return $response;
449
  }
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  /**
452
  * Prepare a single term output for response
453
  *
498
  * @return array Links for the given term.
499
  */
500
  protected function prepare_links( $term ) {
501
+ $base = '/' . $this->namespace . '/' . $this->rest_base;
502
  $links = array(
503
  'self' => array(
504
  'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
515
  $parent_term = get_term( (int) $term->parent, $term->taxonomy );
516
  if ( $parent_term ) {
517
  $links['up'] = array(
518
+ 'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
519
  'embeddable' => true,
520
  );
521
  }
522
  }
523
 
524
+ $taxonomy_obj = get_taxonomy( $term->taxonomy );
525
+ if ( empty( $taxonomy_obj->object_type ) ) {
526
+ return $links;
527
+ }
528
+
529
+ $post_type_links = array();
530
+ foreach ( $taxonomy_obj->object_type as $type ) {
531
+ $post_type_object = get_post_type_object( $type );
532
+ if ( empty( $post_type_object->show_in_rest ) ) {
533
+ continue;
534
+ }
535
+ $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
536
+ $post_type_links[] = array(
537
+ 'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
538
+ );
539
+ }
540
+ if ( ! empty( $post_type_links ) ) {
541
+ $links['http://api.w.org/v2/post_type'] = $post_type_links;
542
+ }
543
+
544
  return $links;
545
  }
546
 
556
  'type' => 'object',
557
  'properties' => array(
558
  'id' => array(
559
+ 'description' => __( 'Unique identifier for the resource.' ),
560
  'type' => 'integer',
561
+ 'context' => array( 'view', 'embed', 'edit' ),
562
  'readonly' => true,
563
  ),
564
  'count' => array(
565
+ 'description' => __( 'Number of published posts for the resource.' ),
566
  'type' => 'integer',
567
+ 'context' => array( 'view', 'edit' ),
568
  'readonly' => true,
569
  ),
570
  'description' => array(
571
+ 'description' => __( 'HTML description of the resource.' ),
572
  'type' => 'string',
573
+ 'context' => array( 'view', 'edit' ),
574
  'arg_options' => array(
575
  'sanitize_callback' => 'wp_filter_post_kses',
576
  ),
577
  ),
578
  'link' => array(
579
+ 'description' => __( 'URL to the resource.' ),
580
  'type' => 'string',
581
  'format' => 'uri',
582
+ 'context' => array( 'view', 'embed', 'edit' ),
583
  'readonly' => true,
584
  ),
585
  'name' => array(
586
+ 'description' => __( 'HTML title for the resource.' ),
587
  'type' => 'string',
588
+ 'context' => array( 'view', 'embed', 'edit' ),
589
  'arg_options' => array(
590
  'sanitize_callback' => 'sanitize_text_field',
591
  ),
592
  'required' => true,
593
  ),
594
  'slug' => array(
595
+ 'description' => __( 'An alphanumeric identifier for the resource unique to its type.' ),
596
  'type' => 'string',
597
+ 'context' => array( 'view', 'embed', 'edit' ),
598
  'arg_options' => array(
599
  'sanitize_callback' => 'sanitize_title',
600
  ),
601
  ),
602
  'taxonomy' => array(
603
+ 'description' => __( 'Type attribution for the resource.' ),
604
  'type' => 'string',
605
  'enum' => array_keys( get_taxonomies() ),
606
+ 'context' => array( 'view', 'embed', 'edit' ),
607
  'readonly' => true,
608
  ),
609
  ),
611
  $taxonomy = get_taxonomy( $this->taxonomy );
612
  if ( $taxonomy->hierarchical ) {
613
  $schema['properties']['parent'] = array(
614
+ 'description' => __( 'The id for the parent of the resource.' ),
615
+ 'type' => 'integer',
616
+ 'context' => array( 'view', 'edit' ),
617
+ );
618
  }
619
  return $this->add_additional_fields_schema( $schema );
620
  }
647
  'description' => __( 'Offset the result set by a specific number of items.' ),
648
  'type' => 'integer',
649
  'sanitize_callback' => 'absint',
650
+ 'validate_callback' => 'rest_validate_request_arg',
651
  );
652
  }
653
  $query_params['order'] = array(
659
  'asc',
660
  'desc',
661
  ),
662
+ 'validate_callback' => 'rest_validate_request_arg',
663
  );
664
  $query_params['orderby'] = array(
665
+ 'description' => __( 'Sort collection by resource attribute.' ),
666
  'type' => 'string',
667
  'sanitize_callback' => 'sanitize_key',
668
  'default' => 'name',
675
  'description',
676
  'count',
677
  ),
678
+ 'validate_callback' => 'rest_validate_request_arg',
679
  );
680
  $query_params['hide_empty'] = array(
681
+ 'description' => __( 'Whether to hide resources not assigned to any posts.' ),
682
  'type' => 'boolean',
683
  'default' => false,
684
+ 'validate_callback' => 'rest_validate_request_arg',
685
  );
686
  if ( $taxonomy->hierarchical ) {
687
  $query_params['parent'] = array(
688
+ 'description' => __( 'Limit result set to resources assigned to a specific parent.' ),
689
  'type' => 'integer',
690
  'sanitize_callback' => 'absint',
691
+ 'validate_callback' => 'rest_validate_request_arg',
692
  );
693
  }
694
  $query_params['post'] = array(
695
+ 'description' => __( 'Limit result set to resources assigned to a specific post.' ),
696
+ 'type' => 'integer',
697
+ 'default' => null,
698
+ 'validate_callback' => 'rest_validate_request_arg',
699
  );
700
  $query_params['slug'] = array(
701
+ 'description' => __( 'Limit result set to resources with a specific slug.' ),
702
  'type' => 'string',
703
+ 'validate_callback' => 'rest_validate_request_arg',
704
  );
705
  return $query_params;
706
  }
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -5,12 +5,17 @@
5
  */
6
  class WP_REST_Users_Controller extends WP_REST_Controller {
7
 
 
 
 
 
 
8
  /**
9
  * Register the routes for the objects of the controller.
10
  */
11
  public function register_routes() {
12
 
13
- register_rest_route( 'wp/v2', '/users', array(
14
  array(
15
  'methods' => WP_REST_Server::READABLE,
16
  'callback' => array( $this, 'get_items' ),
@@ -26,16 +31,15 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
26
  ),
27
  ) ),
28
  ),
29
-
30
  'schema' => array( $this, 'get_public_item_schema' ),
31
  ) );
32
- register_rest_route( 'wp/v2', '/users/(?P<id>[\d]+)', array(
33
  array(
34
  'methods' => WP_REST_Server::READABLE,
35
  'callback' => array( $this, 'get_item' ),
36
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
37
  'args' => array(
38
- 'context' => $this->get_context_param( array( 'default' => 'embed' ) ),
39
  ),
40
  ),
41
  array(
@@ -58,11 +62,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
58
  'reassign' => array(),
59
  ),
60
  ),
61
-
62
  'schema' => array( $this, 'get_public_item_schema' ),
63
  ) );
64
 
65
- register_rest_route( 'wp/v2', '/users/me', array(
66
  'methods' => WP_REST_Server::READABLE,
67
  'callback' => array( $this, 'get_current_item' ),
68
  'args' => array(
@@ -101,9 +104,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
101
 
102
  if ( ! current_user_can( 'list_users' ) ) {
103
  $prepared_args['has_published_posts'] = true;
104
-
105
- // Only display a public subset of information
106
- $request['context'] = 'embed';
107
  }
108
 
109
  if ( '' !== $prepared_args['search'] ) {
@@ -153,7 +153,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
153
  $max_pages = ceil( $total_users / $per_page );
154
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
155
 
156
- $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/users' ) );
157
  if ( $page > 1 ) {
158
  $prev_page = $page - 1;
159
  if ( $prev_page > $max_pages ) {
@@ -181,23 +181,20 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
181
 
182
  $id = (int) $request['id'];
183
  $user = get_userdata( $id );
 
184
 
185
  if ( empty( $id ) || empty( $user->ID ) ) {
186
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
187
  }
188
 
189
  if ( get_current_user_id() === $id ) {
190
  return true;
191
  }
192
 
193
- $context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
194
-
195
- if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
196
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => rest_authorization_required_code() ) );
197
- } else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
198
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => rest_authorization_required_code() ) );
199
- } else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
200
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => rest_authorization_required_code() ) );
201
  }
202
 
203
  return true;
@@ -214,7 +211,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
214
  $user = get_userdata( $id );
215
 
216
  if ( empty( $id ) || empty( $user->ID ) ) {
217
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
218
  }
219
 
220
  $user = $this->prepare_item_for_response( $user, $request );
@@ -238,7 +235,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
238
  $user = wp_get_current_user();
239
  $response = $this->prepare_item_for_response( $user, $request );
240
  $response = rest_ensure_response( $response );
241
- $response->header( 'Location', rest_url( sprintf( '/wp/v2/users/%d', $current_user_id ) ) );
242
  $response->set_status( 302 );
243
 
244
  return $response;
@@ -253,7 +250,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
253
  public function create_item_permissions_check( $request ) {
254
 
255
  if ( ! current_user_can( 'create_users' ) ) {
256
- return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => rest_authorization_required_code() ) );
257
  }
258
 
259
  return true;
@@ -269,7 +266,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
269
  global $wp_roles;
270
 
271
  if ( ! empty( $request['id'] ) ) {
272
- return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) );
 
 
 
 
 
 
 
273
  }
274
 
275
  $user = $this->prepare_item_for_database( $request );
@@ -284,7 +288,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
284
  if ( is_multisite() ) {
285
  $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
286
  if ( ! $user_id ) {
287
- return new WP_Error( 'rest_user_create', __( 'Error creating new user.' ), array( 'status' => 500 ) );
288
  }
289
  $user->ID = $user_id;
290
  $user_id = wp_update_user( $user );
@@ -299,6 +303,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
299
  }
300
 
301
  $user = get_user_by( 'id', $user_id );
 
 
 
 
302
  $this->update_additional_fields_for_object( $user, $request );
303
 
304
  /**
@@ -314,7 +322,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
314
  $response = $this->prepare_item_for_response( $user, $request );
315
  $response = rest_ensure_response( $response );
316
  $response->set_status( 201 );
317
- $response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
318
 
319
  return $response;
320
  }
@@ -330,11 +338,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
330
  $id = (int) $request['id'];
331
 
332
  if ( ! current_user_can( 'edit_user', $id ) ) {
333
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => rest_authorization_required_code() ) );
334
  }
335
 
336
- if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
337
- return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => rest_authorization_required_code() ) );
338
  }
339
 
340
  return true;
@@ -351,7 +359,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
351
 
352
  $user = get_userdata( $id );
353
  if ( ! $user ) {
354
- return new WP_Error( 'rest_user_invalid_id', __( 'User id is invalid.' ), array( 'status' => 400 ) );
355
  }
356
 
357
  if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
@@ -366,8 +374,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
366
  return new WP_Error( 'rest_user_invalid_slug', __( 'Slug is invalid.' ), array( 'status' => 400 ) );
367
  }
368
 
369
- if ( ! empty( $request['role'] ) ) {
370
- $check_permission = $this->check_role_update( $id, $request['role'] );
371
  if ( is_wp_error( $check_permission ) ) {
372
  return $check_permission;
373
  }
@@ -384,6 +392,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
384
  }
385
 
386
  $user = get_user_by( 'id', $id );
 
 
 
 
387
  $this->update_additional_fields_for_object( $user, $request );
388
 
389
  /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
@@ -406,7 +418,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
406
  $id = (int) $request['id'];
407
 
408
  if ( ! current_user_can( 'delete_user', $id ) ) {
409
- return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) );
410
  }
411
 
412
  return true;
@@ -430,24 +442,17 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
430
 
431
  $user = get_userdata( $id );
432
  if ( ! $user ) {
433
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 400 ) );
434
  }
435
 
436
  if ( ! empty( $reassign ) ) {
437
  if ( $reassign === $id || ! get_userdata( $reassign ) ) {
438
- return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user id.' ), array( 'status' => 400 ) );
439
  }
440
  }
441
 
442
  $request->set_param( 'context', 'edit' );
443
- $orig_user = $this->prepare_item_for_response( $user, $request );
444
-
445
- $data = $orig_user->get_data();
446
- $data = array(
447
- 'data' => $data,
448
- 'deleted' => true,
449
- );
450
- $orig_user->set_data( $data );
451
 
452
  /** Include admin user functions to get access to wp_delete_user() */
453
  require_once ABSPATH . 'wp-admin/includes/user.php';
@@ -455,18 +460,19 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
455
  $result = wp_delete_user( $id, $reassign );
456
 
457
  if ( ! $result ) {
458
- return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
459
  }
460
 
461
  /**
462
  * Fires after a user is deleted via the REST API.
463
  *
464
- * @param WP_User $user The user data.
465
- * @param WP_REST_Request $request The request sent to the API.
 
466
  */
467
- do_action( 'rest_delete_user', $user, $data, $request );
468
 
469
- return $orig_user;
470
  }
471
 
472
  /**
@@ -487,7 +493,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
487
  'url' => $user->user_url,
488
  'description' => $user->description,
489
  'link' => get_author_posts_url( $user->ID ),
490
- 'avatar_urls' => rest_get_avatar_urls( $user->user_email ),
491
  'nickname' => $user->nickname,
492
  'slug' => $user->user_nicename,
493
  'registered_date' => date( 'c', strtotime( $user->user_registered ) ),
@@ -496,6 +501,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
496
  'extra_capabilities' => $user->caps,
497
  );
498
 
 
 
 
 
 
 
499
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
500
  $data = $this->add_additional_fields_to_object( $data, $request );
501
  $data = $this->filter_response_by_context( $data, $context );
@@ -524,10 +535,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
524
  protected function prepare_links( $user ) {
525
  $links = array(
526
  'self' => array(
527
- 'href' => rest_url( sprintf( '/wp/v2/users/%d', $user->ID ) ),
528
  ),
529
  'collection' => array(
530
- 'href' => rest_url( '/wp/v2/users' ),
531
  ),
532
  );
533
 
@@ -576,13 +587,16 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
576
  if ( isset( $request['description'] ) ) {
577
  $prepared_user->description = $request['description'];
578
  }
579
- if ( isset( $request['role'] ) ) {
580
- $prepared_user->role = $request['role'];
581
- }
582
  if ( isset( $request['url'] ) ) {
583
  $prepared_user->user_url = $request['url'];
584
  }
585
 
 
 
 
 
 
586
  /**
587
  * Filter user data before inserting user via the REST API.
588
  *
@@ -593,20 +607,28 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
593
  }
594
 
595
  /**
596
- * Determine if the current user is allowed to make the desired role change.
597
  *
598
  * @param integer $user_id
599
- * @param string $role
600
  * @return WP_Error|boolean
601
  */
602
- protected function check_role_update( $user_id, $role ) {
603
  global $wp_roles;
604
 
605
- $potential_role = $wp_roles->role_objects[ $role ];
 
 
 
 
 
 
 
 
 
 
 
606
 
607
- // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
608
- // Multisite super admins can freely edit their blog roles -- they possess all caps.
609
- if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || get_current_user_id() !== $user_id || $potential_role->has_cap( 'edit_users' ) ) {
610
  // The new role must be editable by the logged-in user.
611
 
612
  /** Include admin functions to get access to get_editable_roles() */
@@ -614,13 +636,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
614
 
615
  $editable_roles = get_editable_roles();
616
  if ( empty( $editable_roles[ $role ] ) ) {
617
- return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
618
  }
619
-
620
- return true;
621
  }
622
 
623
- return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => rest_authorization_required_code() ) );
 
624
  }
625
 
626
  /**
@@ -629,18 +650,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
629
  * @return array
630
  */
631
  public function get_item_schema() {
632
- $avatar_properties = array();
633
-
634
- $avatar_sizes = rest_get_avatar_sizes();
635
- foreach ( $avatar_sizes as $size ) {
636
- $avatar_properties[ $size ] = array(
637
- 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
638
- 'type' => 'string',
639
- 'format' => 'uri',
640
- 'context' => array( 'embed', 'view', 'edit' ),
641
- );
642
- }
643
-
644
  global $wp_roles;
645
 
646
  $schema = array(
@@ -649,13 +658,13 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
649
  'type' => 'object',
650
  'properties' => array(
651
  'id' => array(
652
- 'description' => __( 'Unique identifier for the object.' ),
653
  'type' => 'integer',
654
  'context' => array( 'embed', 'view', 'edit' ),
655
  'readonly' => true,
656
  ),
657
  'username' => array(
658
- 'description' => __( 'Login name for the user.' ),
659
  'type' => 'string',
660
  'context' => array( 'edit' ),
661
  'required' => true,
@@ -664,7 +673,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
664
  ),
665
  ),
666
  'name' => array(
667
- 'description' => __( 'Display name for the object.' ),
668
  'type' => 'string',
669
  'context' => array( 'embed', 'view', 'edit' ),
670
  'arg_options' => array(
@@ -672,37 +681,36 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
672
  ),
673
  ),
674
  'first_name' => array(
675
- 'description' => __( 'First name for the object.' ),
676
  'type' => 'string',
677
- 'context' => array( 'view', 'edit' ),
678
  'arg_options' => array(
679
  'sanitize_callback' => 'sanitize_text_field',
680
  ),
681
  ),
682
  'last_name' => array(
683
- 'description' => __( 'Last name for the object.' ),
684
  'type' => 'string',
685
- 'context' => array( 'view', 'edit' ),
686
  'arg_options' => array(
687
  'sanitize_callback' => 'sanitize_text_field',
688
  ),
689
  ),
690
  'email' => array(
691
- 'description' => __( 'The email address for the object.' ),
692
  'type' => 'string',
693
  'format' => 'email',
694
- 'context' => array( 'view', 'edit' ),
695
  'required' => true,
696
  ),
697
  'url' => array(
698
- 'description' => __( 'URL of the object.' ),
699
  'type' => 'string',
700
  'format' => 'uri',
701
  'context' => array( 'embed', 'view', 'edit' ),
702
- 'readonly' => true,
703
  ),
704
  'description' => array(
705
- 'description' => __( 'Description of the object.' ),
706
  'type' => 'string',
707
  'context' => array( 'embed', 'view', 'edit' ),
708
  'arg_options' => array(
@@ -710,29 +718,22 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
710
  ),
711
  ),
712
  'link' => array(
713
- 'description' => __( 'Author URL to the object.' ),
714
  'type' => 'string',
715
  'format' => 'uri',
716
  'context' => array( 'embed', 'view', 'edit' ),
717
  'readonly' => true,
718
  ),
719
- 'avatar_urls' => array(
720
- 'description' => __( 'Avatar URLs for the object.' ),
721
- 'type' => 'object',
722
- 'context' => array( 'embed', 'view', 'edit' ),
723
- 'readonly' => true,
724
- 'properties' => $avatar_properties,
725
- ),
726
  'nickname' => array(
727
- 'description' => __( 'The nickname for the object.' ),
728
  'type' => 'string',
729
- 'context' => array( 'view', 'edit' ),
730
  'arg_options' => array(
731
  'sanitize_callback' => 'sanitize_text_field',
732
  ),
733
  ),
734
  'slug' => array(
735
- 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
736
  'type' => 'string',
737
  'context' => array( 'embed', 'view', 'edit' ),
738
  'arg_options' => array(
@@ -740,35 +741,53 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
740
  ),
741
  ),
742
  'registered_date' => array(
743
- 'description' => __( 'Registration date for the user.' ),
744
  'type' => 'date-time',
745
- 'context' => array( 'view', 'edit' ),
746
  'readonly' => true,
747
  ),
748
  'roles' => array(
749
- 'description' => __( 'Roles assigned to the user.' ),
750
  'type' => 'array',
751
- 'context' => array( 'view', 'edit' ),
752
- 'readonly' => true,
753
- ),
754
- 'role' => array(
755
- 'description' => __( 'Role assigned to the user.' ),
756
- 'type' => 'string',
757
- 'enum' => array_keys( $wp_roles->role_objects ),
758
  ),
759
  'capabilities' => array(
760
- 'description' => __( 'All capabilities assigned to the user.' ),
761
  'type' => 'object',
762
- 'context' => array( 'view', 'edit' ),
763
  ),
764
  'extra_capabilities' => array(
765
- 'description' => __( 'Any extra capabilities assigned to the user.' ),
766
  'type' => 'object',
767
  'context' => array( 'edit' ),
768
  'readonly' => true,
769
  ),
770
  ),
771
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  return $this->add_additional_fields_schema( $schema );
773
  }
774
 
@@ -782,6 +801,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
782
 
783
  $query_params['context']['default'] = 'view';
784
 
 
 
 
 
 
 
785
  $query_params['include'] = array(
786
  'description' => __( 'Limit result set to specific ids.' ),
787
  'type' => 'array',
@@ -792,6 +817,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
792
  'description' => __( 'Offset the result set by a specific number of items.' ),
793
  'type' => 'integer',
794
  'sanitize_callback' => 'absint',
 
795
  );
796
  $query_params['order'] = array(
797
  'default' => 'asc',
@@ -799,6 +825,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
799
  'enum' => array( 'asc', 'desc' ),
800
  'sanitize_callback' => 'sanitize_key',
801
  'type' => 'string',
 
802
  );
803
  $query_params['orderby'] = array(
804
  'default' => 'name',
@@ -811,10 +838,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
811
  ),
812
  'sanitize_callback' => 'sanitize_key',
813
  'type' => 'string',
 
814
  );
815
  $query_params['slug'] = array(
816
- 'description' => __( 'Limit result set to users with a specific slug.' ),
817
  'type' => 'string',
 
818
  );
819
  return $query_params;
820
  }
5
  */
6
  class WP_REST_Users_Controller extends WP_REST_Controller {
7
 
8
+ public function __construct() {
9
+ $this->namespace = 'wp/v2';
10
+ $this->rest_base = 'users';
11
+ }
12
+
13
  /**
14
  * Register the routes for the objects of the controller.
15
  */
16
  public function register_routes() {
17
 
18
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
19
  array(
20
  'methods' => WP_REST_Server::READABLE,
21
  'callback' => array( $this, 'get_items' ),
31
  ),
32
  ) ),
33
  ),
 
34
  'schema' => array( $this, 'get_public_item_schema' ),
35
  ) );
36
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
37
  array(
38
  'methods' => WP_REST_Server::READABLE,
39
  'callback' => array( $this, 'get_item' ),
40
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
41
  'args' => array(
42
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
43
  ),
44
  ),
45
  array(
62
  'reassign' => array(),
63
  ),
64
  ),
 
65
  'schema' => array( $this, 'get_public_item_schema' ),
66
  ) );
67
 
68
+ register_rest_route( $this->namespace, '/' . $this->rest_base . '/me', array(
69
  'methods' => WP_REST_Server::READABLE,
70
  'callback' => array( $this, 'get_current_item' ),
71
  'args' => array(
104
 
105
  if ( ! current_user_can( 'list_users' ) ) {
106
  $prepared_args['has_published_posts'] = true;
 
 
 
107
  }
108
 
109
  if ( '' !== $prepared_args['search'] ) {
153
  $max_pages = ceil( $total_users / $per_page );
154
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
155
 
156
+ $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
157
  if ( $page > 1 ) {
158
  $prev_page = $page - 1;
159
  if ( $prev_page > $max_pages ) {
181
 
182
  $id = (int) $request['id'];
183
  $user = get_userdata( $id );
184
+ $types = get_post_types( array( 'public' => true ), 'names' );
185
 
186
  if ( empty( $id ) || empty( $user->ID ) ) {
187
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
188
  }
189
 
190
  if ( get_current_user_id() === $id ) {
191
  return true;
192
  }
193
 
194
+ if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
195
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource with view context.' ), array( 'status' => rest_authorization_required_code() ) );
196
+ } else if ( ! count_user_posts( $id, $types ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
197
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource.' ), array( 'status' => rest_authorization_required_code() ) );
 
 
 
 
198
  }
199
 
200
  return true;
211
  $user = get_userdata( $id );
212
 
213
  if ( empty( $id ) || empty( $user->ID ) ) {
214
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
215
  }
216
 
217
  $user = $this->prepare_item_for_response( $user, $request );
235
  $user = wp_get_current_user();
236
  $response = $this->prepare_item_for_response( $user, $request );
237
  $response = rest_ensure_response( $response );
238
+ $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $current_user_id ) ) );
239
  $response->set_status( 302 );
240
 
241
  return $response;
250
  public function create_item_permissions_check( $request ) {
251
 
252
  if ( ! current_user_can( 'create_users' ) ) {
253
+ return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create resource.' ), array( 'status' => rest_authorization_required_code() ) );
254
  }
255
 
256
  return true;
266
  global $wp_roles;
267
 
268
  if ( ! empty( $request['id'] ) ) {
269
+ return new WP_Error( 'rest_user_exists', __( 'Cannot create existing resource.' ), array( 'status' => 400 ) );
270
+ }
271
+
272
+ if ( ! empty( $request['roles'] ) ) {
273
+ $check_permission = $this->check_role_update( $request['id'], $request['roles'] );
274
+ if ( is_wp_error( $check_permission ) ) {
275
+ return $check_permission;
276
+ }
277
  }
278
 
279
  $user = $this->prepare_item_for_database( $request );
288
  if ( is_multisite() ) {
289
  $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
290
  if ( ! $user_id ) {
291
+ return new WP_Error( 'rest_user_create', __( 'Error creating new resource.' ), array( 'status' => 500 ) );
292
  }
293
  $user->ID = $user_id;
294
  $user_id = wp_update_user( $user );
303
  }
304
 
305
  $user = get_user_by( 'id', $user_id );
306
+ if ( ! empty( $request['roles'] ) ) {
307
+ array_map( array( $user, 'add_role' ), $request['roles'] );
308
+ }
309
+
310
  $this->update_additional_fields_for_object( $user, $request );
311
 
312
  /**
322
  $response = $this->prepare_item_for_response( $user, $request );
323
  $response = rest_ensure_response( $response );
324
  $response->set_status( 201 );
325
+ $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) );
326
 
327
  return $response;
328
  }
338
  $id = (int) $request['id'];
339
 
340
  if ( ! current_user_can( 'edit_user', $id ) ) {
341
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit resource.' ), array( 'status' => rest_authorization_required_code() ) );
342
  }
343
 
344
+ if ( ! empty( $request['roles'] ) && ! current_user_can( 'edit_users' ) ) {
345
+ return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this resource.' ), array( 'status' => rest_authorization_required_code() ) );
346
  }
347
 
348
  return true;
359
 
360
  $user = get_userdata( $id );
361
  if ( ! $user ) {
362
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 400 ) );
363
  }
364
 
365
  if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
374
  return new WP_Error( 'rest_user_invalid_slug', __( 'Slug is invalid.' ), array( 'status' => 400 ) );
375
  }
376
 
377
+ if ( ! empty( $request['roles'] ) ) {
378
+ $check_permission = $this->check_role_update( $id, $request['roles'] );
379
  if ( is_wp_error( $check_permission ) ) {
380
  return $check_permission;
381
  }
392
  }
393
 
394
  $user = get_user_by( 'id', $id );
395
+ if ( ! empty( $request['roles'] ) ) {
396
+ array_map( array( $user, 'add_role' ), $request['roles'] );
397
+ }
398
+
399
  $this->update_additional_fields_for_object( $user, $request );
400
 
401
  /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
418
  $id = (int) $request['id'];
419
 
420
  if ( ! current_user_can( 'delete_user', $id ) ) {
421
+ return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.' ), array( 'status' => rest_authorization_required_code() ) );
422
  }
423
 
424
  return true;
442
 
443
  $user = get_userdata( $id );
444
  if ( ! $user ) {
445
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 400 ) );
446
  }
447
 
448
  if ( ! empty( $reassign ) ) {
449
  if ( $reassign === $id || ! get_userdata( $reassign ) ) {
450
+ return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid resource id for reassignment.' ), array( 'status' => 400 ) );
451
  }
452
  }
453
 
454
  $request->set_param( 'context', 'edit' );
455
+ $response = $this->prepare_item_for_response( $user, $request );
 
 
 
 
 
 
 
456
 
457
  /** Include admin user functions to get access to wp_delete_user() */
458
  require_once ABSPATH . 'wp-admin/includes/user.php';
460
  $result = wp_delete_user( $id, $reassign );
461
 
462
  if ( ! $result ) {
463
+ return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
464
  }
465
 
466
  /**
467
  * Fires after a user is deleted via the REST API.
468
  *
469
+ * @param WP_User $user The user data.
470
+ * @param WP_REST_Response $response The response returned from the API.
471
+ * @param WP_REST_Request $request The request sent to the API.
472
  */
473
+ do_action( 'rest_delete_user', $user, $response, $request );
474
 
475
+ return $response;
476
  }
477
 
478
  /**
493
  'url' => $user->user_url,
494
  'description' => $user->description,
495
  'link' => get_author_posts_url( $user->ID ),
 
496
  'nickname' => $user->nickname,
497
  'slug' => $user->user_nicename,
498
  'registered_date' => date( 'c', strtotime( $user->user_registered ) ),
501
  'extra_capabilities' => $user->caps,
502
  );
503
 
504
+ $schema = $this->get_item_schema();
505
+
506
+ if ( ! empty( $schema['properties']['avatar_urls'] ) ) {
507
+ $data['avatar_urls'] = rest_get_avatar_urls( $user->user_email );
508
+ }
509
+
510
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
511
  $data = $this->add_additional_fields_to_object( $data, $request );
512
  $data = $this->filter_response_by_context( $data, $context );
535
  protected function prepare_links( $user ) {
536
  $links = array(
537
  'self' => array(
538
+ 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
539
  ),
540
  'collection' => array(
541
+ 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
542
  ),
543
  );
544
 
587
  if ( isset( $request['description'] ) ) {
588
  $prepared_user->description = $request['description'];
589
  }
590
+
 
 
591
  if ( isset( $request['url'] ) ) {
592
  $prepared_user->user_url = $request['url'];
593
  }
594
 
595
+ // setting roles will be handled outside of this function.
596
+ if ( isset( $request['roles'] ) ) {
597
+ $prepared_user->role = false;
598
+ }
599
+
600
  /**
601
  * Filter user data before inserting user via the REST API.
602
  *
607
  }
608
 
609
  /**
610
+ * Determine if the current user is allowed to make the desired roles change.
611
  *
612
  * @param integer $user_id
613
+ * @param array $roles
614
  * @return WP_Error|boolean
615
  */
616
+ protected function check_role_update( $user_id, $roles ) {
617
  global $wp_roles;
618
 
619
+ foreach ( $roles as $role ) {
620
+
621
+ if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
622
+ return new WP_Error( 'rest_user_invalid_role', sprintf( __( 'The role %s does not exist.' ), $role ), array( 'status' => 400 ) );
623
+ }
624
+
625
+ $potential_role = $wp_roles->role_objects[ $role ];
626
+ // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
627
+ // Multisite super admins can freely edit their blog roles -- they possess all caps.
628
+ if ( ! ( is_multisite() && current_user_can( 'manage_sites' ) ) && get_current_user_id() === $user_id && ! $potential_role->has_cap( 'edit_users' ) ) {
629
+ return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => rest_authorization_required_code() ) );
630
+ }
631
 
 
 
 
632
  // The new role must be editable by the logged-in user.
633
 
634
  /** Include admin functions to get access to get_editable_roles() */
636
 
637
  $editable_roles = get_editable_roles();
638
  if ( empty( $editable_roles[ $role ] ) ) {
639
+ return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => 403 ) );
640
  }
 
 
641
  }
642
 
643
+ return true;
644
+
645
  }
646
 
647
  /**
650
  * @return array
651
  */
652
  public function get_item_schema() {
 
 
 
 
 
 
 
 
 
 
 
 
653
  global $wp_roles;
654
 
655
  $schema = array(
658
  'type' => 'object',
659
  'properties' => array(
660
  'id' => array(
661
+ 'description' => __( 'Unique identifier for the resource.' ),
662
  'type' => 'integer',
663
  'context' => array( 'embed', 'view', 'edit' ),
664
  'readonly' => true,
665
  ),
666
  'username' => array(
667
+ 'description' => __( 'Login name for the resource.' ),
668
  'type' => 'string',
669
  'context' => array( 'edit' ),
670
  'required' => true,
673
  ),
674
  ),
675
  'name' => array(
676
+ 'description' => __( 'Display name for the resource.' ),
677
  'type' => 'string',
678
  'context' => array( 'embed', 'view', 'edit' ),
679
  'arg_options' => array(
681
  ),
682
  ),
683
  'first_name' => array(
684
+ 'description' => __( 'First name for the resource.' ),
685
  'type' => 'string',
686
+ 'context' => array( 'edit' ),
687
  'arg_options' => array(
688
  'sanitize_callback' => 'sanitize_text_field',
689
  ),
690
  ),
691
  'last_name' => array(
692
+ 'description' => __( 'Last name for the resource.' ),
693
  'type' => 'string',
694
+ 'context' => array( 'edit' ),
695
  'arg_options' => array(
696
  'sanitize_callback' => 'sanitize_text_field',
697
  ),
698
  ),
699
  'email' => array(
700
+ 'description' => __( 'The email address for the resource.' ),
701
  'type' => 'string',
702
  'format' => 'email',
703
+ 'context' => array( 'edit' ),
704
  'required' => true,
705
  ),
706
  'url' => array(
707
+ 'description' => __( 'URL of the resource.' ),
708
  'type' => 'string',
709
  'format' => 'uri',
710
  'context' => array( 'embed', 'view', 'edit' ),
 
711
  ),
712
  'description' => array(
713
+ 'description' => __( 'Description of the resource.' ),
714
  'type' => 'string',
715
  'context' => array( 'embed', 'view', 'edit' ),
716
  'arg_options' => array(
718
  ),
719
  ),
720
  'link' => array(
721
+ 'description' => __( 'Author URL to the resource.' ),
722
  'type' => 'string',
723
  'format' => 'uri',
724
  'context' => array( 'embed', 'view', 'edit' ),
725
  'readonly' => true,
726
  ),
 
 
 
 
 
 
 
727
  'nickname' => array(
728
+ 'description' => __( 'The nickname for the resource.' ),
729
  'type' => 'string',
730
+ 'context' => array( 'edit' ),
731
  'arg_options' => array(
732
  'sanitize_callback' => 'sanitize_text_field',
733
  ),
734
  ),
735
  'slug' => array(
736
+ 'description' => __( 'An alphanumeric identifier for the resource.' ),
737
  'type' => 'string',
738
  'context' => array( 'embed', 'view', 'edit' ),
739
  'arg_options' => array(
741
  ),
742
  ),
743
  'registered_date' => array(
744
+ 'description' => __( 'Registration date for the resource.' ),
745
  'type' => 'date-time',
746
+ 'context' => array( 'edit' ),
747
  'readonly' => true,
748
  ),
749
  'roles' => array(
750
+ 'description' => __( 'Roles assigned to the resource.' ),
751
  'type' => 'array',
752
+ 'context' => array( 'edit' ),
 
 
 
 
 
 
753
  ),
754
  'capabilities' => array(
755
+ 'description' => __( 'All capabilities assigned to the resource.' ),
756
  'type' => 'object',
757
+ 'context' => array( 'edit' ),
758
  ),
759
  'extra_capabilities' => array(
760
+ 'description' => __( 'Any extra capabilities assigned to the resource.' ),
761
  'type' => 'object',
762
  'context' => array( 'edit' ),
763
  'readonly' => true,
764
  ),
765
  ),
766
  );
767
+
768
+ if ( get_option( 'show_avatars' ) ) {
769
+ $avatar_properties = array();
770
+
771
+ $avatar_sizes = rest_get_avatar_sizes();
772
+ foreach ( $avatar_sizes as $size ) {
773
+ $avatar_properties[ $size ] = array(
774
+ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
775
+ 'type' => 'string',
776
+ 'format' => 'uri',
777
+ 'context' => array( 'embed', 'view', 'edit' ),
778
+ );
779
+ }
780
+
781
+ $schema['properties']['avatar_urls'] = array(
782
+ 'description' => __( 'Avatar URLs for the resource.' ),
783
+ 'type' => 'object',
784
+ 'context' => array( 'embed', 'view', 'edit' ),
785
+ 'readonly' => true,
786
+ 'properties' => $avatar_properties,
787
+ );
788
+
789
+ }
790
+
791
  return $this->add_additional_fields_schema( $schema );
792
  }
793
 
801
 
802
  $query_params['context']['default'] = 'view';
803
 
804
+ $query_params['exclude'] = array(
805
+ 'description' => __( 'Ensure result set excludes specific ids.' ),
806
+ 'type' => 'array',
807
+ 'default' => array(),
808
+ 'sanitize_callback' => 'wp_parse_id_list',
809
+ );
810
  $query_params['include'] = array(
811
  'description' => __( 'Limit result set to specific ids.' ),
812
  'type' => 'array',
817
  'description' => __( 'Offset the result set by a specific number of items.' ),
818
  'type' => 'integer',
819
  'sanitize_callback' => 'absint',
820
+ 'validate_callback' => 'rest_validate_request_arg',
821
  );
822
  $query_params['order'] = array(
823
  'default' => 'asc',
825
  'enum' => array( 'asc', 'desc' ),
826
  'sanitize_callback' => 'sanitize_key',
827
  'type' => 'string',
828
+ 'validate_callback' => 'rest_validate_request_arg',
829
  );
830
  $query_params['orderby'] = array(
831
  'default' => 'name',
838
  ),
839
  'sanitize_callback' => 'sanitize_key',
840
  'type' => 'string',
841
+ 'validate_callback' => 'rest_validate_request_arg',
842
  );
843
  $query_params['slug'] = array(
844
+ 'description' => __( 'Limit result set to resources with a specific slug.' ),
845
  'type' => 'string',
846
+ 'validate_callback' => 'rest_validate_request_arg',
847
  );
848
  return $query_params;
849
  }
plugin.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php
2
  /**
3
  * Plugin Name: WP REST API
4
- * Description: JSON-based REST API for WordPress, developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://wp-api.org
7
- * Version: 2.0-beta11
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -79,24 +79,11 @@ if ( ! class_exists( 'WP_REST_Comments_Controller' ) ) {
79
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
80
  }
81
 
82
- /**
83
- * WP_REST_Meta_Controller class.
84
- */
85
- if ( ! class_exists( 'WP_REST_Meta_Controller' ) ) {
86
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
87
- }
88
-
89
- /**
90
- * WP_REST_Meta_Posts_Controller class.
91
- */
92
- if ( ! class_exists( 'WP_REST_Meta_Posts_Controller' ) ) {
93
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
94
- }
95
-
96
  /**
97
  * REST extras.
98
  */
99
  include_once( dirname( __FILE__ ) . '/extras.php' );
 
100
 
101
  add_filter( 'init', '_add_extra_api_post_type_arguments', 11 );
102
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
@@ -179,10 +166,6 @@ if ( ! function_exists( 'create_initial_rest_routes' ) ) {
179
 
180
  $controller->register_routes();
181
 
182
- if ( post_type_supports( $post_type->name, 'custom-fields' ) ) {
183
- $meta_controller = new WP_REST_Meta_Posts_Controller( $post_type->name );
184
- $meta_controller->register_routes();
185
- }
186
  if ( post_type_supports( $post_type->name, 'revisions' ) ) {
187
  $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
188
  $revisions_controller->register_routes();
@@ -288,3 +271,130 @@ if ( ! function_exists( 'register_api_field' ) ) {
288
  register_rest_field( $object_type, $attributes, $args );
289
  }
290
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /**
3
  * Plugin Name: WP REST API
4
+ * Description: JSON-based REST API for WordPress, originally developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://wp-api.org
7
+ * Version: 2.0-beta12
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
79
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  /**
83
  * REST extras.
84
  */
85
  include_once( dirname( __FILE__ ) . '/extras.php' );
86
+ require_once( dirname( __FILE__ ) . '/core-integration.php' );
87
 
88
  add_filter( 'init', '_add_extra_api_post_type_arguments', 11 );
89
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
166
 
167
  $controller->register_routes();
168
 
 
 
 
 
169
  if ( post_type_supports( $post_type->name, 'revisions' ) ) {
170
  $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
171
  $revisions_controller->register_routes();
271
  register_rest_field( $object_type, $attributes, $args );
272
  }
273
  }
274
+
275
+ if ( ! function_exists( 'rest_validate_request_arg' ) ) {
276
+ /**
277
+ * Validate a request argument based on details registered to the route.
278
+ *
279
+ * @param mixed $value
280
+ * @param WP_REST_Request $request
281
+ * @param string $param
282
+ * @return WP_Error|boolean
283
+ */
284
+ function rest_validate_request_arg( $value, $request, $param ) {
285
+
286
+ $attributes = $request->get_attributes();
287
+ if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
288
+ return true;
289
+ }
290
+ $args = $attributes['args'][ $param ];
291
+
292
+ if ( ! empty( $args['enum'] ) ) {
293
+ if ( ! in_array( $value, $args['enum'] ) ) {
294
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $param, implode( ', ', $args['enum'] ) ) );
295
+ }
296
+ }
297
+
298
+ if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) {
299
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'integer' ) );
300
+ }
301
+
302
+ if ( 'string' === $args['type'] && ! is_string( $value ) ) {
303
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $param, 'string' ) );
304
+ }
305
+
306
+ if ( isset( $args['format'] ) ) {
307
+ switch ( $args['format'] ) {
308
+ case 'date-time' :
309
+ if ( ! rest_parse_date( $value ) ) {
310
+ return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
311
+ }
312
+ break;
313
+
314
+ case 'email' :
315
+ if ( ! is_email( $value ) ) {
316
+ return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
317
+ }
318
+ break;
319
+ }
320
+ }
321
+
322
+ if ( in_array( $args['type'], array( 'numeric', 'integer' ) ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
323
+ if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
324
+ if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
325
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be greater than %d (exclusive)' ), $param, $args['minimum'] ) );
326
+ } else if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
327
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be greater than %d (inclusive)' ), $param, $args['minimum'] ) );
328
+ }
329
+ } else if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
330
+ if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
331
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be less than %d (exclusive)' ), $param, $args['maximum'] ) );
332
+ } else if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
333
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be less than %d (inclusive)' ), $param, $args['maximum'] ) );
334
+ }
335
+ } else if ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
336
+ if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
337
+ if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
338
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (exclusive) and %d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
339
+ }
340
+ } else if ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
341
+ if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
342
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (inclusive) and %d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
343
+ }
344
+ } else if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
345
+ if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
346
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (exclusive) and %d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
347
+ }
348
+ } else if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
349
+ if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
350
+ return new WP_Error( 'rest_invalid_param', sprintf( __( '%s must be between %d (inclusive) and %d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ return true;
357
+ }
358
+ }
359
+
360
+ if ( ! function_exists( 'rest_sanitize_request_arg' ) ) {
361
+ /**
362
+ * Sanitize a request argument based on details registered to the route.
363
+ *
364
+ * @param mixed $value
365
+ * @param WP_REST_Request $request
366
+ * @param string $param
367
+ * @return mixed
368
+ */
369
+ function rest_sanitize_request_arg( $value, $request, $param ) {
370
+
371
+ $attributes = $request->get_attributes();
372
+ if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
373
+ return $value;
374
+ }
375
+ $args = $attributes['args'][ $param ];
376
+
377
+ if ( 'integer' === $args['type'] ) {
378
+ return (int) $value;
379
+ }
380
+
381
+ if ( isset( $args['format'] ) ) {
382
+ switch ( $args['format'] ) {
383
+ case 'date-time' :
384
+ return sanitize_text_field( $value );
385
+
386
+ case 'email' :
387
+ /*
388
+ * sanitize_email() validates, which would be unexpected
389
+ */
390
+ return sanitize_text_field( $value );
391
+
392
+ case 'uri' :
393
+ return esc_url_raw( $value );
394
+ }
395
+ }
396
+
397
+ return $value;
398
+ }
399
+
400
+ }
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.4
5
  Tested up to: 4.5-alpha
6
- Stable tag: 2.0-beta11
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -30,13 +30,151 @@ All tickets for the project are being tracked on [GitHub][]. You can also take a
30
 
31
  == Installation ==
32
 
33
- Drop this directory in and activate it.
34
 
35
  For full-flavoured API support, you'll need to be using pretty permalinks to use the plugin, as it uses custom rewrite rules to power the API.
36
 
 
 
37
  == Changelog ==
38
 
39
- = 2.0 Beta 11.0 =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  * BREAKING CHANGE: Moves Post->Term relations to the Post Resource
42
 
@@ -183,7 +321,7 @@ unnecessary call to `get_item()`.
183
 
184
  (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
185
 
186
- = 2.0 Beta 10.0 =
187
 
188
  * SECURITY: Ensure media of private posts are private too.
189
 
@@ -315,7 +453,7 @@ unnecessary call to `get_item()`.
315
 
316
  (props @danielbachhuber, [#1852](https://github.com/WP-API/WP-API/pull/1852))
317
 
318
- = 2.0 Beta 9.0 =
319
 
320
  * BREAKING CHANGE: Move tags and categories to top-level endpoints.
321
 
@@ -429,7 +567,7 @@ unnecessary call to `get_item()`.
429
 
430
  (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
431
 
432
- = 2.0 Beta 8.0 =
433
 
434
  * Prevent fatals when uploading attachment by including admin utilities.
435
 
@@ -465,7 +603,7 @@ unnecessary call to `get_item()`.
465
 
466
  (props @wpsmith, [#1759](https://github.com/WP-API/WP-API/pull/1759))
467
 
468
- = 2.0 Beta 7.0 =
469
 
470
  * Sync infrastructure from WordPress core as of r35691.
471
 
@@ -496,7 +634,7 @@ unnecessary call to `get_item()`.
496
 
497
  (props @danielbachhuber, [#1726](https://github.com/WP-API/WP-API/pull/1726))
498
 
499
- = 2.0 Beta 6.0 =
500
 
501
  * Remove global inclusion of wp-admin/includes/admin.php
502
 
@@ -592,7 +730,7 @@ unnecessary call to `get_item()`.
592
 
593
  (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
594
 
595
- = 2.0 Beta 5.0 =
596
 
597
  * Load api-core as a compatibility library
598
 
@@ -626,7 +764,7 @@ unnecessary call to `get_item()`.
626
 
627
  (props @joehoyle)
628
 
629
- = 2.0 Beta 4.0 =
630
 
631
  * Show public user information through the user controller.
632
 
@@ -859,7 +997,7 @@ unnecessary call to `get_item()`.
859
  [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
860
  [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
861
 
862
- = 2.0 Beta 3.0 =
863
 
864
  * Add ability to declare sanitization and default options for schema fields.
865
 
@@ -924,7 +1062,7 @@ unnecessary call to `get_item()`.
924
  [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
925
  [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
926
 
927
- = 2.0 Beta 2.0 =
928
 
929
  * Load the WP REST API before the main query runs.
930
 
@@ -1144,7 +1282,7 @@ unnecessary call to `get_item()`.
1144
 
1145
  Reported by @kacperszurek on 2015-05-16.
1146
 
1147
- = 2.0 Beta 1 =
1148
 
1149
  Partial rewrite and evolution of the REST API to prepare for core integration.
1150
 
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.4
5
  Tested up to: 4.5-alpha
6
+ Stable tag: 2.0-beta12
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
30
 
31
  == Installation ==
32
 
33
+ Install the WP REST API via the plugin directory, or by uploading the files manually to your server.
34
 
35
  For full-flavoured API support, you'll need to be using pretty permalinks to use the plugin, as it uses custom rewrite rules to power the API.
36
 
37
+ Once you've installed and activated the plugin, [check out the documentation](http://v2.wp-api.org/) for details on your newly available endpoints.
38
+
39
  == Changelog ==
40
 
41
+ = 2.0 Beta 12.0 (February 9, 2016) =
42
+
43
+ * BREAKING CHANGE: Removes meta endpoints from primary plugin.
44
+
45
+ If your project depends on post meta endpoints, please install [WP REST API Meta Endpoints](https://wordpress.org/plugins/rest-api-meta-endpoints/). For the gory history of meta, read [#1425](https://github.com/WP-API/WP-API/issues/1425) and linked issues. At this time, we recommend using `register_rest_field()` to expose meta ([docs](http://v2.wp-api.org/extending/modifying/)).
46
+
47
+ (props @danielbachhuber, [#2172](https://github.com/WP-API/WP-API/pull/2172))
48
+
49
+ * BREAKING CHANGE: Returns original resource when deleting PTCU.
50
+
51
+ Now that all resources require the `force` param, we don't need to wrap delete responses with the `trash` state.
52
+
53
+ (props @danielbachhuber, [#2163](https://github.com/WP-API/WP-API/pull/2163))
54
+
55
+ * BREAKING CHANGE: Uses `roles` rather than `role` in the Users controller.
56
+
57
+ Building the REST API gives us the opportunity to standardize on `roles`, instead of having both `roles` and `role`.
58
+
59
+ (props @joehoyle, [#2177](https://github.com/WP-API/WP-API/pull/2177))
60
+
61
+ * BREAKING CHANGES: Moves to consistent use of `context` throughout controllers.
62
+
63
+ Contexts limit the data present in the response. Here's how to think of them: `embed` correlates with sidebar representation, `view` represents the primary public view, and `edit` is the data expected for an editor.
64
+
65
+ (props @danielbachhuber, [#2205](https://github.com/WP-API/WP-API/pull/2205), [#2204](https://github.com/WP-API/WP-API/pull/2204), [#2203](https://github.com/WP-API/WP-API/pull/2203), [#2218](https://github.com/WP-API/WP-API/pull/2218), [#2216](https://github.com/WP-API/WP-API/pull/2216), [#2230](https://github.com/WP-API/WP-API/pull/2230), [#2184](https://github.com/WP-API/WP-API/pull/2184), [#2235](https://github.com/WP-API/WP-API/pull/2235))
66
+
67
+ * BREAKING CHANGE: Removes `post_*` query param support for `GET /wp/v2/comments`.
68
+
69
+ The proper pattern is to use `GET /wp/v2/posts` to fetch the post IDs to limit the request to.
70
+
71
+ (props @danielbachhuber, [#2165](https://github.com/WP-API/WP-API/pull/2165))
72
+
73
+ * BREAKING CHANGE: Introduces `rest_validate_request_arg()`/`rest_sanitize_request_arg()`.
74
+
75
+ Dedicated functions means we can use them for validating / sanitizing query args too. Removes `WP_REST_Controller::validate_schema_property()` and `WP_REST_Controller::sanitize_schema_property()`.
76
+
77
+ (props @danielbachhuber, [#2166](https://github.com/WP-API/WP-API/pull/2166), [#2213](https://github.com/WP-API/WP-API/pull/2213))
78
+
79
+ * Requires minimum value of 1 for `page` param.
80
+
81
+ (props @danielbachhuber, [#2241](https://github.com/WP-API/WP-API/pull/2241))
82
+
83
+ * Introduces `media_type` and `mime_type` params for `GET /wp/v2/media`.
84
+
85
+ (props @danielbachhuber, [#2231](https://github.com/WP-API/WP-API/pull/2231))
86
+
87
+ * Uses the term cache for post data.
88
+
89
+ (props @rmccue, [#2234](https://github.com/WP-API/WP-API/pull/2234))
90
+
91
+ * Supports for querying comments where `post=0`.
92
+
93
+ (props @danielbachhuber, [#1865](https://github.com/WP-API/WP-API/pull/1865))
94
+
95
+ * Exposes taxonomy and post type capabilities in `context=edit`.
96
+
97
+ (props @danielbachhuber, [#2216](https://github.com/WP-API/WP-API/pull/2216))
98
+
99
+ * Errors early when user can't GET types or taxonomies when `context=edit`.
100
+
101
+ (props @danielbachhuber, [#2218](https://github.com/WP-API/WP-API/pull/2218))
102
+
103
+ * Passes original $request context to `prepare_items_query`.
104
+
105
+ (props @danielbachhuber, [#2211](https://github.com/WP-API/WP-API/pull/2211))
106
+
107
+ * Adds `parent` and `parent_exclude` params to GET Comments.
108
+
109
+ (props @danielbachhuber, [#2206](https://github.com/WP-API/WP-API/pull/2206))
110
+
111
+ * Enforces minimum 1 and maximum 100 values for `per_page` parameter.
112
+
113
+ (props @danielbachhuber, [#2209](https://github.com/WP-API/WP-API/pull/2209))
114
+
115
+ * Adds `author` and `author_exclude` params to GET Posts and Comments.
116
+
117
+ (props @danielbachhuber, [#2200](https://github.com/WP-API/WP-API/pull/2202), [#2200](https://github.com/WP-API/WP-API/pull/2202))
118
+
119
+ * Adds `menu_order` param for `GET` Pages; support `menu_order` orderby.
120
+
121
+ (props @danielbachhuber, [#2193](https://github.com/WP-API/WP-API/pull/2193))
122
+
123
+ * Only calls `sanitize_text_field()` when sanitizing `type=string,format=email`.
124
+
125
+ (props @danielbachhuber, [#2185](https://github.com/WP-API/WP-API/pull/2185))
126
+
127
+ * Validates `GET /wp/v2/comments` private query params.
128
+
129
+ Returns an error when user doesn't have permission to use them, instead of silently discarding.
130
+
131
+ (props @danielbachhuber, [#2178](https://github.com/WP-API/WP-API/pull/2178))
132
+
133
+ * Explicitly prevents uploading attachments to other attachments or revisions.
134
+
135
+ (props @danielbachhuber, [#2180](https://github.com/WP-API/WP-API/pull/2180))
136
+
137
+ * Permits user urls to be edited through the API.
138
+
139
+ (props @danielbachhuber, [#2182](https://github.com/WP-API/WP-API/pull/2182))
140
+
141
+ * Marks all Status, Type and Taxonomy fields as `readonly`.
142
+
143
+ (props @danielbachhuber, [#2181](https://github.com/WP-API/WP-API/pull/2181))
144
+
145
+ * Adds validation callbacks to collection query params.
146
+
147
+ (props @danielbachhuber, [#2170](https://github.com/WP-API/WP-API/pull/2170), [#2171](https://github.com/WP-API/WP-API/pull/2171), [#2176](https://github.com/WP-API/WP-API/pull/2176), [#2174](https://github.com/WP-API/WP-API/pull/2174), [#2175](https://github.com/WP-API/WP-API/pull/2175))
148
+
149
+ * Links taxonomy terms to the post type collections they support.
150
+
151
+ (props @danielbachhuber, [#2167](https://github.com/WP-API/WP-API/pull/2167))
152
+
153
+ * Returns error when making a `GET` request with invalid context.
154
+
155
+ (props @danielbachhuber, [#2169](https://github.com/WP-API/WP-API/pull/2169))
156
+
157
+ * Adds `trash` status to `GET /wp/v2/statuses`.
158
+
159
+ (props @danielbachhuber, [#2158](https://github.com/WP-API/WP-API/pull/2158))
160
+
161
+ * Indicates when fields have HTML in schema.
162
+
163
+ (props @joehoyle, [#2159](https://github.com/WP-API/WP-API/pull/2159))
164
+
165
+ * Permits viewing of User who has published any Public posts.
166
+
167
+ (props @danielbachhuber, [#2155](https://github.com/WP-API/WP-API/pull/2155))
168
+
169
+ * Respects `show_avatars` option when adding avatars to Users.
170
+
171
+ (props @nullvariable, [#2151](https://github.com/WP-API/WP-API/pull/2151))
172
+
173
+ * Controllers use `$namespace` and `$rest_base` class variables for easier subclassing.
174
+
175
+ (props @danielbachhuber, [#2119](https://github.com/WP-API/WP-API/pull/2119), [#2130](https://github.com/WP-API/WP-API/pull/2130), [#2131](https://github.com/WP-API/WP-API/pull/2131), [#2132](https://github.com/WP-API/WP-API/pull/2132), [#2133](https://github.com/WP-API/WP-API/pull/2133), [#2134](https://github.com/WP-API/WP-API/pull/2134), [#2139](https://github.com/WP-API/WP-API/pull/2139), [#2141](https://github.com/WP-API/WP-API/pull/2141), [#2142](https://github.com/WP-API/WP-API/pull/2142))
176
+
177
+ = 2.0 Beta 11.0 (January 25, 2016) =
178
 
179
  * BREAKING CHANGE: Moves Post->Term relations to the Post Resource
180
 
321
 
322
  (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
323
 
324
+ = 2.0 Beta 10.0 (January 11, 2016) =
325
 
326
  * SECURITY: Ensure media of private posts are private too.
327
 
453
 
454
  (props @danielbachhuber, [#1852](https://github.com/WP-API/WP-API/pull/1852))
455
 
456
+ = 2.0 Beta 9.0 (December 11, 2015) =
457
 
458
  * BREAKING CHANGE: Move tags and categories to top-level endpoints.
459
 
567
 
568
  (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
569
 
570
+ = 2.0 Beta 8.0 (December 1, 2015) =
571
 
572
  * Prevent fatals when uploading attachment by including admin utilities.
573
 
603
 
604
  (props @wpsmith, [#1759](https://github.com/WP-API/WP-API/pull/1759))
605
 
606
+ = 2.0 Beta 7.0 (November 17, 2015) =
607
 
608
  * Sync infrastructure from WordPress core as of r35691.
609
 
634
 
635
  (props @danielbachhuber, [#1726](https://github.com/WP-API/WP-API/pull/1726))
636
 
637
+ = 2.0 Beta 6.0 (November 12, 2015) =
638
 
639
  * Remove global inclusion of wp-admin/includes/admin.php
640
 
730
 
731
  (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
732
 
733
+ = 2.0 Beta 5.0 (October 23, 2015) =
734
 
735
  * Load api-core as a compatibility library
736
 
764
 
765
  (props @joehoyle)
766
 
767
+ = 2.0 Beta 4.0 (August 14, 2015) =
768
 
769
  * Show public user information through the user controller.
770
 
997
  [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
998
  [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
999
 
1000
+ = 2.0 Beta 3.0 (July 1, 2015) =
1001
 
1002
  * Add ability to declare sanitization and default options for schema fields.
1003
 
1062
  [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
1063
  [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
1064
 
1065
+ = 2.0 Beta 2.0 (May 28, 2015) =
1066
 
1067
  * Load the WP REST API before the main query runs.
1068
 
1282
 
1283
  Reported by @kacperszurek on 2015-05-16.
1284
 
1285
+ = 2.0 Beta 1 (April 28, 2015) =
1286
 
1287
  Partial rewrite and evolution of the REST API to prepare for core integration.
1288