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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta3 to 2.0-beta4

CHANGELOG.md CHANGED
@@ -1,5 +1,238 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 3.0
4
 
5
  - Add ability to declare sanitization and default options for schema fields.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 4.0
4
+
5
+ - Show public user information through the user controller.
6
+
7
+ 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.
8
+
9
+ 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.
10
+
11
+ (props @joehoyle, @rmccue, @Shelob9, [#1397][gh-1397], [#839][gh-839], [#1435][gh-1435])
12
+
13
+ - Send schema in OPTIONS requests and index.
14
+
15
+ 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.
16
+
17
+ 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.
18
+
19
+ **⚠️ This breaks backwards compatibility** for clients relying on schemas being at their own routes. These clients should instead send `OPTIONS` requests.
20
+
21
+ 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:
22
+
23
+ ```php
24
+ register_rest_route( 'test-ns', '/test', array(
25
+ array(
26
+ 'methods' => 'GET',
27
+ 'callback' => 'my_test_callback',
28
+ ),
29
+
30
+ 'schema' => 'my_schema_callback',
31
+ ) );
32
+ ```
33
+
34
+ (props @rmccue, [#1415][gh-1415], [#1222][gh-1222], [#1305][gh-1305])
35
+
36
+ - Update JavaScript API for version 2.
37
+
38
+ Our fantastic JavaScript API from version 1 is now available for version 2, refreshed with the latest and greatest changes.
39
+
40
+ 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`.
41
+
42
+ (props @tlovett1, @kadamwhite, @nathanrice, [#1374][gh-1374], [#1320][gh-1320])
43
+
44
+ - Embed links inside items in a collection.
45
+
46
+ 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.
47
+
48
+ 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).
49
+
50
+ 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.
51
+
52
+ (props @rmccue, [#1459][gh-1459], [#865][gh-865])
53
+
54
+ - Fix potential XSS vulnerability.
55
+
56
+ Requests from other origins could potentially run code on the API domain, allowing cross-origin access to authentication cookies or similar.
57
+
58
+ Reported by @xknown on 2015-07-23.
59
+
60
+ - Move `/posts` `WP_Query` vars back to `filter` param.
61
+
62
+ 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.
63
+
64
+ 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.
65
+
66
+ **⚠️ This breaks backwards compatibility** for users using WP Query vars. Simply change your `x=y` parameter to `filter[x]=y`.
67
+
68
+ (props @WP-API, [#1420][gh-1420])
69
+
70
+ - Respect `rest_base` for taxonomies.
71
+
72
+ **⚠️ This breaks backwards compatibility** by changing the `/wp/v2/posts/{id}/terms/post_tag` endpoint to `/wp/v2/posts/{id}/tag`.
73
+
74
+ (props @joehoyle, [#1466][gh-1466])
75
+
76
+ - Add permission check for retrieving the posts collection in edit context.
77
+
78
+ 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.
79
+
80
+ (props @danielpunkass, [#1412][gh-1412])
81
+
82
+ - Ensure the REST API URL always has a trailing slash.
83
+
84
+ 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.
85
+
86
+ (props @danielpunkass, @rmccue, [#1426][gh-1426], [#1442][gh-1442], [#1455][gh-1455], [#1467][gh-1467])
87
+
88
+ - Use `wp_json_encode` instead of `json_encode`
89
+
90
+ 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.
91
+
92
+ (props @rmccue, @pento, [#1417][gh-1417])
93
+
94
+ - Add `role` to schema for users.
95
+
96
+ The available roles you can assign to a user are now available in the schema as an `enum`.
97
+
98
+ (props @joehoyle, [#1400][gh-1400])
99
+
100
+ - Use the schema for validation inside the comments controller.
101
+
102
+ 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.
103
+
104
+ (props @joehoyle, [#1422][gh-1422])
105
+
106
+ - Don't set the Location header in update responses.
107
+
108
+ 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.
109
+
110
+ (props @rachelbaker, [#1441][gh-1441])
111
+
112
+ - Re-enable the `rest_insert_post` action hook for `WP_REST_Posts_Controller`
113
+
114
+ 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.
115
+
116
+ (props @jaredcobb, [#1427][gh-1427], [#1424][gh-1424])
117
+
118
+ - Fix post taxonomy terms link URLs.
119
+
120
+ When moving the routes in a previous beta, we forgot to correct the links on post objects to the new correct route. Sorry!
121
+
122
+ (props @rachelbaker, @joehoyle, [#1447][gh-1447], [#1383][gh-1383])
123
+
124
+ - Use `wp_get_attachment_image_src()` on the image sizes in attachments.
125
+
126
+ 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.
127
+
128
+ (props @joehoyle, [#1462][gh-1462])
129
+
130
+ - Make the embed context a default, not forced.
131
+
132
+ 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.
133
+
134
+ (props @rmccue, [#1464][gh-1464])
135
+
136
+ - Ensure we always use the `term_taxonomy_id` and never expose `term_id` publicly.
137
+
138
+ Previously, `term_id` was inadvertently exposed in some error responses.
139
+
140
+ (props @jdolan, [#1430][gh-1430])
141
+
142
+ - Fix adding alt text to attachments on creation.
143
+
144
+ Previously, this could only be set when updating an attachment, not when creating one.
145
+
146
+ (props @joehoyle, [#1398][gh-1398])
147
+
148
+ - Throw an error when registering routes without a namespace.
149
+
150
+ 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.)
151
+
152
+ 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.
153
+
154
+ (props @joehoyle, @rmccue, [#1355][gh-1355])
155
+
156
+ - Show links on embeds.
157
+
158
+ Previously, links were accidentally stripped from embedded response data.
159
+
160
+ (props @rmccue, [#1472][gh-1472])
161
+
162
+ - Clarify insufficient permisssion error when editing posts.
163
+
164
+ (props @danielpunkass, [#1411][gh-1411])
165
+
166
+ - Improve @return inline docs for rest_ensure_response()
167
+
168
+ (props @Shelob9, [#1328][gh-1328])
169
+
170
+ - Check taxonomies exist before trying to set properties.
171
+
172
+ (props @joehoyle, @rachelbaker, [#1354][gh-1354])
173
+
174
+ - Update controllers to ensure we use `sanitize_callback` wherever possible.
175
+
176
+ (props @joehoyle, [#1399][gh-1399])
177
+
178
+ - Add more phpDoc documentation, and correct existing documentation.
179
+
180
+ (props @Shelob9, @rmccue, [#1432][gh-1432], [#1433][gh-1433], [#1465][gh-1465])
181
+
182
+ - Update testing infrastructure.
183
+
184
+ Travis now runs our coding standards tests in parallel, and now uses the new, faster container-based testing infrastructure.
185
+
186
+ (props @ntwb, @frozzare, [#1449][gh-1449], [#1457][gh-1457])
187
+
188
+ [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta3...2.0-beta4)
189
+
190
+ [gh-839]: https://github.com/WP-API/WP-API/issues/839
191
+ [gh-865]: https://github.com/WP-API/WP-API/issues/865
192
+ [gh-1222]: https://github.com/WP-API/WP-API/issues/1222
193
+ [gh-1305]: https://github.com/WP-API/WP-API/issues/1305
194
+ [gh-1310]: https://github.com/WP-API/WP-API/issues/1310
195
+ [gh-1320]: https://github.com/WP-API/WP-API/issues/1320
196
+ [gh-1328]: https://github.com/WP-API/WP-API/issues/1328
197
+ [gh-1354]: https://github.com/WP-API/WP-API/issues/1354
198
+ [gh-1355]: https://github.com/WP-API/WP-API/issues/1355
199
+ [gh-1372]: https://github.com/WP-API/WP-API/issues/1372
200
+ [gh-1374]: https://github.com/WP-API/WP-API/issues/1374
201
+ [gh-1383]: https://github.com/WP-API/WP-API/issues/1383
202
+ [gh-1397]: https://github.com/WP-API/WP-API/issues/1397
203
+ [gh-1398]: https://github.com/WP-API/WP-API/issues/1398
204
+ [gh-1399]: https://github.com/WP-API/WP-API/issues/1399
205
+ [gh-1400]: https://github.com/WP-API/WP-API/issues/1400
206
+ [gh-1402]: https://github.com/WP-API/WP-API/issues/1402
207
+ [gh-1411]: https://github.com/WP-API/WP-API/issues/1411
208
+ [gh-1412]: https://github.com/WP-API/WP-API/issues/1412
209
+ [gh-1413]: https://github.com/WP-API/WP-API/issues/1413
210
+ [gh-1415]: https://github.com/WP-API/WP-API/issues/1415
211
+ [gh-1417]: https://github.com/WP-API/WP-API/issues/1417
212
+ [gh-1420]: https://github.com/WP-API/WP-API/issues/1420
213
+ [gh-1422]: https://github.com/WP-API/WP-API/issues/1422
214
+ [gh-1424]: https://github.com/WP-API/WP-API/issues/1424
215
+ [gh-1426]: https://github.com/WP-API/WP-API/issues/1426
216
+ [gh-1427]: https://github.com/WP-API/WP-API/issues/1427
217
+ [gh-1430]: https://github.com/WP-API/WP-API/issues/1430
218
+ [gh-1432]: https://github.com/WP-API/WP-API/issues/1432
219
+ [gh-1433]: https://github.com/WP-API/WP-API/issues/1433
220
+ [gh-1435]: https://github.com/WP-API/WP-API/issues/1435
221
+ [gh-1441]: https://github.com/WP-API/WP-API/issues/1441
222
+ [gh-1442]: https://github.com/WP-API/WP-API/issues/1442
223
+ [gh-1447]: https://github.com/WP-API/WP-API/issues/1447
224
+ [gh-1449]: https://github.com/WP-API/WP-API/issues/1449
225
+ [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
226
+ [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
227
+ [gh-1457]: https://github.com/WP-API/WP-API/issues/1457
228
+ [gh-1459]: https://github.com/WP-API/WP-API/issues/1459
229
+ [gh-1462]: https://github.com/WP-API/WP-API/issues/1462
230
+ [gh-1464]: https://github.com/WP-API/WP-API/issues/1464
231
+ [gh-1465]: https://github.com/WP-API/WP-API/issues/1465
232
+ [gh-1466]: https://github.com/WP-API/WP-API/issues/1466
233
+ [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
234
+ [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
235
+
236
  ## 2.0 Beta 3.0
237
 
238
  - Add ability to declare sanitization and default options for schema fields.
README.md CHANGED
@@ -4,6 +4,7 @@ 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
 
8
  ## WARNING
9
 
@@ -49,6 +50,9 @@ There's no fixed timeline for integration into core at this time, but getting cl
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
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
  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
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,6 +234,7 @@ 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
  */
238
  function rest_mysql_to_rfc3339( $date_string ) {
239
  $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
+ * @return string Date formatted for ISO8601/RFC3339.
238
  */
239
  function rest_mysql_to_rfc3339( $date_string ) {
240
  $formatted = mysql2date( 'c', $date_string, false );
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -73,6 +73,10 @@ 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
  $this->update_additional_fields_for_object( $attachment, $request );
77
 
78
  $response = $this->get_item( array(
@@ -103,16 +107,15 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
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,15 +128,15 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
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,7 +169,13 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
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,16 +206,25 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
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.',
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
  $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
  $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
 
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
  '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.',
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -22,54 +22,10 @@ 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' => 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,40 +43,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,11 +53,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,8 +152,18 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,13 +242,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,48 +701,52 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,14 +756,18 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,7 +784,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,6 +796,9 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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,11 +809,18 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
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
  );
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
  '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
  'force' => array(),
54
  ),
55
  ),
 
56
 
57
+ 'schema' => array( $this, 'get_public_item_schema' ),
 
 
58
  ) );
59
  }
60
 
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
  'id' => $id,
243
  'context' => 'edit',
244
  ) );
 
 
 
 
 
245
 
246
+ return rest_ensure_response( $response );
247
  }
248
 
249
  /**
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
  '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
  'description' => 'Karma for the object.',
785
  'type' => 'integer',
786
  'context' => array( 'edit' ),
 
787
  ),
788
  'link' => array(
789
  'description' => 'URL to the object.',
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
  '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
  );
lib/endpoints/class-wp-rest-meta-controller.php CHANGED
@@ -50,13 +50,10 @@ 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' => 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,10 +70,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
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,10 +78,8 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
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,11 +101,16 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
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,14 +352,14 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
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
 
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
  '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
  '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
  '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
  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
 
lib/endpoints/class-wp-rest-post-statuses-controller.php CHANGED
@@ -8,18 +8,19 @@ 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
- '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
 
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
 
lib/endpoints/class-wp-rest-post-types-controller.php CHANGED
@@ -8,23 +8,24 @@ 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
- '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
 
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
 
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -27,18 +27,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,6 +43,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,10 +73,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,11 +85,17 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,12 +225,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,6 +367,23 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
370
  return $response;
371
  }
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  /**
374
  * Check if a given request has access to read a post
375
  *
@@ -381,7 +395,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,7 +682,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
668
  }
669
  // Post slug
670
  if ( isset( $request['slug'] ) ) {
671
- $prepared_post->post_name = sanitize_title( $request['slug'] );
672
  }
673
 
674
  // Author
@@ -718,12 +732,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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,7 +751,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
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':
@@ -1141,13 +1155,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1141
  continue;
1142
  }
1143
 
1144
- if ( 'post_tag' === $tax ) {
1145
- $terms_url = rest_url( '/wp/v2/terms/tag' );
1146
- } else {
1147
- $terms_url = rest_url( '/wp/v2/terms/' . $tax );
1148
- }
1149
-
1150
- $terms_url = add_query_arg( 'post', $post->ID, $terms_url );
1151
 
1152
  $links['http://v2.wp-api.org/term'][] = array(
1153
  'href' => $terms_url,
@@ -1247,6 +1256,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1247
  'description' => 'An alphanumeric identifier for the object unique to its type.',
1248
  'type' => 'string',
1249
  'context' => array( 'view', 'edit', 'embed' ),
 
 
 
1250
  ),
1251
  'status' => array(
1252
  'description' => 'A named status for the object.',
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
  '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
  ),
74
  ),
75
  ),
76
+
77
+ 'schema' => array( $this, 'get_public_item_schema' ),
 
 
78
  ) );
79
  }
80
 
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
 
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
  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
  $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
  }
683
  // Post slug
684
  if ( isset( $request['slug'] ) ) {
685
+ $prepared_post->post_name = $request['slug'];
686
  }
687
 
688
  // Author
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
  * @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':
1155
  continue;
1156
  }
1157
 
1158
+ $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1159
+ $terms_url = rest_url( trailingslashit( $base ) . $post->ID . '/terms/' . $tax_base );
 
 
 
 
 
1160
 
1161
  $links['http://v2.wp-api.org/term'][] = array(
1162
  'href' => $terms_url,
1256
  'description' => 'An alphanumeric identifier for the object unique to its type.',
1257
  'type' => 'string',
1258
  'context' => array( 'view', 'edit', 'embed' ),
1259
+ 'arg_options' => array(
1260
+ 'sanitize_callback' => 'sanitize_title',
1261
+ ),
1262
  ),
1263
  'status' => array(
1264
  'description' => 'A named status for the object.',
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -16,19 +16,21 @@ 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
 
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,11 +46,7 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
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,7 +125,8 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
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;
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
  '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
  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;
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -18,14 +18,18 @@ 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
- '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,12 +48,9 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
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
 
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
  '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
 
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -8,22 +8,25 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
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
 
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
 
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -32,20 +32,10 @@ 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' => 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,28 +47,15 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
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,6 +170,15 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
193
 
194
  $term = wp_insert_term( $name, $this->taxonomy, $args );
195
  if ( is_wp_error( $term ) ) {
 
 
 
 
 
 
 
 
 
196
  return $term;
197
  }
198
 
@@ -496,44 +482,54 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
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(
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
  '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
 
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
  '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(
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -15,7 +15,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,6 +27,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
28
  ),
29
  ) ),
30
  ),
 
 
31
  ) );
32
  register_rest_route( 'wp/v2', '/users/(?P<id>[\d]+)', array(
33
  array(
@@ -56,6 +57,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
56
  'reassign' => array(),
57
  ),
58
  ),
 
 
59
  ) );
60
 
61
  register_rest_route( 'wp/v2', '/users/me', array(
@@ -64,12 +67,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,10 +87,24 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,10 +205,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,6 +234,13 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,10 +305,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,21 +349,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,6 +472,13 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
473
 
474
  $data->add_links( $this->prepare_links( $user ) );
475
 
 
 
 
 
 
 
 
476
  return apply_filters( 'rest_prepare_user', $data, $user, $request );
477
  }
478
 
@@ -538,12 +544,18 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,10 +569,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,6 +603,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
595
  );
596
  }
597
 
 
 
598
  $schema = array(
599
  '$schema' => 'http://json-schema.org/draft-04/schema#',
600
  'title' => 'user',
@@ -611,7 +621,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,7 +642,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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,6 +698,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
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.',
15
  array(
16
  'methods' => WP_REST_Server::READABLE,
17
  'callback' => array( $this, 'get_items' ),
 
18
  'args' => $query_params,
19
  ),
20
  array(
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
  '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
  'args' => array(
68
  'context' => array(),
69
  ),
70
+ 'schema' => array( $this, 'get_public_item_schema' ),
71
  ));
 
 
 
 
 
72
  }
73
 
74
  /**
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
  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
 
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
  'id' => $user_id,
306
  'context' => 'edit',
307
  ));
 
 
308
 
309
+ return rest_ensure_response( $response );
310
  }
311
 
312
  /**
349
  return $orig_user;
350
  }
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  /**
353
  * Check if a given request has access to read a user
354
  *
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
  $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
  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
  );
604
  }
605
 
606
+ global $wp_roles;
607
+
608
  $schema = array(
609
  '$schema' => 'http://json-schema.org/draft-04/schema#',
610
  'title' => 'user',
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
  '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
  '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.',
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,22 +15,84 @@ require_once ( ABSPATH . 'wp-admin/includes/admin.php' );
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,6 +143,11 @@ class WP_REST_Server {
81
  '/' => array(
82
  'callback' => array( $this, 'get_index' ),
83
  'methods' => 'GET',
 
 
 
 
 
84
  ),
85
  );
86
  }
@@ -165,7 +232,7 @@ class WP_REST_Server {
165
  }
166
  $error = compact( 'code', 'message' );
167
 
168
- return json_encode( array( $error ) );
169
  }
170
 
171
  /**
@@ -200,7 +267,7 @@ class WP_REST_Server {
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,20 +302,6 @@ class WP_REST_Server {
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,13 +357,13 @@ class WP_REST_Server {
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,8 +390,12 @@ class WP_REST_Server {
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,13 +486,15 @@ class WP_REST_Server {
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,6 +560,9 @@ class WP_REST_Server {
501
  'namespace' => array(
502
  'default' => $namespace,
503
  ),
 
 
 
504
  ),
505
  ),
506
  ) );
@@ -590,6 +652,20 @@ class WP_REST_Server {
590
  return array_keys( $this->namespaces );
591
  }
592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  /**
594
  * Match the request to a callback and call it
595
  *
@@ -597,6 +673,21 @@ class WP_REST_Server {
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,7 +809,7 @@ class WP_REST_Server {
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,7 +817,7 @@ class WP_REST_Server {
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,7 +853,7 @@ class WP_REST_Server {
762
 
763
  $data = array(
764
  'namespace' => $namespace,
765
- 'routes' => $this->get_route_data( $endpoints ),
766
  );
767
  $response = rest_ensure_response( $data );
768
 
@@ -785,43 +876,15 @@ class WP_REST_Server {
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,6 +904,74 @@ class WP_REST_Server {
841
  return apply_filters( 'rest_route_data', $available, $routes );
842
  }
843
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844
  /**
845
  * Send a HTTP status code
846
  *
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
+ /**
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
  '/' => 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
  }
233
  $error = compact( 'code', 'message' );
234
 
235
+ return wp_json_encode( array( $error ) );
236
  }
237
 
238
  /**
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
  $result = $this->check_authentication();
303
 
304
  if ( ! is_wp_error( $result ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  $result = $this->dispatch( $request );
306
  }
307
 
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
  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
  }
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
  'namespace' => array(
561
  'default' => $namespace,
562
  ),
563
+ 'context' => array(
564
+ 'default' => 'view',
565
+ ),
566
  ),
567
  ),
568
  ) );
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
  * @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
  *
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
  '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
 
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
  * 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
  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
  *
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-beta3
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-beta3' );
18
 
19
  /**
20
  * Include our files for the API.
@@ -69,20 +69,57 @@ function register_rest_route( $namespace, $route, $args = array(), $override = f
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,13 +170,17 @@ add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
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,21 +461,18 @@ function rest_get_url_prefix() {
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,8 +540,7 @@ function rest_ensure_request( $request ) {
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,6 +623,7 @@ function rest_handle_options_request( $response, $handler, $request ) {
586
  }
587
 
588
  $response = new WP_REST_Response();
 
589
 
590
  $accept = array();
591
 
@@ -596,15 +634,13 @@ function rest_handle_options_request( $response, $handler, $request ) {
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,3 +741,19 @@ if ( ! function_exists( 'json_last_error_msg' ) ) :
705
  }
706
  }
707
  endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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-beta4' );
18
 
19
  /**
20
  * Include our files for the API.
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
  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
  * @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
  * 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
  }
624
 
625
  $response = new WP_REST_Response();
626
+ $data = array();
627
 
628
  $accept = array();
629
 
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
  }
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
+ }
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-beta3
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,7 +36,525 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
39
- = Version 2.0 Beta 1 =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  Partial rewrite and evolution of the REST API to prepare for core integration.
42
 
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
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 4.0 =
40
+
41
+ * Show public user information through the user controller.
42
+
43
+ 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.
44
+
45
+ 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.
46
+
47
+ (props @joehoyle, @rmccue, @Shelob9, [#1397][gh-1397], [#839][gh-839], [#1435][gh-1435])
48
+
49
+ * Send schema in OPTIONS requests and index.
50
+
51
+ 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.
52
+
53
+ 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.
54
+
55
+ **⚠️ This breaks backwards compatibility** for clients relying on schemas being at their own routes. These clients should instead send `OPTIONS` requests.
56
+
57
+ 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:
58
+
59
+ ```php
60
+ register_rest_route( 'test-ns', '/test', array(
61
+ array(
62
+ 'methods' => 'GET',
63
+ 'callback' => 'my_test_callback',
64
+ ),
65
+
66
+ 'schema' => 'my_schema_callback',
67
+ ) );
68
+ ```
69
+
70
+ (props @rmccue, [#1415][gh-1415], [#1222][gh-1222], [#1305][gh-1305])
71
+
72
+ * Update JavaScript API for version 2.
73
+
74
+ Our fantastic JavaScript API from version 1 is now available for version 2, refreshed with the latest and greatest changes.
75
+
76
+ 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`.
77
+
78
+ (props @tlovett1, @kadamwhite, @nathanrice, [#1374][gh-1374], [#1320][gh-1320])
79
+
80
+ * Embed links inside items in a collection.
81
+
82
+ 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.
83
+
84
+ 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).
85
+
86
+ 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.
87
+
88
+ (props @rmccue, [#1459][gh-1459], [#865][gh-865])
89
+
90
+ * Fix potential XSS vulnerability.
91
+
92
+ Requests from other origins could potentially run code on the API domain, allowing cross-origin access to authentication cookies or similar.
93
+
94
+ Reported by @xknown on 2015-07-23.
95
+
96
+ * Move `/posts` `WP_Query` vars back to `filter` param.
97
+
98
+ 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.
99
+
100
+ 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.
101
+
102
+ **⚠️ This breaks backwards compatibility** for users using WP Query vars. Simply change your `x=y` parameter to `filter[x]=y`.
103
+
104
+ (props @WP-API, [#1420][gh-1420])
105
+
106
+ * Respect `rest_base` for taxonomies.
107
+
108
+ **⚠️ This breaks backwards compatibility** by changing the `/wp/v2/posts/{id}/terms/post_tag` endpoint to `/wp/v2/posts/{id}/tag`.
109
+
110
+ (props @joehoyle, [#1466][gh-1466])
111
+
112
+ * Add permission check for retrieving the posts collection in edit context.
113
+
114
+ 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.
115
+
116
+ (props @danielpunkass, [#1412][gh-1412])
117
+
118
+ * Ensure the REST API URL always has a trailing slash.
119
+
120
+ 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.
121
+
122
+ (props @danielpunkass, @rmccue, [#1426][gh-1426], [#1442][gh-1442], [#1455][gh-1455], [#1467][gh-1467])
123
+
124
+ * Use `wp_json_encode` instead of `json_encode`
125
+
126
+ 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.
127
+
128
+ (props @rmccue, @pento, [#1417][gh-1417])
129
+
130
+ * Add `role` to schema for users.
131
+
132
+ The available roles you can assign to a user are now available in the schema as an `enum`.
133
+
134
+ (props @joehoyle, [#1400][gh-1400])
135
+
136
+ * Use the schema for validation inside the comments controller.
137
+
138
+ 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.
139
+
140
+ (props @joehoyle, [#1422][gh-1422])
141
+
142
+ * Don't set the Location header in update responses.
143
+
144
+ 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.
145
+
146
+ (props @rachelbaker, [#1441][gh-1441])
147
+
148
+ * Re-enable the `rest_insert_post` action hook for `WP_REST_Posts_Controller`
149
+
150
+ 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.
151
+
152
+ (props @jaredcobb, [#1427][gh-1427], [#1424][gh-1424])
153
+
154
+ * Fix post taxonomy terms link URLs.
155
+
156
+ When moving the routes in a previous beta, we forgot to correct the links on post objects to the new correct route. Sorry!
157
+
158
+ (props @rachelbaker, @joehoyle, [#1447][gh-1447], [#1383][gh-1383])
159
+
160
+ * Use `wp_get_attachment_image_src()` on the image sizes in attachments.
161
+
162
+ 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.
163
+
164
+ (props @joehoyle, [#1462][gh-1462])
165
+
166
+ * Make the embed context a default, not forced.
167
+
168
+ 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.
169
+
170
+ (props @rmccue, [#1464][gh-1464])
171
+
172
+ * Ensure we always use the `term_taxonomy_id` and never expose `term_id` publicly.
173
+
174
+ Previously, `term_id` was inadvertently exposed in some error responses.
175
+
176
+ (props @jdolan, [#1430][gh-1430])
177
+
178
+ * Fix adding alt text to attachments on creation.
179
+
180
+ Previously, this could only be set when updating an attachment, not when creating one.
181
+
182
+ (props @joehoyle, [#1398][gh-1398])
183
+
184
+ * Throw an error when registering routes without a namespace.
185
+
186
+ 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.)
187
+
188
+ 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.
189
+
190
+ (props @joehoyle, @rmccue, [#1355][gh-1355])
191
+
192
+ * Show links on embeds.
193
+
194
+ Previously, links were accidentally stripped from embedded response data.
195
+
196
+ (props @rmccue, [#1472][gh-1472])
197
+
198
+ * Clarify insufficient permisssion error when editing posts.
199
+
200
+ (props @danielpunkass, [#1411][gh-1411])
201
+
202
+ * Improve @return inline docs for rest_ensure_response()
203
+
204
+ (props @Shelob9, [#1328][gh-1328])
205
+
206
+ * Check taxonomies exist before trying to set properties.
207
+
208
+ (props @joehoyle, @rachelbaker, [#1354][gh-1354])
209
+
210
+ * Update controllers to ensure we use `sanitize_callback` wherever possible.
211
+
212
+ (props @joehoyle, [#1399][gh-1399])
213
+
214
+ * Add more phpDoc documentation, and correct existing documentation.
215
+
216
+ (props @Shelob9, @rmccue, [#1432][gh-1432], [#1433][gh-1433], [#1465][gh-1465])
217
+
218
+ * Update testing infrastructure.
219
+
220
+ Travis now runs our coding standards tests in parallel, and now uses the new, faster container-based testing infrastructure.
221
+
222
+ (props @ntwb, @frozzare, [#1449][gh-1449], [#1457][gh-1457])
223
+
224
+ [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta3...2.0-beta4)
225
+
226
+ [gh-839]: https://github.com/WP-API/WP-API/issues/839
227
+ [gh-865]: https://github.com/WP-API/WP-API/issues/865
228
+ [gh-1222]: https://github.com/WP-API/WP-API/issues/1222
229
+ [gh-1305]: https://github.com/WP-API/WP-API/issues/1305
230
+ [gh-1310]: https://github.com/WP-API/WP-API/issues/1310
231
+ [gh-1320]: https://github.com/WP-API/WP-API/issues/1320
232
+ [gh-1328]: https://github.com/WP-API/WP-API/issues/1328
233
+ [gh-1354]: https://github.com/WP-API/WP-API/issues/1354
234
+ [gh-1355]: https://github.com/WP-API/WP-API/issues/1355
235
+ [gh-1372]: https://github.com/WP-API/WP-API/issues/1372
236
+ [gh-1374]: https://github.com/WP-API/WP-API/issues/1374
237
+ [gh-1383]: https://github.com/WP-API/WP-API/issues/1383
238
+ [gh-1397]: https://github.com/WP-API/WP-API/issues/1397
239
+ [gh-1398]: https://github.com/WP-API/WP-API/issues/1398
240
+ [gh-1399]: https://github.com/WP-API/WP-API/issues/1399
241
+ [gh-1400]: https://github.com/WP-API/WP-API/issues/1400
242
+ [gh-1402]: https://github.com/WP-API/WP-API/issues/1402
243
+ [gh-1411]: https://github.com/WP-API/WP-API/issues/1411
244
+ [gh-1412]: https://github.com/WP-API/WP-API/issues/1412
245
+ [gh-1413]: https://github.com/WP-API/WP-API/issues/1413
246
+ [gh-1415]: https://github.com/WP-API/WP-API/issues/1415
247
+ [gh-1417]: https://github.com/WP-API/WP-API/issues/1417
248
+ [gh-1420]: https://github.com/WP-API/WP-API/issues/1420
249
+ [gh-1422]: https://github.com/WP-API/WP-API/issues/1422
250
+ [gh-1424]: https://github.com/WP-API/WP-API/issues/1424
251
+ [gh-1426]: https://github.com/WP-API/WP-API/issues/1426
252
+ [gh-1427]: https://github.com/WP-API/WP-API/issues/1427
253
+ [gh-1430]: https://github.com/WP-API/WP-API/issues/1430
254
+ [gh-1432]: https://github.com/WP-API/WP-API/issues/1432
255
+ [gh-1433]: https://github.com/WP-API/WP-API/issues/1433
256
+ [gh-1435]: https://github.com/WP-API/WP-API/issues/1435
257
+ [gh-1441]: https://github.com/WP-API/WP-API/issues/1441
258
+ [gh-1442]: https://github.com/WP-API/WP-API/issues/1442
259
+ [gh-1447]: https://github.com/WP-API/WP-API/issues/1447
260
+ [gh-1449]: https://github.com/WP-API/WP-API/issues/1449
261
+ [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
262
+ [gh-1455]: https://github.com/WP-API/WP-API/issues/1455
263
+ [gh-1457]: https://github.com/WP-API/WP-API/issues/1457
264
+ [gh-1459]: https://github.com/WP-API/WP-API/issues/1459
265
+ [gh-1462]: https://github.com/WP-API/WP-API/issues/1462
266
+ [gh-1464]: https://github.com/WP-API/WP-API/issues/1464
267
+ [gh-1465]: https://github.com/WP-API/WP-API/issues/1465
268
+ [gh-1466]: https://github.com/WP-API/WP-API/issues/1466
269
+ [gh-1467]: https://github.com/WP-API/WP-API/issues/1467
270
+ [gh-1472]: https://github.com/WP-API/WP-API/issues/1472
271
+
272
+ = 2.0 Beta 3.0 =
273
+
274
+ * Add ability to declare sanitization and default options for schema fields.
275
+
276
+ The `arg_options` array can be used to declare the sanitization callback,
277
+ default value, or requirement of a field.
278
+
279
+ (props @joehoyle, [#1345][gh-1345])
280
+ (props @joehoyle, [#1346][gh-1346])
281
+
282
+ * Expand supported parameters for creating and updating Comments.
283
+
284
+ (props @rachelbaker, [#1245][gh-1245])
285
+
286
+ * Declare collection parameters for Terms of a Post.
287
+
288
+ Define the available collection parameters in `get_collection_params()` and
289
+ allow Terms of a Post to be queried by term order.
290
+
291
+ (props @danielbachhuber, [#1332][gh-1332])
292
+
293
+ * Improve the Attachment error message for an invalid Content-Disposition
294
+
295
+ (props @danielbachhuber, [#1317][gh-1317])
296
+
297
+ * Return 200 status when updating Attachments, Comments, and Users.
298
+
299
+ (props @rachelbaker, [#1348][gh-1348])
300
+
301
+ * Remove unnecessary `handle_format_param()` method.
302
+
303
+ (props @danielbachhuber, [#1331][gh-1331])
304
+
305
+ * Add `author_avatar_url` field to the Comment response and schema.
306
+
307
+ (props @rachelbaker [#1327][gh-1327])
308
+
309
+ * Introduce `rest_do_request()` for making REST requests internally.
310
+
311
+ (props @danielbachhuber, [#1333][gh-1333])
312
+
313
+ * Remove unused DateTime class.
314
+
315
+ (props @rmccue, [#1314][gh-1314])
316
+
317
+ * Add inline documentation for `$wp_rest_server` global.
318
+
319
+ (props @Shelob9, [#1324][gh-1324])
320
+
321
+ [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta2...2.0-beta3)
322
+ [gh-1245]: https://github.com/WP-API/WP-API/issues/1245
323
+ [gh-1314]: https://github.com/WP-API/WP-API/issues/1314
324
+ [gh-1317]: https://github.com/WP-API/WP-API/issues/1317
325
+ [gh-1318]: https://github.com/WP-API/WP-API/issues/1318
326
+ [gh-1324]: https://github.com/WP-API/WP-API/issues/1324
327
+ [gh-1326]: https://github.com/WP-API/WP-API/issues/1326
328
+ [gh-1327]: https://github.com/WP-API/WP-API/issues/1327
329
+ [gh-1331]: https://github.com/WP-API/WP-API/issues/1331
330
+ [gh-1332]: https://github.com/WP-API/WP-API/issues/1332
331
+ [gh-1333]: https://github.com/WP-API/WP-API/issues/1333
332
+ [gh-1345]: https://github.com/WP-API/WP-API/issues/1345
333
+ [gh-1346]: https://github.com/WP-API/WP-API/issues/1346
334
+ [gh-1347]: https://github.com/WP-API/WP-API/issues/1347
335
+ [gh-1348]: https://github.com/WP-API/WP-API/issues/1348
336
+
337
+ = 2.0 Beta 2.0 =
338
+
339
+ * Load the WP REST API before the main query runs.
340
+
341
+ The `rest_api_loaded` function now hooks into the `parse_request` action.
342
+ This change prevents the main query from being run on every request and
343
+ allows sites to set `WP_USE_THEMES` to `false`. Previously, the main query
344
+ was always being run (`SELECT * FROM wp_posts LIMIT 10`), even though the
345
+ result was never used and couldn't be cached.
346
+
347
+ (props @rmccue, [#1270][gh-1270])
348
+
349
+ * Register a new field on an existing WordPress object type.
350
+
351
+ Introduces `register_api_field()` to add a field to an object and
352
+ its schema.
353
+
354
+ (props @joehoyle, @rachelbaker, [#927][gh-927])
355
+ (props @joehoyle, [#1207][gh-1207])
356
+ (props @joehoyle, [#1243][gh-1243])
357
+
358
+ * Add endpoints for viewing, creating, updating, and deleting Terms for a Post.
359
+
360
+ The new `WP_REST_Posts_Terms_Controller` class controller supports routes for
361
+ Terms that belong to a Post.
362
+
363
+ (props @joehoyle, @danielbachhuber, [#1216][gh-1216])
364
+
365
+ * Add pagination headers for collection queries.
366
+
367
+ The `X-WP-Total` and `X-WP-TotalPages` are now present in terms, comments,
368
+ and users collection responses.
369
+
370
+ (props @danielbachhuber, [#1182][gh-1182])
371
+ (props @danielbachhuber, [#1191][gh-1191])
372
+ (props @danielbachhuber, @joehoyle, [#1197][gh-1197])
373
+
374
+ * List registered namespaces in the index for feature detection.
375
+
376
+ The index (`/wp-json` by default) now contains a list of the available
377
+ namespaces. This allows for simple feature detection. You can grab the index
378
+ and check namespaces for `wp/v3` or `pluginname/v2`, which indicate the
379
+ supported endpoints on the site.
380
+
381
+ (props @rmccue,, [#1283][gh-1283])
382
+
383
+ * Standardize link property relations and support embedding for all resources.
384
+
385
+ Change link properties to use IANA-registered relations. Also adds embedding
386
+ support to Attachments, Comments and Terms.
387
+
388
+ (props @rmccue, @rachelbaker, [#1284][gh-1284])
389
+
390
+ * Add support for Composer dependency management.
391
+
392
+ Allows you to recursively install/update the WP REST API inside of WordPress
393
+ plugins or themes.
394
+
395
+ (props @QWp6t, [#1157][gh-1157])
396
+
397
+ * Return full objects in the delete response.
398
+
399
+ Instead of returning a random message when deleting a Post, Comment, Term, or
400
+ User provide the original resource data.
401
+
402
+ (props @danielbachhuber, [#1253][gh-1253])
403
+ (props @danielbachhuber, [#1254][gh-1254])
404
+ (props @danielbachhuber, [#1255][gh-1255])
405
+ (props @danielbachhuber, [#1256][gh-1256])
406
+
407
+ * Return programmatically readable error messages for invalid or missing
408
+ required parameters.
409
+
410
+ (props @joehoyle, [#1175][gh-1175])
411
+
412
+ * Declare supported arguments for Comment and User collection queries.
413
+
414
+ (props @danielbachhuber, [#1211][gh-1211])
415
+ (props @danielbachhuber, [#1217][gh-1217])
416
+
417
+ * Automatically validate parameters based on Schema data.
418
+
419
+ (props @joehoyle, [#1128][gh-1128])
420
+
421
+ * Use the `show_in_rest` attributes for exposing Taxonomies.
422
+
423
+ (props @joehoyle, [#1279][gh-1279])
424
+
425
+ * Handle `parent` when creating or updating a Term.
426
+
427
+ (props @joehoyle, [#1221][gh-1221])
428
+
429
+ * Limit fields returned in `embed` context User responses.
430
+
431
+ (props @rachelbaker, [#1251][gh-1251])
432
+
433
+ * Only include `parent` in term response when tax is hierarchical.
434
+
435
+ (props @danielbachhuber, [#1189][gh-1189])
436
+
437
+ * Fix bug in creating comments if `type` was not set.
438
+
439
+ (props @rachelbaker, [#1244][gh-1244])
440
+
441
+ * Rename `post_name` field to `post_slug`.
442
+
443
+ (props @danielbachhuber, [#1235][gh-1235])
444
+
445
+ * Add check when creating a user to verify the provided role is valid.
446
+
447
+ (props @rachelbaker, [#1267][gh-1267])
448
+
449
+ * Add link properties to the Post Status response.
450
+
451
+ (props @joehoyle, [#1243][gh-1243])
452
+
453
+ * Return `0` for `parent` in Post response instead of `null`.
454
+
455
+ (props @danielbachhuber, [#1269][gh-1269])
456
+
457
+ * Only link `author` when there's a valid author
458
+
459
+ (props @danielbachhuber, [#1203][gh-1203])
460
+
461
+ * Only permit querying by parent term when tax is hierarchical.
462
+
463
+ (props @danielbachhuber, [#1219][gh-1219])
464
+
465
+ * Only permit deleting posts of the proper type
466
+
467
+ (props @danielbachhuber, [#1257][gh-1257])
468
+
469
+ * Set pagination headers even when no found posts.
470
+
471
+ (props @danielbachhuber, [#1209][gh-1209])
472
+
473
+ * Correct prefix in `rest_request_parameter_order` filter.
474
+
475
+ (props @quasel, [#1158][gh-1158])
476
+
477
+ * Retool `WP_REST_Terms_Controller` to follow Posts controller pattern.
478
+
479
+ (props @danielbachhuber, [#1170][gh-1170])
480
+
481
+ * Remove unused `accept_json argument` from the `register_routes` method.
482
+
483
+ (props @quasel, [#1160][gh-1160])
484
+
485
+ * Fix typo in `sanitize_params` inline documentation.
486
+
487
+ (props @Shelob9, [#1226][gh-1226])
488
+
489
+ * Remove commented out code in dispatch method.
490
+
491
+ (props @rachelbaker, [#1162][gh-1162])
492
+
493
+
494
+ [View all changes](https://github.com/WP-API/WP-API/compare/2.0-beta1.1...2.0-beta2)
495
+ [gh-927]: https://github.com/WP-API/WP-API/issues/927
496
+ [gh-1128]: https://github.com/WP-API/WP-API/issues/1128
497
+ [gh-1157]: https://github.com/WP-API/WP-API/issues/1157
498
+ [gh-1158]: https://github.com/WP-API/WP-API/issues/1158
499
+ [gh-1160]: https://github.com/WP-API/WP-API/issues/1160
500
+ [gh-1162]: https://github.com/WP-API/WP-API/issues/1162
501
+ [gh-1168]: https://github.com/WP-API/WP-API/issues/1168
502
+ [gh-1170]: https://github.com/WP-API/WP-API/issues/1170
503
+ [gh-1171]: https://github.com/WP-API/WP-API/issues/1171
504
+ [gh-1175]: https://github.com/WP-API/WP-API/issues/1175
505
+ [gh-1176]: https://github.com/WP-API/WP-API/issues/1176
506
+ [gh-1177]: https://github.com/WP-API/WP-API/issues/1177
507
+ [gh-1181]: https://github.com/WP-API/WP-API/issues/1181
508
+ [gh-1182]: https://github.com/WP-API/WP-API/issues/1182
509
+ [gh-1188]: https://github.com/WP-API/WP-API/issues/1188
510
+ [gh-1189]: https://github.com/WP-API/WP-API/issues/1189
511
+ [gh-1191]: https://github.com/WP-API/WP-API/issues/1191
512
+ [gh-1197]: https://github.com/WP-API/WP-API/issues/1197
513
+ [gh-1200]: https://github.com/WP-API/WP-API/issues/1200
514
+ [gh-1203]: https://github.com/WP-API/WP-API/issues/1203
515
+ [gh-1207]: https://github.com/WP-API/WP-API/issues/1207
516
+ [gh-1209]: https://github.com/WP-API/WP-API/issues/1209
517
+ [gh-1210]: https://github.com/WP-API/WP-API/issues/1210
518
+ [gh-1211]: https://github.com/WP-API/WP-API/issues/1211
519
+ [gh-1216]: https://github.com/WP-API/WP-API/issues/1216
520
+ [gh-1217]: https://github.com/WP-API/WP-API/issues/1217
521
+ [gh-1219]: https://github.com/WP-API/WP-API/issues/1219
522
+ [gh-1221]: https://github.com/WP-API/WP-API/issues/1221
523
+ [gh-1226]: https://github.com/WP-API/WP-API/issues/1226
524
+ [gh-1235]: https://github.com/WP-API/WP-API/issues/1235
525
+ [gh-1243]: https://github.com/WP-API/WP-API/issues/1243
526
+ [gh-1244]: https://github.com/WP-API/WP-API/issues/1244
527
+ [gh-1249]: https://github.com/WP-API/WP-API/issues/1249
528
+ [gh-1251]: https://github.com/WP-API/WP-API/issues/1251
529
+ [gh-1253]: https://github.com/WP-API/WP-API/issues/1253
530
+ [gh-1254]: https://github.com/WP-API/WP-API/issues/1254
531
+ [gh-1255]: https://github.com/WP-API/WP-API/issues/1255
532
+ [gh-1256]: https://github.com/WP-API/WP-API/issues/1256
533
+ [gh-1257]: https://github.com/WP-API/WP-API/issues/1257
534
+ [gh-1259]: https://github.com/WP-API/WP-API/issues/1259
535
+ [gh-1267]: https://github.com/WP-API/WP-API/issues/1267
536
+ [gh-1268]: https://github.com/WP-API/WP-API/issues/1268
537
+ [gh-1269]: https://github.com/WP-API/WP-API/issues/1269
538
+ [gh-1270]: https://github.com/WP-API/WP-API/issues/1270
539
+ [gh-1276]: https://github.com/WP-API/WP-API/issues/1276
540
+ [gh-1277]: https://github.com/WP-API/WP-API/issues/1277
541
+ [gh-1279]: https://github.com/WP-API/WP-API/issues/1279
542
+ [gh-1283]: https://github.com/WP-API/WP-API/issues/1283
543
+ [gh-1284]: https://github.com/WP-API/WP-API/issues/1284
544
+ [gh-1295]: https://github.com/WP-API/WP-API/issues/1295
545
+ [gh-1301]: https://github.com/WP-API/WP-API/issues/1301
546
+
547
+
548
+ = 2.0 Beta 1.1 =
549
+
550
+ * Fix user access security vulnerability.
551
+
552
+ Authenticated users were able to escalate their privileges bypassing the
553
+ expected capabilities check.
554
+
555
+ Reported by @kacperszurek on 2015-05-16.
556
+
557
+ = 2.0 Beta 1 =
558
 
559
  Partial rewrite and evolution of the REST API to prepare for core integration.
560
 
wp-api.js CHANGED
@@ -1,26 +1,20 @@
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,59 +34,49 @@
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,7 +127,7 @@
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,7 +157,7 @@
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,7 +171,7 @@
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,7 +198,7 @@
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,35 +221,28 @@
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,13 +260,11 @@
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,25 +272,10 @@
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,26 +283,25 @@
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,39 +310,32 @@
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,39 +346,32 @@
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,34 +379,39 @@
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,41 +420,34 @@
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,22 +468,26 @@
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,7 +499,7 @@
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,13 +522,7 @@
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,9 +563,7 @@
622
  'private': false,
623
  queryable: true,
624
  show_in_list: true,
625
- meta: {
626
- links: {}
627
- }
628
  },
629
 
630
  /**
@@ -647,13 +586,9 @@
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,46 +617,45 @@
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,7 +679,7 @@
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,10 +753,15 @@
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,29 +797,12 @@
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,20 +814,32 @@
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,29 +848,11 @@
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,13 +861,15 @@
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,4 +902,12 @@
984
  }
985
  );
986
 
987
- })( wp, WP_API_Settings, Backbone, _, window );
 
 
 
 
 
 
 
 
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
  };
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
  });
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
 
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
  } else {
172
  // Otherwise, get the object directly
173
  object = new parentModel.constructor( {
174
+ id: parent
175
  });
176
 
177
  // Note that this acts asynchronously
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
  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
  defaults: {
261
  name: '',
262
  slug: null,
263
+ description: '',
264
  labels: {},
265
+ types: [],
266
  show_cloud: false,
267
+ hierarchical: false
 
 
 
268
  }
269
  }
270
  );
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
  * @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
  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
  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
  /**
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
  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
  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
  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
  name: '',
523
  description: '',
524
  labels: {},
525
+ hierarchical: false
 
 
 
 
 
 
526
  },
527
 
528
  /**
563
  'private': false,
564
  queryable: true,
565
  show_in_list: true,
566
+ _links: {}
 
 
567
  },
568
 
569
  /**
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
  */
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
 
680
  _.extend( options.data, this.state.data );
681
 
682
+ if ( 'undefined' === typeof options.data.page ) {
683
  if ( ! this.hasMore() ) {
684
  return false;
685
  }
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
  {
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
  {
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
  * @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
  * @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
  }
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 ) );