WordPress REST API (Version 2) - Version 2.0-beta3.1

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta4.1 to 2.0-beta3.1

CHANGELOG.md CHANGED
@@ -1,244 +1,11 @@
1
  # Changelog
2
 
3
- ## 2.0 Beta 4.1
4
 
5
  - Ensure media of private posts are private too.
6
 
7
  Reported by @danielbachhuber on 2016-01-08.
8
 
9
- ## 2.0 Beta 4.0
10
-
11
- - Show public user information through the user controller.
12
-
13
- In WordPress as of [r32683](https://core.trac.wordpress.org/changeset/32683) (scheduled for 4.3), `WP_User_Query` now has support for getting users with published posts.
14
-
15
- To match current behaviour in WordPress themes and feeds, we now expose this public user information. This includes the avatar, description, user ID, custom URL, display name, and URL, for users who have published at least one post on the site. This information is available to all clients; other fields and data for all users are still only available when authenticated.
16
-
17
- (props @joehoyle, @rmccue, @Shelob9, [#1397][gh-1397], [#839][gh-839], [#1435][gh-1435])
18
-
19
- - Send schema in OPTIONS requests and index.
20
-
21
- Rather than using separate `/schema` endpoints, the schema for items is now available through an OPTIONS request to the route. This means that full documentation is now available for endpoints through an OPTIONS request; this includes available methods, what data you can pass to the endpoint, and the data you'll get back.
22
-
23
- This data is now also available in the main index and namespace indexes. Simply request the index with `context=help` to get full schema data. Warning: this response will be huge. The schema for single endpoints is also available in the collection's OPTIONS response.
24
-
25
- **⚠️ This breaks backwards compatibility** for clients relying on schemas being at their own routes. These clients should instead send `OPTIONS` requests.
26
-
27
- Custom endpoints can register their own schema via the `schema` option on the route. This option should live side-by-side with the endpoints (similar to `relation` in WP's meta queries), so your registration call will look something like:
28
-
29
- ```php
30
- register_rest_route( 'test-ns', '/test', array(
31
- array(
32
- 'methods' => 'GET',
33
- 'callback' => 'my_test_callback',
34
- ),
35
-
36
- 'schema' => 'my_schema_callback',
37
- ) );
38
- ```
39
-
40
- (props @rmccue, [#1415][gh-1415], [#1222][gh-1222], [#1305][gh-1305])
41
-
42
- - Update JavaScript API for version 2.
43
-
44
- Our fantastic JavaScript API from version 1 is now available for version 2, refreshed with the latest and greatest changes.
45
-
46
- As a refresher: if you want to use it, simply make your script depend on `wp-api` when you enqueue it. If you want to enqueue the script manually, add `wp_enqueue_script( 'wp-api' )` to a callback on `wp_enqueue_scripts`.
47
-
48
- (props @tlovett1, @kadamwhite, @nathanrice, [#1374][gh-1374], [#1320][gh-1320])
49
-
50
- - Embed links inside items in a collection.
51
-
52
- Previously when fetching a collection of items, you only received the items themselves. To fetch the links as well via embedding, you needed to make a request to the single item with `_embed` set.
53
-
54
- No longer! You can now request a collection with embeds enabled (try `/wp/v2/posts?_embed`). This will embed links inside each item, allowing you to build interface items much easier (for example, post archive pages can get featured image data at the same time).
55
-
56
- This also applies to custom endpoints. Any endpoint that returns a list of objects will automatically have the embedding applied to objects inside the list.
57
-
58
- (props @rmccue, [#1459][gh-1459], [#865][gh-865])
59
-
60
- - Fix potential XSS vulnerability.
61
-
62
- Requests from other origins could potentially run code on the API domain, allowing cross-origin access to authentication cookies or similar.
63
-
64
- Reported by @xknown on 2015-07-23.
65
-
66
- - Move `/posts` `WP_Query` vars back to `filter` param.
67
-
68
- In version 1, we had internal `WP_Query` vars available via `filter` (e.g. `filter[s]=search+term`). For our first betas of version 2, we tried something different and exposed these directly on the endpoint. The experiment has now concluded; we didn't like this that much, so `filter` is back.
69
-
70
- We plan on adding nicer looking arguments to collections in future releases, with a view towards being consistent across different collections. We also plan on opening up the underlying query vars via `filter` for users, comments, and terms as well.
71
-
72
- **⚠️ This breaks backwards compatibility** for users using WP Query vars. Simply change your `x=y` parameter to `filter[x]=y`.
73
-
74
- (props @WP-API, [#1420][gh-1420])
75
-
76
- - Respect `rest_base` for taxonomies.
77
-
78
- **⚠️ This breaks backwards compatibility** by changing the `/wp/v2/posts/{id}/terms/post_tag` endpoint to `/wp/v2/posts/{id}/tag`.
79
-
80
- (props @joehoyle, [#1466][gh-1466])
81
-
82
- - Add permission check for retrieving the posts collection in edit context.
83
-
84
- By extension of the fact that getting any individual post yields a forbidden context error when the `context=edit` and the user is not authorized, the user should also not be permitted to list any post items when unauthorized.
85
-
86
- (props @danielpunkass, [#1412][gh-1412])
87
-
88
- - Ensure the REST API URL always has a trailing slash.
89
-
90
- Previously, when pretty permalinks were enabled, the API URL during autodiscovery looked like `/wp-json`, whereas the non-pretty permalink URL looked like `?rest_route=/`. These are now consistent, and always end with a slash character to simplify client URL building.
91
-
92
- (props @danielpunkass, @rmccue, [#1426][gh-1426], [#1442][gh-1442], [#1455][gh-1455], [#1467][gh-1467])
93
-
94
- - Use `wp_json_encode` instead of `json_encode`
95
-
96
- Since WordPress 4.1, `wp_json_encode` has been available to ensure encoded values are sane, and that non-UTF8 encodings are supported. We now use this function rather than doing the encode ourselves.
97
-
98
- (props @rmccue, @pento, [#1417][gh-1417])
99
-
100
- - Add `role` to schema for users.
101
-
102
- The available roles you can assign to a user are now available in the schema as an `enum`.
103
-
104
- (props @joehoyle, [#1400][gh-1400])
105
-
106
- - Use the schema for validation inside the comments controller.
107
-
108
- Previously, the schema was merely a decorative element for documentation inside the comments controller. To bring it inline with our other controllers, the schema is now used internally for validation.
109
-
110
- (props @joehoyle, [#1422][gh-1422])
111
-
112
- - Don't set the Location header in update responses.
113
-
114
- Previously, the Location header was sent when updating resources due to some inadvertent copypasta. This header should only be sent when creating to direct clients to the new resource, and isn't required when you're already on the correct resource.
115
-
116
- (props @rachelbaker, [#1441][gh-1441])
117
-
118
- - Re-enable the `rest_insert_post` action hook for `WP_REST_Posts_Controller`
119
-
120
- This was disabled during 2.0 development to avoid breaking lots of plugins on the `json_insert_post` action. Now that we've changed namespaces and are Mostly Stable (tm), we can re-enable the action.
121
-
122
- (props @jaredcobb, [#1427][gh-1427], [#1424][gh-1424])
123
-
124
- - Fix post taxonomy terms link URLs.
125
-
126
- When moving the routes in a previous beta, we forgot to correct the links on post objects to the new correct route. Sorry!
127
-
128
- (props @rachelbaker, @joehoyle, [#1447][gh-1447], [#1383][gh-1383])
129
-
130
- - Use `wp_get_attachment_image_src()` on the image sizes in attachments.
131
-
132
- Since the first versions of the API, we've been building attachment URLs via `str_replace`. Who knows why we were doing this, but it caused problems with custom attachment URLs (such as CDN-hosted images). This now correctly uses the internal functions and filters.
133
-
134
- (props @joehoyle, [#1462][gh-1462])
135
-
136
- - Make the embed context a default, not forced.
137
-
138
- If you want embeds to bring in full data rather than with `context=edit`, you can now change the link to specify `context=view` explicitly.
139
-
140
- (props @rmccue, [#1464][gh-1464])
141
-
142
- - Ensure we always use the `term_taxonomy_id` and never expose `term_id` publicly.
143
-
144
- Previously, `term_id` was inadvertently exposed in some error responses.
145
-
146
- (props @jdolan, [#1430][gh-1430])
147
-
148
- - Fix adding alt text to attachments on creation.
149
-
150
- Previously, this could only be set when updating an attachment, not when creating one.
151
-
152
- (props @joehoyle, [#1398][gh-1398])
153
-
154
- - Throw an error when registering routes without a namespace.
155
-
156
- Namespaces should **always** be provided when registering routes. We now throw a `doing_it_wrong` error when attempting to register one. (Previously, this caused a warning, or an invalid internal route.)
157
-
158
- If you *really* need to register namespaceless routes (e.g. to replicate an existing API), call `WP_REST_Server::register_route` directly rather than using the convenience function.
159
-
160
- (props @joehoyle, @rmccue, [#1355][gh-1355])
161
-
162
- - Show links on embeds.
163
-
164
- Previously, links were accidentally stripped from embedded response data.
165
-
166
- (props @rmccue, [#1472][gh-1472])
167
-
168
- - Clarify insufficient permisssion error when editing posts.
169
-
170
- (props @danielpunkass, [#1411][gh-1411])
171
-
172
- - Improve @return inline docs for rest_ensure_response()
173
-
174
- (props @Shelob9, [#1328][gh-1328])
175
-
176
- - Check taxonomies exist before trying to set properties.
177
-
178
- (props @joehoyle, @rachelbaker, [#1354][gh-1354])
179
-
180
- - Update controllers to ensure we use `sanitize_callback` wherever possible.
181
-
182
- (props @joehoyle, [#1399][gh-1399])
183
-
184
- - Add more phpDoc documentation, and correct existing documentation.
185
-
186
- (props @Shelob9, @rmccue, [#1432][gh-1432], [#1433][gh-1433], [#1465][gh-1465])
187
-
188
- - Update testing infrastructure.
189
-
190
- Travis now runs our coding standards tests in parallel, and now uses the new, faster container-based testing infrastructure.
191
-
192
- (props @ntwb, @frozzare, [#1449][gh-1449], [#1457][gh-1457])
193
-
194
- [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta3...2.0-beta4)
195
-
196
- [gh-839]: https://github.com/WP-API/WP-API/issues/839
197
- [gh-865]: https://github.com/WP-API/WP-API/issues/865
198
- [gh-1222]: https://github.com/WP-API/WP-API/issues/1222
199
- [gh-1305]: https://github.com/WP-API/WP-API/issues/1305
200
- [gh-1310]: https://github.com/WP-API/WP-API/issues/1310
201
- [gh-1320]: https://github.com/WP-API/WP-API/issues/1320
202
- [gh-1328]: https://github.com/WP-API/WP-API/issues/1328
203
- [gh-1354]: https://github.com/WP-API/WP-API/issues/1354
204
- [gh-1355]: https://github.com/WP-API/WP-API/issues/1355
205
- [gh-1372]: https://github.com/WP-API/WP-API/issues/1372
206
- [gh-1374]: https://github.com/WP-API/WP-API/issues/1374
207
- [gh-1383]: https://github.com/WP-API/WP-API/issues/1383
208
- [gh-1397]: https://github.com/WP-API/WP-API/issues/1397
209
- [gh-1398]: https://github.com/WP-API/WP-API/issues/1398
210
- [gh-1399]: https://github.com/WP-API/WP-API/issues/1399
211
- [gh-1400]: https://github.com/WP-API/WP-API/issues/1400
212
- [gh-1402]: https://github.com/WP-API/WP-API/issues/1402
213
- [gh-1411]: https://github.com/WP-API/WP-API/issues/1411
214
- [gh-1412]: https://github.com/WP-API/WP-API/issues/1412
215
- [gh-1413]: https://github.com/WP-API/WP-API/issues/1413
216
- [gh-1415]: https://github.com/WP-API/WP-API/issues/1415
217
- [gh-1417]: https://github.com/WP-API/WP-API/issues/1417
218
- [gh-1420]: https://github.com/WP-API/WP-API/issues/1420
219
- [gh-1422]: https://github.com/WP-API/WP-API/issues/1422
220
- [gh-1424]: https://github.com/WP-API/WP-API/issues/1424
221
- [gh-1426]: https://github.com/WP-API/WP-API/issues/1426
222
- [gh-1427]: https://github.com/WP-API/WP-API/issues/1427
223
- [gh-1430]: https://github.com/WP-API/WP-API/issues/1430
224
- [gh-1432]: https://github.com/WP-API/WP-API/issues/1432
225
- [gh-1433]: https://github.com/WP-API/WP-API/issues/1433
226
- [gh-1435]: https://github.com/WP-API/WP-API/issues/1435
227
- [gh-1441]: https://github.com/WP-API/WP-API/issues/1441
228
- [gh-1442]: https://github.com/WP-API/WP-API/issues/1442
229
- [gh-1447]: https://github.com/WP-API/WP-API/issues/1447
230
- [gh-1449]: https://github.com/WP-API/WP-API/issues/1449
231
- [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
232
- [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
233
- [gh-1457]: https://github.com/WP-API/WP-API/issues/1457
234
- [gh-1459]: https://github.com/WP-API/WP-API/issues/1459
235
- [gh-1462]: https://github.com/WP-API/WP-API/issues/1462
236
- [gh-1464]: https://github.com/WP-API/WP-API/issues/1464
237
- [gh-1465]: https://github.com/WP-API/WP-API/issues/1465
238
- [gh-1466]: https://github.com/WP-API/WP-API/issues/1466
239
- [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
240
- [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
241
-
242
  ## 2.0 Beta 3.0
243
 
244
  - Add ability to declare sanitization and default options for schema fields.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 3.1
4
 
5
  - Ensure media of private posts are private too.
6
 
7
  Reported by @danielbachhuber on 2016-01-08.
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ## 2.0 Beta 3.0
10
 
11
  - Add ability to declare sanitization and default options for schema fields.
README.md CHANGED
@@ -4,7 +4,6 @@ Access your WordPress site's data through an easy-to-use HTTP REST API.
4
 
5
  [![Build Status](https://travis-ci.org/WP-API/WP-API.svg?branch=develop)](https://travis-ci.org/WP-API/WP-API)
6
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WP-API/WP-API/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/WP-API/WP-API/?branch=develop)
7
- [![codecov.io](http://codecov.io/github/WP-API/WP-API/coverage.svg?branch=develop)](http://codecov.io/github/WP-API/WP-API?branch=develop)
8
 
9
  ## WARNING
10
 
@@ -50,9 +49,6 @@ There's no fixed timeline for integration into core at this time, but getting cl
50
  Drop this directory in and activate it. You need to be using pretty permalinks
51
  to use the plugin, as it uses custom rewrite rules to power the API.
52
 
53
- Also, be sure to use the `trunk` branch of WordPress Core as there are potentially
54
- recent commits to Core that the REST API relies on.
55
-
56
  ## Issue Tracking
57
 
58
  All tickets for the project are being tracked on [GitHub][]. You can also take a
4
 
5
  [![Build Status](https://travis-ci.org/WP-API/WP-API.svg?branch=develop)](https://travis-ci.org/WP-API/WP-API)
6
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WP-API/WP-API/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/WP-API/WP-API/?branch=develop)
 
7
 
8
  ## WARNING
9
 
49
  Drop this directory in and activate it. You need to be using pretty permalinks
50
  to use the plugin, as it uses custom rewrite rules to power the API.
51
 
 
 
 
52
  ## Issue Tracking
53
 
54
  All tickets for the project are being tracked on [GitHub][]. You can also take a
extras.php CHANGED
@@ -74,7 +74,7 @@ function rest_output_link_header() {
74
 
75
  $api_root = get_rest_url();
76
 
77
- if ( empty( $api_root ) ) {
78
  return;
79
  }
80
 
@@ -234,7 +234,6 @@ function rest_get_date_with_gmt( $date, $force_utc = false ) {
234
  * information. Including any information on the offset could be misleading.
235
  *
236
  * @param string $date
237
- * @return string Date formatted for ISO8601/RFC3339.
238
  */
239
  function rest_mysql_to_rfc3339( $date_string ) {
240
  $formatted = mysql2date( 'c', $date_string, false );
74
 
75
  $api_root = get_rest_url();
76
 
77
+ if ( empty($api_root) ) {
78
  return;
79
  }
80
 
234
  * information. Including any information on the offset could be misleading.
235
  *
236
  * @param string $date
 
237
  */
238
  function rest_mysql_to_rfc3339( $date_string ) {
239
  $formatted = mysql2date( 'c', $date_string, false );
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -73,10 +73,6 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
73
 
74
  wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
75
 
76
- if ( isset( $request['alt_text'] ) ) {
77
- update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
78
- }
79
-
80
  $this->update_additional_fields_for_object( $attachment, $request );
81
 
82
  $response = $this->get_item( array(
@@ -107,15 +103,16 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
107
  $data = $response->get_data();
108
 
109
  if ( isset( $request['alt_text'] ) ) {
110
- update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
111
  }
112
 
113
  $response = $this->get_item( array(
114
  'id' => $data['id'],
115
  'context' => 'edit',
116
  ));
117
-
118
- return rest_ensure_response( $response );
 
119
  }
120
 
121
  /**
@@ -128,15 +125,15 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
128
  $prepared_attachment = parent::prepare_item_for_database( $request );
129
 
130
  if ( isset( $request['caption'] ) ) {
131
- $prepared_attachment->post_excerpt = $request['caption'];
132
  }
133
 
134
  if ( isset( $request['description'] ) ) {
135
- $prepared_attachment->post_content = $request['description'];
136
  }
137
 
138
  if ( isset( $request['post'] ) ) {
139
- $prepared_attachment->post_parent = (int) $request['post'];
140
  }
141
 
142
  return $prepared_attachment;
@@ -169,13 +166,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
169
 
170
  foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
171
  // Use the same method image_downsize() does
172
- $image_src = wp_get_attachment_image_src( $post->ID, $size );
173
-
174
- if ( ! $image_src ) {
175
- continue;
176
- }
177
-
178
- $size_data['source_url'] = $image_src[0];
179
  }
180
  } else {
181
  $data['media_details']['sizes'] = new stdClass;
@@ -206,25 +197,16 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
206
  'description' => 'Alternative text to display when attachment is not displayed.',
207
  'type' => 'string',
208
  'context' => array( 'view', 'edit', 'embed' ),
209
- 'arg_options' => array(
210
- 'sanitize_callback' => 'sanitize_text_field',
211
- ),
212
  );
213
  $schema['properties']['caption'] = array(
214
  'description' => 'The caption for the attachment.',
215
  'type' => 'string',
216
  'context' => array( 'view', 'edit' ),
217
- 'arg_options' => array(
218
- 'sanitize_callback' => 'wp_filter_post_kses',
219
- ),
220
  );
221
  $schema['properties']['description'] = array(
222
  'description' => 'The description for the attachment.',
223
  'type' => 'string',
224
  'context' => array( 'view', 'edit' ),
225
- 'arg_options' => array(
226
- 'sanitize_callback' => 'wp_filter_post_kses',
227
- ),
228
  );
229
  $schema['properties']['media_type'] = array(
230
  'description' => 'Type of attachment.',
73
 
74
  wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
75
 
 
 
 
 
76
  $this->update_additional_fields_for_object( $attachment, $request );
77
 
78
  $response = $this->get_item( array(
103
  $data = $response->get_data();
104
 
105
  if ( isset( $request['alt_text'] ) ) {
106
+ update_post_meta( $data['id'], '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
107
  }
108
 
109
  $response = $this->get_item( array(
110
  'id' => $data['id'],
111
  'context' => 'edit',
112
  ));
113
+ $response = rest_ensure_response( $response );
114
+ $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $data['id'] ) );
115
+ return $response;
116
  }
117
 
118
  /**
125
  $prepared_attachment = parent::prepare_item_for_database( $request );
126
 
127
  if ( isset( $request['caption'] ) ) {
128
+ $prepared_attachment->post_excerpt = wp_filter_post_kses( $request['caption'] );
129
  }
130
 
131
  if ( isset( $request['description'] ) ) {
132
+ $prepared_attachment->post_content = wp_filter_post_kses( $request['description'] );
133
  }
134
 
135
  if ( isset( $request['post'] ) ) {
136
+ $prepared_attachment->post_parent = (int) $request['post_parent'];
137
  }
138
 
139
  return $prepared_attachment;
166
 
167
  foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
168
  // Use the same method image_downsize() does
169
+ $size_data['source_url'] = str_replace( $img_url_basename, $size_data['file'], $data['source_url'] );
 
 
 
 
 
 
170
  }
171
  } else {
172
  $data['media_details']['sizes'] = new stdClass;
197
  'description' => 'Alternative text to display when attachment is not displayed.',
198
  'type' => 'string',
199
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
200
  );
201
  $schema['properties']['caption'] = array(
202
  'description' => 'The caption for the attachment.',
203
  'type' => 'string',
204
  'context' => array( 'view', 'edit' ),
 
 
 
205
  );
206
  $schema['properties']['description'] = array(
207
  'description' => 'The description for the attachment.',
208
  'type' => 'string',
209
  'context' => array( 'view', 'edit' ),
 
 
 
210
  );
211
  $schema['properties']['media_type'] = array(
212
  'description' => 'Type of attachment.',
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -22,10 +22,54 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
22
  'methods' => WP_REST_Server::CREATABLE,
23
  'callback' => array( $this, 'create_item' ),
24
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
25
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  ),
27
-
28
- 'schema' => array( $this, 'get_public_item_schema' ),
29
  ) );
30
 
31
  register_rest_route( 'wp/v2', '/comments/(?P<id>[\d]+)', array(
@@ -43,7 +87,40 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
43
  'methods' => WP_REST_Server::EDITABLE,
44
  'callback' => array( $this, 'update_item' ),
45
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
46
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  ),
48
  array(
49
  'methods' => WP_REST_Server::DELETABLE,
@@ -53,8 +130,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
53
  'force' => array(),
54
  ),
55
  ),
 
56
 
57
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
58
  ) );
59
  }
60
 
@@ -152,18 +232,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
152
  }
153
 
154
  $prepared_comment = $this->prepare_item_for_database( $request );
155
-
156
  // Setting remaining values before wp_insert_comment so we can
157
  // use wp_allow_comment().
158
- if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
159
- $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
160
- }
161
- if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
162
- $prepared_comment['comment_author_email'] = '';
163
- }
164
- if ( ! isset( $prepared_comment['comment_author_url'] ) ) {
165
- $prepared_comment['comment_author_url'] = '';
166
- }
167
  $prepared_comment['comment_author_IP'] = '127.0.0.1';
168
  $prepared_comment['comment_agent'] = '';
169
  $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
@@ -242,8 +312,13 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
242
  'id' => $id,
243
  'context' => 'edit',
244
  ) );
 
 
 
 
 
245
 
246
- return rest_ensure_response( $response );
247
  }
248
 
249
  /**
@@ -701,52 +776,48 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
701
  'type' => 'integer',
702
  'context' => array( 'view', 'edit', 'embed' ),
703
  'readonly' => true,
704
- ),
705
  'author' => array(
706
  'description' => 'The ID of the user object, if author was a user.',
707
  'type' => 'integer',
708
  'context' => array( 'view', 'edit', 'embed' ),
709
- ),
710
  'author_avatar_urls' => array(
711
  'description' => 'Avatar URLs for the object author.',
712
  'type' => 'object',
713
  'context' => array( 'view', 'edit', 'embed' ),
714
  'readonly' => true,
715
  'properties' => $avatar_properties,
716
- ),
717
  'author_email' => array(
718
  'description' => 'Email address for the object author.',
719
  'type' => 'string',
720
  'format' => 'email',
721
  'context' => array( 'edit' ),
722
- ),
723
  'author_ip' => array(
724
  'description' => 'IP address for the object author.',
725
  'type' => 'string',
726
  'context' => array( 'edit' ),
727
  'readonly' => true,
728
- ),
729
  'author_name' => array(
730
  'description' => 'Display name for the object author.',
731
  'type' => 'string',
732
  'context' => array( 'view', 'edit', 'embed' ),
733
- 'arg_options' => array(
734
- 'sanitize_callback' => 'sanitize_text_field',
735
- 'default' => '',
736
  ),
737
- ),
738
  'author_url' => array(
739
  'description' => 'URL for the object author.',
740
  'type' => 'string',
741
  'format' => 'uri',
742
  'context' => array( 'view', 'edit', 'embed' ),
743
- ),
744
  'author_user_agent' => array(
745
  'description' => 'User agent for the object author.',
746
  'type' => 'string',
747
  'context' => array( 'edit' ),
748
  'readonly' => true,
749
- ),
750
  'content' => array(
751
  'description' => 'The content for the object.',
752
  'type' => 'object',
@@ -756,18 +827,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
756
  'description' => 'Content for the object, as it exists in the database.',
757
  'type' => 'string',
758
  'context' => array( 'edit' ),
759
- ),
760
  'rendered' => array(
761
  'description' => 'Content for the object, transformed for display.',
762
  'type' => 'string',
763
  'context' => array( 'view', 'edit', 'embed' ),
 
764
  ),
765
  ),
766
- 'arg_options' => array(
767
- 'sanitize_callback' => 'wp_filter_post_kses',
768
- 'default' => '',
769
- ),
770
- ),
771
  'date' => array(
772
  'description' => 'The date the object was published.',
773
  'type' => 'string',
@@ -784,6 +851,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
784
  'description' => 'Karma for the object.',
785
  'type' => 'integer',
786
  'context' => array( 'edit' ),
 
787
  ),
788
  'link' => array(
789
  'description' => 'URL to the object.',
@@ -796,9 +864,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
796
  'description' => 'The ID for the parent of the object.',
797
  'type' => 'integer',
798
  'context' => array( 'view', 'edit', 'embed' ),
799
- 'arg_options' => array(
800
- 'default' => 0,
801
- ),
802
  ),
803
  'post' => array(
804
  'description' => 'The ID of the associated post object.',
@@ -809,18 +874,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
809
  'description' => 'State of the object.',
810
  'type' => 'string',
811
  'context' => array( 'view', 'edit' ),
812
- 'arg_options' => array(
813
- 'sanitize_callback' => 'sanitize_key',
814
- ),
815
  ),
816
  'type' => array(
817
  'description' => 'Type of Comment for the object.',
818
  'type' => 'string',
819
  'context' => array( 'view', 'edit', 'embed' ),
820
- 'arg_options' => array(
821
- 'sanitize_callback' => 'sanitize_key',
822
- 'default' => '',
823
- ),
824
  ),
825
  ),
826
  );
22
  'methods' => WP_REST_Server::CREATABLE,
23
  'callback' => array( $this, 'create_item' ),
24
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
25
+ 'args' => array(
26
+ 'post' => array(
27
+ 'required' => true,
28
+ 'sanitize_callback' => 'absint',
29
+ ),
30
+ 'type' => array(
31
+ 'default' => '',
32
+ 'sanitize_callback' => 'sanitize_key',
33
+ ),
34
+ 'parent' => array(
35
+ 'default' => 0,
36
+ 'sanitize_callback' => 'absint',
37
+ ),
38
+ 'content' => array(
39
+ 'default' => '',
40
+ 'sanitize_callback' => 'wp_filter_post_kses',
41
+ ),
42
+ 'author' => array(
43
+ 'default' => 0,
44
+ 'sanitize_callback' => 'absint',
45
+ ),
46
+ 'author_name' => array(
47
+ 'default' => '',
48
+ 'sanitize_callback' => 'sanitize_text_field',
49
+ ),
50
+ 'author_email' => array(
51
+ 'default' => '',
52
+ 'sanitize_callback' => 'sanitize_email',
53
+ ),
54
+ 'author_url' => array(
55
+ 'default' => '',
56
+ 'sanitize_callback' => 'esc_url_raw',
57
+ ),
58
+ 'karma' => array(
59
+ 'default' => 0,
60
+ 'sanitize_callback' => 'absint',
61
+ ),
62
+ 'status' => array(
63
+ 'sanitize_callback' => 'sanitize_key',
64
+ ),
65
+ 'date' => array(
66
+ 'default' => current_time( 'mysql' ),
67
+ ),
68
+ 'date_gmt' => array(
69
+ 'default' => current_time( 'mysql', true ),
70
+ ),
71
+ ),
72
  ),
 
 
73
  ) );
74
 
75
  register_rest_route( 'wp/v2', '/comments/(?P<id>[\d]+)', array(
87
  'methods' => WP_REST_Server::EDITABLE,
88
  'callback' => array( $this, 'update_item' ),
89
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
90
+ 'args' => array(
91
+ 'post' => array(
92
+ 'sanitize_callback' => 'absint',
93
+ ),
94
+ 'type' => array(
95
+ 'sanitize_callback' => 'sanitize_key',
96
+ ),
97
+ 'parent' => array(
98
+ 'sanitize_callback' => 'absint',
99
+ ),
100
+ 'content' => array(
101
+ 'sanitize_callback' => 'wp_filter_post_kses',
102
+ ),
103
+ 'author' => array(
104
+ 'sanitize_callback' => 'absint',
105
+ ),
106
+ 'author_name' => array(
107
+ 'sanitize_callback' => 'sanitize_text_field',
108
+ ),
109
+ 'author_email' => array(
110
+ 'sanitize_callback' => 'sanitize_email',
111
+ ),
112
+ 'author_url' => array(
113
+ 'sanitize_callback' => 'esc_url_raw',
114
+ ),
115
+ 'karma' => array(
116
+ 'sanitize_callback' => 'absint',
117
+ ),
118
+ 'status' => array(
119
+ 'sanitize_callback' => 'sanitize_key',
120
+ ),
121
+ 'date' => array(),
122
+ 'date_gmt' => array(),
123
+ ),
124
  ),
125
  array(
126
  'methods' => WP_REST_Server::DELETABLE,
130
  'force' => array(),
131
  ),
132
  ),
133
+ ) );
134
 
135
+ register_rest_route( 'wp/v2', '/comments/schema', array(
136
+ 'methods' => WP_REST_Server::READABLE,
137
+ 'callback' => array( $this, 'get_public_item_schema' ),
138
  ) );
139
  }
140
 
232
  }
233
 
234
  $prepared_comment = $this->prepare_item_for_database( $request );
 
235
  // Setting remaining values before wp_insert_comment so we can
236
  // use wp_allow_comment().
 
 
 
 
 
 
 
 
 
237
  $prepared_comment['comment_author_IP'] = '127.0.0.1';
238
  $prepared_comment['comment_agent'] = '';
239
  $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
312
  'id' => $id,
313
  'context' => 'edit',
314
  ) );
315
+ $response = rest_ensure_response( $response );
316
+ if ( is_wp_error( $response ) ) {
317
+ return $response;
318
+ }
319
+ $response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment->comment_ID ) );
320
 
321
+ return $response;
322
  }
323
 
324
  /**
776
  'type' => 'integer',
777
  'context' => array( 'view', 'edit', 'embed' ),
778
  'readonly' => true,
779
+ ),
780
  'author' => array(
781
  'description' => 'The ID of the user object, if author was a user.',
782
  'type' => 'integer',
783
  'context' => array( 'view', 'edit', 'embed' ),
784
+ ),
785
  'author_avatar_urls' => array(
786
  'description' => 'Avatar URLs for the object author.',
787
  'type' => 'object',
788
  'context' => array( 'view', 'edit', 'embed' ),
789
  'readonly' => true,
790
  'properties' => $avatar_properties,
791
+ ),
792
  'author_email' => array(
793
  'description' => 'Email address for the object author.',
794
  'type' => 'string',
795
  'format' => 'email',
796
  'context' => array( 'edit' ),
797
+ ),
798
  'author_ip' => array(
799
  'description' => 'IP address for the object author.',
800
  'type' => 'string',
801
  'context' => array( 'edit' ),
802
  'readonly' => true,
803
+ ),
804
  'author_name' => array(
805
  'description' => 'Display name for the object author.',
806
  'type' => 'string',
807
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
808
  ),
 
809
  'author_url' => array(
810
  'description' => 'URL for the object author.',
811
  'type' => 'string',
812
  'format' => 'uri',
813
  'context' => array( 'view', 'edit', 'embed' ),
814
+ ),
815
  'author_user_agent' => array(
816
  'description' => 'User agent for the object author.',
817
  'type' => 'string',
818
  'context' => array( 'edit' ),
819
  'readonly' => true,
820
+ ),
821
  'content' => array(
822
  'description' => 'The content for the object.',
823
  'type' => 'object',
827
  'description' => 'Content for the object, as it exists in the database.',
828
  'type' => 'string',
829
  'context' => array( 'edit' ),
830
+ ),
831
  'rendered' => array(
832
  'description' => 'Content for the object, transformed for display.',
833
  'type' => 'string',
834
  'context' => array( 'view', 'edit', 'embed' ),
835
+ ),
836
  ),
837
  ),
 
 
 
 
 
838
  'date' => array(
839
  'description' => 'The date the object was published.',
840
  'type' => 'string',
851
  'description' => 'Karma for the object.',
852
  'type' => 'integer',
853
  'context' => array( 'edit' ),
854
+ 'readonly' => true,
855
  ),
856
  'link' => array(
857
  'description' => 'URL to the object.',
864
  'description' => 'The ID for the parent of the object.',
865
  'type' => 'integer',
866
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
867
  ),
868
  'post' => array(
869
  'description' => 'The ID of the associated post object.',
874
  'description' => 'State of the object.',
875
  'type' => 'string',
876
  'context' => array( 'view', 'edit' ),
 
 
 
877
  ),
878
  'type' => array(
879
  'description' => 'Type of Comment for the object.',
880
  'type' => 'string',
881
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
 
882
  ),
883
  ),
884
  );
lib/endpoints/class-wp-rest-meta-controller.php CHANGED
@@ -50,10 +50,13 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
50
  'methods' => WP_REST_Server::CREATABLE,
51
  'callback' => array( $this, 'create_item' ),
52
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
53
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
 
 
 
 
 
54
  ),
55
-
56
- 'schema' => array( $this, 'get_public_item_schema' ),
57
  ) );
58
  register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta/(?P<id>[\d]+)', array(
59
  array(
@@ -70,7 +73,10 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
70
  'methods' => WP_REST_Server::EDITABLE,
71
  'callback' => array( $this, 'update_item' ),
72
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
73
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
 
 
 
74
  ),
75
  array(
76
  'methods' => WP_REST_Server::DELETABLE,
@@ -78,8 +84,10 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
78
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
79
  'args' => array(),
80
  ),
81
-
82
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
83
  ) );
84
  }
85
 
@@ -101,16 +109,11 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
101
  'description' => 'Unique identifier for the object.',
102
  'type' => 'int',
103
  'context' => array( 'edit' ),
104
- 'readonly' => true,
105
  ),
106
  'key' => array(
107
  'description' => 'The key for the custom field.',
108
  'type' => 'string',
109
  'context' => array( 'edit' ),
110
- 'required' => true,
111
- 'arg_options' => array(
112
- 'sanitize_callback' => 'sanitize_text_field',
113
- ),
114
  ),
115
  'value' => array(
116
  'description' => 'The value of the custom field.',
@@ -352,14 +355,14 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
352
  return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
353
  }
354
 
355
- if ( empty( $request['key'] ) ) {
356
- return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
357
- }
358
-
359
  if ( is_protected_meta( $request['key'] ) ) {
360
  return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $request['key'] ), array( 'status' => 403 ) );
361
  }
362
 
 
 
 
 
363
  $meta_key = wp_slash( $request['key'] );
364
  $value = wp_slash( $request['value'] );
365
 
50
  'methods' => WP_REST_Server::CREATABLE,
51
  'callback' => array( $this, 'create_item' ),
52
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
53
+ 'args' => array(
54
+ 'key' => array(
55
+ 'required' => true,
56
+ ),
57
+ 'value' => array(),
58
+ ),
59
  ),
 
 
60
  ) );
61
  register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/meta/(?P<id>[\d]+)', array(
62
  array(
73
  'methods' => WP_REST_Server::EDITABLE,
74
  'callback' => array( $this, 'update_item' ),
75
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
76
+ 'args' => array(
77
+ 'key' => array(),
78
+ 'value' => array(),
79
+ ),
80
  ),
81
  array(
82
  'methods' => WP_REST_Server::DELETABLE,
84
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
85
  'args' => array(),
86
  ),
87
+ ) );
88
+ register_rest_route( 'wp/v2', $this->parent_base . '/meta/schema', array(
89
+ 'methods' => WP_REST_Server::READABLE,
90
+ 'callback' => array( $this, 'get_public_item_schema' ),
91
  ) );
92
  }
93
 
109
  'description' => 'Unique identifier for the object.',
110
  'type' => 'int',
111
  'context' => array( 'edit' ),
 
112
  ),
113
  'key' => array(
114
  'description' => 'The key for the custom field.',
115
  'type' => 'string',
116
  'context' => array( 'edit' ),
 
 
 
 
117
  ),
118
  'value' => array(
119
  'description' => 'The value of the custom field.',
355
  return new WP_Error( $code, __( 'Invalid provided meta data for action.' ), array( 'status' => 400 ) );
356
  }
357
 
 
 
 
 
358
  if ( is_protected_meta( $request['key'] ) ) {
359
  return new WP_Error( 'rest_meta_protected', sprintf( __( '%s is marked as a protected field.' ), $request['key'] ), array( 'status' => 403 ) );
360
  }
361
 
362
+ if ( empty( $request['key'] ) ) {
363
+ return new WP_Error( 'rest_meta_invalid_key', __( 'Invalid meta key.' ), array( 'status' => 400 ) );
364
+ }
365
+
366
  $meta_key = wp_slash( $request['key'] );
367
  $value = wp_slash( $request['value'] );
368
 
lib/endpoints/class-wp-rest-post-statuses-controller.php CHANGED
@@ -8,19 +8,18 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
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
- ),
15
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
16
  ) );
17
 
18
  register_rest_route( 'wp/v2', '/statuses/(?P<status>[\w-]+)', array(
19
- array(
20
- 'methods' => WP_REST_Server::READABLE,
21
- 'callback' => array( $this, 'get_item' ),
22
- ),
23
- 'schema' => array( $this, 'get_public_item_schema' ),
24
  ) );
25
  }
26
 
8
  public function register_routes() {
9
 
10
  register_rest_route( 'wp/v2', '/statuses', array(
11
+ 'methods' => WP_REST_Server::READABLE,
12
+ 'callback' => array( $this, 'get_items' ),
13
+ ) );
14
+
15
+ register_rest_route( 'wp/v2', '/statuses/schema', array(
16
+ 'methods' => WP_REST_Server::READABLE,
17
+ 'callback' => array( $this, 'get_public_item_schema' ),
18
  ) );
19
 
20
  register_rest_route( 'wp/v2', '/statuses/(?P<status>[\w-]+)', array(
21
+ 'methods' => WP_REST_Server::READABLE,
22
+ 'callback' => array( $this, 'get_item' ),
 
 
 
23
  ) );
24
  }
25
 
lib/endpoints/class-wp-rest-post-types-controller.php CHANGED
@@ -8,24 +8,23 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
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' => array(
15
- 'post_type' => array(
16
- 'sanitize_callback' => 'sanitize_key',
17
- ),
18
  ),
19
  ),
20
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
 
 
21
  ) );
22
 
23
  register_rest_route( 'wp/v2', '/types/(?P<type>[\w-]+)', array(
24
- array(
25
- 'methods' => WP_REST_Server::READABLE,
26
- 'callback' => array( $this, 'get_item' ),
27
- ),
28
- 'schema' => array( $this, 'get_public_item_schema' ),
29
  ) );
30
  }
31
 
8
  public function register_routes() {
9
 
10
  register_rest_route( 'wp/v2', '/types', array(
11
+ 'methods' => WP_REST_Server::READABLE,
12
+ 'callback' => array( $this, 'get_items' ),
13
+ 'args' => array(
14
+ 'post_type' => array(
15
+ 'sanitize_callback' => 'sanitize_key',
 
 
16
  ),
17
  ),
18
+ ) );
19
+
20
+ register_rest_route( 'wp/v2', '/types/schema', array(
21
+ 'methods' => WP_REST_Server::READABLE,
22
+ 'callback' => array( $this, 'get_public_item_schema' ),
23
  ) );
24
 
25
  register_rest_route( 'wp/v2', '/types/(?P<type>[\w-]+)', array(
26
+ 'methods' => WP_REST_Server::READABLE,
27
+ 'callback' => array( $this, 'get_item' ),
 
 
 
28
  ) );
29
  }
30
 
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -27,14 +27,18 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
27
  'default' => 10,
28
  'sanitize_callback' => 'absint',
29
  ),
30
- 'filter' => array(),
31
  );
32
 
 
 
 
 
 
 
33
  register_rest_route( 'wp/v2', '/' . $base, array(
34
  array(
35
  'methods' => WP_REST_Server::READABLE,
36
  'callback' => array( $this, 'get_items' ),
37
- 'permission_callback' => array( $this, 'get_items_permissions_check' ),
38
  'args' => $posts_args,
39
  ),
40
  array(
@@ -43,8 +47,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
43
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
44
  'args' => $this->get_endpoint_args_for_item_schema( true ),
45
  ),
46
-
47
- 'schema' => array( $this, 'get_public_item_schema' ),
48
  ) );
49
  register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
50
  array(
@@ -73,8 +75,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
73
  ),
74
  ),
75
  ),
76
-
77
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
78
  ) );
79
  }
80
 
@@ -85,17 +89,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
85
  * @return WP_Error|WP_REST_Response
86
  */
87
  public function get_items( $request ) {
88
- $args = array();
89
- $args['paged'] = $request['page'];
90
- $args['posts_per_page'] = $request['per_page'];
91
-
92
- if ( isset( $request['filter'] ) ) {
93
- $args = array_merge( $args, $request['filter'] );
94
- unset( $args['filter'] );
95
- }
96
-
97
- // Force the post_type argument, since it's not a user input variable
98
  $args['post_type'] = $this->post_type;
 
 
 
99
 
100
  /**
101
  * Alter the query arguments for a request.
@@ -225,7 +223,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
225
 
226
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
227
 
228
- do_action( 'rest_insert_post', $post, $request, true );
 
 
 
 
 
229
 
230
  $response = $this->get_item( array(
231
  'id' => $post_id,
@@ -367,23 +370,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
367
  return $response;
368
  }
369
 
370
- /**
371
- * Check if a given request has access to read /posts
372
- *
373
- * @param WP_REST_Request $request Full details about the request.
374
- * @return bool|WP_Error
375
- */
376
- public function get_items_permissions_check( $request ) {
377
-
378
- $post_type = get_post_type_object( $this->post_type );
379
-
380
- if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
381
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => 403 ) );
382
- }
383
-
384
- return true;
385
- }
386
-
387
  /**
388
  * Check if a given request has access to read a post
389
  *
@@ -395,7 +381,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
395
  $post = get_post( (int) $request['id'] );
396
 
397
  if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
398
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => 403 ) );
399
  }
400
 
401
  if ( $post ) {
@@ -682,7 +668,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
682
  }
683
  // Post slug
684
  if ( isset( $request['slug'] ) ) {
685
- $prepared_post->post_name = $request['slug'];
686
  }
687
 
688
  // Author
@@ -732,12 +718,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
732
 
733
  // Comment status
734
  if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
735
- $prepared_post->comment_status = $request['comment_status'];
736
  }
737
 
738
  // Ping status
739
  if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
740
- $prepared_post->ping_status = $request['ping_status'];
741
  }
742
 
743
  return apply_filters( 'rest_pre_insert_' . $this->post_type, $prepared_post, $request );
@@ -751,7 +737,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
751
  * @return WP_Error|string $post_status
752
  */
753
  protected function handle_status_param( $post_status, $post_type ) {
754
- $post_status = $post_status;
755
 
756
  switch ( $post_status ) {
757
  case 'draft':
@@ -1152,8 +1138,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1152
  continue;
1153
  }
1154
 
1155
- $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1156
- $terms_url = rest_url( trailingslashit( $base ) . $post->ID . '/terms/' . $tax_base );
 
 
 
 
 
1157
 
1158
  $links['http://v2.wp-api.org/term'][] = array(
1159
  'href' => $terms_url,
@@ -1253,9 +1244,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1253
  'description' => 'An alphanumeric identifier for the object unique to its type.',
1254
  'type' => 'string',
1255
  'context' => array( 'view', 'edit', 'embed' ),
1256
- 'arg_options' => array(
1257
- 'sanitize_callback' => 'sanitize_title',
1258
- ),
1259
  ),
1260
  'status' => array(
1261
  'description' => 'A named status for the object.',
27
  'default' => 10,
28
  'sanitize_callback' => 'absint',
29
  ),
 
30
  );
31
 
32
+ foreach ( $this->get_allowed_query_vars() as $var ) {
33
+ if ( ! isset( $posts_args[ $var ] ) ) {
34
+ $posts_args[ $var ] = array();
35
+ }
36
+ }
37
+
38
  register_rest_route( 'wp/v2', '/' . $base, array(
39
  array(
40
  'methods' => WP_REST_Server::READABLE,
41
  'callback' => array( $this, 'get_items' ),
 
42
  'args' => $posts_args,
43
  ),
44
  array(
47
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
48
  'args' => $this->get_endpoint_args_for_item_schema( true ),
49
  ),
 
 
50
  ) );
51
  register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
52
  array(
75
  ),
76
  ),
77
  ),
78
+ ) );
79
+ register_rest_route( 'wp/v2', '/' . $base . '/schema', array(
80
+ 'methods' => WP_REST_Server::READABLE,
81
+ 'callback' => array( $this, 'get_public_item_schema' ),
82
  ) );
83
  }
84
 
89
  * @return WP_Error|WP_REST_Response
90
  */
91
  public function get_items( $request ) {
92
+ $args = (array) $request->get_params();
 
 
 
 
 
 
 
 
 
93
  $args['post_type'] = $this->post_type;
94
+ $args['paged'] = $args['page'];
95
+ $args['posts_per_page'] = $args['per_page'];
96
+ unset( $args['page'] );
97
 
98
  /**
99
  * Alter the query arguments for a request.
223
 
224
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
225
 
226
+ /**
227
+ * @TODO: Enable rest_insert_post() action after
228
+ * Media Controller has been migrated to new style.
229
+ *
230
+ * do_action( 'rest_insert_post', $post, $request, true );
231
+ */
232
 
233
  $response = $this->get_item( array(
234
  'id' => $post_id,
370
  return $response;
371
  }
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  /**
374
  * Check if a given request has access to read a post
375
  *
381
  $post = get_post( (int) $request['id'] );
382
 
383
  if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
384
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
385
  }
386
 
387
  if ( $post ) {
668
  }
669
  // Post slug
670
  if ( isset( $request['slug'] ) ) {
671
+ $prepared_post->post_name = sanitize_title( $request['slug'] );
672
  }
673
 
674
  // Author
718
 
719
  // Comment status
720
  if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
721
+ $prepared_post->comment_status = sanitize_text_field( $request['comment_status'] );
722
  }
723
 
724
  // Ping status
725
  if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
726
+ $prepared_post->ping_status = sanitize_text_field( $request['ping_status'] );
727
  }
728
 
729
  return apply_filters( 'rest_pre_insert_' . $this->post_type, $prepared_post, $request );
737
  * @return WP_Error|string $post_status
738
  */
739
  protected function handle_status_param( $post_status, $post_type ) {
740
+ $post_status = sanitize_text_field( $post_status );
741
 
742
  switch ( $post_status ) {
743
  case 'draft':
1138
  continue;
1139
  }
1140
 
1141
+ if ( 'post_tag' === $tax ) {
1142
+ $terms_url = rest_url( '/wp/v2/terms/tag' );
1143
+ } else {
1144
+ $terms_url = rest_url( '/wp/v2/terms/' . $tax );
1145
+ }
1146
+
1147
+ $terms_url = add_query_arg( 'post', $post->ID, $terms_url );
1148
 
1149
  $links['http://v2.wp-api.org/term'][] = array(
1150
  'href' => $terms_url,
1244
  'description' => 'An alphanumeric identifier for the object unique to its type.',
1245
  'type' => 'string',
1246
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
1247
  ),
1248
  'status' => array(
1249
  'description' => 'A named status for the object.',
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -16,21 +16,19 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
16
  */
17
  public function register_routes() {
18
 
19
- $base = $this->posts_controller->get_post_type_base( $this->post_type );
20
- $tax_base = $this->terms_controller->get_taxonomy_base( $this->taxonomy );
21
 
22
  $query_params = $this->get_collection_params();
23
- register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s', $base, $tax_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' => $query_params,
29
  ),
30
- 'schema' => array( $this, 'get_public_item_schema' ),
31
  ) );
32
 
33
- register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s/(?P<term_id>[\d]+)', $base, $tax_base ), array(
34
  array(
35
  'methods' => WP_REST_Server::READABLE,
36
  'callback' => array( $this, 'get_item' ),
@@ -46,7 +44,11 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
46
  'callback' => array( $this, 'delete_item' ),
47
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
48
  ),
49
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
 
 
50
  ) );
51
  }
52
 
@@ -125,8 +127,7 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
125
  return $is_request_valid;
126
  }
127
 
128
- $term = get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy );
129
- $tt_ids = wp_set_object_terms( $post->ID, $term->term_id, $this->taxonomy, true );
130
 
131
  if ( is_wp_error( $tt_ids ) ) {
132
  return $tt_ids;
16
  */
17
  public function register_routes() {
18
 
19
+ $base = $this->posts_controller->get_post_type_base( $this->post_type );
 
20
 
21
  $query_params = $this->get_collection_params();
22
+ register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s', $base, $this->taxonomy ), array(
23
  array(
24
  'methods' => WP_REST_Server::READABLE,
25
  'callback' => array( $this, 'get_items' ),
26
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
27
  'args' => $query_params,
28
  ),
 
29
  ) );
30
 
31
+ register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s/(?P<term_id>[\d]+)', $base, $this->taxonomy ), array(
32
  array(
33
  'methods' => WP_REST_Server::READABLE,
34
  'callback' => array( $this, 'get_item' ),
44
  'callback' => array( $this, 'delete_item' ),
45
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
46
  ),
47
+ ) );
48
+
49
+ register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/terms/%s', $base, $this->taxonomy ) . '/schema', array(
50
+ 'methods' => WP_REST_Server::READABLE,
51
+ 'callback' => array( $this, 'get_public_item_schema' ),
52
  ) );
53
  }
54
 
127
  return $is_request_valid;
128
  }
129
 
130
+ $tt_ids = wp_set_object_terms( $post->ID, $term_id, $this->taxonomy, true );
 
131
 
132
  if ( is_wp_error( $tt_ids ) ) {
133
  return $tt_ids;
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -18,18 +18,14 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
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' => array(
26
- 'context' => array(
27
- 'default' => 'view',
28
- ),
29
  ),
30
  ),
31
-
32
- 'schema' => array( $this, 'get_public_item_schema' ),
33
  ) );
34
 
35
  register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions/(?P<id>[\d]+)', array(
@@ -48,10 +44,13 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
48
  'callback' => array( $this, 'delete_item' ),
49
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
50
  ),
51
-
52
- 'schema' => array( $this, 'get_public_item_schema' ),
53
  ));
54
 
 
 
 
 
 
55
  }
56
 
57
  /**
18
  public function register_routes() {
19
 
20
  register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions', array(
21
+ 'methods' => WP_REST_Server::READABLE,
22
+ 'callback' => array( $this, 'get_items' ),
23
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
24
+ 'args' => array(
25
+ 'context' => array(
26
+ 'default' => 'view',
 
 
27
  ),
28
  ),
 
 
29
  ) );
30
 
31
  register_rest_route( 'wp/v2', '/' . $this->parent_base . '/(?P<parent_id>[\d]+)/revisions/(?P<id>[\d]+)', array(
44
  'callback' => array( $this, 'delete_item' ),
45
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
46
  ),
 
 
47
  ));
48
 
49
+ register_rest_route( 'wp/v2', '/' . $this->parent_base . '/revisions/schema', array(
50
+ 'methods' => WP_REST_Server::READABLE,
51
+ 'callback' => array( $this, 'get_public_item_schema' ),
52
+ ) );
53
+
54
  }
55
 
56
  /**
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -8,25 +8,22 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
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' => array(
15
- 'post_type' => array(
16
- 'sanitize_callback' => 'sanitize_key',
17
- ),
18
  ),
19
  ),
20
- 'schema' => array( $this, 'get_public_item_schema' ),
21
  ) );
22
-
 
 
 
23
  register_rest_route( 'wp/v2', '/taxonomies/(?P<taxonomy>[\w-]+)', array(
24
- array(
25
- 'methods' => WP_REST_Server::READABLE,
26
- 'callback' => array( $this, 'get_item' ),
27
- 'permission_callback' => array( $this, 'get_item_permissions_check' ),
28
- ),
29
- 'schema' => array( $this, 'get_public_item_schema' ),
30
  ) );
31
  }
32
 
8
  public function register_routes() {
9
 
10
  register_rest_route( 'wp/v2', '/taxonomies', array(
11
+ 'methods' => WP_REST_Server::READABLE,
12
+ 'callback' => array( $this, 'get_items' ),
13
+ 'args' => array(
14
+ 'post_type' => array(
15
+ 'sanitize_callback' => 'sanitize_key',
 
 
16
  ),
17
  ),
 
18
  ) );
19
+ register_rest_route( 'wp/v2', '/taxonomies/schema', array(
20
+ 'methods' => WP_REST_Server::READABLE,
21
+ 'callback' => array( $this, 'get_public_item_schema' ),
22
+ ) );
23
  register_rest_route( 'wp/v2', '/taxonomies/(?P<taxonomy>[\w-]+)', array(
24
+ 'methods' => WP_REST_Server::READABLE,
25
+ 'callback' => array( $this, 'get_item' ),
26
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
 
 
 
27
  ) );
28
  }
29
 
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -32,10 +32,20 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
32
  'methods' => WP_REST_Server::CREATABLE,
33
  'callback' => array( $this, 'create_item' ),
34
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
35
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
 
 
 
 
 
 
 
 
 
 
 
 
36
  ),
37
-
38
- 'schema' => array( $this, 'get_public_item_schema' ),
39
  ));
40
  register_rest_route( 'wp/v2', '/terms/' . $base . '/(?P<id>[\d]+)', array(
41
  array(
@@ -47,15 +57,28 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
47
  'methods' => WP_REST_Server::EDITABLE,
48
  'callback' => array( $this, 'update_item' ),
49
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
50
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
 
 
 
 
 
 
 
 
 
 
 
51
  ),
52
  array(
53
  'methods' => WP_REST_Server::DELETABLE,
54
  'callback' => array( $this, 'delete_item' ),
55
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
56
  ),
57
-
58
- 'schema' => array( $this, 'get_public_item_schema' ),
 
 
59
  ) );
60
  }
61
 
@@ -170,15 +193,6 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
170
 
171
  $term = wp_insert_term( $name, $this->taxonomy, $args );
172
  if ( is_wp_error( $term ) ) {
173
-
174
- // If we're going to inform the client that the term exists, give them the identifier
175
- // they can actually use (term_taxonomy_id) -- NOT term_id.
176
-
177
- if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
178
- $existing_term = get_term( $term_id, $this->taxonomy );
179
- $term->add_data( $existing_term->term_taxonomy_id, 'term_exists' );
180
- }
181
-
182
  return $term;
183
  }
184
 
@@ -482,54 +496,44 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
482
  'type' => 'integer',
483
  'context' => array( 'view', 'embed' ),
484
  'readonly' => true,
485
- ),
486
  'count' => array(
487
  'description' => 'Number of published posts for the object.',
488
  'type' => 'integer',
489
  'context' => array( 'view' ),
490
  'readonly' => true,
491
- ),
492
  'description' => array(
493
  'description' => 'A human-readable description of the object.',
494
  'type' => 'string',
495
  'context' => array( 'view' ),
496
- 'arg_options' => array(
497
- 'sanitize_callback' => 'wp_filter_post_kses',
498
  ),
499
- ),
500
  'link' => array(
501
  'description' => 'URL to the object.',
502
  'type' => 'string',
503
  'format' => 'uri',
504
  'context' => array( 'view', 'embed' ),
505
  'readonly' => true,
506
- ),
507
  'name' => array(
508
  'description' => 'The title for the object.',
509
  'type' => 'string',
510
  'context' => array( 'view', 'embed' ),
511
- 'arg_options' => array(
512
- 'sanitize_callback' => 'sanitize_text_field',
513
  ),
514
- 'required' => true,
515
- ),
516
  'slug' => array(
517
  'description' => 'An alphanumeric identifier for the object unique to its type.',
518
  'type' => 'string',
519
  'context' => array( 'view', 'embed' ),
520
- 'arg_options' => array(
521
- 'sanitize_callback' => 'sanitize_title',
522
  ),
523
- ),
524
  'taxonomy' => array(
525
  'description' => 'Type attribution for the object.',
526
  'type' => 'string',
527
  'enum' => array_keys( get_taxonomies() ),
528
  'context' => array( 'view', 'embed' ),
529
  'readonly' => true,
 
530
  ),
531
- ),
532
- );
533
  $taxonomy = get_taxonomy( $this->taxonomy );
534
  if ( $taxonomy->hierarchical ) {
535
  $schema['properties']['parent'] = array(
32
  'methods' => WP_REST_Server::CREATABLE,
33
  'callback' => array( $this, 'create_item' ),
34
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
35
+ 'args' => array(
36
+ 'name' => array(
37
+ 'required' => true,
38
+ 'sanitize_callback' => 'sanitize_text_field',
39
+ ),
40
+ 'description' => array(
41
+ 'sanitize_callback' => 'wp_filter_post_kses',
42
+ ),
43
+ 'slug' => array(
44
+ 'sanitize_callback' => 'sanitize_title',
45
+ ),
46
+ 'parent' => array(),
47
+ ),
48
  ),
 
 
49
  ));
50
  register_rest_route( 'wp/v2', '/terms/' . $base . '/(?P<id>[\d]+)', array(
51
  array(
57
  'methods' => WP_REST_Server::EDITABLE,
58
  'callback' => array( $this, 'update_item' ),
59
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
60
+ 'args' => array(
61
+ 'name' => array(
62
+ 'sanitize_callback' => 'sanitize_text_field',
63
+ ),
64
+ 'description' => array(
65
+ 'sanitize_callback' => 'wp_filter_post_kses',
66
+ ),
67
+ 'slug' => array(
68
+ 'sanitize_callback' => 'sanitize_title',
69
+ ),
70
+ 'parent' => array(),
71
+ ),
72
  ),
73
  array(
74
  'methods' => WP_REST_Server::DELETABLE,
75
  'callback' => array( $this, 'delete_item' ),
76
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
77
  ),
78
+ ) );
79
+ register_rest_route( 'wp/v2', '/terms/' . $base . '/schema', array(
80
+ 'methods' => WP_REST_Server::READABLE,
81
+ 'callback' => array( $this, 'get_public_item_schema' ),
82
  ) );
83
  }
84
 
193
 
194
  $term = wp_insert_term( $name, $this->taxonomy, $args );
195
  if ( is_wp_error( $term ) ) {
 
 
 
 
 
 
 
 
 
196
  return $term;
197
  }
198
 
496
  'type' => 'integer',
497
  'context' => array( 'view', 'embed' ),
498
  'readonly' => true,
499
+ ),
500
  'count' => array(
501
  'description' => 'Number of published posts for the object.',
502
  'type' => 'integer',
503
  'context' => array( 'view' ),
504
  'readonly' => true,
505
+ ),
506
  'description' => array(
507
  'description' => 'A human-readable description of the object.',
508
  'type' => 'string',
509
  'context' => array( 'view' ),
 
 
510
  ),
 
511
  'link' => array(
512
  'description' => 'URL to the object.',
513
  'type' => 'string',
514
  'format' => 'uri',
515
  'context' => array( 'view', 'embed' ),
516
  'readonly' => true,
517
+ ),
518
  'name' => array(
519
  'description' => 'The title for the object.',
520
  'type' => 'string',
521
  'context' => array( 'view', 'embed' ),
 
 
522
  ),
 
 
523
  'slug' => array(
524
  'description' => 'An alphanumeric identifier for the object unique to its type.',
525
  'type' => 'string',
526
  'context' => array( 'view', 'embed' ),
 
 
527
  ),
 
528
  'taxonomy' => array(
529
  'description' => 'Type attribution for the object.',
530
  'type' => 'string',
531
  'enum' => array_keys( get_taxonomies() ),
532
  'context' => array( 'view', 'embed' ),
533
  'readonly' => true,
534
+ ),
535
  ),
536
+ );
 
537
  $taxonomy = get_taxonomy( $this->taxonomy );
538
  if ( $taxonomy->hierarchical ) {
539
  $schema['properties']['parent'] = array(
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -15,6 +15,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
15
  array(
16
  'methods' => WP_REST_Server::READABLE,
17
  'callback' => array( $this, 'get_items' ),
 
18
  'args' => $query_params,
19
  ),
20
  array(
@@ -27,8 +28,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
27
  ),
28
  ) ),
29
  ),
30
-
31
- 'schema' => array( $this, 'get_public_item_schema' ),
32
  ) );
33
  register_rest_route( 'wp/v2', '/users/(?P<id>[\d]+)', array(
34
  array(
@@ -57,8 +56,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
57
  'reassign' => array(),
58
  ),
59
  ),
60
-
61
- 'schema' => array( $this, 'get_public_item_schema' ),
62
  ) );
63
 
64
  register_rest_route( 'wp/v2', '/users/me', array(
@@ -67,8 +64,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
67
  'args' => array(
68
  'context' => array(),
69
  ),
70
- 'schema' => array( $this, 'get_public_item_schema' ),
71
  ));
 
 
 
 
 
72
  }
73
 
74
  /**
@@ -87,24 +88,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
87
  'id' => 'ID',
88
  'name' => 'display_name',
89
  'registered_date' => 'registered',
90
- );
91
  $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
92
  $prepared_args['search'] = $request['search'];
93
 
94
- if ( ! current_user_can( 'list_users' ) ) {
95
- $prepared_args['has_published_posts'] = true;
96
-
97
- // Only display a public subset of information
98
- $request['context'] = 'embed';
99
- }
100
-
101
- /**
102
- * Filter arguments, before passing to WP_User_Query, when querying users via the REST API
103
- *
104
- * @see https://codex.wordpress.org/Class_Reference/WP_User_Query
105
- * @param array $prepared_args Arguments for WP_User_Query
106
- * @param WP_REST_Request $request The current request
107
- */
108
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
109
 
110
  $query = new WP_User_Query( $prepared_args );
@@ -205,6 +192,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
205
  return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) );
206
  }
207
 
 
 
 
 
208
  $user = $this->prepare_item_for_database( $request );
209
 
210
  if ( is_multisite() ) {
@@ -234,13 +225,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
234
 
235
  $this->update_additional_fields_for_object( $user, $request );
236
 
237
- /**
238
- * Fires after a user is created via the REST API
239
- *
240
- * @param object $user Data used to create user (not a WP_User object)
241
- * @param WP_REST_Request $request Request object.
242
- * @param bool $bool A boolean that is false.
243
- */
244
  do_action( 'rest_insert_user', $user, $request, false );
245
 
246
  $response = $this->get_item( array(
@@ -305,8 +289,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
305
  'id' => $user_id,
306
  'context' => 'edit',
307
  ));
 
 
308
 
309
- return rest_ensure_response( $response );
310
  }
311
 
312
  /**
@@ -349,6 +335,21 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
349
  return $orig_user;
350
  }
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  /**
353
  * Check if a given request has access to read a user
354
  *
@@ -472,13 +473,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
472
 
473
  $data->add_links( $this->prepare_links( $user ) );
474
 
475
- /**
476
- * Filter user data before returning via the REST API
477
- *
478
- * @param WP_REST_Response $data Response data
479
- * @param object $user User object used to create response
480
- * @param WP_REST_Request $request Request object.
481
- */
482
  return apply_filters( 'rest_prepare_user', $data, $user, $request );
483
  }
484
 
@@ -544,18 +538,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
544
  $prepared_user->description = $request['description'];
545
  }
546
  if ( isset( $request['role'] ) ) {
547
- $prepared_user->role = $request['role'];
548
  }
549
  if ( isset( $request['url'] ) ) {
550
  $prepared_user->user_url = $request['url'];
551
  }
552
 
553
- /**
554
- * Filter user data before inserting user via REST API
555
- *
556
- * @param object $prepared_user User object.
557
- * @param WP_REST_Request $request Request object.
558
- */
559
  return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
560
  }
561
 
@@ -569,6 +557,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
569
  protected function check_role_update( $user_id, $role ) {
570
  global $wp_roles;
571
 
 
 
 
 
572
  $potential_role = $wp_roles->role_objects[ $role ];
573
 
574
  // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
@@ -603,8 +595,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
603
  );
604
  }
605
 
606
- global $wp_roles;
607
-
608
  $schema = array(
609
  '$schema' => 'http://json-schema.org/draft-04/schema#',
610
  'title' => 'user',
@@ -621,7 +611,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
621
  'description' => 'All capabilities assigned to the user.',
622
  'type' => 'object',
623
  'context' => array( 'view', 'edit' ),
624
- ),
625
  'description' => array(
626
  'description' => 'Description of the object.',
627
  'type' => 'string',
@@ -642,7 +632,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
642
  'type' => 'object',
643
  'context' => array( 'edit' ),
644
  'readonly' => true,
645
- ),
646
  'first_name' => array(
647
  'description' => 'First name for the object.',
648
  'type' => 'string',
@@ -698,12 +688,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
698
  'description' => 'Roles assigned to the user.',
699
  'type' => 'array',
700
  'context' => array( 'view', 'edit' ),
701
- 'readonly' => true,
702
- ),
703
- 'role' => array(
704
- 'description' => 'Role assigned to the user.',
705
- 'type' => 'string',
706
- 'enum' => array_keys( $wp_roles->role_objects ),
707
  ),
708
  'slug' => array(
709
  'description' => 'An alphanumeric identifier for the object unique to its type.',
15
  array(
16
  'methods' => WP_REST_Server::READABLE,
17
  'callback' => array( $this, 'get_items' ),
18
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
19
  'args' => $query_params,
20
  ),
21
  array(
28
  ),
29
  ) ),
30
  ),
 
 
31
  ) );
32
  register_rest_route( 'wp/v2', '/users/(?P<id>[\d]+)', array(
33
  array(
56
  'reassign' => array(),
57
  ),
58
  ),
 
 
59
  ) );
60
 
61
  register_rest_route( 'wp/v2', '/users/me', array(
64
  'args' => array(
65
  'context' => array(),
66
  ),
 
67
  ));
68
+
69
+ register_rest_route( 'wp/v2', '/users/schema', array(
70
+ 'methods' => WP_REST_Server::READABLE,
71
+ 'callback' => array( $this, 'get_public_item_schema' ),
72
+ ) );
73
  }
74
 
75
  /**
88
  'id' => 'ID',
89
  'name' => 'display_name',
90
  'registered_date' => 'registered',
91
+ );
92
  $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
93
  $prepared_args['search'] = $request['search'];
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
96
 
97
  $query = new WP_User_Query( $prepared_args );
192
  return new WP_Error( 'rest_user_exists', __( 'Cannot create existing user.' ), array( 'status' => 400 ) );
193
  }
194
 
195
+ if ( ! empty( $request['role'] ) && ! isset( $wp_roles->role_objects[ $request['role'] ] ) ) {
196
+ return new WP_Error( 'rest_user_invalid_role', __( 'Role is invalid.' ), array( 'status' => 400 ) );
197
+ }
198
+
199
  $user = $this->prepare_item_for_database( $request );
200
 
201
  if ( is_multisite() ) {
225
 
226
  $this->update_additional_fields_for_object( $user, $request );
227
 
 
 
 
 
 
 
 
228
  do_action( 'rest_insert_user', $user, $request, false );
229
 
230
  $response = $this->get_item( array(
289
  'id' => $user_id,
290
  'context' => 'edit',
291
  ));
292
+ $response = rest_ensure_response( $response );
293
+ $response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
294
 
295
+ return $response;
296
  }
297
 
298
  /**
335
  return $orig_user;
336
  }
337
 
338
+ /**
339
+ * Check if a given request has access to list users
340
+ *
341
+ * @param WP_REST_Request $request Full details about the request.
342
+ * @return bool
343
+ */
344
+ public function get_items_permissions_check( $request ) {
345
+
346
+ if ( ! current_user_can( 'list_users' ) ) {
347
+ return false;
348
+ }
349
+
350
+ return true;
351
+ }
352
+
353
  /**
354
  * Check if a given request has access to read a user
355
  *
473
 
474
  $data->add_links( $this->prepare_links( $user ) );
475
 
 
 
 
 
 
 
 
476
  return apply_filters( 'rest_prepare_user', $data, $user, $request );
477
  }
478
 
538
  $prepared_user->description = $request['description'];
539
  }
540
  if ( isset( $request['role'] ) ) {
541
+ $prepared_user->role = sanitize_text_field( $request['role'] );
542
  }
543
  if ( isset( $request['url'] ) ) {
544
  $prepared_user->user_url = $request['url'];
545
  }
546
 
 
 
 
 
 
 
547
  return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
548
  }
549
 
557
  protected function check_role_update( $user_id, $role ) {
558
  global $wp_roles;
559
 
560
+ if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
561
+ return new WP_Error( 'rest_user_invalid_role', __( 'Role is invalid.' ), array( 'status' => 400 ) );
562
+ }
563
+
564
  $potential_role = $wp_roles->role_objects[ $role ];
565
 
566
  // Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
595
  );
596
  }
597
 
 
 
598
  $schema = array(
599
  '$schema' => 'http://json-schema.org/draft-04/schema#',
600
  'title' => 'user',
611
  'description' => 'All capabilities assigned to the user.',
612
  'type' => 'object',
613
  'context' => array( 'view', 'edit' ),
614
+ ),
615
  'description' => array(
616
  'description' => 'Description of the object.',
617
  'type' => 'string',
632
  'type' => 'object',
633
  'context' => array( 'edit' ),
634
  'readonly' => true,
635
+ ),
636
  'first_name' => array(
637
  'description' => 'First name for the object.',
638
  'type' => 'string',
688
  'description' => 'Roles assigned to the user.',
689
  'type' => 'array',
690
  'context' => array( 'view', 'edit' ),
 
 
 
 
 
 
691
  ),
692
  'slug' => array(
693
  'description' => 'An alphanumeric identifier for the object unique to its type.',
lib/infrastructure/class-wp-rest-request.php CHANGED
@@ -680,7 +680,7 @@ class WP_REST_Request implements ArrayAccess {
680
 
681
  $param = $this->get_param( $key );
682
 
683
- if ( null !== $param && ! empty( $arg['validate_callback'] ) ) {
684
  $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
685
 
686
  if ( false === $valid_check ) {
680
 
681
  $param = $this->get_param( $key );
682
 
683
+ if ( null !== $param && ! empty( $arg['validate_callback']) ) {
684
  $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
685
 
686
  if ( false === $valid_check ) {
lib/infrastructure/class-wp-rest-server.php CHANGED
@@ -7,7 +7,7 @@
7
  * @package WordPress
8
  */
9
 
10
- require_once( ABSPATH . 'wp-admin/includes/admin.php' );
11
 
12
  /**
13
  * WordPress REST API server handler
@@ -15,84 +15,22 @@ require_once( ABSPATH . 'wp-admin/includes/admin.php' );
15
  * @package WordPress
16
  */
17
  class WP_REST_Server {
18
- /**
19
- * GET transport method.
20
- *
21
- * @var string
22
- */
23
  const METHOD_GET = 'GET';
24
-
25
- /**
26
- * POST transport method.
27
- *
28
- * @var string
29
- */
30
  const METHOD_POST = 'POST';
31
-
32
- /**
33
- * PUT transport method.
34
- *
35
- * @var string
36
- */
37
  const METHOD_PUT = 'PUT';
38
-
39
- /**
40
- * PATCH transport method.
41
- *
42
- * @var string
43
- */
44
  const METHOD_PATCH = 'PATCH';
45
-
46
- /**
47
- * DELETE transport method.
48
- *
49
- * @var string
50
- */
51
  const METHOD_DELETE = 'DELETE';
52
 
53
- /**
54
- * Alias for GET transport method.
55
- *
56
- * @var string
57
- */
58
  const READABLE = 'GET';
59
-
60
- /**
61
- * Alias for POST transport method.
62
- *
63
- * @var string
64
- */
65
  const CREATABLE = 'POST';
66
-
67
- /**
68
- * Alias for GET, PUT, PATCH transport methods together.
69
- *
70
- * @var string
71
- */
72
  const EDITABLE = 'POST, PUT, PATCH';
73
-
74
- /**
75
- * Alias for DELETE transport method.
76
- *
77
- * @var string
78
- */
79
  const DELETABLE = 'DELETE';
80
-
81
- /**
82
- * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
83
- *
84
- * @var string
85
- */
86
  const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
87
 
88
  /**
89
  * Does the endpoint accept raw JSON entities?
90
  */
91
  const ACCEPT_RAW = 64;
92
-
93
- /**
94
- * Does the endpoint accept encoded JSON?
95
- */
96
  const ACCEPT_JSON = 128;
97
 
98
  /**
@@ -143,11 +81,6 @@ class WP_REST_Server {
143
  '/' => array(
144
  'callback' => array( $this, 'get_index' ),
145
  'methods' => 'GET',
146
- 'args' => array(
147
- 'context' => array(
148
- 'default' => 'view',
149
- ),
150
- ),
151
  ),
152
  );
153
  }
@@ -232,7 +165,7 @@ class WP_REST_Server {
232
  }
233
  $error = compact( 'code', 'message' );
234
 
235
- return wp_json_encode( array( $error ) );
236
  }
237
 
238
  /**
@@ -267,7 +200,7 @@ class WP_REST_Server {
267
  }
268
 
269
  // Check for invalid characters (only alphanumeric allowed)
270
- if ( ! is_string( $_GET['_jsonp'] ) || preg_match( '/[^\w\.]/', $_GET['_jsonp'] ) ) {
271
  echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 );
272
  return false;
273
  }
@@ -302,6 +235,20 @@ class WP_REST_Server {
302
  $result = $this->check_authentication();
303
 
304
  if ( ! is_wp_error( $result ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  $result = $this->dispatch( $request );
306
  }
307
 
@@ -357,13 +304,13 @@ class WP_REST_Server {
357
  // Embed links inside the request
358
  $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
359
 
360
- $result = wp_json_encode( $result );
361
 
362
  $json_error_message = $this->get_json_last_error();
363
  if ( $json_error_message ) {
364
  $json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) );
365
  $result = $this->error_to_response( $json_error_obj );
366
- $result = wp_json_encode( $result->data[0] );
367
  }
368
 
369
  if ( isset( $_GET['_jsonp'] ) ) {
@@ -390,12 +337,8 @@ class WP_REST_Server {
390
  if ( ! empty( $links ) ) {
391
  // Convert links to part of the data
392
  $data['_links'] = $links;
393
- }
394
- if ( $embed ) {
395
- // Is this a numeric array?
396
- if ( rest_is_list( $data ) ) {
397
- $data = array_map( array( $this, 'embed_links' ), $data );
398
- } else {
399
  $data = $this->embed_links( $data );
400
  }
401
  }
@@ -486,15 +429,13 @@ class WP_REST_Server {
486
  }
487
 
488
  // Embedded resources get passed context=embed
489
- if ( empty( $query_params['context'] ) ) {
490
- $query_params['context'] = 'embed';
491
- }
492
 
493
  $request = new WP_REST_Request( 'GET', $parsed['path'] );
494
  $request->set_query_params( $query_params );
495
  $response = $this->dispatch( $request );
496
 
497
- $embeds[] = $this->response_to_data( $response, false );
498
  }
499
 
500
  // Did we get any real links?
@@ -560,9 +501,6 @@ class WP_REST_Server {
560
  'namespace' => array(
561
  'default' => $namespace,
562
  ),
563
- 'context' => array(
564
- 'default' => 'view',
565
- ),
566
  ),
567
  ),
568
  ) );
@@ -652,20 +590,6 @@ class WP_REST_Server {
652
  return array_keys( $this->namespaces );
653
  }
654
 
655
- /**
656
- * Get specified options for a route.
657
- *
658
- * @param string $route Route pattern to fetch options for.
659
- * @return array|null Data as an associative array if found, or null if not found.
660
- */
661
- public function get_route_options( $route ) {
662
- if ( ! isset( $this->route_options[ $route ] ) ) {
663
- return null;
664
- }
665
-
666
- return $this->route_options[ $route ];
667
- }
668
-
669
  /**
670
  * Match the request to a callback and call it
671
  *
@@ -673,21 +597,6 @@ class WP_REST_Server {
673
  * @return WP_REST_Response Response returned by the callback
674
  */
675
  public function dispatch( $request ) {
676
- /**
677
- * Allow hijacking the request before dispatching
678
- *
679
- * If `$result` is non-empty, this value will be used to serve the
680
- * request instead.
681
- *
682
- * @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
683
- * @param WP_REST_Server $this Server instance
684
- * @param WP_REST_Request $request Request used to generate the response
685
- */
686
- $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
687
- if ( ! empty( $result ) ) {
688
- return $result;
689
- }
690
-
691
  $method = $request->get_method();
692
  $path = $request->get_route();
693
 
@@ -809,7 +718,7 @@ class WP_REST_Server {
809
  *
810
  * @return array Index entity
811
  */
812
- public function get_index( $request ) {
813
  // General site data
814
  $available = array(
815
  'name' => get_option( 'blogname' ),
@@ -817,7 +726,7 @@ class WP_REST_Server {
817
  'url' => get_option( 'siteurl' ),
818
  'namespaces' => array_keys( $this->namespaces ),
819
  'authentication' => array(),
820
- 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
821
  );
822
 
823
  $response = new WP_REST_Response( $available );
@@ -853,7 +762,7 @@ class WP_REST_Server {
853
 
854
  $data = array(
855
  'namespace' => $namespace,
856
- 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ),
857
  );
858
  $response = rest_ensure_response( $data );
859
 
@@ -876,15 +785,43 @@ class WP_REST_Server {
876
  * Get the publicly-visible data for routes.
877
  *
878
  * @param array $routes Routes to get data for
879
- * @param string $context Context for data. One of 'view', 'help'.
880
  * @return array Route data to expose in indexes.
881
  */
882
- public function get_data_for_routes( $routes, $context = 'view' ) {
883
  $available = array();
884
  // Find the available routes
885
  foreach ( $routes as $route => $callbacks ) {
886
- $data = $this->get_data_for_route( $route, $callbacks, $context );
887
- if ( empty( $data ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
888
  continue;
889
  }
890
 
@@ -904,74 +841,6 @@ class WP_REST_Server {
904
  return apply_filters( 'rest_route_data', $available, $routes );
905
  }
906
 
907
- /**
908
- * Get publicly-visible data for the route.
909
- *
910
- * @param string $route Route to get data for.
911
- * @param array $callbacks Callbacks to convert to data.
912
- * @param string $context Context for the data.
913
- * @return array|null Data for the route, or null if no publicly-visible data.
914
- */
915
- public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
916
- $data = array(
917
- 'namespace' => '',
918
- 'methods' => array(),
919
- 'endpoints' => array(),
920
- );
921
- if ( isset( $this->route_options[ $route ] ) ) {
922
- $options = $this->route_options[ $route ];
923
- if ( isset( $options['namespace'] ) ) {
924
- $data['namespace'] = $options['namespace'];
925
- }
926
- if ( isset( $options['schema'] ) && 'help' === $context ) {
927
- $data['schema'] = call_user_func( $options['schema'] );
928
- }
929
- }
930
-
931
- $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
932
-
933
- foreach ( $callbacks as $callback ) {
934
- // Skip to the next route if any callback is hidden
935
- if ( empty( $callback['show_in_index'] ) ) {
936
- continue;
937
- }
938
-
939
- $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
940
- $endpoint_data = array(
941
- 'methods' => array_keys( $callback['methods'] ),
942
- );
943
-
944
- if ( isset( $callback['args'] ) ) {
945
- $endpoint_data['args'] = array();
946
- foreach ( $callback['args'] as $key => $opts ) {
947
- $arg_data = array(
948
- 'required' => ! empty( $opts['required'] ),
949
- );
950
- if ( isset( $opts['default'] ) ) {
951
- $arg_data['default'] = $opts['default'];
952
- }
953
- $endpoint_data['args'][ $key ] = $arg_data;
954
- }
955
- }
956
-
957
- $data['endpoints'][] = $endpoint_data;
958
-
959
- // For non-variable routes, generate links
960
- if ( strpos( $route, '{' ) === false ) {
961
- $data['_links'] = array(
962
- 'self' => rest_url( $route ),
963
- );
964
- }
965
- }
966
-
967
- if ( empty( $data['methods'] ) ) {
968
- // No methods supported, hide the route
969
- return null;
970
- }
971
-
972
- return $data;
973
- }
974
-
975
  /**
976
  * Send a HTTP status code
977
  *
7
  * @package WordPress
8
  */
9
 
10
+ require_once ( ABSPATH . 'wp-admin/includes/admin.php' );
11
 
12
  /**
13
  * WordPress REST API server handler
15
  * @package WordPress
16
  */
17
  class WP_REST_Server {
 
 
 
 
 
18
  const METHOD_GET = 'GET';
 
 
 
 
 
 
19
  const METHOD_POST = 'POST';
 
 
 
 
 
 
20
  const METHOD_PUT = 'PUT';
 
 
 
 
 
 
21
  const METHOD_PATCH = 'PATCH';
 
 
 
 
 
 
22
  const METHOD_DELETE = 'DELETE';
23
 
 
 
 
 
 
24
  const READABLE = 'GET';
 
 
 
 
 
 
25
  const CREATABLE = 'POST';
 
 
 
 
 
 
26
  const EDITABLE = 'POST, PUT, PATCH';
 
 
 
 
 
 
27
  const DELETABLE = 'DELETE';
 
 
 
 
 
 
28
  const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
29
 
30
  /**
31
  * Does the endpoint accept raw JSON entities?
32
  */
33
  const ACCEPT_RAW = 64;
 
 
 
 
34
  const ACCEPT_JSON = 128;
35
 
36
  /**
81
  '/' => array(
82
  'callback' => array( $this, 'get_index' ),
83
  'methods' => 'GET',
 
 
 
 
 
84
  ),
85
  );
86
  }
165
  }
166
  $error = compact( 'code', 'message' );
167
 
168
+ return json_encode( array( $error ) );
169
  }
170
 
171
  /**
200
  }
201
 
202
  // Check for invalid characters (only alphanumeric allowed)
203
+ if ( ! is_string( $_GET['_jsonp'] ) || preg_match( '/\W\./', $_GET['_jsonp'] ) ) {
204
  echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 );
205
  return false;
206
  }
235
  $result = $this->check_authentication();
236
 
237
  if ( ! is_wp_error( $result ) ) {
238
+ /**
239
+ * Allow hijacking the request before dispatching
240
+ *
241
+ * If `$result` is non-empty, this value will be used to serve the
242
+ * request instead.
243
+ *
244
+ * @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
245
+ * @param WP_REST_Server $this Server instance
246
+ * @param WP_REST_Request $request Request used to generate the response
247
+ */
248
+ $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
249
+ }
250
+
251
+ if ( empty( $result ) ) {
252
  $result = $this->dispatch( $request );
253
  }
254
 
304
  // Embed links inside the request
305
  $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
306
 
307
+ $result = json_encode( $result );
308
 
309
  $json_error_message = $this->get_json_last_error();
310
  if ( $json_error_message ) {
311
  $json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) );
312
  $result = $this->error_to_response( $json_error_obj );
313
+ $result = json_encode( $result->data[0] );
314
  }
315
 
316
  if ( isset( $_GET['_jsonp'] ) ) {
337
  if ( ! empty( $links ) ) {
338
  // Convert links to part of the data
339
  $data['_links'] = $links;
340
+
341
+ if ( $embed ) {
 
 
 
 
342
  $data = $this->embed_links( $data );
343
  }
344
  }
429
  }
430
 
431
  // Embedded resources get passed context=embed
432
+ $query_params['context'] = 'embed';
 
 
433
 
434
  $request = new WP_REST_Request( 'GET', $parsed['path'] );
435
  $request->set_query_params( $query_params );
436
  $response = $this->dispatch( $request );
437
 
438
+ $embeds[] = $response;
439
  }
440
 
441
  // Did we get any real links?
501
  'namespace' => array(
502
  'default' => $namespace,
503
  ),
 
 
 
504
  ),
505
  ),
506
  ) );
590
  return array_keys( $this->namespaces );
591
  }
592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  /**
594
  * Match the request to a callback and call it
595
  *
597
  * @return WP_REST_Response Response returned by the callback
598
  */
599
  public function dispatch( $request ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  $method = $request->get_method();
601
  $path = $request->get_route();
602
 
718
  *
719
  * @return array Index entity
720
  */
721
+ public function get_index() {
722
  // General site data
723
  $available = array(
724
  'name' => get_option( 'blogname' ),
726
  'url' => get_option( 'siteurl' ),
727
  'namespaces' => array_keys( $this->namespaces ),
728
  'authentication' => array(),
729
+ 'routes' => $this->get_route_data( $this->get_routes() ),
730
  );
731
 
732
  $response = new WP_REST_Response( $available );
762
 
763
  $data = array(
764
  'namespace' => $namespace,
765
+ 'routes' => $this->get_route_data( $endpoints ),
766
  );
767
  $response = rest_ensure_response( $data );
768
 
785
  * Get the publicly-visible data for routes.
786
  *
787
  * @param array $routes Routes to get data for
 
788
  * @return array Route data to expose in indexes.
789
  */
790
+ protected function get_route_data( $routes ) {
791
  $available = array();
792
  // Find the available routes
793
  foreach ( $routes as $route => $callbacks ) {
794
+ $data = array(
795
+ 'namespace' => '',
796
+ 'methods' => array(),
797
+ );
798
+ if ( isset( $this->route_options[ $route ] ) ) {
799
+ $options = $this->route_options[ $route ];
800
+ if ( isset( $options['namespace'] ) ) {
801
+ $data['namespace'] = $options['namespace'];
802
+ }
803
+ }
804
+
805
+ $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
806
+
807
+ foreach ( $callbacks as $callback ) {
808
+ // Skip to the next route if any callback is hidden
809
+ if ( empty( $callback['show_in_index'] ) ) {
810
+ continue;
811
+ }
812
+
813
+ $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
814
+
815
+ // For non-variable routes, generate links
816
+ if ( strpos( $route, '{' ) === false ) {
817
+ $data['_links'] = array(
818
+ 'self' => rest_url( $route ),
819
+ );
820
+ }
821
+ }
822
+
823
+ if ( empty( $data['methods'] ) ) {
824
+ // No methods supported, hide the route
825
  continue;
826
  }
827
 
841
  return apply_filters( 'rest_route_data', $available, $routes );
842
  }
843
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844
  /**
845
  * Send a HTTP status code
846
  *
plugin.php CHANGED
@@ -4,7 +4,7 @@
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-beta4.1
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -14,7 +14,7 @@
14
  *
15
  * @var string
16
  */
17
- define( 'REST_API_VERSION', '2.0-beta4.1' );
18
 
19
  /**
20
  * Include our files for the API.
@@ -69,57 +69,20 @@ function register_rest_route( $namespace, $route, $args = array(), $override = f
69
  'callback' => null,
70
  'args' => array(),
71
  );
72
- foreach ( $args as $key => &$arg_group ) {
73
- if ( ! is_numeric( $arg_group ) ) {
74
- // Route option, skip here
75
- continue;
76
- }
77
-
78
  $arg_group = array_merge( $defaults, $arg_group );
79
  }
80
 
81
- if ( $namespace ) {
82
- $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
83
- } else {
84
- // Non-namespaced routes are not allowed, with the exception of the main
85
- // and namespace indexes. If you really need to register a
86
- // non-namespaced route, call `WP_REST_Server::register_route` directly.
87
- _doing_it_wrong( 'register_rest_route', 'Routes must be namespaced with plugin name and version', 'WPAPI-2.0' );
88
-
89
- $full_route = '/' . trim( $route, '/' );
90
- }
91
-
92
  $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
93
  }
94
 
95
  /**
96
  * Register a new field on an existing WordPress object type
97
  *
98
- * @global array $wp_rest_additional_fields Holds registered fields, organized
99
- * by object type.
100
- *
101
- * @param string|array $object_type Object(s) the field is being registered
102
- * to, "post"|"term"|"comment" etc.
103
- * @param string $attribute The attribute name.
104
- * @param array $args {
105
- * Optional. An array of arguments used to handle the registered field.
106
- *
107
- * @type string|array|null $get_callback Optional. The callback function
108
- * used to retrieve the field
109
- * value. Default is 'null', the
110
- * field will not be returned in
111
- * the response.
112
- * @type string|array|null $update_callback Optional. The callback function
113
- * used to set and update the
114
- * field value. Default is 'null',
115
- * the value cannot be set or
116
- * updated.
117
- * @type string|array|null schema Optional. The callback function
118
- * used to create the schema for
119
- * this field. Default is 'null',
120
- * no schema entry will be
121
- * returned.
122
- * }
123
  * @return bool|wp_error
124
  */
125
  function register_api_field( $object_type, $attribute, $args = array() ) {
@@ -170,17 +133,13 @@ add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
170
  function _add_extra_api_taxonomy_arguments() {
171
  global $wp_taxonomies;
172
 
173
- if ( isset( $wp_taxonomies['category'] ) ) {
174
- $wp_taxonomies['category']->show_in_rest = true;
175
- $wp_taxonomies['category']->rest_base = 'category';
176
- $wp_taxonomies['category']->rest_controller_class = 'WP_REST_Terms_Controller';
177
- }
178
 
179
- if ( isset( $wp_taxonomies['post_tag'] ) ) {
180
- $wp_taxonomies['post_tag']->show_in_rest = true;
181
- $wp_taxonomies['post_tag']->rest_base = 'tag';
182
- $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
183
- }
184
  }
185
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
186
 
@@ -461,18 +420,21 @@ function rest_get_url_prefix() {
461
  * @param string $scheme Optional. Sanitization scheme. Default 'json'.
462
  * @return string Full URL to the endpoint.
463
  */
464
- function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
465
- if ( empty( $path ) ) {
466
- $path = '/';
467
- }
468
-
469
  if ( get_option( 'permalink_structure' ) ) {
470
  $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
471
- $url .= '/' . ltrim( $path, '/' );
 
 
 
472
  } else {
473
  $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
474
 
475
- $path = '/' . ltrim( $path, '/' );
 
 
 
 
476
 
477
  $url = add_query_arg( 'rest_route', $path, $url );
478
  }
@@ -540,7 +502,8 @@ function rest_ensure_request( $request ) {
540
  * immediately check for this value.
541
  *
542
  * @param WP_Error|WP_HTTP_ResponseInterface|mixed $response Response to check.
543
- * @return WP_Error|WP_HTTP_ResponseInterface|WP_REST_Response WP_Error if response generated an error, WP_HTTP_ResponseInterface if response is a already an instance, otherwise returns a new WP_REST_Response instance.
 
544
  */
545
  function rest_ensure_response( $response ) {
546
  if ( is_wp_error( $response ) ) {
@@ -623,7 +586,6 @@ function rest_handle_options_request( $response, $handler, $request ) {
623
  }
624
 
625
  $response = new WP_REST_Response();
626
- $data = array();
627
 
628
  $accept = array();
629
 
@@ -634,13 +596,15 @@ function rest_handle_options_request( $response, $handler, $request ) {
634
  continue;
635
  }
636
 
637
- $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
638
- $accept = array_merge( $accept, $data['methods'] );
 
639
  break;
640
  }
 
 
641
  $response->header( 'Accept', implode( ', ', $accept ) );
642
 
643
- $response->set_data( $data );
644
  return $response;
645
  }
646
 
@@ -741,19 +705,3 @@ if ( ! function_exists( 'json_last_error_msg' ) ) :
741
  }
742
  }
743
  endif;
744
-
745
- /**
746
- * Is the variable a list? (Numeric-indexed array)
747
- *
748
- * @param mixed $data Variable to check.
749
- * @return boolean
750
- */
751
- function rest_is_list( $data ) {
752
- if ( ! is_array( $data ) ) {
753
- return false;
754
- }
755
-
756
- $keys = array_keys( $data );
757
- $string_keys = array_filter( $keys, 'is_string' );
758
- return count( $string_keys ) === 0;
759
- }
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-beta3.1
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
14
  *
15
  * @var string
16
  */
17
+ define( 'REST_API_VERSION', '2.0-beta3.1' );
18
 
19
  /**
20
  * Include our files for the API.
69
  'callback' => null,
70
  'args' => array(),
71
  );
72
+ foreach ( $args as &$arg_group ) {
 
 
 
 
 
73
  $arg_group = array_merge( $defaults, $arg_group );
74
  }
75
 
76
+ $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
 
 
 
 
 
 
 
 
 
 
77
  $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
78
  }
79
 
80
  /**
81
  * Register a new field on an existing WordPress object type
82
  *
83
+ * @param string|array $object_type "post"|"term"|"comment" etc
84
+ * @param string $attribute The attribute name
85
+ * @param array $args
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  * @return bool|wp_error
87
  */
88
  function register_api_field( $object_type, $attribute, $args = array() ) {
133
  function _add_extra_api_taxonomy_arguments() {
134
  global $wp_taxonomies;
135
 
136
+ $wp_taxonomies['category']->show_in_rest = true;
137
+ $wp_taxonomies['category']->rest_base = 'category';
138
+ $wp_taxonomies['category']->rest_controller_class = 'WP_REST_Terms_Controller';
 
 
139
 
140
+ $wp_taxonomies['post_tag']->show_in_rest = true;
141
+ $wp_taxonomies['post_tag']->rest_base = 'tag';
142
+ $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
 
 
143
  }
144
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
145
 
420
  * @param string $scheme Optional. Sanitization scheme. Default 'json'.
421
  * @return string Full URL to the endpoint.
422
  */
423
+ function get_rest_url( $blog_id = null, $path = '', $scheme = 'json' ) {
 
 
 
 
424
  if ( get_option( 'permalink_structure' ) ) {
425
  $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
426
+
427
+ if ( ! empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false ) {
428
+ $url .= '/' . ltrim( $path, '/' );
429
+ }
430
  } else {
431
  $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
432
 
433
+ if ( empty( $path ) ) {
434
+ $path = '/';
435
+ } else {
436
+ $path = '/' . ltrim( $path, '/' );
437
+ }
438
 
439
  $url = add_query_arg( 'rest_route', $path, $url );
440
  }
502
  * immediately check for this value.
503
  *
504
  * @param WP_Error|WP_HTTP_ResponseInterface|mixed $response Response to check.
505
+ * @return mixed WP_Error if present, WP_HTTP_ResponseInterface if instance,
506
+ * otherwise WP_REST_Response.
507
  */
508
  function rest_ensure_response( $response ) {
509
  if ( is_wp_error( $response ) ) {
586
  }
587
 
588
  $response = new WP_REST_Response();
 
589
 
590
  $accept = array();
591
 
596
  continue;
597
  }
598
 
599
+ foreach ( $endpoints as $endpoint ) {
600
+ $accept = array_merge( $accept, $endpoint['methods'] );
601
+ }
602
  break;
603
  }
604
+ $accept = array_keys( $accept );
605
+
606
  $response->header( 'Accept', implode( ', ', $accept ) );
607
 
 
608
  return $response;
609
  }
610
 
705
  }
706
  }
707
  endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: rmccue, rachelbaker
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3-alpha
5
  Tested up to: 4.3-beta
6
- Stable tag: 2.0-beta4.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,531 +36,13 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
39
- = 2.0 Beta 4.1 =
40
 
41
  * Ensure media of private posts are private too.
42
 
43
  Reported by @danielbachhuber on 2016-01-08.
44
 
45
- = 2.0 Beta 4.0 =
46
-
47
- * Show public user information through the user controller.
48
-
49
- In WordPress as of [r32683](https://core.trac.wordpress.org/changeset/32683) (scheduled for 4.3), `WP_User_Query` now has support for getting users with published posts.
50
-
51
- To match current behaviour in WordPress themes and feeds, we now expose this public user information. This includes the avatar, description, user ID, custom URL, display name, and URL, for users who have published at least one post on the site. This information is available to all clients; other fields and data for all users are still only available when authenticated.
52
-
53
- (props @joehoyle, @rmccue, @Shelob9, [#1397][gh-1397], [#839][gh-839], [#1435][gh-1435])
54
-
55
- * Send schema in OPTIONS requests and index.
56
-
57
- Rather than using separate `/schema` endpoints, the schema for items is now available through an OPTIONS request to the route. This means that full documentation is now available for endpoints through an OPTIONS request; this includes available methods, what data you can pass to the endpoint, and the data you'll get back.
58
-
59
- This data is now also available in the main index and namespace indexes. Simply request the index with `context=help` to get full schema data. Warning: this response will be huge. The schema for single endpoints is also available in the collection's OPTIONS response.
60
-
61
- **⚠️ This breaks backwards compatibility** for clients relying on schemas being at their own routes. These clients should instead send `OPTIONS` requests.
62
-
63
- Custom endpoints can register their own schema via the `schema` option on the route. This option should live side-by-side with the endpoints (similar to `relation` in WP's meta queries), so your registration call will look something like:
64
-
65
- ```php
66
- register_rest_route( 'test-ns', '/test', array(
67
- array(
68
- 'methods' => 'GET',
69
- 'callback' => 'my_test_callback',
70
- ),
71
-
72
- 'schema' => 'my_schema_callback',
73
- ) );
74
- ```
75
-
76
- (props @rmccue, [#1415][gh-1415], [#1222][gh-1222], [#1305][gh-1305])
77
-
78
- * Update JavaScript API for version 2.
79
-
80
- Our fantastic JavaScript API from version 1 is now available for version 2, refreshed with the latest and greatest changes.
81
-
82
- As a refresher: if you want to use it, simply make your script depend on `wp-api` when you enqueue it. If you want to enqueue the script manually, add `wp_enqueue_script( 'wp-api' )` to a callback on `wp_enqueue_scripts`.
83
-
84
- (props @tlovett1, @kadamwhite, @nathanrice, [#1374][gh-1374], [#1320][gh-1320])
85
-
86
- * Embed links inside items in a collection.
87
-
88
- Previously when fetching a collection of items, you only received the items themselves. To fetch the links as well via embedding, you needed to make a request to the single item with `_embed` set.
89
-
90
- No longer! You can now request a collection with embeds enabled (try `/wp/v2/posts?_embed`). This will embed links inside each item, allowing you to build interface items much easier (for example, post archive pages can get featured image data at the same time).
91
-
92
- This also applies to custom endpoints. Any endpoint that returns a list of objects will automatically have the embedding applied to objects inside the list.
93
-
94
- (props @rmccue, [#1459][gh-1459], [#865][gh-865])
95
-
96
- * Fix potential XSS vulnerability.
97
-
98
- Requests from other origins could potentially run code on the API domain, allowing cross-origin access to authentication cookies or similar.
99
-
100
- Reported by @xknown on 2015-07-23.
101
-
102
- * Move `/posts` `WP_Query` vars back to `filter` param.
103
-
104
- In version 1, we had internal `WP_Query` vars available via `filter` (e.g. `filter[s]=search+term`). For our first betas of version 2, we tried something different and exposed these directly on the endpoint. The experiment has now concluded; we didn't like this that much, so `filter` is back.
105
-
106
- We plan on adding nicer looking arguments to collections in future releases, with a view towards being consistent across different collections. We also plan on opening up the underlying query vars via `filter` for users, comments, and terms as well.
107
-
108
- **⚠️ This breaks backwards compatibility** for users using WP Query vars. Simply change your `x=y` parameter to `filter[x]=y`.
109
-
110
- (props @WP-API, [#1420][gh-1420])
111
-
112
- * Respect `rest_base` for taxonomies.
113
-
114
- **⚠️ This breaks backwards compatibility** by changing the `/wp/v2/posts/{id}/terms/post_tag` endpoint to `/wp/v2/posts/{id}/tag`.
115
-
116
- (props @joehoyle, [#1466][gh-1466])
117
-
118
- * Add permission check for retrieving the posts collection in edit context.
119
-
120
- By extension of the fact that getting any individual post yields a forbidden context error when the `context=edit` and the user is not authorized, the user should also not be permitted to list any post items when unauthorized.
121
-
122
- (props @danielpunkass, [#1412][gh-1412])
123
-
124
- * Ensure the REST API URL always has a trailing slash.
125
-
126
- Previously, when pretty permalinks were enabled, the API URL during autodiscovery looked like `/wp-json`, whereas the non-pretty permalink URL looked like `?rest_route=/`. These are now consistent, and always end with a slash character to simplify client URL building.
127
-
128
- (props @danielpunkass, @rmccue, [#1426][gh-1426], [#1442][gh-1442], [#1455][gh-1455], [#1467][gh-1467])
129
-
130
- * Use `wp_json_encode` instead of `json_encode`
131
-
132
- Since WordPress 4.1, `wp_json_encode` has been available to ensure encoded values are sane, and that non-UTF8 encodings are supported. We now use this function rather than doing the encode ourselves.
133
-
134
- (props @rmccue, @pento, [#1417][gh-1417])
135
-
136
- * Add `role` to schema for users.
137
-
138
- The available roles you can assign to a user are now available in the schema as an `enum`.
139
-
140
- (props @joehoyle, [#1400][gh-1400])
141
-
142
- * Use the schema for validation inside the comments controller.
143
-
144
- Previously, the schema was merely a decorative element for documentation inside the comments controller. To bring it inline with our other controllers, the schema is now used internally for validation.
145
-
146
- (props @joehoyle, [#1422][gh-1422])
147
-
148
- * Don't set the Location header in update responses.
149
-
150
- Previously, the Location header was sent when updating resources due to some inadvertent copypasta. This header should only be sent when creating to direct clients to the new resource, and isn't required when you're already on the correct resource.
151
-
152
- (props @rachelbaker, [#1441][gh-1441])
153
-
154
- * Re-enable the `rest_insert_post` action hook for `WP_REST_Posts_Controller`
155
-
156
- This was disabled during 2.0 development to avoid breaking lots of plugins on the `json_insert_post` action. Now that we've changed namespaces and are Mostly Stable (tm), we can re-enable the action.
157
-
158
- (props @jaredcobb, [#1427][gh-1427], [#1424][gh-1424])
159
-
160
- * Fix post taxonomy terms link URLs.
161
-
162
- When moving the routes in a previous beta, we forgot to correct the links on post objects to the new correct route. Sorry!
163
-
164
- (props @rachelbaker, @joehoyle, [#1447][gh-1447], [#1383][gh-1383])
165
-
166
- * Use `wp_get_attachment_image_src()` on the image sizes in attachments.
167
-
168
- Since the first versions of the API, we've been building attachment URLs via `str_replace`. Who knows why we were doing this, but it caused problems with custom attachment URLs (such as CDN-hosted images). This now correctly uses the internal functions and filters.
169
-
170
- (props @joehoyle, [#1462][gh-1462])
171
-
172
- * Make the embed context a default, not forced.
173
-
174
- If you want embeds to bring in full data rather than with `context=edit`, you can now change the link to specify `context=view` explicitly.
175
-
176
- (props @rmccue, [#1464][gh-1464])
177
-
178
- * Ensure we always use the `term_taxonomy_id` and never expose `term_id` publicly.
179
-
180
- Previously, `term_id` was inadvertently exposed in some error responses.
181
-
182
- (props @jdolan, [#1430][gh-1430])
183
-
184
- * Fix adding alt text to attachments on creation.
185
-
186
- Previously, this could only be set when updating an attachment, not when creating one.
187
-
188
- (props @joehoyle, [#1398][gh-1398])
189
-
190
- * Throw an error when registering routes without a namespace.
191
-
192
- Namespaces should **always** be provided when registering routes. We now throw a `doing_it_wrong` error when attempting to register one. (Previously, this caused a warning, or an invalid internal route.)
193
-
194
- If you *really* need to register namespaceless routes (e.g. to replicate an existing API), call `WP_REST_Server::register_route` directly rather than using the convenience function.
195
-
196
- (props @joehoyle, @rmccue, [#1355][gh-1355])
197
-
198
- * Show links on embeds.
199
-
200
- Previously, links were accidentally stripped from embedded response data.
201
-
202
- (props @rmccue, [#1472][gh-1472])
203
-
204
- * Clarify insufficient permisssion error when editing posts.
205
-
206
- (props @danielpunkass, [#1411][gh-1411])
207
-
208
- * Improve @return inline docs for rest_ensure_response()
209
-
210
- (props @Shelob9, [#1328][gh-1328])
211
-
212
- * Check taxonomies exist before trying to set properties.
213
-
214
- (props @joehoyle, @rachelbaker, [#1354][gh-1354])
215
-
216
- * Update controllers to ensure we use `sanitize_callback` wherever possible.
217
-
218
- (props @joehoyle, [#1399][gh-1399])
219
-
220
- * Add more phpDoc documentation, and correct existing documentation.
221
-
222
- (props @Shelob9, @rmccue, [#1432][gh-1432], [#1433][gh-1433], [#1465][gh-1465])
223
-
224
- * Update testing infrastructure.
225
-
226
- Travis now runs our coding standards tests in parallel, and now uses the new, faster container-based testing infrastructure.
227
-
228
- (props @ntwb, @frozzare, [#1449][gh-1449], [#1457][gh-1457])
229
-
230
- [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta3...2.0-beta4)
231
-
232
- [gh-839]: https://github.com/WP-API/WP-API/issues/839
233
- [gh-865]: https://github.com/WP-API/WP-API/issues/865
234
- [gh-1222]: https://github.com/WP-API/WP-API/issues/1222
235
- [gh-1305]: https://github.com/WP-API/WP-API/issues/1305
236
- [gh-1310]: https://github.com/WP-API/WP-API/issues/1310
237
- [gh-1320]: https://github.com/WP-API/WP-API/issues/1320
238
- [gh-1328]: https://github.com/WP-API/WP-API/issues/1328
239
- [gh-1354]: https://github.com/WP-API/WP-API/issues/1354
240
- [gh-1355]: https://github.com/WP-API/WP-API/issues/1355
241
- [gh-1372]: https://github.com/WP-API/WP-API/issues/1372
242
- [gh-1374]: https://github.com/WP-API/WP-API/issues/1374
243
- [gh-1383]: https://github.com/WP-API/WP-API/issues/1383
244
- [gh-1397]: https://github.com/WP-API/WP-API/issues/1397
245
- [gh-1398]: https://github.com/WP-API/WP-API/issues/1398
246
- [gh-1399]: https://github.com/WP-API/WP-API/issues/1399
247
- [gh-1400]: https://github.com/WP-API/WP-API/issues/1400
248
- [gh-1402]: https://github.com/WP-API/WP-API/issues/1402
249
- [gh-1411]: https://github.com/WP-API/WP-API/issues/1411
250
- [gh-1412]: https://github.com/WP-API/WP-API/issues/1412
251
- [gh-1413]: https://github.com/WP-API/WP-API/issues/1413
252
- [gh-1415]: https://github.com/WP-API/WP-API/issues/1415
253
- [gh-1417]: https://github.com/WP-API/WP-API/issues/1417
254
- [gh-1420]: https://github.com/WP-API/WP-API/issues/1420
255
- [gh-1422]: https://github.com/WP-API/WP-API/issues/1422
256
- [gh-1424]: https://github.com/WP-API/WP-API/issues/1424
257
- [gh-1426]: https://github.com/WP-API/WP-API/issues/1426
258
- [gh-1427]: https://github.com/WP-API/WP-API/issues/1427
259
- [gh-1430]: https://github.com/WP-API/WP-API/issues/1430
260
- [gh-1432]: https://github.com/WP-API/WP-API/issues/1432
261
- [gh-1433]: https://github.com/WP-API/WP-API/issues/1433
262
- [gh-1435]: https://github.com/WP-API/WP-API/issues/1435
263
- [gh-1441]: https://github.com/WP-API/WP-API/issues/1441
264
- [gh-1442]: https://github.com/WP-API/WP-API/issues/1442
265
- [gh-1447]: https://github.com/WP-API/WP-API/issues/1447
266
- [gh-1449]: https://github.com/WP-API/WP-API/issues/1449
267
- [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
268
- [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
269
- [gh-1457]: https://github.com/WP-API/WP-API/issues/1457
270
- [gh-1459]: https://github.com/WP-API/WP-API/issues/1459
271
- [gh-1462]: https://github.com/WP-API/WP-API/issues/1462
272
- [gh-1464]: https://github.com/WP-API/WP-API/issues/1464
273
- [gh-1465]: https://github.com/WP-API/WP-API/issues/1465
274
- [gh-1466]: https://github.com/WP-API/WP-API/issues/1466
275
- [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
276
- [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
277
-
278
- = 2.0 Beta 3.0 =
279
-
280
- * Add ability to declare sanitization and default options for schema fields.
281
-
282
- The `arg_options` array can be used to declare the sanitization callback,
283
- default value, or requirement of a field.
284
-
285
- (props @joehoyle, [#1345][gh-1345])
286
- (props @joehoyle, [#1346][gh-1346])
287
-
288
- * Expand supported parameters for creating and updating Comments.
289
-
290
- (props @rachelbaker, [#1245][gh-1245])
291
-
292
- * Declare collection parameters for Terms of a Post.
293
-
294
- Define the available collection parameters in `get_collection_params()` and
295
- allow Terms of a Post to be queried by term order.
296
-
297
- (props @danielbachhuber, [#1332][gh-1332])
298
-
299
- * Improve the Attachment error message for an invalid Content-Disposition
300
-
301
- (props @danielbachhuber, [#1317][gh-1317])
302
-
303
- * Return 200 status when updating Attachments, Comments, and Users.
304
-
305
- (props @rachelbaker, [#1348][gh-1348])
306
-
307
- * Remove unnecessary `handle_format_param()` method.
308
-
309
- (props @danielbachhuber, [#1331][gh-1331])
310
-
311
- * Add `author_avatar_url` field to the Comment response and schema.
312
-
313
- (props @rachelbaker [#1327][gh-1327])
314
-
315
- * Introduce `rest_do_request()` for making REST requests internally.
316
-
317
- (props @danielbachhuber, [#1333][gh-1333])
318
-
319
- * Remove unused DateTime class.
320
-
321
- (props @rmccue, [#1314][gh-1314])
322
-
323
- * Add inline documentation for `$wp_rest_server` global.
324
-
325
- (props @Shelob9, [#1324][gh-1324])
326
-
327
- [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta2...2.0-beta3)
328
- [gh-1245]: https://github.com/WP-API/WP-API/issues/1245
329
- [gh-1314]: https://github.com/WP-API/WP-API/issues/1314
330
- [gh-1317]: https://github.com/WP-API/WP-API/issues/1317
331
- [gh-1318]: https://github.com/WP-API/WP-API/issues/1318
332
- [gh-1324]: https://github.com/WP-API/WP-API/issues/1324
333
- [gh-1326]: https://github.com/WP-API/WP-API/issues/1326
334
- [gh-1327]: https://github.com/WP-API/WP-API/issues/1327
335
- [gh-1331]: https://github.com/WP-API/WP-API/issues/1331
336
- [gh-1332]: https://github.com/WP-API/WP-API/issues/1332
337
- [gh-1333]: https://github.com/WP-API/WP-API/issues/1333
338
- [gh-1345]: https://github.com/WP-API/WP-API/issues/1345
339
- [gh-1346]: https://github.com/WP-API/WP-API/issues/1346
340
- [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
341
- [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
342
-
343
- = 2.0 Beta 2.0 =
344
-
345
- * Load the WP REST API before the main query runs.
346
-
347
- The `rest_api_loaded` function now hooks into the `parse_request` action.
348
- This change prevents the main query from being run on every request and
349
- allows sites to set `WP_USE_THEMES` to `false`. Previously, the main query
350
- was always being run (`SELECT * FROM wp_posts LIMIT 10`), even though the
351
- result was never used and couldn't be cached.
352
-
353
- (props @rmccue, [#1270][gh-1270])
354
-
355
- * Register a new field on an existing WordPress object type.
356
-
357
- Introduces `register_api_field()` to add a field to an object and
358
- its schema.
359
-
360
- (props @joehoyle, @rachelbaker, [#927][gh-927])
361
- (props @joehoyle, [#1207][gh-1207])
362
- (props @joehoyle, [#1243][gh-1243])
363
-
364
- * Add endpoints for viewing, creating, updating, and deleting Terms for a Post.
365
-
366
- The new `WP_REST_Posts_Terms_Controller` class controller supports routes for
367
- Terms that belong to a Post.
368
-
369
- (props @joehoyle, @danielbachhuber, [#1216][gh-1216])
370
-
371
- * Add pagination headers for collection queries.
372
-
373
- The `X-WP-Total` and `X-WP-TotalPages` are now present in terms, comments,
374
- and users collection responses.
375
-
376
- (props @danielbachhuber, [#1182][gh-1182])
377
- (props @danielbachhuber, [#1191][gh-1191])
378
- (props @danielbachhuber, @joehoyle, [#1197][gh-1197])
379
-
380
- * List registered namespaces in the index for feature detection.
381
-
382
- The index (`/wp-json` by default) now contains a list of the available
383
- namespaces. This allows for simple feature detection. You can grab the index
384
- and check namespaces for `wp/v3` or `pluginname/v2`, which indicate the
385
- supported endpoints on the site.
386
-
387
- (props @rmccue,, [#1283][gh-1283])
388
-
389
- * Standardize link property relations and support embedding for all resources.
390
-
391
- Change link properties to use IANA-registered relations. Also adds embedding
392
- support to Attachments, Comments and Terms.
393
-
394
- (props @rmccue, @rachelbaker, [#1284][gh-1284])
395
-
396
- * Add support for Composer dependency management.
397
-
398
- Allows you to recursively install/update the WP REST API inside of WordPress
399
- plugins or themes.
400
-
401
- (props @QWp6t, [#1157][gh-1157])
402
-
403
- * Return full objects in the delete response.
404
-
405
- Instead of returning a random message when deleting a Post, Comment, Term, or
406
- User provide the original resource data.
407
-
408
- (props @danielbachhuber, [#1253][gh-1253])
409
- (props @danielbachhuber, [#1254][gh-1254])
410
- (props @danielbachhuber, [#1255][gh-1255])
411
- (props @danielbachhuber, [#1256][gh-1256])
412
-
413
- * Return programmatically readable error messages for invalid or missing
414
- required parameters.
415
-
416
- (props @joehoyle, [#1175][gh-1175])
417
-
418
- * Declare supported arguments for Comment and User collection queries.
419
-
420
- (props @danielbachhuber, [#1211][gh-1211])
421
- (props @danielbachhuber, [#1217][gh-1217])
422
-
423
- * Automatically validate parameters based on Schema data.
424
-
425
- (props @joehoyle, [#1128][gh-1128])
426
-
427
- * Use the `show_in_rest` attributes for exposing Taxonomies.
428
-
429
- (props @joehoyle, [#1279][gh-1279])
430
-
431
- * Handle `parent` when creating or updating a Term.
432
-
433
- (props @joehoyle, [#1221][gh-1221])
434
-
435
- * Limit fields returned in `embed` context User responses.
436
-
437
- (props @rachelbaker, [#1251][gh-1251])
438
-
439
- * Only include `parent` in term response when tax is hierarchical.
440
-
441
- (props @danielbachhuber, [#1189][gh-1189])
442
-
443
- * Fix bug in creating comments if `type` was not set.
444
-
445
- (props @rachelbaker, [#1244][gh-1244])
446
-
447
- * Rename `post_name` field to `post_slug`.
448
-
449
- (props @danielbachhuber, [#1235][gh-1235])
450
-
451
- * Add check when creating a user to verify the provided role is valid.
452
-
453
- (props @rachelbaker, [#1267][gh-1267])
454
-
455
- * Add link properties to the Post Status response.
456
-
457
- (props @joehoyle, [#1243][gh-1243])
458
-
459
- * Return `0` for `parent` in Post response instead of `null`.
460
-
461
- (props @danielbachhuber, [#1269][gh-1269])
462
-
463
- * Only link `author` when there's a valid author
464
-
465
- (props @danielbachhuber, [#1203][gh-1203])
466
-
467
- * Only permit querying by parent term when tax is hierarchical.
468
-
469
- (props @danielbachhuber, [#1219][gh-1219])
470
-
471
- * Only permit deleting posts of the proper type
472
-
473
- (props @danielbachhuber, [#1257][gh-1257])
474
-
475
- * Set pagination headers even when no found posts.
476
-
477
- (props @danielbachhuber, [#1209][gh-1209])
478
-
479
- * Correct prefix in `rest_request_parameter_order` filter.
480
-
481
- (props @quasel, [#1158][gh-1158])
482
-
483
- * Retool `WP_REST_Terms_Controller` to follow Posts controller pattern.
484
-
485
- (props @danielbachhuber, [#1170][gh-1170])
486
-
487
- * Remove unused `accept_json argument` from the `register_routes` method.
488
-
489
- (props @quasel, [#1160][gh-1160])
490
-
491
- * Fix typo in `sanitize_params` inline documentation.
492
-
493
- (props @Shelob9, [#1226][gh-1226])
494
-
495
- * Remove commented out code in dispatch method.
496
-
497
- (props @rachelbaker, [#1162][gh-1162])
498
-
499
-
500
- [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta1.1...2.0-beta2)
501
- [gh-927]: https://github.com/WP-API/WP-API/issues/927
502
- [gh-1128]: https://github.com/WP-API/WP-API/issues/1128
503
- [gh-1157]: https://github.com/WP-API/WP-API/issues/1157
504
- [gh-1158]: https://github.com/WP-API/WP-API/issues/1158
505
- [gh-1160]: https://github.com/WP-API/WP-API/issues/1160
506
- [gh-1162]: https://github.com/WP-API/WP-API/issues/1162
507
- [gh-1168]: https://github.com/WP-API/WP-API/issues/1168
508
- [gh-1170]: https://github.com/WP-API/WP-API/issues/1170
509
- [gh-1171]: https://github.com/WP-API/WP-API/issues/1171
510
- [gh-1175]: https://github.com/WP-API/WP-API/issues/1175
511
- [gh-1176]: https://github.com/WP-API/WP-API/issues/1176
512
- [gh-1177]: https://github.com/WP-API/WP-API/issues/1177
513
- [gh-1181]: https://github.com/WP-API/WP-API/issues/1181
514
- [gh-1182]: https://github.com/WP-API/WP-API/issues/1182
515
- [gh-1188]: https://github.com/WP-API/WP-API/issues/1188
516
- [gh-1189]: https://github.com/WP-API/WP-API/issues/1189
517
- [gh-1191]: https://github.com/WP-API/WP-API/issues/1191
518
- [gh-1197]: https://github.com/WP-API/WP-API/issues/1197
519
- [gh-1200]: https://github.com/WP-API/WP-API/issues/1200
520
- [gh-1203]: https://github.com/WP-API/WP-API/issues/1203
521
- [gh-1207]: https://github.com/WP-API/WP-API/issues/1207
522
- [gh-1209]: https://github.com/WP-API/WP-API/issues/1209
523
- [gh-1210]: https://github.com/WP-API/WP-API/issues/1210
524
- [gh-1211]: https://github.com/WP-API/WP-API/issues/1211
525
- [gh-1216]: https://github.com/WP-API/WP-API/issues/1216
526
- [gh-1217]: https://github.com/WP-API/WP-API/issues/1217
527
- [gh-1219]: https://github.com/WP-API/WP-API/issues/1219
528
- [gh-1221]: https://github.com/WP-API/WP-API/issues/1221
529
- [gh-1226]: https://github.com/WP-API/WP-API/issues/1226
530
- [gh-1235]: https://github.com/WP-API/WP-API/issues/1235
531
- [gh-1243]: https://github.com/WP-API/WP-API/issues/1243
532
- [gh-1244]: https://github.com/WP-API/WP-API/issues/1244
533
- [gh-1249]: https://github.com/WP-API/WP-API/issues/1249
534
- [gh-1251]: https://github.com/WP-API/WP-API/issues/1251
535
- [gh-1253]: https://github.com/WP-API/WP-API/issues/1253
536
- [gh-1254]: https://github.com/WP-API/WP-API/issues/1254
537
- [gh-1255]: https://github.com/WP-API/WP-API/issues/1255
538
- [gh-1256]: https://github.com/WP-API/WP-API/issues/1256
539
- [gh-1257]: https://github.com/WP-API/WP-API/issues/1257
540
- [gh-1259]: https://github.com/WP-API/WP-API/issues/1259
541
- [gh-1267]: https://github.com/WP-API/WP-API/issues/1267
542
- [gh-1268]: https://github.com/WP-API/WP-API/issues/1268
543
- [gh-1269]: https://github.com/WP-API/WP-API/issues/1269
544
- [gh-1270]: https://github.com/WP-API/WP-API/issues/1270
545
- [gh-1276]: https://github.com/WP-API/WP-API/issues/1276
546
- [gh-1277]: https://github.com/WP-API/WP-API/issues/1277
547
- [gh-1279]: https://github.com/WP-API/WP-API/issues/1279
548
- [gh-1283]: https://github.com/WP-API/WP-API/issues/1283
549
- [gh-1284]: https://github.com/WP-API/WP-API/issues/1284
550
- [gh-1295]: https://github.com/WP-API/WP-API/issues/1295
551
- [gh-1301]: https://github.com/WP-API/WP-API/issues/1301
552
-
553
-
554
- = 2.0 Beta 1.1 =
555
-
556
- * Fix user access security vulnerability.
557
-
558
- Authenticated users were able to escalate their privileges bypassing the
559
- expected capabilities check.
560
-
561
- Reported by @kacperszurek on 2015-05-16.
562
-
563
- = 2.0 Beta 1 =
564
 
565
  Partial rewrite and evolution of the REST API to prepare for core integration.
566
 
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3-alpha
5
  Tested up to: 4.3-beta
6
+ Stable tag: 2.0-beta3.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
36
 
37
  == Changelog ==
38
 
39
+ = 2.0 Beta 3.1 =
40
 
41
  * Ensure media of private posts are private too.
42
 
43
  Reported by @danielbachhuber on 2016-01-08.
44
 
45
+ = Version 2.0 Beta 1 =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  Partial rewrite and evolution of the REST API to prepare for core integration.
48
 
wp-api.js CHANGED
@@ -1,20 +1,26 @@
1
- ( function( WP_API_Settings, Backbone, _, window, undefined ) {
 
2
  'use strict';
3
 
 
 
 
 
 
 
4
  window.wp = window.wp || {};
5
- var pad;
6
- wp.api = {
7
- models: {},
8
- collections: {},
9
- utils: {}
10
- };
11
 
12
- /**
13
- * ECMAScript 5 shim, from MDN
14
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
15
- */
 
 
 
 
16
  if ( ! Date.prototype.toISOString ) {
17
- pad = function( number ) {
18
  var r = String( number );
19
  if ( r.length === 1 ) {
20
  r = '0' + r;
@@ -34,49 +40,59 @@
34
  };
35
  }
36
 
 
 
 
37
 
38
- var origParse = Date.parse;
39
 
40
- /**
41
- * Parse date into ISO8601 format
42
- *
43
- * @param {Date} date
44
- */
45
- wp.api.utils.parseISO8601 = function( date ) {
46
- var timestamp, struct, i, k,
47
- minutesOffset = 0,
48
- numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];;
49
-
50
- // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
51
- // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
52
- // implementations could be faster
53
- // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
54
- if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
55
- // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
56
- for ( i = 0; ( k = numericKeys[i] ); ++i) {
57
- struct[k] = +struct[k] || 0;
58
- }
59
 
60
- // allow undefined days and months
61
- struct[2] = ( +struct[2] || 1 ) - 1;
62
- struct[3] = +struct[3] || 1;
 
 
 
 
 
 
 
 
 
 
63
 
64
- if ( struct[8] !== 'Z' && struct[9] !== undefined ) {
65
- minutesOffset = struct[10] * 60 + struct[11];
66
 
67
- if ( struct[9] === '+' ) {
68
- minutesOffset = 0 - minutesOffset;
 
69
  }
 
 
 
 
 
70
  }
71
 
72
- timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] );
73
- } else {
74
- timestamp = Date.parse ? Date.parse( date ) : NaN;
75
- }
76
 
77
- return timestamp;
78
- };
 
 
 
79
 
 
 
 
 
 
 
80
 
81
  /**
82
  * Array of parseable dates
@@ -127,7 +143,7 @@
127
  });
128
 
129
  // Parse the author into a User object
130
- if ( 'undefined' !== typeof response.author ) {
131
  response.author = new wp.api.models.User( response.author );
132
  }
133
 
@@ -157,7 +173,7 @@
157
 
158
  var parentModel = this;
159
 
160
- if ( 'undefined' !== typeof this.parentModel ) {
161
  /**
162
  * Probably a better way to do this. Perhaps grab a cached version of the
163
  * instantiated model?
@@ -171,7 +187,7 @@
171
  } else {
172
  // Otherwise, get the object directly
173
  object = new parentModel.constructor( {
174
- id: parent
175
  });
176
 
177
  // Note that this acts asynchronously
@@ -198,7 +214,7 @@
198
  sync: function( method, model, options ) {
199
  options = options || {};
200
 
201
- if ( 'undefined' !== typeof WP_API_Settings.nonce ) {
202
  var beforeSend = options.beforeSend;
203
 
204
  options.beforeSend = function( xhr ) {
@@ -221,28 +237,35 @@
221
  wp.api.models.User = BaseModel.extend(
222
  /** @lends User.prototype */
223
  {
224
- idAttribute: 'id',
225
 
226
  urlRoot: WP_API_Settings.root + '/users',
227
 
228
  defaults: {
229
- id: null,
230
- avatar_url: {},
231
- capabilities: {},
232
- description: '',
233
  email: '',
234
- extra_capabilities: {},
 
235
  first_name: '',
236
  last_name: '',
237
- link: '',
238
- name: '',
239
  nickname: '',
240
- registered_date: new Date(),
241
- roles: [],
242
  slug: '',
243
- url: '',
244
- username: '',
245
- _links: {}
 
 
 
 
 
 
 
 
 
 
 
 
246
  }
247
  }
248
  );
@@ -260,11 +283,13 @@
260
  defaults: {
261
  name: '',
262
  slug: null,
263
- description: '',
264
  labels: {},
265
- types: [],
266
  show_cloud: false,
267
- hierarchical: false
 
 
 
268
  }
269
  }
270
  );
@@ -272,10 +297,25 @@
272
  /**
273
  * Backbone model for term
274
  */
275
- wp.api.models.Term = BaseModel.extend(
276
  /** @lends Term.prototype */
277
  {
278
- idAttribute: 'id',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  /**
281
  * Return URL for the model
@@ -283,25 +323,26 @@
283
  * @returns {string}
284
  */
285
  url: function() {
286
- var id = this.get( 'id' );
287
  id = id || '';
288
 
289
- return WP_API_Settings.root + '/taxonomies/' + this.get( 'taxonomy' ) + '/terms/' + id;
290
  },
291
 
292
  defaults: {
293
- id: null,
294
  name: '',
295
  slug: '',
296
  description: '',
297
  parent: null,
298
  count: 0,
299
  link: '',
300
- taxonomy: '',
301
- _links: {}
 
302
  }
303
 
304
- }
305
  );
306
 
307
  /**
@@ -310,32 +351,39 @@
310
  wp.api.models.Post = BaseModel.extend( _.extend(
311
  /** @lends Post.prototype */
312
  {
313
- idAttribute: 'id',
314
 
315
  urlRoot: WP_API_Settings.root + '/posts',
316
 
317
  defaults: {
318
- id: null,
 
 
 
 
 
 
 
319
  date: new Date(),
320
  date_gmt: new Date(),
321
- guid: {},
322
- link: '',
323
  modified: new Date(),
324
  modified_gmt: new Date(),
325
- password: '',
326
  slug: '',
327
- status: 'draft',
328
- type: 'post',
329
- title: {},
330
- content: {},
331
- author: null,
332
- excerpt: {},
333
- featured_image: null,
334
  comment_status: 'open',
335
  ping_status: 'open',
336
  sticky: false,
337
- format: 'standard',
338
- _links: {}
 
 
 
 
 
 
339
  }
340
  }, TimeStampedMixin, HierarchicalMixin )
341
  );
@@ -346,32 +394,39 @@
346
  wp.api.models.Page = BaseModel.extend( _.extend(
347
  /** @lends Page.prototype */
348
  {
349
- idAttribute: 'id',
350
 
351
  urlRoot: WP_API_Settings.root + '/pages',
352
 
353
  defaults: {
354
- id: null,
355
- date: new Date(),
356
- date_gmt: new Date(),
357
- guid: {},
 
 
 
358
  link: '',
 
359
  modified: new Date(),
 
360
  modified_gmt: new Date(),
361
- password: '',
 
 
362
  slug: '',
363
- status: 'draft',
364
- type: 'page',
365
- title: {},
366
- content: {},
367
- author: null,
368
- excerpt: {},
369
- featured_image: null,
370
  comment_status: 'closed',
371
- ping_status: 'closed',
372
- menu_order: null,
373
- template: '',
374
- _links: {}
 
 
 
 
375
  }
376
  }, TimeStampedMixin, HierarchicalMixin )
377
  );
@@ -379,39 +434,34 @@
379
  /**
380
  * Backbone model for revisions
381
  */
382
- wp.api.models.Revision = BaseModel.extend( _.extend(
383
  /** @lends Revision.prototype */
384
  {
385
- idAttribute: 'id',
386
-
387
  /**
388
- * Return URL for the model
389
  *
390
  * @returns {string}
391
  */
392
  url: function() {
393
- var id = this.get( 'id' ) || '';
 
 
 
 
394
 
395
- return WP_API_Settings.root + '/posts/' + id + '/revisions';
396
  },
397
 
398
- defaults: {
399
- id: null,
400
- author: null,
401
- date: new Date(),
402
- date_gmt: new Date(),
403
- guid: {},
404
- modified: new Date(),
405
- modified_gmt: new Date(),
406
- parent: 0,
407
- slug: '',
408
- title: {},
409
- content: {},
410
- excerpt: {},
411
- _links: {}
412
  }
413
-
414
- }, TimeStampedMixin, HierarchicalMixin )
415
  );
416
 
417
  /**
@@ -420,34 +470,41 @@
420
  wp.api.models.Media = BaseModel.extend( _.extend(
421
  /** @lends Media.prototype */
422
  {
423
- idAttribute: 'id',
424
 
425
  urlRoot: WP_API_Settings.root + '/media',
426
 
427
  defaults: {
428
- id: null,
429
- date: new Date(),
430
- date_gmt: new Date(),
431
- guid: {},
 
 
 
432
  link: '',
 
433
  modified: new Date(),
434
- modified_gmt: new Date(),
435
- password: '',
436
  slug: '',
437
- status: 'draft',
438
- type: 'attachment',
439
- title: {},
440
- author: null,
441
  comment_status: 'open',
442
  ping_status: 'open',
443
- alt_text: '',
444
- caption: '',
445
- description: '',
446
- media_type: '',
447
- media_details: {},
448
- post: null,
449
- source_url: '',
450
- _links: {}
 
 
 
 
 
451
  },
452
 
453
  /**
@@ -468,26 +525,22 @@
468
  wp.api.models.Comment = BaseModel.extend( _.extend(
469
  /** @lends Comment.prototype */
470
  {
471
- idAttribute: 'id',
472
 
473
  defaults: {
474
- id: null,
475
- author: null,
476
- author_email: '',
477
- author_ip: '',
478
- author_name: '',
479
- author_url: '',
480
- author_user_agent: '',
481
- content: {},
482
- date: new Date(),
483
- date_gmt: new Date(),
484
- karma: 0,
485
- link: '',
486
- parent: 0,
487
  post: null,
 
488
  status: 'hold',
489
  type: '',
490
- _links: {}
 
 
 
 
 
 
 
491
  },
492
 
493
  /**
@@ -499,7 +552,7 @@
499
  var post_id = this.get( 'post' );
500
  post_id = post_id || '';
501
 
502
- var id = this.get( 'id' );
503
  id = id || '';
504
 
505
  return WP_API_Settings.root + '/posts/' + post_id + '/comments/' + id;
@@ -522,7 +575,13 @@
522
  name: '',
523
  description: '',
524
  labels: {},
525
- hierarchical: false
 
 
 
 
 
 
526
  },
527
 
528
  /**
@@ -563,7 +622,9 @@
563
  'private': false,
564
  queryable: true,
565
  show_in_list: true,
566
- _links: {}
 
 
567
  },
568
 
569
  /**
@@ -586,9 +647,13 @@
586
  }
587
  );
588
 
589
- /**
590
- * Contains basic collection functionality such as pagination
591
- */
 
 
 
 
592
  var BaseCollection = Backbone.Collection.extend(
593
  /** @lends BaseCollection.prototype */
594
  {
@@ -617,45 +682,46 @@
617
  */
618
  sync: function( method, model, options ) {
619
  options = options || {};
620
- var beforeSend = options.beforeSend,
621
- self = this;
622
 
623
- if ( 'undefined' !== typeof WP_API_Settings.nonce ) {
624
  options.beforeSend = function( xhr ) {
625
  xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
626
 
627
  if ( beforeSend ) {
628
- return beforeSend.apply( self, arguments );
629
  }
630
  };
631
  }
632
 
633
  if ( 'read' === method ) {
 
 
634
  if ( options.data ) {
635
- self.state.data = _.clone( options.data );
636
 
637
- delete self.state.data.page;
638
  } else {
639
- self.state.data = options.data = {};
640
  }
641
 
642
- if ( 'undefined' === typeof options.data.page ) {
643
- self.state.currentPage = null;
644
- self.state.totalPages = null;
645
- self.state.totalObjects = null;
646
  } else {
647
- self.state.currentPage = options.data.page - 1;
648
  }
649
 
650
  var success = options.success;
651
  options.success = function( data, textStatus, request ) {
652
- self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 );
653
- self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 );
654
 
655
- if ( self.state.currentPage === null ) {
656
- self.state.currentPage = 1;
657
  } else {
658
- self.state.currentPage++;
659
  }
660
 
661
  if ( success ) {
@@ -679,7 +745,7 @@
679
 
680
  _.extend( options.data, this.state.data );
681
 
682
- if ( 'undefined' === typeof options.data.page ) {
683
  if ( ! this.hasMore() ) {
684
  return false;
685
  }
@@ -753,15 +819,10 @@
753
  wp.api.collections.PostStatuses = BaseCollection.extend(
754
  /** @lends PostStatuses.prototype */
755
  {
756
- url: WP_API_Settings.root + '/statuses',
757
 
758
- model: wp.api.models.PostStatus,
759
 
760
- parse: function( response ) {
761
- var responseArray = _.values( response );
762
-
763
- return this.constructor.__super__.parse.call( this, responseArray );
764
- }
765
  }
766
  );
767
 
@@ -797,12 +858,29 @@
797
  {
798
  model: wp.api.models.Comment,
799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  /**
801
  * Return URL for collection
802
  *
803
  * @returns {string}
804
  */
805
- url: WP_API_Settings.root + '/comments'
 
 
806
  }
807
  );
808
 
@@ -814,32 +892,20 @@
814
  {
815
  model: wp.api.models.PostType,
816
 
817
- url: WP_API_Settings.root + '/types',
818
-
819
- parse: function( response ) {
820
- var responseArray = [];
821
-
822
- for ( var property in response ) {
823
- if ( response.hasOwnProperty( property ) ) {
824
- responseArray.push( response[property] );
825
- }
826
- }
827
-
828
- return this.constructor.__super__.parse.call( this, responseArray );
829
- }
830
  }
831
  );
832
 
833
  /**
834
  * Backbone terms collection
835
- *
836
- * Usage: new wp.api.collections.Terms( {}, { taxonomy: 'taxonomy-slug' } )
837
  */
838
  wp.api.collections.Terms = BaseCollection.extend(
839
  /** @lends Terms.prototype */
840
  {
841
  model: wp.api.models.Term,
842
 
 
 
843
  taxonomy: 'category',
844
 
845
  /**
@@ -848,11 +914,29 @@
848
  * @constructs
849
  */
850
  initialize: function( models, options ) {
851
- if ( 'undefined' !== typeof options && options.taxonomy ) {
852
- this.taxonomy = options.taxonomy;
 
 
 
 
 
 
 
 
853
  }
854
 
855
- BaseCollection.prototype.initialize.apply( this, arguments );
 
 
 
 
 
 
 
 
 
 
856
  },
857
 
858
  /**
@@ -861,15 +945,13 @@
861
  * @returns {string}
862
  */
863
  url: function() {
864
- return WP_API_Settings.root + '/terms/' + this.taxonomy;
865
  }
866
  }
867
  );
868
 
869
  /**
870
  * Backbone revisions collection
871
- *
872
- * Usage: new wp.api.collections.Revisions( {}, { parent: POST_ID } )
873
  */
874
  wp.api.collections.Revisions = BaseCollection.extend(
875
  /** @lends Revisions.prototype */
@@ -902,12 +984,4 @@
902
  }
903
  );
904
 
905
- /**
906
- * Todo: Handle schema endpoints
907
- */
908
-
909
- /**
910
- * Todo: Handle post meta
911
- */
912
-
913
- })( WP_API_Settings, Backbone, _, window, ( void 0 ) );
1
+ (function( window, undefined ) {
2
+
3
  'use strict';
4
 
5
+ function WP_API() {
6
+ this.models = {};
7
+ this.collections = {};
8
+ this.views = {};
9
+ }
10
+
11
  window.wp = window.wp || {};
12
+ wp.api = wp.api || new WP_API();
 
 
 
 
 
13
 
14
+ })( window );
15
+
16
+ (function( Backbone, _, window, undefined ) {
17
+
18
+ //'use strict';
19
+
20
+ // ECMAScript 5 shim, from MDN
21
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
22
  if ( ! Date.prototype.toISOString ) {
23
+ var pad = function( number ) {
24
  var r = String( number );
25
  if ( r.length === 1 ) {
26
  r = '0' + r;
40
  };
41
  }
42
 
43
+ function WP_API_Utils() {
44
+ var origParse = Date.parse,
45
+ numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
46
 
 
47
 
48
+ this.parseISO8601 = function( date ) {
49
+ var timestamp, struct, i, k,
50
+ minutesOffset = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
53
+ // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
54
+ // implementations could be faster
55
+ // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
56
+ if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
57
+ // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
58
+ for ( i = 0; ( k = numericKeys[i] ); ++i) {
59
+ struct[k] = +struct[k] || 0;
60
+ }
61
+
62
+ // allow undefined days and months
63
+ struct[2] = ( +struct[2] || 1 ) - 1;
64
+ struct[3] = +struct[3] || 1;
65
 
66
+ if ( struct[8] !== 'Z' && struct[9] !== undefined ) {
67
+ minutesOffset = struct[10] * 60 + struct[11];
68
 
69
+ if ( struct[9] === '+' ) {
70
+ minutesOffset = 0 - minutesOffset;
71
+ }
72
  }
73
+
74
+ timestamp = Date.UTC( struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7] );
75
+ }
76
+ else {
77
+ timestamp = origParse ? origParse( date ) : NaN;
78
  }
79
 
80
+ return timestamp;
81
+ };
82
+ }
 
83
 
84
+ window.wp = window.wp || {};
85
+ wp.api = wp.api || {};
86
+ wp.api.utils = wp.api.utils || new WP_API_Utils();
87
+
88
+ })( Backbone, _, window );
89
 
90
+ /* global WP_API_Settings:false */
91
+ // Suppress warning about parse function's unused "options" argument:
92
+ /* jshint unused:false */
93
+ (function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
94
+
95
+ 'use strict';
96
 
97
  /**
98
  * Array of parseable dates
143
  });
144
 
145
  // Parse the author into a User object
146
+ if ( response.author !== 'undefined' ) {
147
  response.author = new wp.api.models.User( response.author );
148
  }
149
 
173
 
174
  var parentModel = this;
175
 
176
+ if ( typeof this.parentModel !== 'undefined' ) {
177
  /**
178
  * Probably a better way to do this. Perhaps grab a cached version of the
179
  * instantiated model?
187
  } else {
188
  // Otherwise, get the object directly
189
  object = new parentModel.constructor( {
190
+ ID: parent
191
  });
192
 
193
  // Note that this acts asynchronously
214
  sync: function( method, model, options ) {
215
  options = options || {};
216
 
217
+ if ( typeof WP_API_Settings.nonce !== 'undefined' ) {
218
  var beforeSend = options.beforeSend;
219
 
220
  options.beforeSend = function( xhr ) {
237
  wp.api.models.User = BaseModel.extend(
238
  /** @lends User.prototype */
239
  {
240
+ idAttribute: 'ID',
241
 
242
  urlRoot: WP_API_Settings.root + '/users',
243
 
244
  defaults: {
245
+ ID: null,
246
+ username: '',
 
 
247
  email: '',
248
+ password: '',
249
+ name: '',
250
  first_name: '',
251
  last_name: '',
 
 
252
  nickname: '',
 
 
253
  slug: '',
254
+ URL: '',
255
+ avatar: '',
256
+ meta: {
257
+ links: {}
258
+ }
259
+ },
260
+
261
+ /**
262
+ * Return avatar URL
263
+ *
264
+ * @param {number} size
265
+ * @returns {string}
266
+ */
267
+ avatar: function( size ) {
268
+ return this.get( 'avatar' ) + '&s=' + size;
269
  }
270
  }
271
  );
283
  defaults: {
284
  name: '',
285
  slug: null,
 
286
  labels: {},
287
+ types: {},
288
  show_cloud: false,
289
+ hierarchical: false,
290
+ meta: {
291
+ links: {}
292
+ }
293
  }
294
  }
295
  );
297
  /**
298
  * Backbone model for term
299
  */
300
+ wp.api.models.Term = BaseModel.extend( _.extend(
301
  /** @lends Term.prototype */
302
  {
303
+ idAttribute: 'ID',
304
+
305
+ taxonomy: 'category',
306
+
307
+ /**
308
+ * @class Represent a term
309
+ * @augments Backbone.Model
310
+ * @constructs
311
+ */
312
+ initialize: function( attributes, options ) {
313
+ if ( typeof options !== 'undefined' ) {
314
+ if ( options.taxonomy ) {
315
+ this.taxonomy = options.taxonomy;
316
+ }
317
+ }
318
+ },
319
 
320
  /**
321
  * Return URL for the model
323
  * @returns {string}
324
  */
325
  url: function() {
326
+ var id = this.get( 'ID' );
327
  id = id || '';
328
 
329
+ return WP_API_Settings.root + '/taxonomies/' + this.taxonomy + '/terms/' + id;
330
  },
331
 
332
  defaults: {
333
+ ID: null,
334
  name: '',
335
  slug: '',
336
  description: '',
337
  parent: null,
338
  count: 0,
339
  link: '',
340
+ meta: {
341
+ links: {}
342
+ }
343
  }
344
 
345
+ }, TimeStampedMixin, HierarchicalMixin )
346
  );
347
 
348
  /**
351
  wp.api.models.Post = BaseModel.extend( _.extend(
352
  /** @lends Post.prototype */
353
  {
354
+ idAttribute: 'ID',
355
 
356
  urlRoot: WP_API_Settings.root + '/posts',
357
 
358
  defaults: {
359
+ ID: null,
360
+ title: '',
361
+ status: 'draft',
362
+ type: 'post',
363
+ author: new wp.api.models.User(),
364
+ content: '',
365
+ link: '',
366
+ 'parent': 0,
367
  date: new Date(),
368
  date_gmt: new Date(),
 
 
369
  modified: new Date(),
370
  modified_gmt: new Date(),
371
+ format: 'standard',
372
  slug: '',
373
+ guid: '',
374
+ excerpt: '',
375
+ menu_order: 0,
 
 
 
 
376
  comment_status: 'open',
377
  ping_status: 'open',
378
  sticky: false,
379
+ date_tz: 'Etc/UTC',
380
+ modified_tz: 'Etc/UTC',
381
+ featured_image: null,
382
+ terms: {},
383
+ post_meta: {},
384
+ meta: {
385
+ links: {}
386
+ }
387
  }
388
  }, TimeStampedMixin, HierarchicalMixin )
389
  );
394
  wp.api.models.Page = BaseModel.extend( _.extend(
395
  /** @lends Page.prototype */
396
  {
397
+ idAttribute: 'ID',
398
 
399
  urlRoot: WP_API_Settings.root + '/pages',
400
 
401
  defaults: {
402
+ ID: null,
403
+ title: '',
404
+ status: 'draft',
405
+ type: 'page',
406
+ author: new wp.api.models.User(),
407
+ content: '',
408
+ parent: 0,
409
  link: '',
410
+ date: new Date(),
411
  modified: new Date(),
412
+ date_gmt: new Date(),
413
  modified_gmt: new Date(),
414
+ date_tz: 'Etc/UTC',
415
+ modified_tz: 'Etc/UTC',
416
+ format: 'standard',
417
  slug: '',
418
+ guid: '',
419
+ excerpt: '',
420
+ menu_order: 0,
 
 
 
 
421
  comment_status: 'closed',
422
+ ping_status: 'open',
423
+ sticky: false,
424
+ password: '',
425
+ meta: {
426
+ links: {}
427
+ },
428
+ featured_image: null,
429
+ terms: []
430
  }
431
  }, TimeStampedMixin, HierarchicalMixin )
432
  );
434
  /**
435
  * Backbone model for revisions
436
  */
437
+ wp.api.models.Revision = wp.api.models.Post.extend(
438
  /** @lends Revision.prototype */
439
  {
 
 
440
  /**
441
+ * Return URL for model
442
  *
443
  * @returns {string}
444
  */
445
  url: function() {
446
+ var parent_id = this.get( 'parent' );
447
+ parent_id = parent_id || '';
448
+
449
+ var id = this.get( 'ID' );
450
+ id = id || '';
451
 
452
+ return WP_API_Settings.root + '/posts/' + parent_id + '/revisions/' + id;
453
  },
454
 
455
+ /**
456
+ * @class Represent a revision
457
+ * @augments Backbone.Model
458
+ * @constructs
459
+ */
460
+ initialize: function() {
461
+ // Todo: what of the parent model is a page?
462
+ this.parentModel = wp.api.models.Post;
 
 
 
 
 
 
463
  }
464
+ }
 
465
  );
466
 
467
  /**
470
  wp.api.models.Media = BaseModel.extend( _.extend(
471
  /** @lends Media.prototype */
472
  {
473
+ idAttribute: 'ID',
474
 
475
  urlRoot: WP_API_Settings.root + '/media',
476
 
477
  defaults: {
478
+ ID: null,
479
+ title: '',
480
+ status: 'inherit',
481
+ type: 'attachment',
482
+ author: new wp.api.models.User(),
483
+ content: '',
484
+ parent: 0,
485
  link: '',
486
+ date: new Date(),
487
  modified: new Date(),
488
+ format: 'standard',
 
489
  slug: '',
490
+ guid: '',
491
+ excerpt: '',
492
+ menu_order: 0,
 
493
  comment_status: 'open',
494
  ping_status: 'open',
495
+ sticky: false,
496
+ date_tz: 'Etc/UTC',
497
+ modified_tz: 'Etc/UTC',
498
+ date_gmt: new Date(),
499
+ modified_gmt: new Date(),
500
+ meta: {
501
+ links: {}
502
+ },
503
+ terms: [],
504
+ source: '',
505
+ is_image: true,
506
+ attachment_meta: {},
507
+ image_meta: {}
508
  },
509
 
510
  /**
525
  wp.api.models.Comment = BaseModel.extend( _.extend(
526
  /** @lends Comment.prototype */
527
  {
528
+ idAttribute: 'ID',
529
 
530
  defaults: {
531
+ ID: null,
 
 
 
 
 
 
 
 
 
 
 
 
532
  post: null,
533
+ content: '',
534
  status: 'hold',
535
  type: '',
536
+ parent: 0,
537
+ author: new wp.api.models.User(),
538
+ date: new Date(),
539
+ date_gmt: new Date(),
540
+ date_tz: 'Etc/UTC',
541
+ meta: {
542
+ links: {}
543
+ }
544
  },
545
 
546
  /**
552
  var post_id = this.get( 'post' );
553
  post_id = post_id || '';
554
 
555
+ var id = this.get( 'ID' );
556
  id = id || '';
557
 
558
  return WP_API_Settings.root + '/posts/' + post_id + '/comments/' + id;
575
  name: '',
576
  description: '',
577
  labels: {},
578
+ queryable: false,
579
+ searchable: false,
580
+ hierarchical: false,
581
+ meta: {
582
+ links: {}
583
+ },
584
+ taxonomies: []
585
  },
586
 
587
  /**
622
  'private': false,
623
  queryable: true,
624
  show_in_list: true,
625
+ meta: {
626
+ links: {}
627
+ }
628
  },
629
 
630
  /**
647
  }
648
  );
649
 
650
+ })( wp, WP_API_Settings, Backbone, _, window );
651
+
652
+ /* global WP_API_Settings:false */
653
+ (function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
654
+
655
+ 'use strict';
656
+
657
  var BaseCollection = Backbone.Collection.extend(
658
  /** @lends BaseCollection.prototype */
659
  {
682
  */
683
  sync: function( method, model, options ) {
684
  options = options || {};
685
+ var beforeSend = options.beforeSend;
 
686
 
687
+ if ( typeof WP_API_Settings.nonce !== 'undefined' ) {
688
  options.beforeSend = function( xhr ) {
689
  xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
690
 
691
  if ( beforeSend ) {
692
+ return beforeSend.apply( this, arguments );
693
  }
694
  };
695
  }
696
 
697
  if ( 'read' === method ) {
698
+ var SELF = this;
699
+
700
  if ( options.data ) {
701
+ SELF.state.data = _.clone( options.data );
702
 
703
+ delete SELF.state.data.page;
704
  } else {
705
+ SELF.state.data = options.data = {};
706
  }
707
 
708
+ if ( typeof options.data.page === 'undefined' ) {
709
+ SELF.state.currentPage = null;
710
+ SELF.state.totalPages = null;
711
+ SELF.state.totalObjects = null;
712
  } else {
713
+ SELF.state.currentPage = options.data.page - 1;
714
  }
715
 
716
  var success = options.success;
717
  options.success = function( data, textStatus, request ) {
718
+ SELF.state.totalPages = parseInt( request.getResponseHeader( 'X-WP-TotalPages' ), 10 );
719
+ SELF.state.totalObjects = parseInt( request.getResponseHeader( 'X-WP-Total' ), 10 );
720
 
721
+ if ( SELF.state.currentPage === null ) {
722
+ SELF.state.currentPage = 1;
723
  } else {
724
+ SELF.state.currentPage++;
725
  }
726
 
727
  if ( success ) {
745
 
746
  _.extend( options.data, this.state.data );
747
 
748
+ if ( typeof options.data.page === 'undefined' ) {
749
  if ( ! this.hasMore() ) {
750
  return false;
751
  }
819
  wp.api.collections.PostStatuses = BaseCollection.extend(
820
  /** @lends PostStatuses.prototype */
821
  {
822
+ url: WP_API_Settings.root + '/posts/statuses',
823
 
824
+ model: wp.api.models.PostStatus
825
 
 
 
 
 
 
826
  }
827
  );
828
 
858
  {
859
  model: wp.api.models.Comment,
860
 
861
+ post: null,
862
+
863
+ /**
864
+ * @class Represent an array of comments
865
+ * @augments Backbone.Collection
866
+ * @constructs
867
+ */
868
+ initialize: function( models, options ) {
869
+ BaseCollection.prototype.initialize.apply( this, arguments );
870
+
871
+ if ( options && options.post ) {
872
+ this.post = options.post;
873
+ }
874
+ },
875
+
876
  /**
877
  * Return URL for collection
878
  *
879
  * @returns {string}
880
  */
881
+ url: function() {
882
+ return WP_API_Settings.root + '/posts/' + this.post + '/comments';
883
+ }
884
  }
885
  );
886
 
892
  {
893
  model: wp.api.models.PostType,
894
 
895
+ url: WP_API_Settings.root + '/posts/types'
 
 
 
 
 
 
 
 
 
 
 
 
896
  }
897
  );
898
 
899
  /**
900
  * Backbone terms collection
 
 
901
  */
902
  wp.api.collections.Terms = BaseCollection.extend(
903
  /** @lends Terms.prototype */
904
  {
905
  model: wp.api.models.Term,
906
 
907
+ type: 'post',
908
+
909
  taxonomy: 'category',
910
 
911
  /**
914
  * @constructs
915
  */
916
  initialize: function( models, options ) {
917
+ BaseCollection.prototype.initialize.apply( this, arguments );
918
+
919
+ if ( typeof options !== 'undefined' ) {
920
+ if ( options.type ) {
921
+ this.type = options.type;
922
+ }
923
+
924
+ if ( options.taxonomy ) {
925
+ this.taxonomy = options.taxonomy;
926
+ }
927
  }
928
 
929
+ this.on( 'add', _.bind( this.addModel, this ) );
930
+ },
931
+
932
+ /**
933
+ * We need to set the type and taxonomy for each model
934
+ *
935
+ * @param {Backbone.model} model
936
+ */
937
+ addModel: function( model ) {
938
+ model.type = this.type;
939
+ model.taxonomy = this.taxonomy;
940
  },
941
 
942
  /**
945
  * @returns {string}
946
  */
947
  url: function() {
948
+ return WP_API_Settings.root + '/posts/types/' + this.type + '/taxonomies/' + this.taxonomy + '/terms/';
949
  }
950
  }
951
  );
952
 
953
  /**
954
  * Backbone revisions collection
 
 
955
  */
956
  wp.api.collections.Revisions = BaseCollection.extend(
957
  /** @lends Revisions.prototype */
984
  }
985
  );
986
 
987
+ })( wp, WP_API_Settings, Backbone, _, window );