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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta8 to 2.0-beta9

CHANGELOG.md CHANGED
@@ -1,5 +1,119 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 8.0
4
 
5
  - Prevent fatals when uploading attachment by including admin utilities.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 9.0
4
+
5
+ - BREAKING CHANGE: Move tags and categories to top-level endpoints.
6
+
7
+ Tags are now accessible at `/wp/v2/tags`, and categories accessible at `/wp/v2/categories`. Post terms reside at `/wp/v2/posts/<id>/tags` and `/wp/v2/<id>/categories`.
8
+
9
+ (props @danielbachhuber, [#1802](https://github.com/WP-API/WP-API/pull/1802))
10
+
11
+ - BREAKING CHANGE: Return object for requests to `/wp/v2/taxonomies`.
12
+
13
+ This is consistent with `/wp/v2/types` and `/wp/v2/statuses`.
14
+
15
+ (props @danielbachhuber, [#1825](https://github.com/WP-API/WP-API/pull/1825))
16
+
17
+ - BREAKING CHANGE: Remove `rest_get_timezone()`.
18
+
19
+ `json_get_timezone()` was only ever used in v1. This function causes fatals, and shouldn't be used.
20
+
21
+ (props @danielbachhuber, [#1823](https://github.com/WP-API/WP-API/pull/1823))
22
+
23
+ - BREAKING CHANGE: Rename `register_api_field()` to `register_rest_field()`.
24
+
25
+ Introduces a `register_api_field()` function for backwards compat, which calls `_doing_it_wrong()`. However, `register_api_field()` won't ever be committed to WordPress core, so you should update your function calls.
26
+
27
+ (props @danielbachhuber, [#1824](https://github.com/WP-API/WP-API/pull/1824))
28
+
29
+ - BREAKING CHANGE: Change taxonomies' `post_type` argument to `type`.
30
+
31
+ It's consistent with how we're exposing post types in the API.
32
+
33
+ (props @danielbachhuber, [#1824](https://github.com/WP-API/WP-API/pull/1824))
34
+
35
+ - Sync infrastructure with shipped in WordPress 4.4.
36
+
37
+ * `wp-includes/rest-api/rest-functions.php` is removed, and its functions moved into `wp-includes/rest-api.php`.
38
+ * Send nocache headers for REST requests. [#34832](https://core.trac.wordpress.org/ticket/34832)
39
+ * Fix handling of HEAD requests. [#34837](https://core.trac.wordpress.org/ticket/34837)
40
+ * Mark `WP_REST_Server::get_raw_data()` as static. [#34768](https://core.trac.wordpress.org/ticket/34768)
41
+ * Unabbreviate error string. [#34818](https://core.trac.wordpress.org/ticket/34818)
42
+
43
+ - Change terms endpoints to use `term_id` not `tt_id`.
44
+
45
+ (props @joehoyle, [#1837](https://github.com/WP-API/WP-API/pull/1837))
46
+
47
+ - Standardize declaration of `context` param for `GET` requests across controllers.
48
+
49
+ However, we're still inconsistent in which controllers expose which params. Follow [#1845](https://github.com/WP-API/WP-API/issues/1845) for further discussion.
50
+
51
+ (props @danielbachhuber, [#1795](https://github.com/WP-API/WP-API/pull/1795), [#1835](https://github.com/WP-API/WP-API/pull/1835), [#1838](https://github.com/WP-API/WP-API/pull/1838))
52
+
53
+ - Link types / taxonomies to their collections, and vice versa.
54
+
55
+ Collections link to their type / taxonomy with the `about` relation; types / taxonomies link to their colletion with the `item` relation, which is imperfect and may change in the future.
56
+
57
+ (props @danielbachhuber, [#1814](https://github.com/WP-API/WP-API/pull/1814), [#1817](https://github.com/WP-API/WP-API/pull/1817), [#1829](https://github.com/WP-API/WP-API/pull/1829). [#1846](https://github.com/WP-API/WP-API/pull/1846))
58
+
59
+ - Add missing 'wp/v2' in Location Response header when creating new Post Meta.
60
+
61
+ (props @johanmynhardt, [#1790](https://github.com/WP-API/WP-API/pull/1790))
62
+
63
+ - Expose Post collection query params, including `author`, `order`, `orderby` and `status`.
64
+
65
+ (props @danielbachhuber, [#1793](https://github.com/WP-API/WP-API/pull/1793))
66
+
67
+ - Ignore sticky posts by default.
68
+
69
+ (props @danielbachhuber, [#1801](https://github.com/WP-API/WP-API/pull/1801))
70
+
71
+ - Include `full` image size in attachment `sizes` attribute.
72
+
73
+ (props @danielbachhuber, [#1806](https://github.com/WP-API/WP-API/pull/1806))
74
+
75
+ - In text strings, use `id` instead of `ID`.
76
+
77
+ `ID` is an implementation artifact. Our Resources use `id`.
78
+
79
+ (props @danielbachhuber, [#1803](https://github.com/WP-API/WP-API/pull/1803))
80
+
81
+ - Ensure `attachment.sizes[]` use `mime_type` instead of `mime-type`.
82
+
83
+ (props @danielbachhuber, [#1809](https://github.com/WP-API/WP-API/pull/1809))
84
+
85
+ - Introduce `rest_authorization_required_code()`.
86
+
87
+ Many controllers returned incorrect HTTP codes, which this also fixes.
88
+
89
+ (props @danielbachhuber, [#1808](https://github.com/WP-API/WP-API/pull/1808))
90
+
91
+ - Respect core's `comment_registration` setting.
92
+
93
+ If it's enabled, require users to be logged in to comment.
94
+
95
+ (props @danielbachhuber, [#1826](https://github.com/WP-API/WP-API/pull/1826))
96
+
97
+ - Default to wildcard when searching users.
98
+
99
+ (props @danielbachhuber, [#1827](https://github.com/WP-API/WP-API/pull/1827))
100
+
101
+ - Bring the wp-api.js library up to date for v2 of the REST API.
102
+
103
+ (props @adamsilverstein, [#1828](https://github.com/WP-API/WP-API/pull/1828))
104
+
105
+ - Add `rest_prepare_status` filter.
106
+
107
+ (props @danielbachhuber, [#1830](https://github.com/WP-API/WP-API/pull/1830))
108
+
109
+ - Make `prepare_*` filters more consistent.
110
+
111
+ (props @danielbachhuber, [#1831](https://github.com/WP-API/WP-API/pull/1831))
112
+
113
+ - Add `rest_prepare_post_type` filter for post types.
114
+
115
+ (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
116
+
117
  ## 2.0 Beta 8.0
118
 
119
  - Prevent fatals when uploading attachment by including admin utilities.
core/rest-api.php CHANGED
@@ -26,6 +26,7 @@ if ( file_exists( ABSPATH . WPINC . '/class-wp-http-response.php' ) ) {
26
 
27
  /** Main API functions */
28
  include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
 
29
 
30
  /** WP_REST_Server class */
31
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-server.php' );
@@ -39,9 +40,6 @@ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-respons
39
  /** WP_REST_Request class */
40
  require_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-request.php' );
41
 
42
- /** REST functions */
43
- include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/rest-functions.php' );
44
-
45
  /** REST filters */
46
  include_once( dirname( __FILE__ ) . '/wp-includes/filters.php' );
47
 
26
 
27
  /** Main API functions */
28
  include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
29
+ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api.php' );
30
 
31
  /** WP_REST_Server class */
32
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-server.php' );
40
  /** WP_REST_Request class */
41
  require_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-request.php' );
42
 
 
 
 
43
  /** REST filters */
44
  include_once( dirname( __FILE__ ) . '/wp-includes/filters.php' );
45
 
core/wp-includes/rest-api.php CHANGED
@@ -4,6 +4,7 @@
4
  *
5
  * @package WordPress
6
  * @subpackage REST_API
 
7
  */
8
 
9
  /**
@@ -13,14 +14,637 @@
13
  */
14
  define( 'REST_API_VERSION', '2.0' );
15
 
16
- /** WP_REST_Server class */
17
- require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- /** WP_REST_Response class */
20
- require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' );
 
21
 
22
- /** WP_REST_Request class */
23
- require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' );
24
 
25
- /** REST functions */
26
- require_once( ABSPATH . WPINC . '/rest-api/rest-functions.php' );
4
  *
5
  * @package WordPress
6
  * @subpackage REST_API
7
+ * @since 4.4.0
8
  */
9
 
10
  /**
14
  */
15
  define( 'REST_API_VERSION', '2.0' );
16
 
17
+ /**
18
+ * Registers a REST API route.
19
+ *
20
+ * @since 4.4.0
21
+ *
22
+ * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
23
+ *
24
+ * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
25
+ * @param string $route The base URL for route you are adding.
26
+ * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
27
+ * multiple methods. Default empty array.
28
+ * @param bool $override Optional. If the route already exists, should we override it? True overrides,
29
+ * false merges (with newer overriding if duplicate keys exist). Default false.
30
+ * @return bool True on success, false on error.
31
+ */
32
+ function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
33
+ /** @var WP_REST_Server $wp_rest_server */
34
+ global $wp_rest_server;
35
+
36
+ if ( empty( $namespace ) ) {
37
+ /*
38
+ * Non-namespaced routes are not allowed, with the exception of the main
39
+ * and namespace indexes. If you really need to register a
40
+ * non-namespaced route, call `WP_REST_Server::register_route` directly.
41
+ */
42
+ _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
43
+ return false;
44
+ } else if ( empty( $route ) ) {
45
+ _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
46
+ return false;
47
+ }
48
+
49
+ if ( isset( $args['callback'] ) ) {
50
+ // Upgrade a single set to multiple.
51
+ $args = array( $args );
52
+ }
53
+
54
+ $defaults = array(
55
+ 'methods' => 'GET',
56
+ 'callback' => null,
57
+ 'args' => array(),
58
+ );
59
+ foreach ( $args as $key => &$arg_group ) {
60
+ if ( ! is_numeric( $arg_group ) ) {
61
+ // Route option, skip here.
62
+ continue;
63
+ }
64
+
65
+ $arg_group = array_merge( $defaults, $arg_group );
66
+ }
67
+
68
+ $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
69
+ $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
70
+ return true;
71
+ }
72
+
73
+ /**
74
+ * Registers rewrite rules for the API.
75
+ *
76
+ * @since 4.4.0
77
+ *
78
+ * @see rest_api_register_rewrites()
79
+ * @global WP $wp Current WordPress environment instance.
80
+ */
81
+ function rest_api_init() {
82
+ rest_api_register_rewrites();
83
+
84
+ global $wp;
85
+ $wp->add_query_var( 'rest_route' );
86
+ }
87
+
88
+ /**
89
+ * Adds REST rewrite rules.
90
+ *
91
+ * @since 4.4.0
92
+ *
93
+ * @see add_rewrite_rule()
94
+ */
95
+ function rest_api_register_rewrites() {
96
+ add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
97
+ add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
98
+ }
99
+
100
+ /**
101
+ * Registers the default REST API filters.
102
+ *
103
+ * Attached to the {@see 'rest_api_init'} action
104
+ * to make testing and disabling these filters easier.
105
+ *
106
+ * @since 4.4.0
107
+ */
108
+ function rest_api_default_filters() {
109
+ // Deprecated reporting.
110
+ add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
111
+ add_filter( 'deprecated_function_trigger_error', '__return_false' );
112
+ add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
113
+ add_filter( 'deprecated_argument_trigger_error', '__return_false' );
114
+
115
+ // Default serving.
116
+ add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
117
+ add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
118
+
119
+ add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
120
+ }
121
+
122
+ /**
123
+ * Loads the REST API.
124
+ *
125
+ * @since 4.4.0
126
+ *
127
+ * @global WP $wp Current WordPress environment instance.
128
+ * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
129
+ */
130
+ function rest_api_loaded() {
131
+ if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
132
+ return;
133
+ }
134
+
135
+ /**
136
+ * Whether this is a REST Request.
137
+ *
138
+ * @since 4.4.0
139
+ * @var bool
140
+ */
141
+ define( 'REST_REQUEST', true );
142
+
143
+ /** @var WP_REST_Server $wp_rest_server */
144
+ global $wp_rest_server;
145
+
146
+ /**
147
+ * Filter the REST Server Class.
148
+ *
149
+ * This filter allows you to adjust the server class used by the API, using a
150
+ * different class to handle requests.
151
+ *
152
+ * @since 4.4.0
153
+ *
154
+ * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
155
+ */
156
+ $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
157
+ $wp_rest_server = new $wp_rest_server_class;
158
+
159
+ /**
160
+ * Fires when preparing to serve an API request.
161
+ *
162
+ * Endpoint objects should be created and register their hooks on this action rather
163
+ * than another action to ensure they're only loaded when needed.
164
+ *
165
+ * @since 4.4.0
166
+ *
167
+ * @param WP_REST_Server $wp_rest_server Server object.
168
+ */
169
+ do_action( 'rest_api_init', $wp_rest_server );
170
+
171
+ // Fire off the request.
172
+ $wp_rest_server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
173
+
174
+ // We're done.
175
+ die();
176
+ }
177
+
178
+ /**
179
+ * Retrieves the URL prefix for any API resource.
180
+ *
181
+ * @since 4.4.0
182
+ *
183
+ * @return string Prefix.
184
+ */
185
+ function rest_get_url_prefix() {
186
+ /**
187
+ * Filter the REST URL prefix.
188
+ *
189
+ * @since 4.4.0
190
+ *
191
+ * @param string $prefix URL prefix. Default 'wp-json'.
192
+ */
193
+ return apply_filters( 'rest_url_prefix', 'wp-json' );
194
+ }
195
+
196
+ /**
197
+ * Retrieves the URL to a REST endpoint on a site.
198
+ *
199
+ * Note: The returned URL is NOT escaped.
200
+ *
201
+ * @since 4.4.0
202
+ *
203
+ * @todo Check if this is even necessary
204
+ *
205
+ * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
206
+ * @param string $path Optional. REST route. Default '/'.
207
+ * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
208
+ * @return string Full URL to the endpoint.
209
+ */
210
+ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
211
+ if ( empty( $path ) ) {
212
+ $path = '/';
213
+ }
214
+
215
+ if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
216
+ $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
217
+ $url .= '/' . ltrim( $path, '/' );
218
+ } else {
219
+ $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
220
+
221
+ $path = '/' . ltrim( $path, '/' );
222
+
223
+ $url = add_query_arg( 'rest_route', $path, $url );
224
+ }
225
+
226
+ if ( is_ssl() ) {
227
+ // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
228
+ if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
229
+ $url = set_url_scheme( $url, 'https' );
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Filter the REST URL.
235
+ *
236
+ * Use this filter to adjust the url returned by the `get_rest_url` function.
237
+ *
238
+ * @since 4.4.0
239
+ *
240
+ * @param string $url REST URL.
241
+ * @param string $path REST route.
242
+ * @param int $blog_id Blog ID.
243
+ * @param string $scheme Sanitization scheme.
244
+ */
245
+ return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
246
+ }
247
+
248
+ /**
249
+ * Retrieves the URL to a REST endpoint.
250
+ *
251
+ * Note: The returned URL is NOT escaped.
252
+ *
253
+ * @since 4.4.0
254
+ *
255
+ * @param string $path Optional. REST route. Default empty.
256
+ * @param string $scheme Optional. Sanitization scheme. Default 'json'.
257
+ * @return string Full URL to the endpoint.
258
+ */
259
+ function rest_url( $path = '', $scheme = 'json' ) {
260
+ return get_rest_url( null, $path, $scheme );
261
+ }
262
+
263
+ /**
264
+ * Do a REST request.
265
+ *
266
+ * Used primarily to route internal requests through WP_REST_Server.
267
+ *
268
+ * @since 4.4.0
269
+ *
270
+ * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
271
+ *
272
+ * @param WP_REST_Request|string $request Request.
273
+ * @return WP_REST_Response REST response.
274
+ */
275
+ function rest_do_request( $request ) {
276
+ global $wp_rest_server;
277
+ $request = rest_ensure_request( $request );
278
+ return $wp_rest_server->dispatch( $request );
279
+ }
280
+
281
+ /**
282
+ * Ensures request arguments are a request object (for consistency).
283
+ *
284
+ * @since 4.4.0
285
+ *
286
+ * @param array|WP_REST_Request $request Request to check.
287
+ * @return WP_REST_Request REST request instance.
288
+ */
289
+ function rest_ensure_request( $request ) {
290
+ if ( $request instanceof WP_REST_Request ) {
291
+ return $request;
292
+ }
293
+
294
+ return new WP_REST_Request( 'GET', '', $request );
295
+ }
296
+
297
+ /**
298
+ * Ensures a REST response is a response object (for consistency).
299
+ *
300
+ * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
301
+ * without needing to double-check the object. Will also allow WP_Error to indicate error
302
+ * responses, so users should immediately check for this value.
303
+ *
304
+ * @since 4.4.0
305
+ *
306
+ * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
307
+ * @return mixed WP_Error if response generated an error, WP_HTTP_Response if response
308
+ * is a already an instance, otherwise returns a new WP_REST_Response instance.
309
+ */
310
+ function rest_ensure_response( $response ) {
311
+ if ( is_wp_error( $response ) ) {
312
+ return $response;
313
+ }
314
+
315
+ if ( $response instanceof WP_HTTP_Response ) {
316
+ return $response;
317
+ }
318
+
319
+ return new WP_REST_Response( $response );
320
+ }
321
+
322
+ /**
323
+ * Handles _deprecated_function() errors.
324
+ *
325
+ * @since 4.4.0
326
+ *
327
+ * @param string $function Function name.
328
+ * @param string $replacement Replacement function name.
329
+ * @param string $version Version.
330
+ */
331
+ function rest_handle_deprecated_function( $function, $replacement, $version ) {
332
+ if ( ! empty( $replacement ) ) {
333
+ /* translators: 1: function name, 2: WordPress version number, 3: new function name */
334
+ $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
335
+ } else {
336
+ /* translators: 1: function name, 2: WordPress version number */
337
+ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
338
+ }
339
+
340
+ header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
341
+ }
342
+
343
+ /**
344
+ * Handles _deprecated_argument() errors.
345
+ *
346
+ * @since 4.4.0
347
+ *
348
+ * @param string $function Function name.
349
+ * @param string $replacement Replacement function name.
350
+ * @param string $version Version.
351
+ */
352
+ function rest_handle_deprecated_argument( $function, $replacement, $version ) {
353
+ if ( ! empty( $replacement ) ) {
354
+ /* translators: 1: function name, 2: WordPress version number, 3: new argument name */
355
+ $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $replacement );
356
+ } else {
357
+ /* translators: 1: function name, 2: WordPress version number */
358
+ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
359
+ }
360
+
361
+ header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
362
+ }
363
+
364
+ /**
365
+ * Sends Cross-Origin Resource Sharing headers with API requests.
366
+ *
367
+ * @since 4.4.0
368
+ *
369
+ * @param mixed $value Response data.
370
+ * @return mixed Response data.
371
+ */
372
+ function rest_send_cors_headers( $value ) {
373
+ $origin = get_http_origin();
374
+
375
+ if ( $origin ) {
376
+ header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
377
+ header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
378
+ header( 'Access-Control-Allow-Credentials: true' );
379
+ }
380
+
381
+ return $value;
382
+ }
383
+
384
+ /**
385
+ * Handles OPTIONS requests for the server.
386
+ *
387
+ * This is handled outside of the server code, as it doesn't obey normal route
388
+ * mapping.
389
+ *
390
+ * @since 4.4.0
391
+ *
392
+ * @param mixed $response Current response, either response or `null` to indicate pass-through.
393
+ * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
394
+ * @param WP_REST_Request $request The request that was used to make current response.
395
+ * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
396
+ */
397
+ function rest_handle_options_request( $response, $handler, $request ) {
398
+ if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
399
+ return $response;
400
+ }
401
+
402
+ $response = new WP_REST_Response();
403
+ $data = array();
404
+
405
+ $accept = array();
406
+
407
+ foreach ( $handler->get_routes() as $route => $endpoints ) {
408
+ $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
409
+
410
+ if ( ! $match ) {
411
+ continue;
412
+ }
413
+
414
+ $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
415
+ $accept = array_merge( $accept, $data['methods'] );
416
+ break;
417
+ }
418
+ $response->header( 'Accept', implode( ', ', $accept ) );
419
+
420
+ $response->set_data( $data );
421
+ return $response;
422
+ }
423
+
424
+ /**
425
+ * Sends the "Allow" header to state all methods that can be sent to the current route.
426
+ *
427
+ * @since 4.4.0
428
+ *
429
+ * @param WP_REST_Response $response Current response being served.
430
+ * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
431
+ * @param WP_REST_Request $request The request that was used to make current response.
432
+ * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
433
+ */
434
+ function rest_send_allow_header( $response, $server, $request ) {
435
+ $matched_route = $response->get_matched_route();
436
+
437
+ if ( ! $matched_route ) {
438
+ return $response;
439
+ }
440
+
441
+ $routes = $server->get_routes();
442
+
443
+ $allowed_methods = array();
444
+
445
+ // Get the allowed methods across the routes.
446
+ foreach ( $routes[ $matched_route ] as $_handler ) {
447
+ foreach ( $_handler['methods'] as $handler_method => $value ) {
448
+
449
+ if ( ! empty( $_handler['permission_callback'] ) ) {
450
+
451
+ $permission = call_user_func( $_handler['permission_callback'], $request );
452
+
453
+ $allowed_methods[ $handler_method ] = true === $permission;
454
+ } else {
455
+ $allowed_methods[ $handler_method ] = true;
456
+ }
457
+ }
458
+ }
459
+
460
+ // Strip out all the methods that are not allowed (false values).
461
+ $allowed_methods = array_filter( $allowed_methods );
462
+
463
+ if ( $allowed_methods ) {
464
+ $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
465
+ }
466
+
467
+ return $response;
468
+ }
469
+
470
+ /**
471
+ * Adds the REST API URL to the WP RSD endpoint.
472
+ *
473
+ * @since 4.4.0
474
+ *
475
+ * @see get_rest_url()
476
+ */
477
+ function rest_output_rsd() {
478
+ $api_root = get_rest_url();
479
+
480
+ if ( empty( $api_root ) ) {
481
+ return;
482
+ }
483
+ ?>
484
+ <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
485
+ <?php
486
+ }
487
+
488
+ /**
489
+ * Outputs the REST API link tag into page header.
490
+ *
491
+ * @since 4.4.0
492
+ *
493
+ * @see get_rest_url()
494
+ */
495
+ function rest_output_link_wp_head() {
496
+ $api_root = get_rest_url();
497
+
498
+ if ( empty( $api_root ) ) {
499
+ return;
500
+ }
501
+
502
+ echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
503
+ }
504
+
505
+ /**
506
+ * Sends a Link header for the REST API.
507
+ *
508
+ * @since 4.4.0
509
+ */
510
+ function rest_output_link_header() {
511
+ if ( headers_sent() ) {
512
+ return;
513
+ }
514
+
515
+ $api_root = get_rest_url();
516
+
517
+ if ( empty( $api_root ) ) {
518
+ return;
519
+ }
520
+
521
+ header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
522
+ }
523
+
524
+ /**
525
+ * Checks for errors when using cookie-based authentication.
526
+ *
527
+ * WordPress' built-in cookie authentication is always active
528
+ * for logged in users. However, the API has to check nonces
529
+ * for each request to ensure users are not vulnerable to CSRF.
530
+ *
531
+ * @since 4.4.0
532
+ *
533
+ * @global mixed $wp_rest_auth_cookie
534
+ *
535
+ * @param WP_Error|mixed $result Error from another authentication handler, null if we should handle it,
536
+ * or another value if not.
537
+ * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
538
+ */
539
+ function rest_cookie_check_errors( $result ) {
540
+ if ( ! empty( $result ) ) {
541
+ return $result;
542
+ }
543
+
544
+ global $wp_rest_auth_cookie;
545
+
546
+ /*
547
+ * Is cookie authentication being used? (If we get an auth
548
+ * error, but we're still logged in, another authentication
549
+ * must have been used).
550
+ */
551
+ if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
552
+ return $result;
553
+ }
554
+
555
+ // Determine if there is a nonce.
556
+ $nonce = null;
557
+
558
+ if ( isset( $_REQUEST['_wpnonce'] ) ) {
559
+ $nonce = $_REQUEST['_wpnonce'];
560
+ } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
561
+ $nonce = $_SERVER['HTTP_X_WP_NONCE'];
562
+ }
563
+
564
+ if ( null === $nonce ) {
565
+ // No nonce at all, so act as if it's an unauthenticated request.
566
+ wp_set_current_user( 0 );
567
+ return true;
568
+ }
569
+
570
+ // Check the nonce.
571
+ $result = wp_verify_nonce( $nonce, 'wp_rest' );
572
+
573
+ if ( ! $result ) {
574
+ return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
575
+ }
576
+
577
+ return true;
578
+ }
579
+
580
+ /**
581
+ * Collects cookie authentication status.
582
+ *
583
+ * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
584
+ *
585
+ * @since 4.4.0
586
+ *
587
+ * @see current_action()
588
+ * @global mixed $wp_rest_auth_cookie
589
+ */
590
+ function rest_cookie_collect_status() {
591
+ global $wp_rest_auth_cookie;
592
+
593
+ $status_type = current_action();
594
+
595
+ if ( 'auth_cookie_valid' !== $status_type ) {
596
+ $wp_rest_auth_cookie = substr( $status_type, 12 );
597
+ return;
598
+ }
599
+
600
+ $wp_rest_auth_cookie = true;
601
+ }
602
+
603
+ /**
604
+ * Parses an RFC3339 timestamp into a DateTime.
605
+ *
606
+ * @since 4.4.0
607
+ *
608
+ * @param string $date RFC3339 timestamp.
609
+ * @param bool $force_utc Optional. Whether to force UTC timezone instead of using
610
+ * the timestamp's timezone. Default false.
611
+ * @return DateTime DateTime instance.
612
+ */
613
+ function rest_parse_date( $date, $force_utc = false ) {
614
+ if ( $force_utc ) {
615
+ $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
616
+ }
617
+
618
+ $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
619
+
620
+ if ( ! preg_match( $regex, $date, $matches ) ) {
621
+ return false;
622
+ }
623
+
624
+ return strtotime( $date );
625
+ }
626
+
627
+ /**
628
+ * Retrieves a local date with its GMT equivalent, in MySQL datetime format.
629
+ *
630
+ * @since 4.4.0
631
+ *
632
+ * @see rest_parse_date()
633
+ *
634
+ * @param string $date RFC3339 timestamp.
635
+ * @param bool $force_utc Whether a UTC timestamp should be forced. Default false.
636
+ * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
637
+ * null on failure.
638
+ */
639
+ function rest_get_date_with_gmt( $date, $force_utc = false ) {
640
+ $date = rest_parse_date( $date, $force_utc );
641
 
642
+ if ( empty( $date ) ) {
643
+ return null;
644
+ }
645
 
646
+ $utc = date( 'Y-m-d H:i:s', $date );
647
+ $local = get_date_from_gmt( $utc );
648
 
649
+ return array( $local, $utc );
650
+ }
core/wp-includes/rest-api/class-wp-rest-request.php CHANGED
@@ -847,7 +847,7 @@ class WP_REST_Request implements ArrayAccess {
847
  $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
848
 
849
  if ( false === $valid_check ) {
850
- $invalid_params[ $key ] = __( 'Invalid param.' );
851
  }
852
 
853
  if ( is_wp_error( $valid_check ) ) {
847
  $valid_check = call_user_func( $arg['validate_callback'], $param, $this, $key );
848
 
849
  if ( false === $valid_check ) {
850
+ $invalid_params[ $key ] = __( 'Invalid parameter.' );
851
  }
852
 
853
  if ( is_wp_error( $valid_check ) ) {
core/wp-includes/rest-api/class-wp-rest-server.php CHANGED
@@ -236,6 +236,20 @@ class WP_REST_Server {
236
  $this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' );
237
  $this->send_header( 'Access-Control-Allow-Headers', 'Authorization' );
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  /**
240
  * Filter whether the REST API is enabled.
241
  *
@@ -780,7 +794,8 @@ class WP_REST_Server {
780
  $callback = $handler['callback'];
781
  $response = null;
782
 
783
- if ( empty( $handler['methods'][ $method ] ) ) {
 
784
  continue;
785
  }
786
 
@@ -1158,7 +1173,7 @@ class WP_REST_Server {
1158
  *
1159
  * @return string Raw request data.
1160
  */
1161
- public function get_raw_data() {
1162
  global $HTTP_RAW_POST_DATA;
1163
 
1164
  /*
236
  $this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' );
237
  $this->send_header( 'Access-Control-Allow-Headers', 'Authorization' );
238
 
239
+ /**
240
+ * Send nocache headers on authenticated requests.
241
+ *
242
+ * @since 4.4.0
243
+ *
244
+ * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
245
+ */
246
+ $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
247
+ if ( $send_no_cache_headers ) {
248
+ foreach ( wp_get_nocache_headers() as $header => $header_value ) {
249
+ $this->send_header( $header, $header_value );
250
+ }
251
+ }
252
+
253
  /**
254
  * Filter whether the REST API is enabled.
255
  *
794
  $callback = $handler['callback'];
795
  $response = null;
796
 
797
+ $checked_method = 'HEAD' === $method ? 'GET' : $method;
798
+ if ( empty( $handler['methods'][ $checked_method ] ) ) {
799
  continue;
800
  }
801
 
1173
  *
1174
  * @return string Raw request data.
1175
  */
1176
+ public static function get_raw_data() {
1177
  global $HTTP_RAW_POST_DATA;
1178
 
1179
  /*
core/wp-includes/rest-api/rest-functions.php DELETED
@@ -1,643 +0,0 @@
1
- <?php
2
- /**
3
- * REST API functions.
4
- *
5
- * @package WordPress
6
- * @subpackage REST_API
7
- * @since 4.4.0
8
- */
9
-
10
- /**
11
- * Registers a REST API route.
12
- *
13
- * @since 4.4.0
14
- *
15
- * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
16
- *
17
- * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
18
- * @param string $route The base URL for route you are adding.
19
- * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
20
- * multiple methods. Default empty array.
21
- * @param bool $override Optional. If the route already exists, should we override it? True overrides,
22
- * false merges (with newer overriding if duplicate keys exist). Default false.
23
- * @return bool True on success, false on error.
24
- */
25
- function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
26
- /** @var WP_REST_Server $wp_rest_server */
27
- global $wp_rest_server;
28
-
29
- if ( empty( $namespace ) ) {
30
- /*
31
- * Non-namespaced routes are not allowed, with the exception of the main
32
- * and namespace indexes. If you really need to register a
33
- * non-namespaced route, call `WP_REST_Server::register_route` directly.
34
- */
35
- _doing_it_wrong( 'register_rest_route', 'Routes must be namespaced with plugin or theme name and version.', '4.4.0' );
36
- return false;
37
- } else if ( empty( $route ) ) {
38
- _doing_it_wrong( 'register_rest_route', 'Route must be specified.', '4.4.0' );
39
- return false;
40
- }
41
-
42
- if ( isset( $args['callback'] ) ) {
43
- // Upgrade a single set to multiple.
44
- $args = array( $args );
45
- }
46
-
47
- $defaults = array(
48
- 'methods' => 'GET',
49
- 'callback' => null,
50
- 'args' => array(),
51
- );
52
- foreach ( $args as $key => &$arg_group ) {
53
- if ( ! is_numeric( $arg_group ) ) {
54
- // Route option, skip here.
55
- continue;
56
- }
57
-
58
- $arg_group = array_merge( $defaults, $arg_group );
59
- }
60
-
61
- $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
62
- $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
63
- return true;
64
- }
65
-
66
- /**
67
- * Registers rewrite rules for the API.
68
- *
69
- * @since 4.4.0
70
- *
71
- * @see rest_api_register_rewrites()
72
- * @global WP $wp Current WordPress environment instance.
73
- */
74
- function rest_api_init() {
75
- rest_api_register_rewrites();
76
-
77
- global $wp;
78
- $wp->add_query_var( 'rest_route' );
79
- }
80
-
81
- /**
82
- * Adds REST rewrite rules.
83
- *
84
- * @since 4.4.0
85
- *
86
- * @see add_rewrite_rule()
87
- */
88
- function rest_api_register_rewrites() {
89
- add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
90
- add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
91
- }
92
-
93
- /**
94
- * Registers the default REST API filters.
95
- *
96
- * Attached to the {@see 'rest_api_init'} action
97
- * to make testing and disabling these filters easier.
98
- *
99
- * @since 4.4.0
100
- */
101
- function rest_api_default_filters() {
102
- // Deprecated reporting.
103
- add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
104
- add_filter( 'deprecated_function_trigger_error', '__return_false' );
105
- add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
106
- add_filter( 'deprecated_argument_trigger_error', '__return_false' );
107
-
108
- // Default serving.
109
- add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
110
- add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
111
-
112
- add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
113
- }
114
-
115
- /**
116
- * Loads the REST API.
117
- *
118
- * @since 4.4.0
119
- *
120
- * @global WP $wp Current WordPress environment instance.
121
- * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
122
- */
123
- function rest_api_loaded() {
124
- if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
125
- return;
126
- }
127
-
128
- /**
129
- * Whether this is a REST Request.
130
- *
131
- * @since 4.4.0
132
- * @var bool
133
- */
134
- define( 'REST_REQUEST', true );
135
-
136
- /** @var WP_REST_Server $wp_rest_server */
137
- global $wp_rest_server;
138
-
139
- /**
140
- * Filter the REST Server Class.
141
- *
142
- * This filter allows you to adjust the server class used by the API, using a
143
- * different class to handle requests.
144
- *
145
- * @since 4.4.0
146
- *
147
- * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
148
- */
149
- $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
150
- $wp_rest_server = new $wp_rest_server_class;
151
-
152
- /**
153
- * Fires when preparing to serve an API request.
154
- *
155
- * Endpoint objects should be created and register their hooks on this action rather
156
- * than another action to ensure they're only loaded when needed.
157
- *
158
- * @since 4.4.0
159
- *
160
- * @param WP_REST_Server $wp_rest_server Server object.
161
- */
162
- do_action( 'rest_api_init', $wp_rest_server );
163
-
164
- // Fire off the request.
165
- $wp_rest_server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
166
-
167
- // We're done.
168
- die();
169
- }
170
-
171
- /**
172
- * Retrieves the URL prefix for any API resource.
173
- *
174
- * @since 4.4.0
175
- *
176
- * @return string Prefix.
177
- */
178
- function rest_get_url_prefix() {
179
- /**
180
- * Filter the REST URL prefix.
181
- *
182
- * @since 4.4.0
183
- *
184
- * @param string $prefix URL prefix. Default 'wp-json'.
185
- */
186
- return apply_filters( 'rest_url_prefix', 'wp-json' );
187
- }
188
-
189
- /**
190
- * Retrieves the URL to a REST endpoint on a site.
191
- *
192
- * Note: The returned URL is NOT escaped.
193
- *
194
- * @since 4.4.0
195
- *
196
- * @todo Check if this is even necessary
197
- *
198
- * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
199
- * @param string $path Optional. REST route. Default '/'.
200
- * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
201
- * @return string Full URL to the endpoint.
202
- */
203
- function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
204
- if ( empty( $path ) ) {
205
- $path = '/';
206
- }
207
-
208
- if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
209
- $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
210
- $url .= '/' . ltrim( $path, '/' );
211
- } else {
212
- $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
213
-
214
- $path = '/' . ltrim( $path, '/' );
215
-
216
- $url = add_query_arg( 'rest_route', $path, $url );
217
- }
218
-
219
- if ( is_ssl() ) {
220
- // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
221
- if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
222
- $url = set_url_scheme( $url, 'https' );
223
- }
224
- }
225
-
226
- /**
227
- * Filter the REST URL.
228
- *
229
- * Use this filter to adjust the url returned by the `get_rest_url` function.
230
- *
231
- * @since 4.4.0
232
- *
233
- * @param string $url REST URL.
234
- * @param string $path REST route.
235
- * @param int $blog_id Blog ID.
236
- * @param string $scheme Sanitization scheme.
237
- */
238
- return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
239
- }
240
-
241
- /**
242
- * Retrieves the URL to a REST endpoint.
243
- *
244
- * Note: The returned URL is NOT escaped.
245
- *
246
- * @since 4.4.0
247
- *
248
- * @param string $path Optional. REST route. Default empty.
249
- * @param string $scheme Optional. Sanitization scheme. Default 'json'.
250
- * @return string Full URL to the endpoint.
251
- */
252
- function rest_url( $path = '', $scheme = 'json' ) {
253
- return get_rest_url( null, $path, $scheme );
254
- }
255
-
256
- /**
257
- * Do a REST request.
258
- *
259
- * Used primarily to route internal requests through WP_REST_Server.
260
- *
261
- * @since 4.4.0
262
- *
263
- * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
264
- *
265
- * @param WP_REST_Request|string $request Request.
266
- * @return WP_REST_Response REST response.
267
- */
268
- function rest_do_request( $request ) {
269
- global $wp_rest_server;
270
- $request = rest_ensure_request( $request );
271
- return $wp_rest_server->dispatch( $request );
272
- }
273
-
274
- /**
275
- * Ensures request arguments are a request object (for consistency).
276
- *
277
- * @since 4.4.0
278
- *
279
- * @param array|WP_REST_Request $request Request to check.
280
- * @return WP_REST_Request REST request instance.
281
- */
282
- function rest_ensure_request( $request ) {
283
- if ( $request instanceof WP_REST_Request ) {
284
- return $request;
285
- }
286
-
287
- return new WP_REST_Request( 'GET', '', $request );
288
- }
289
-
290
- /**
291
- * Ensures a REST response is a response object (for consistency).
292
- *
293
- * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
294
- * without needing to double-check the object. Will also allow WP_Error to indicate error
295
- * responses, so users should immediately check for this value.
296
- *
297
- * @since 4.4.0
298
- *
299
- * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
300
- * @return mixed WP_Error if response generated an error, WP_HTTP_Response if response
301
- * is a already an instance, otherwise returns a new WP_REST_Response instance.
302
- */
303
- function rest_ensure_response( $response ) {
304
- if ( is_wp_error( $response ) ) {
305
- return $response;
306
- }
307
-
308
- if ( $response instanceof WP_HTTP_Response ) {
309
- return $response;
310
- }
311
-
312
- return new WP_REST_Response( $response );
313
- }
314
-
315
- /**
316
- * Handles _deprecated_function() errors.
317
- *
318
- * @since 4.4.0
319
- *
320
- * @param string $function Function name.
321
- * @param string $replacement Replacement function name.
322
- * @param string $version Version.
323
- */
324
- function rest_handle_deprecated_function( $function, $replacement, $version ) {
325
- if ( ! empty( $replacement ) ) {
326
- /* translators: 1: function name, 2: WordPress version number, 3: new function name */
327
- $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
328
- } else {
329
- /* translators: 1: function name, 2: WordPress version number */
330
- $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
331
- }
332
-
333
- header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
334
- }
335
-
336
- /**
337
- * Handles _deprecated_argument() errors.
338
- *
339
- * @since 4.4.0
340
- *
341
- * @param string $function Function name.
342
- * @param string $replacement Replacement function name.
343
- * @param string $version Version.
344
- */
345
- function rest_handle_deprecated_argument( $function, $replacement, $version ) {
346
- if ( ! empty( $replacement ) ) {
347
- /* translators: 1: function name, 2: WordPress version number, 3: new argument name */
348
- $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $replacement );
349
- } else {
350
- /* translators: 1: function name, 2: WordPress version number */
351
- $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
352
- }
353
-
354
- header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
355
- }
356
-
357
- /**
358
- * Sends Cross-Origin Resource Sharing headers with API requests.
359
- *
360
- * @since 4.4.0
361
- *
362
- * @param mixed $value Response data.
363
- * @return mixed Response data.
364
- */
365
- function rest_send_cors_headers( $value ) {
366
- $origin = get_http_origin();
367
-
368
- if ( $origin ) {
369
- header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
370
- header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
371
- header( 'Access-Control-Allow-Credentials: true' );
372
- }
373
-
374
- return $value;
375
- }
376
-
377
- /**
378
- * Handles OPTIONS requests for the server.
379
- *
380
- * This is handled outside of the server code, as it doesn't obey normal route
381
- * mapping.
382
- *
383
- * @since 4.4.0
384
- *
385
- * @param mixed $response Current response, either response or `null` to indicate pass-through.
386
- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
387
- * @param WP_REST_Request $request The request that was used to make current response.
388
- * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
389
- */
390
- function rest_handle_options_request( $response, $handler, $request ) {
391
- if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
392
- return $response;
393
- }
394
-
395
- $response = new WP_REST_Response();
396
- $data = array();
397
-
398
- $accept = array();
399
-
400
- foreach ( $handler->get_routes() as $route => $endpoints ) {
401
- $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
402
-
403
- if ( ! $match ) {
404
- continue;
405
- }
406
-
407
- $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
408
- $accept = array_merge( $accept, $data['methods'] );
409
- break;
410
- }
411
- $response->header( 'Accept', implode( ', ', $accept ) );
412
-
413
- $response->set_data( $data );
414
- return $response;
415
- }
416
-
417
- /**
418
- * Sends the "Allow" header to state all methods that can be sent to the current route.
419
- *
420
- * @since 4.4.0
421
- *
422
- * @param WP_REST_Response $response Current response being served.
423
- * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
424
- * @param WP_REST_Request $request The request that was used to make current response.
425
- * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
426
- */
427
- function rest_send_allow_header( $response, $server, $request ) {
428
- $matched_route = $response->get_matched_route();
429
-
430
- if ( ! $matched_route ) {
431
- return $response;
432
- }
433
-
434
- $routes = $server->get_routes();
435
-
436
- $allowed_methods = array();
437
-
438
- // Get the allowed methods across the routes.
439
- foreach ( $routes[ $matched_route ] as $_handler ) {
440
- foreach ( $_handler['methods'] as $handler_method => $value ) {
441
-
442
- if ( ! empty( $_handler['permission_callback'] ) ) {
443
-
444
- $permission = call_user_func( $_handler['permission_callback'], $request );
445
-
446
- $allowed_methods[ $handler_method ] = true === $permission;
447
- } else {
448
- $allowed_methods[ $handler_method ] = true;
449
- }
450
- }
451
- }
452
-
453
- // Strip out all the methods that are not allowed (false values).
454
- $allowed_methods = array_filter( $allowed_methods );
455
-
456
- if ( $allowed_methods ) {
457
- $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
458
- }
459
-
460
- return $response;
461
- }
462
-
463
- /**
464
- * Adds the REST API URL to the WP RSD endpoint.
465
- *
466
- * @since 4.4.0
467
- *
468
- * @see get_rest_url()
469
- */
470
- function rest_output_rsd() {
471
- $api_root = get_rest_url();
472
-
473
- if ( empty( $api_root ) ) {
474
- return;
475
- }
476
- ?>
477
- <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
478
- <?php
479
- }
480
-
481
- /**
482
- * Outputs the REST API link tag into page header.
483
- *
484
- * @since 4.4.0
485
- *
486
- * @see get_rest_url()
487
- */
488
- function rest_output_link_wp_head() {
489
- $api_root = get_rest_url();
490
-
491
- if ( empty( $api_root ) ) {
492
- return;
493
- }
494
-
495
- echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
496
- }
497
-
498
- /**
499
- * Sends a Link header for the REST API.
500
- *
501
- * @since 4.4.0
502
- */
503
- function rest_output_link_header() {
504
- if ( headers_sent() ) {
505
- return;
506
- }
507
-
508
- $api_root = get_rest_url();
509
-
510
- if ( empty( $api_root ) ) {
511
- return;
512
- }
513
-
514
- header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
515
- }
516
-
517
- /**
518
- * Checks for errors when using cookie-based authentication.
519
- *
520
- * WordPress' built-in cookie authentication is always active
521
- * for logged in users. However, the API has to check nonces
522
- * for each request to ensure users are not vulnerable to CSRF.
523
- *
524
- * @since 4.4.0
525
- *
526
- * @global mixed $wp_rest_auth_cookie
527
- *
528
- * @param WP_Error|mixed $result Error from another authentication handler, null if we should handle it,
529
- * or another value if not.
530
- * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
531
- */
532
- function rest_cookie_check_errors( $result ) {
533
- if ( ! empty( $result ) ) {
534
- return $result;
535
- }
536
-
537
- global $wp_rest_auth_cookie;
538
-
539
- /*
540
- * Is cookie authentication being used? (If we get an auth
541
- * error, but we're still logged in, another authentication
542
- * must have been used).
543
- */
544
- if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
545
- return $result;
546
- }
547
-
548
- // Determine if there is a nonce.
549
- $nonce = null;
550
-
551
- if ( isset( $_REQUEST['_wpnonce'] ) ) {
552
- $nonce = $_REQUEST['_wpnonce'];
553
- } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
554
- $nonce = $_SERVER['HTTP_X_WP_NONCE'];
555
- }
556
-
557
- if ( null === $nonce ) {
558
- // No nonce at all, so act as if it's an unauthenticated request.
559
- wp_set_current_user( 0 );
560
- return true;
561
- }
562
-
563
- // Check the nonce.
564
- $result = wp_verify_nonce( $nonce, 'wp_rest' );
565
-
566
- if ( ! $result ) {
567
- return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
568
- }
569
-
570
- return true;
571
- }
572
-
573
- /**
574
- * Collects cookie authentication status.
575
- *
576
- * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
577
- *
578
- * @since 4.4.0
579
- *
580
- * @see current_action()
581
- * @global mixed $wp_rest_auth_cookie
582
- */
583
- function rest_cookie_collect_status() {
584
- global $wp_rest_auth_cookie;
585
-
586
- $status_type = current_action();
587
-
588
- if ( 'auth_cookie_valid' !== $status_type ) {
589
- $wp_rest_auth_cookie = substr( $status_type, 12 );
590
- return;
591
- }
592
-
593
- $wp_rest_auth_cookie = true;
594
- }
595
-
596
- /**
597
- * Parses an RFC3339 timestamp into a DateTime.
598
- *
599
- * @since 4.4.0
600
- *
601
- * @param string $date RFC3339 timestamp.
602
- * @param bool $force_utc Optional. Whether to force UTC timezone instead of using
603
- * the timestamp's timezone. Default false.
604
- * @return DateTime DateTime instance.
605
- */
606
- function rest_parse_date( $date, $force_utc = false ) {
607
- if ( $force_utc ) {
608
- $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
609
- }
610
-
611
- $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
612
-
613
- if ( ! preg_match( $regex, $date, $matches ) ) {
614
- return false;
615
- }
616
-
617
- return strtotime( $date );
618
- }
619
-
620
- /**
621
- * Retrieves a local date with its GMT equivalent, in MySQL datetime format.
622
- *
623
- * @since 4.4.0
624
- *
625
- * @see rest_parse_date()
626
- *
627
- * @param string $date RFC3339 timestamp.
628
- * @param bool $force_utc Whether a UTC timestamp should be forced. Default false.
629
- * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
630
- * null on failure.
631
- */
632
- function rest_get_date_with_gmt( $date, $force_utc = false ) {
633
- $date = rest_parse_date( $date, $force_utc );
634
-
635
- if ( empty( $date ) ) {
636
- return null;
637
- }
638
-
639
- $utc = date( 'Y-m-d H:i:s', $date );
640
- $local = get_date_from_gmt( $utc );
641
-
642
- return array( $local, $utc );
643
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
extras.php CHANGED
@@ -68,38 +68,6 @@ function rest_get_avatar_sizes() {
68
  return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
69
  }
70
 
71
- /**
72
- * Retrieves the timezone object for the site.
73
- *
74
- * @since 4.4.0
75
- *
76
- * @return DateTimeZone DateTimeZone instance.
77
- */
78
- function rest_get_timezone() {
79
- static $zone = null;
80
-
81
- if ( null !== $zone ) {
82
- return $zone;
83
- }
84
-
85
- $tzstring = get_option( 'timezone_string' );
86
-
87
- if ( ! $tzstring ) {
88
- // Create a UTC+- zone if no timezone string exists.
89
- $current_offset = get_option( 'gmt_offset' );
90
- if ( 0 === $current_offset ) {
91
- $tzstring = 'UTC';
92
- } elseif ( $current_offset < 0 ) {
93
- $tzstring = 'Etc/GMT' . $current_offset;
94
- } else {
95
- $tzstring = 'Etc/GMT+' . $current_offset;
96
- }
97
- }
98
- $zone = new DateTimeZone( $tzstring );
99
-
100
- return $zone;
101
- }
102
-
103
  /**
104
  * Retrieves the avatar url for a user who provided a user ID or email address.
105
  *
68
  return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  /**
72
  * Retrieves the avatar url for a user who provided a user ID or email address.
73
  *
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -21,7 +21,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
21
  $parent = get_post( (int) $request['post'] );
22
  $post_parent_type = get_post_type_object( $parent->post_type );
23
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
24
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => 401 ) );
25
  }
26
  }
27
 
@@ -183,15 +183,31 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
183
  $img_url_basename = wp_basename( $data['source_url'] );
184
 
185
  foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
 
 
 
 
 
 
186
  // Use the same method image_downsize() does
187
  $image_src = wp_get_attachment_image_src( $post->ID, $size );
188
-
189
  if ( ! $image_src ) {
190
  continue;
191
  }
192
 
193
  $size_data['source_url'] = $image_src[0];
194
  }
 
 
 
 
 
 
 
 
 
 
 
195
  } else {
196
  $data['media_details']['sizes'] = new stdClass;
197
  }
@@ -201,21 +217,20 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
201
  $data = $this->filter_response_by_context( $data, $context );
202
 
203
  // Wrap the data in a response object
204
- $data = rest_ensure_response( $data );
205
 
206
- $data->add_links( $this->prepare_links( $post ) );
207
 
208
  /**
209
  * Filter an attachment returned from the API.
210
  *
211
  * Allows modification of the attachment right before it is returned.
212
  *
213
- * @param array $data Key value array of attachment data: alt_text, caption, description,
214
- * media_type, media_details, post, source_url. Piossibly media_details.
215
- * @param WP_Post $post The attachment post.
216
- * @param WP_REST_Request $request Request used to generate the response.
217
  */
218
- return apply_filters( 'rest_prepare_attachment', $data, $post, $request );
219
  }
220
 
221
  /**
@@ -265,7 +280,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
265
  'readonly' => true,
266
  );
267
  $schema['properties']['post'] = array(
268
- 'description' => 'The ID for the associated post of the attachment.',
269
  'type' => 'integer',
270
  'context' => array( 'view', 'edit' ),
271
  );
21
  $parent = get_post( (int) $request['post'] );
22
  $post_parent_type = get_post_type_object( $parent->post_type );
23
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
24
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) );
25
  }
26
  }
27
 
183
  $img_url_basename = wp_basename( $data['source_url'] );
184
 
185
  foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
186
+
187
+ if ( isset( $size_data['mime-type'] ) ) {
188
+ $size_data['mime_type'] = $size_data['mime-type'];
189
+ unset( $size_data['mime-type'] );
190
+ }
191
+
192
  // Use the same method image_downsize() does
193
  $image_src = wp_get_attachment_image_src( $post->ID, $size );
 
194
  if ( ! $image_src ) {
195
  continue;
196
  }
197
 
198
  $size_data['source_url'] = $image_src[0];
199
  }
200
+
201
+ $full_src = wp_get_attachment_image_src( $post->ID, 'full' );
202
+ if ( ! empty( $full_src ) ) {
203
+ $data['media_details']['sizes']['full'] = array(
204
+ 'file' => wp_basename( $full_src[0] ),
205
+ 'width' => $full_src[1],
206
+ 'height' => $full_src[2],
207
+ 'mime_type' => $post->post_mime_type,
208
+ 'source_url' => $full_src[0],
209
+ );
210
+ }
211
  } else {
212
  $data['media_details']['sizes'] = new stdClass;
213
  }
217
  $data = $this->filter_response_by_context( $data, $context );
218
 
219
  // Wrap the data in a response object
220
+ $response = rest_ensure_response( $data );
221
 
222
+ $response->add_links( $this->prepare_links( $post ) );
223
 
224
  /**
225
  * Filter an attachment returned from the API.
226
  *
227
  * Allows modification of the attachment right before it is returned.
228
  *
229
+ * @param WP_REST_Response $response The response object.
230
+ * @param WP_Post $post The original attachment post.
231
+ * @param WP_REST_Request $request Request used to generate the response.
 
232
  */
233
+ return apply_filters( 'rest_prepare_attachment', $response, $post, $request );
234
  }
235
 
236
  /**
280
  'readonly' => true,
281
  );
282
  $schema['properties']['post'] = array(
283
+ 'description' => 'The id for the associated post of the attachment.',
284
  'type' => 'integer',
285
  'context' => array( 'view', 'edit' ),
286
  );
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -10,13 +10,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
10
  */
11
  public function register_routes() {
12
 
13
- $query_params = $this->get_collection_params();
14
  register_rest_route( 'wp/v2', '/comments', array(
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(
22
  'methods' => WP_REST_Server::CREATABLE,
@@ -34,9 +33,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
34
  'callback' => array( $this, 'get_item' ),
35
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
36
  'args' => array(
37
- 'context' => array(
38
- 'default' => 'view',
39
- ),
40
  ),
41
  ),
42
  array(
@@ -121,12 +118,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
121
 
122
  $comment = get_comment( $id );
123
  if ( empty( $comment ) ) {
124
- return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
125
  }
126
 
127
  $post = get_post( $comment->comment_post_ID );
128
  if ( empty( $post ) ) {
129
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
130
  }
131
 
132
  $data = $this->prepare_item_for_response( $comment, $request );
@@ -148,7 +145,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
148
 
149
  $post = get_post( $request['post'] );
150
  if ( empty( $post ) ) {
151
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
152
  }
153
 
154
  $prepared_comment = $this->prepare_item_for_database( $request );
@@ -240,7 +237,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
240
 
241
  $comment = get_comment( $id );
242
  if ( empty( $comment ) ) {
243
- return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
244
  }
245
 
246
  if ( isset( $request['type'] ) && $request['type'] !== $comment->comment_type ) {
@@ -293,7 +290,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
293
 
294
  $comment = get_comment( $id );
295
  if ( empty( $comment ) ) {
296
- return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
297
  }
298
 
299
  /**
@@ -365,7 +362,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
365
  }
366
 
367
  if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'manage_comments' ) ) {
368
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => 403 ) );
369
  }
370
 
371
  return true;
@@ -387,17 +384,17 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
387
  }
388
 
389
  if ( ! $this->check_read_permission( $comment ) ) {
390
- return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => 403 ) );
391
  }
392
 
393
  $post = get_post( $comment->comment_post_ID );
394
 
395
  if ( $post && ! $this->check_read_post_permission( $post ) ) {
396
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => 403 ) );
397
  }
398
 
399
  if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
400
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => 403 ) );
401
  }
402
 
403
  return true;
@@ -411,15 +408,19 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
411
  */
412
  public function create_item_permissions_check( $request ) {
413
 
 
 
 
 
414
  // Limit who can set comment `author`, `karma` or `status` to anything other than the default.
415
  if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
416
- return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => 403 ) );
417
  }
418
  if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
419
- return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => 403 ) );
420
  }
421
  if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
422
- return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => 403 ) );
423
  }
424
 
425
  // If the post id isn't specified, presume we can create.
@@ -432,7 +433,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
432
  if ( $post ) {
433
 
434
  if ( ! $this->check_read_post_permission( $post ) ) {
435
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => 403 ) );
436
  }
437
 
438
  if ( ! comments_open( $post->ID ) ) {
@@ -456,7 +457,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
456
  $comment = get_comment( $id );
457
 
458
  if ( $comment && ! $this->check_edit_permission( $comment ) ) {
459
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => 403 ) );
460
  }
461
 
462
  return true;
@@ -508,11 +509,20 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
508
  $data = $this->add_additional_fields_to_object( $data, $request );
509
 
510
  // Wrap the data in a response object
511
- $data = rest_ensure_response( $data );
512
 
513
- $data->add_links( $this->prepare_links( $comment ) );
514
 
515
- return apply_filters( 'rest_prepare_comment', $data, $comment, $request );
 
 
 
 
 
 
 
 
 
516
  }
517
 
518
  /**
@@ -758,7 +768,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
758
  'readonly' => true,
759
  ),
760
  'author' => array(
761
- 'description' => 'The ID of the user object, if author was a user.',
762
  'type' => 'integer',
763
  'context' => array( 'view', 'edit', 'embed' ),
764
  ),
@@ -848,7 +858,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
848
  'readonly' => true,
849
  ),
850
  'parent' => array(
851
- 'description' => 'The ID for the parent of the object.',
852
  'type' => 'integer',
853
  'context' => array( 'view', 'edit', 'embed' ),
854
  'arg_options' => array(
@@ -856,7 +866,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
856
  ),
857
  ),
858
  'post' => array(
859
- 'description' => 'The ID of the associated post object.',
860
  'type' => 'integer',
861
  'context' => array( 'view', 'edit' ),
862
  ),
@@ -889,6 +899,9 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
889
  */
890
  public function get_collection_params() {
891
  $query_params = parent::get_collection_params();
 
 
 
892
  $query_params['author_email'] = array(
893
  'default' => null,
894
  'description' => 'Limit result set to that from a specific author email.',
10
  */
11
  public function register_routes() {
12
 
 
13
  register_rest_route( 'wp/v2', '/comments', array(
14
  array(
15
  'methods' => WP_REST_Server::READABLE,
16
  'callback' => array( $this, 'get_items' ),
17
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
18
+ 'args' => $this->get_collection_params(),
19
  ),
20
  array(
21
  'methods' => WP_REST_Server::CREATABLE,
33
  'callback' => array( $this, 'get_item' ),
34
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
35
  'args' => array(
36
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 
 
37
  ),
38
  ),
39
  array(
118
 
119
  $comment = get_comment( $id );
120
  if ( empty( $comment ) ) {
121
+ return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
122
  }
123
 
124
  $post = get_post( $comment->comment_post_ID );
125
  if ( empty( $post ) ) {
126
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
127
  }
128
 
129
  $data = $this->prepare_item_for_response( $comment, $request );
145
 
146
  $post = get_post( $request['post'] );
147
  if ( empty( $post ) ) {
148
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
149
  }
150
 
151
  $prepared_comment = $this->prepare_item_for_database( $request );
237
 
238
  $comment = get_comment( $id );
239
  if ( empty( $comment ) ) {
240
+ return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
241
  }
242
 
243
  if ( isset( $request['type'] ) && $request['type'] !== $comment->comment_type ) {
290
 
291
  $comment = get_comment( $id );
292
  if ( empty( $comment ) ) {
293
+ return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
294
  }
295
 
296
  /**
362
  }
363
 
364
  if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'manage_comments' ) ) {
365
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
366
  }
367
 
368
  return true;
384
  }
385
 
386
  if ( ! $this->check_read_permission( $comment ) ) {
387
+ return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => rest_authorization_required_code() ) );
388
  }
389
 
390
  $post = get_post( $comment->comment_post_ID );
391
 
392
  if ( $post && ! $this->check_read_post_permission( $post ) ) {
393
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
394
  }
395
 
396
  if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
397
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
398
  }
399
 
400
  return true;
408
  */
409
  public function create_item_permissions_check( $request ) {
410
 
411
+ if ( ! is_user_logged_in() && get_option( 'comment_registration' ) ) {
412
+ return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
413
+ }
414
+
415
  // Limit who can set comment `author`, `karma` or `status` to anything other than the default.
416
  if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
417
+ return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => rest_authorization_required_code() ) );
418
  }
419
  if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
420
+ return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => rest_authorization_required_code() ) );
421
  }
422
  if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
423
+ return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => rest_authorization_required_code() ) );
424
  }
425
 
426
  // If the post id isn't specified, presume we can create.
433
  if ( $post ) {
434
 
435
  if ( ! $this->check_read_post_permission( $post ) ) {
436
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
437
  }
438
 
439
  if ( ! comments_open( $post->ID ) ) {
457
  $comment = get_comment( $id );
458
 
459
  if ( $comment && ! $this->check_edit_permission( $comment ) ) {
460
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => rest_authorization_required_code() ) );
461
  }
462
 
463
  return true;
509
  $data = $this->add_additional_fields_to_object( $data, $request );
510
 
511
  // Wrap the data in a response object
512
+ $response = rest_ensure_response( $data );
513
 
514
+ $response->add_links( $this->prepare_links( $comment ) );
515
 
516
+ /**
517
+ * Filter a comment returned from the API.
518
+ *
519
+ * Allows modification of the comment right before it is returned.
520
+ *
521
+ * @param WP_REST_Response $response The response object.
522
+ * @param object $comment The original comment object.
523
+ * @param WP_REST_Request $request Request used to generate the response.
524
+ */
525
+ return apply_filters( 'rest_prepare_comment', $response, $comment, $request );
526
  }
527
 
528
  /**
768
  'readonly' => true,
769
  ),
770
  'author' => array(
771
+ 'description' => 'The id of the user object, if author was a user.',
772
  'type' => 'integer',
773
  'context' => array( 'view', 'edit', 'embed' ),
774
  ),
858
  'readonly' => true,
859
  ),
860
  'parent' => array(
861
+ 'description' => 'The id for the parent of the object.',
862
  'type' => 'integer',
863
  'context' => array( 'view', 'edit', 'embed' ),
864
  'arg_options' => array(
866
  ),
867
  ),
868
  'post' => array(
869
+ 'description' => 'The id of the associated post object.',
870
  'type' => 'integer',
871
  'context' => array( 'view', 'edit' ),
872
  ),
899
  */
900
  public function get_collection_params() {
901
  $query_params = parent::get_collection_params();
902
+
903
+ $query_params['context']['default'] = 'view';
904
+
905
  $query_params['author_email'] = array(
906
  'default' => null,
907
  'description' => 'Limit result set to that from a specific author email.',
lib/endpoints/class-wp-rest-controller.php CHANGED
@@ -219,6 +219,7 @@ abstract class WP_REST_Controller {
219
  */
220
  public function get_collection_params() {
221
  return array(
 
222
  'page' => array(
223
  'description' => 'Current page of the collection.',
224
  'type' => 'integer',
@@ -239,6 +240,37 @@ abstract class WP_REST_Controller {
239
  );
240
  }
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  /**
243
  * Add the values from additional fields to a data object.
244
  *
219
  */
220
  public function get_collection_params() {
221
  return array(
222
+ 'context' => $this->get_context_param(),
223
  'page' => array(
224
  'description' => 'Current page of the collection.',
225
  'type' => 'integer',
240
  );
241
  }
242
 
243
+ /**
244
+ * Get the magical context param.
245
+ *
246
+ * Ensures consistent description between endpoints, and populates enum from schema.
247
+ *
248
+ * @param array $args
249
+ * @return array
250
+ */
251
+ public function get_context_param( $args = array() ) {
252
+ $param_details = array(
253
+ 'description' => 'Scope under which the request is made; determines fields present in response.',
254
+ 'type' => 'string',
255
+ );
256
+ $schema = $this->get_item_schema();
257
+ $contexts = array();
258
+ if ( empty( $schema['properties'] ) ) {
259
+ return array_merge( $param_details, $args );
260
+ }
261
+ $contexts = array();
262
+ foreach ( $schema['properties'] as $key => $attributes ) {
263
+ if ( ! empty( $attributes['context'] ) ) {
264
+ $contexts = array_merge( $contexts, $attributes['context'] );
265
+ }
266
+ }
267
+ if ( ! empty( $contexts ) ) {
268
+ $param_details['enum'] = array_unique( $contexts );
269
+ rsort( $param_details['enum'] );
270
+ }
271
+ return array_merge( $param_details, $args );
272
+ }
273
+
274
  /**
275
  * Add the values from additional fields to a data object.
276
  *
lib/endpoints/class-wp-rest-meta-controller.php CHANGED
@@ -40,11 +40,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
40
  'methods' => WP_REST_Server::READABLE,
41
  'callback' => array( $this, 'get_items' ),
42
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
43
- 'args' => array(
44
- 'context' => array(
45
- 'default' => 'view',
46
- ),
47
- ),
48
  ),
49
  array(
50
  'methods' => WP_REST_Server::CREATABLE,
@@ -61,9 +57,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
61
  'callback' => array( $this, 'get_item' ),
62
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
63
  'args' => array(
64
- 'context' => array(
65
- 'default' => 'view',
66
- ),
67
  ),
68
  ),
69
  array(
@@ -126,6 +120,19 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
126
  return $schema;
127
  }
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  /**
130
  * Get the meta ID column for the relevant table.
131
  *
@@ -191,7 +198,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
191
  $meta = get_metadata_by_mid( $this->parent_type, $mid );
192
 
193
  if ( empty( $meta ) ) {
194
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
195
  }
196
 
197
  if ( absint( $meta->$parent_column ) !== $parent_id ) {
@@ -265,7 +272,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
265
  $current = get_metadata_by_mid( $this->parent_type, $mid );
266
 
267
  if ( empty( $current ) ) {
268
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
269
  }
270
 
271
  if ( absint( $current->$parent_column ) !== $parent_id ) {
@@ -399,7 +406,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
399
 
400
  $response->set_status( 201 );
401
  $data = $response->get_data();
402
- $response->header( 'Location', rest_url( $this->parent_base . '/' . $parent_id . '/meta/' . $data['id'] ) );
403
 
404
  /* This action is documented in lib/endpoints/class-wp-rest-meta-controller.php */
405
  do_action( 'rest_insert_meta', $data, $request, true );
@@ -427,7 +434,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
427
  $current = get_metadata_by_mid( $this->parent_type, $mid );
428
 
429
  if ( empty( $current ) ) {
430
- return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta ID.' ), array( 'status' => 404 ) );
431
  }
432
 
433
  if ( absint( $current->$parent_column ) !== (int) $parent_id ) {
40
  'methods' => WP_REST_Server::READABLE,
41
  'callback' => array( $this, 'get_items' ),
42
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
43
+ 'args' => $this->get_collection_params(),
 
 
 
 
44
  ),
45
  array(
46
  'methods' => WP_REST_Server::CREATABLE,
57
  'callback' => array( $this, 'get_item' ),
58
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
59
  'args' => array(
60
+ 'context' => $this->get_context_param( array( 'default' => 'edit' ) ),
 
 
61
  ),
62
  ),
63
  array(
120
  return $schema;
121
  }
122
 
123
+ /**
124
+ * Get the query params for collections
125
+ *
126
+ * @return array
127
+ */
128
+ public function get_collection_params() {
129
+ $params = parent::get_collection_params();
130
+ $new_params = array();
131
+ $new_params['context'] = $params['context'];
132
+ $new_params['context']['default'] = 'edit';
133
+ return $new_params;
134
+ }
135
+
136
  /**
137
  * Get the meta ID column for the relevant table.
138
  *
198
  $meta = get_metadata_by_mid( $this->parent_type, $mid );
199
 
200
  if ( empty( $meta ) ) {
201
+ return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
202
  }
203
 
204
  if ( absint( $meta->$parent_column ) !== $parent_id ) {
272
  $current = get_metadata_by_mid( $this->parent_type, $mid );
273
 
274
  if ( empty( $current ) ) {
275
+ return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
276
  }
277
 
278
  if ( absint( $current->$parent_column ) !== $parent_id ) {
406
 
407
  $response->set_status( 201 );
408
  $data = $response->get_data();
409
+ $response->header( 'Location', rest_url( 'wp/v2' . '/' . $this->parent_base . '/' . $parent_id . '/meta/' . $data['id'] ) );
410
 
411
  /* This action is documented in lib/endpoints/class-wp-rest-meta-controller.php */
412
  do_action( 'rest_insert_meta', $data, $request, true );
434
  $current = get_metadata_by_mid( $this->parent_type, $mid );
435
 
436
  if ( empty( $current ) ) {
437
+ return new WP_Error( 'rest_meta_invalid_id', __( 'Invalid meta id.' ), array( 'status' => 404 ) );
438
  }
439
 
440
  if ( absint( $current->$parent_column ) !== (int) $parent_id ) {
lib/endpoints/class-wp-rest-meta-posts-controller.php CHANGED
@@ -45,16 +45,16 @@ class WP_REST_Meta_Posts_Controller extends WP_REST_Meta_Controller {
45
  $parent = get_post( (int) $request['parent_id'] );
46
 
47
  if ( empty( $parent ) || empty( $parent->ID ) ) {
48
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
49
  }
50
 
51
  if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
52
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => 403 ) );
53
  }
54
 
55
  $post_type = get_post_type_object( $parent->post_type );
56
  if ( ! current_user_can( $post_type->cap->edit_post, $parent->ID ) ) {
57
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view the meta for this post.' ), array( 'status' => 403 ) );
58
  }
59
  return true;
60
  }
@@ -99,16 +99,16 @@ class WP_REST_Meta_Posts_Controller extends WP_REST_Meta_Controller {
99
  $parent = get_post( (int) $request['parent_id'] );
100
 
101
  if ( empty( $parent ) || empty( $parent->ID ) ) {
102
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
103
  }
104
 
105
  if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
106
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => 403 ) );
107
  }
108
 
109
  $post_type = get_post_type_object( $parent->post_type );
110
  if ( ! current_user_can( $post_type->cap->delete_post, $parent->ID ) ) {
111
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot delete the meta for this post.' ), array( 'status' => 403 ) );
112
  }
113
  return true;
114
  }
45
  $parent = get_post( (int) $request['parent_id'] );
46
 
47
  if ( empty( $parent ) || empty( $parent->ID ) ) {
48
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
49
  }
50
 
51
  if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
52
+ return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
53
  }
54
 
55
  $post_type = get_post_type_object( $parent->post_type );
56
  if ( ! current_user_can( $post_type->cap->edit_post, $parent->ID ) ) {
57
+ return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view the meta for this post.' ), array( 'status' => rest_authorization_required_code() ) );
58
  }
59
  return true;
60
  }
99
  $parent = get_post( (int) $request['parent_id'] );
100
 
101
  if ( empty( $parent ) || empty( $parent->ID ) ) {
102
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
103
  }
104
 
105
  if ( ! $this->parent_controller->check_read_permission( $parent ) ) {
106
+ return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
107
  }
108
 
109
  $post_type = get_post_type_object( $parent->post_type );
110
  if ( ! current_user_can( $post_type->cap->delete_post, $parent->ID ) ) {
111
+ return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot delete the meta for this post.' ), array( 'status' => rest_authorization_required_code() ) );
112
  }
113
  return true;
114
  }
lib/endpoints/class-wp-rest-post-statuses-controller.php CHANGED
@@ -11,6 +11,7 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
 
14
  ),
15
  'schema' => array( $this, 'get_public_item_schema' ),
16
  ) );
@@ -19,6 +20,9 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
19
  array(
20
  'methods' => WP_REST_Server::READABLE,
21
  'callback' => array( $this, 'get_item' ),
 
 
 
22
  ),
23
  'schema' => array( $this, 'get_public_item_schema' ),
24
  ) );
@@ -70,7 +74,7 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
70
  */
71
  public function prepare_item_for_response( $status, $request ) {
72
  if ( ( false === $status->public && ! is_user_logged_in() ) || ( true === $status->internal && is_user_logged_in() ) ) {
73
- return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => 403 ) );
74
  }
75
 
76
  $data = array(
@@ -87,17 +91,26 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
87
  $data = $this->filter_response_by_context( $data, $context );
88
  $data = $this->add_additional_fields_to_object( $data, $request );
89
 
90
- $data = rest_ensure_response( $data );
91
 
92
  $posts_controller = new WP_REST_Posts_Controller( 'post' );
93
 
94
  if ( 'publish' === $status->name ) {
95
- $data->add_link( 'archives', rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) );
96
  } else {
97
- $data->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) ) );
98
  }
99
 
100
- return $data;
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
  /**
@@ -151,4 +164,15 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
151
  return $this->add_additional_fields_schema( $schema );
152
  }
153
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
14
+ 'args' => $this->get_collection_params(),
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
23
+ 'args' => array(
24
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
25
+ ),
26
  ),
27
  'schema' => array( $this, 'get_public_item_schema' ),
28
  ) );
74
  */
75
  public function prepare_item_for_response( $status, $request ) {
76
  if ( ( false === $status->public && ! is_user_logged_in() ) || ( true === $status->internal && is_user_logged_in() ) ) {
77
+ return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => rest_authorization_required_code() ) );
78
  }
79
 
80
  $data = array(
91
  $data = $this->filter_response_by_context( $data, $context );
92
  $data = $this->add_additional_fields_to_object( $data, $request );
93
 
94
+ $response = rest_ensure_response( $data );
95
 
96
  $posts_controller = new WP_REST_Posts_Controller( 'post' );
97
 
98
  if ( 'publish' === $status->name ) {
99
+ $response->add_link( 'archives', rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) );
100
  } else {
101
+ $response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( '/wp/v2/' . $posts_controller->get_post_type_base( 'post' ) ) ) );
102
  }
103
 
104
+ /**
105
+ * Filter a status returned from the API.
106
+ *
107
+ * Allows modification of the status data right before it is returned.
108
+ *
109
+ * @param WP_REST_Response $response The response object.
110
+ * @param object $status The original status object.
111
+ * @param WP_REST_Request $request Request used to generate the response.
112
+ */
113
+ return apply_filters( 'rest_prepare_status', $response, $status, $request );
114
  }
115
 
116
  /**
164
  return $this->add_additional_fields_schema( $schema );
165
  }
166
 
167
+ /**
168
+ * Get the query params for collections
169
+ *
170
+ * @return array
171
+ */
172
+ public function get_collection_params() {
173
+ return array(
174
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
175
+ );
176
+ }
177
+
178
  }
lib/endpoints/class-wp-rest-post-types-controller.php CHANGED
@@ -11,11 +11,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
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
  ) );
@@ -24,6 +20,9 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
24
  array(
25
  'methods' => WP_REST_Server::READABLE,
26
  'callback' => array( $this, 'get_item' ),
 
 
 
27
  ),
28
  'schema' => array( $this, 'get_public_item_schema' ),
29
  ) );
@@ -42,7 +41,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
42
  if ( is_wp_error( $post_type ) ) {
43
  continue;
44
  }
45
- $data[ $obj->name ] = $post_type;
46
  }
47
  return $data;
48
  }
@@ -70,7 +69,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
70
  */
71
  public function prepare_item_for_response( $post_type, $request ) {
72
  if ( false === $post_type->public ) {
73
- return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view type.' ), array( 'status' => 403 ) );
74
  }
75
 
76
  $data = array(
@@ -84,7 +83,29 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
84
  $data = $this->filter_response_by_context( $data, $context );
85
  $data = $this->add_additional_fields_to_object( $data, $request );
86
 
87
- return $data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
 
90
  /**
@@ -128,4 +149,15 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
128
  return $this->add_additional_fields_schema( $schema );
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
14
+ 'args' => $this->get_collection_params(),
 
 
 
 
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
20
  array(
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
23
+ 'args' => array(
24
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
25
+ ),
26
  ),
27
  'schema' => array( $this, 'get_public_item_schema' ),
28
  ) );
41
  if ( is_wp_error( $post_type ) ) {
42
  continue;
43
  }
44
+ $data[ $obj->name ] = $this->prepare_response_for_collection( $post_type );
45
  }
46
  return $data;
47
  }
69
  */
70
  public function prepare_item_for_response( $post_type, $request ) {
71
  if ( false === $post_type->public ) {
72
+ return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view type.' ), array( 'status' => rest_authorization_required_code() ) );
73
  }
74
 
75
  $data = array(
83
  $data = $this->filter_response_by_context( $data, $context );
84
  $data = $this->add_additional_fields_to_object( $data, $request );
85
 
86
+ // Wrap the data in a response object.
87
+ $response = rest_ensure_response( $data );
88
+
89
+ $base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
90
+ $response->add_links( array(
91
+ 'collection' => array(
92
+ 'href' => rest_url( 'wp/v2/types' ),
93
+ ),
94
+ 'item' => array(
95
+ 'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
96
+ ),
97
+ ) );
98
+
99
+ /**
100
+ * Filter a post type returned from the API.
101
+ *
102
+ * Allows modification of the post type data right before it is returned.
103
+ *
104
+ * @param WP_REST_Response $response The response object.
105
+ * @param object $item The original post type object.
106
+ * @param WP_REST_Request $request Request used to generate the response.
107
+ */
108
+ return apply_filters( 'rest_prepare_post_type', $response, $post_type, $request );
109
  }
110
 
111
  /**
149
  return $this->add_additional_fields_schema( $schema );
150
  }
151
 
152
+ /**
153
+ * Get the query params for collections
154
+ *
155
+ * @return array
156
+ */
157
+ public function get_collection_params() {
158
+ return array(
159
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
160
+ );
161
+ }
162
+
163
  }
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -15,14 +15,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
15
 
16
  $base = $this->get_post_type_base( $this->post_type );
17
 
18
- $posts_args = $this->get_collection_params();
19
-
20
  register_rest_route( 'wp/v2', '/' . $base, 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' => $posts_args,
26
  ),
27
  array(
28
  'methods' => WP_REST_Server::CREATABLE,
@@ -39,9 +37,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
39
  'callback' => array( $this, 'get_item' ),
40
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
41
  'args' => array(
42
- 'context' => array(
43
- 'default' => 'view',
44
- ),
45
  ),
46
  ),
47
  array(
@@ -73,9 +69,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
73
  */
74
  public function get_items( $request ) {
75
  $args = array();
 
76
  $args['paged'] = $request['page'];
77
  $args['posts_per_page'] = $request['per_page'];
78
  $args['post_parent'] = $request['parent'];
 
 
79
 
80
  if ( is_array( $request['filter'] ) ) {
81
  $args = array_merge( $args, $request['filter'] );
@@ -148,7 +147,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
148
  $post = get_post( $id );
149
 
150
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
151
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
152
  }
153
 
154
  $data = $this->prepare_item_for_response( $post, $request );
@@ -244,7 +243,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
244
  $post = get_post( $id );
245
 
246
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
247
- return new WP_Error( 'rest_post_invalid_id', __( 'Post ID is invalid.' ), array( 'status' => 400 ) );
248
  }
249
 
250
  $post = $this->prepare_item_for_database( $request );
@@ -315,7 +314,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
315
  $post = get_post( $id );
316
 
317
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
318
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
319
  }
320
 
321
  $supports_trash = ( EMPTY_TRASH_DAYS > 0 );
@@ -334,7 +333,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
334
  $supports_trash = apply_filters( 'rest_post_trashable', $supports_trash, $post );
335
 
336
  if ( ! $this->check_delete_permission( $post ) ) {
337
- return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => 401 ) );
338
  }
339
 
340
  $request = new WP_REST_Request( 'GET', '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $post->ID );
@@ -396,7 +395,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
396
  $post_type = get_post_type_object( $this->post_type );
397
 
398
  if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
399
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => 403 ) );
400
  }
401
 
402
  return true;
@@ -413,7 +412,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
413
  $post = get_post( (int) $request['id'] );
414
 
415
  if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
416
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => 403 ) );
417
  }
418
 
419
  if ( $post ) {
@@ -434,15 +433,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
434
  $post_type = get_post_type_object( $this->post_type );
435
 
436
  if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
437
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
438
  }
439
 
440
  if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
441
- return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => 403 ) );
442
  }
443
 
444
  if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
445
- return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => 403 ) );
446
  }
447
 
448
  return current_user_can( $post_type->cap->create_posts );
@@ -464,15 +463,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
464
  }
465
 
466
  if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
467
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => 403 ) );
468
  }
469
 
470
  if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
471
- return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => 403 ) );
472
  }
473
 
474
  if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
475
- return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => 403 ) );
476
  }
477
 
478
  return true;
@@ -489,7 +488,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
489
  $post = get_post( $request['id'] );
490
 
491
  if ( $post && ! $this->check_delete_permission( $post ) ) {
492
- return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => 403 ) );
493
  }
494
 
495
  return true;
@@ -524,6 +523,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
524
  $query_args['post_status'] = 'inherit';
525
  }
526
 
 
 
 
 
527
  return $query_args;
528
  }
529
 
@@ -643,7 +646,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
643
  * Prepare a single post for create or update.
644
  *
645
  * @param WP_REST_Request $request Request object.
646
- * @return WP_Error|obj $prepared_post Post object.
647
  */
648
  protected function prepare_item_for_database( $request ) {
649
  $prepared_post = new stdClass;
@@ -759,7 +762,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
759
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
760
  $parent = get_post( (int) $request['parent'] );
761
  if ( empty( $parent ) ) {
762
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent ID.' ), array( 'status' => 400 ) );
763
  }
764
 
765
  $prepared_post->post_parent = (int) $parent->ID;
@@ -809,13 +812,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
809
  break;
810
  case 'private':
811
  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
812
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type' ), array( 'status' => 403 ) );
813
  }
814
  break;
815
  case 'publish':
816
  case 'future':
817
  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
818
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type' ), array( 'status' => 403 ) );
819
  }
820
  break;
821
  default:
@@ -851,7 +854,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
851
  $author = get_userdata( $post_author );
852
 
853
  if ( ! $author ) {
854
- return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) );
855
  }
856
  }
857
 
@@ -872,7 +875,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
872
  if ( $result ) {
873
  return true;
874
  } else {
875
- return new WP_Error( 'rest_invalid_featured_image', __( 'Invalid featured image ID.' ), array( 'status' => 400 ) );
876
  }
877
  } else {
878
  return delete_post_thumbnail( $post_id );
@@ -917,7 +920,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
917
  *
918
  * Correctly handles posts with the inherit status.
919
  *
920
- * @param obj $post Post object.
921
  * @return bool Can we read it?
922
  */
923
  public function check_read_permission( $post ) {
@@ -961,7 +964,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
961
  /**
962
  * Check if we can edit a post.
963
  *
964
- * @param obj $post Post object.
965
  * @return bool Can we edit it?
966
  */
967
  protected function check_update_permission( $post ) {
@@ -977,7 +980,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
977
  /**
978
  * Check if we can create a post.
979
  *
980
- * @param obj $post Post object.
981
  * @return bool Can we create it?.
982
  */
983
  protected function check_create_permission( $post ) {
@@ -993,7 +996,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
993
  /**
994
  * Check if we can delete a post.
995
  *
996
- * @param obj $post Post object.
997
  * @return bool Can we delete it?
998
  */
999
  protected function check_delete_permission( $post ) {
@@ -1136,9 +1139,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1136
  $data = $this->add_additional_fields_to_object( $data, $request );
1137
 
1138
  // Wrap the data in a response object.
1139
- $data = rest_ensure_response( $data );
1140
 
1141
- $data->add_links( $this->prepare_links( $post ) );
1142
 
1143
  /**
1144
  * Filter the post data for a response.
@@ -1146,11 +1149,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1146
  * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
1147
  * prepared for the response.
1148
  *
1149
- * @param array $data An array of post data, prepared for response.
1150
- * @param WP_Post $post Post object.
1151
- * @param WP_REST_Request $request Request object.
1152
  */
1153
- return apply_filters( 'rest_prepare_' . $this->post_type, $data, $post, $request );
1154
  }
1155
 
1156
  /**
@@ -1165,10 +1168,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1165
  // Entity meta
1166
  $links = array(
1167
  'self' => array(
1168
- 'href' => rest_url( trailingslashit( $base ) . $post->ID ),
1169
  ),
1170
  'collection' => array(
1171
- 'href' => rest_url( $base ),
 
 
 
1172
  ),
1173
  );
1174
 
@@ -1230,7 +1236,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1230
  }
1231
 
1232
  $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1233
- $terms_url = rest_url( trailingslashit( $base ) . $post->ID . '/terms/' . $tax_base );
1234
 
1235
  $links['https://api.w.org/term'][] = array(
1236
  'href' => $terms_url,
@@ -1352,7 +1358,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1352
  $post_type_obj = get_post_type_object( $this->post_type );
1353
  if ( $post_type_obj->hierarchical ) {
1354
  $schema['properties']['parent'] = array(
1355
- 'description' => 'The ID for the parent of the object.',
1356
  'type' => 'integer',
1357
  'context' => array( 'view', 'edit' ),
1358
  );
@@ -1448,7 +1454,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1448
 
1449
  case 'author':
1450
  $schema['properties']['author'] = array(
1451
- 'description' => 'The ID for the author of the object.',
1452
  'type' => 'integer',
1453
  'context' => array( 'view', 'edit', 'embed' ),
1454
  );
@@ -1476,7 +1482,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1476
 
1477
  case 'thumbnail':
1478
  $schema['properties']['featured_image'] = array(
1479
- 'description' => 'ID of the featured image for the object.',
1480
  'type' => 'integer',
1481
  'context' => array( 'view', 'edit' ),
1482
  );
@@ -1544,8 +1550,61 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1544
  */
1545
  public function get_collection_params() {
1546
  $params = parent::get_collection_params();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1547
  $params['filter'] = array();
1548
  return $params;
1549
  }
1550
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1551
  }
15
 
16
  $base = $this->get_post_type_base( $this->post_type );
17
 
 
 
18
  register_rest_route( 'wp/v2', '/' . $base, array(
19
  array(
20
  'methods' => WP_REST_Server::READABLE,
21
  'callback' => array( $this, 'get_items' ),
22
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
23
+ 'args' => $this->get_collection_params(),
24
  ),
25
  array(
26
  'methods' => WP_REST_Server::CREATABLE,
37
  'callback' => array( $this, 'get_item' ),
38
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
39
  'args' => array(
40
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 
 
41
  ),
42
  ),
43
  array(
69
  */
70
  public function get_items( $request ) {
71
  $args = array();
72
+ $args['author'] = $request['author'];
73
  $args['paged'] = $request['page'];
74
  $args['posts_per_page'] = $request['per_page'];
75
  $args['post_parent'] = $request['parent'];
76
+ $args['post_status'] = $request['status'];
77
+ $args['s'] = $request['search'];
78
 
79
  if ( is_array( $request['filter'] ) ) {
80
  $args = array_merge( $args, $request['filter'] );
147
  $post = get_post( $id );
148
 
149
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
150
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
151
  }
152
 
153
  $data = $this->prepare_item_for_response( $post, $request );
243
  $post = get_post( $id );
244
 
245
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
246
+ return new WP_Error( 'rest_post_invalid_id', __( 'Post id is invalid.' ), array( 'status' => 400 ) );
247
  }
248
 
249
  $post = $this->prepare_item_for_database( $request );
314
  $post = get_post( $id );
315
 
316
  if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
317
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
318
  }
319
 
320
  $supports_trash = ( EMPTY_TRASH_DAYS > 0 );
333
  $supports_trash = apply_filters( 'rest_post_trashable', $supports_trash, $post );
334
 
335
  if ( ! $this->check_delete_permission( $post ) ) {
336
+ return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) );
337
  }
338
 
339
  $request = new WP_REST_Request( 'GET', '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $post->ID );
395
  $post_type = get_post_type_object( $this->post_type );
396
 
397
  if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
398
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
399
  }
400
 
401
  return true;
412
  $post = get_post( (int) $request['id'] );
413
 
414
  if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
415
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => rest_authorization_required_code() ) );
416
  }
417
 
418
  if ( $post ) {
433
  $post_type = get_post_type_object( $this->post_type );
434
 
435
  if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
436
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
437
  }
438
 
439
  if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
440
+ return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
441
  }
442
 
443
  if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
444
+ return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
445
  }
446
 
447
  return current_user_can( $post_type->cap->create_posts );
463
  }
464
 
465
  if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
466
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
467
  }
468
 
469
  if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
470
+ return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
471
  }
472
 
473
  if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
474
+ return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
475
  }
476
 
477
  return true;
488
  $post = get_post( $request['id'] );
489
 
490
  if ( $post && ! $this->check_delete_permission( $post ) ) {
491
+ return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => rest_authorization_required_code() ) );
492
  }
493
 
494
  return true;
523
  $query_args['post_status'] = 'inherit';
524
  }
525
 
526
+ if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
527
+ $query_args['ignore_sticky_posts'] = true;
528
+ }
529
+
530
  return $query_args;
531
  }
532
 
646
  * Prepare a single post for create or update.
647
  *
648
  * @param WP_REST_Request $request Request object.
649
+ * @return WP_Error|object $prepared_post Post object.
650
  */
651
  protected function prepare_item_for_database( $request ) {
652
  $prepared_post = new stdClass;
762
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
763
  $parent = get_post( (int) $request['parent'] );
764
  if ( empty( $parent ) ) {
765
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent id.' ), array( 'status' => 400 ) );
766
  }
767
 
768
  $prepared_post->post_parent = (int) $parent->ID;
812
  break;
813
  case 'private':
814
  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
815
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
816
  }
817
  break;
818
  case 'publish':
819
  case 'future':
820
  if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
821
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
822
  }
823
  break;
824
  default:
854
  $author = get_userdata( $post_author );
855
 
856
  if ( ! $author ) {
857
+ return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) );
858
  }
859
  }
860
 
875
  if ( $result ) {
876
  return true;
877
  } else {
878
+ return new WP_Error( 'rest_invalid_featured_image', __( 'Invalid featured image id.' ), array( 'status' => 400 ) );
879
  }
880
  } else {
881
  return delete_post_thumbnail( $post_id );
920
  *
921
  * Correctly handles posts with the inherit status.
922
  *
923
+ * @param object $post Post object.
924
  * @return bool Can we read it?
925
  */
926
  public function check_read_permission( $post ) {
964
  /**
965
  * Check if we can edit a post.
966
  *
967
+ * @param object $post Post object.
968
  * @return bool Can we edit it?
969
  */
970
  protected function check_update_permission( $post ) {
980
  /**
981
  * Check if we can create a post.
982
  *
983
+ * @param object $post Post object.
984
  * @return bool Can we create it?.
985
  */
986
  protected function check_create_permission( $post ) {
996
  /**
997
  * Check if we can delete a post.
998
  *
999
+ * @param object $post Post object.
1000
  * @return bool Can we delete it?
1001
  */
1002
  protected function check_delete_permission( $post ) {
1139
  $data = $this->add_additional_fields_to_object( $data, $request );
1140
 
1141
  // Wrap the data in a response object.
1142
+ $response = rest_ensure_response( $data );
1143
 
1144
+ $response->add_links( $this->prepare_links( $post ) );
1145
 
1146
  /**
1147
  * Filter the post data for a response.
1149
  * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
1150
  * prepared for the response.
1151
  *
1152
+ * @param WP_REST_Response $response The response object.
1153
+ * @param WP_Post $post Post object.
1154
+ * @param WP_REST_Request $request Request object.
1155
  */
1156
+ return apply_filters( 'rest_prepare_' . $this->post_type, $response, $post, $request );
1157
  }
1158
 
1159
  /**
1168
  // Entity meta
1169
  $links = array(
1170
  'self' => array(
1171
+ 'href' => rest_url( trailingslashit( $base ) . $post->ID ),
1172
  ),
1173
  'collection' => array(
1174
+ 'href' => rest_url( $base ),
1175
+ ),
1176
+ 'about' => array(
1177
+ 'href' => rest_url( '/wp/v2/types/' . $this->post_type ),
1178
  ),
1179
  );
1180
 
1236
  }
1237
 
1238
  $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1239
+ $terms_url = rest_url( trailingslashit( $base ) . $post->ID . '/' . $tax_base );
1240
 
1241
  $links['https://api.w.org/term'][] = array(
1242
  'href' => $terms_url,
1358
  $post_type_obj = get_post_type_object( $this->post_type );
1359
  if ( $post_type_obj->hierarchical ) {
1360
  $schema['properties']['parent'] = array(
1361
+ 'description' => 'The id for the parent of the object.',
1362
  'type' => 'integer',
1363
  'context' => array( 'view', 'edit' ),
1364
  );
1454
 
1455
  case 'author':
1456
  $schema['properties']['author'] = array(
1457
+ 'description' => 'The id for the author of the object.',
1458
  'type' => 'integer',
1459
  'context' => array( 'view', 'edit', 'embed' ),
1460
  );
1482
 
1483
  case 'thumbnail':
1484
  $schema['properties']['featured_image'] = array(
1485
+ 'description' => 'The id of the featured image for the object.',
1486
  'type' => 'integer',
1487
  'context' => array( 'view', 'edit' ),
1488
  );
1550
  */
1551
  public function get_collection_params() {
1552
  $params = parent::get_collection_params();
1553
+
1554
+ $params['context']['default'] = 'view';
1555
+
1556
+ if ( post_type_supports( $this->post_type, 'author' ) ) {
1557
+ $params['author'] = array(
1558
+ 'description' => 'Limit result set to posts assigned to a specific author.',
1559
+ 'type' => 'integer',
1560
+ 'default' => null,
1561
+ 'sanitize_callback' => 'absint',
1562
+ );
1563
+ }
1564
+ $params['order'] = array(
1565
+ 'description' => 'Order sort attribute ascending or descending.',
1566
+ 'type' => 'string',
1567
+ 'default' => 'asc',
1568
+ 'enum' => array( 'asc', 'desc' ),
1569
+ );
1570
+ $params['orderby'] = array(
1571
+ 'description' => 'Sort collection by object attribute.',
1572
+ 'type' => 'string',
1573
+ 'default' => 'name',
1574
+ 'enum' => array(
1575
+ 'id',
1576
+ 'title',
1577
+ 'slug',
1578
+ ),
1579
+ );
1580
+ $params['status'] = array(
1581
+ 'default' => 'publish',
1582
+ 'description' => 'Limit result set to posts assigned a specific status.',
1583
+ 'sanitize_callback' => 'sanitize_key',
1584
+ 'type' => 'string',
1585
+ 'validate_callback' => array( $this, 'validate_user_can_query_private_statuses' ),
1586
+ );
1587
  $params['filter'] = array();
1588
  return $params;
1589
  }
1590
 
1591
+ /**
1592
+ * Validate whether the user can query private statuses
1593
+ *
1594
+ * @param mixed $value
1595
+ * @param WP_REST_Request $request
1596
+ * @param string $parameter
1597
+ * @return WP_Error|bool
1598
+ */
1599
+ public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
1600
+ if ( 'publish' === $value ) {
1601
+ return true;
1602
+ }
1603
+ $post_type_obj = get_post_type_object( $this->post_type );
1604
+ if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
1605
+ return true;
1606
+ }
1607
+ return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden' ), array( 'status' => rest_authorization_required_code() ) );
1608
+ }
1609
+
1610
  }
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -19,22 +19,24 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
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' ),
37
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
 
 
 
38
  ),
39
  array(
40
  'methods' => WP_REST_Server::CREATABLE,
@@ -104,11 +106,11 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
104
 
105
  $terms = wp_get_object_terms( $post->ID, $this->taxonomy );
106
 
107
- if ( ! in_array( $term_id, wp_list_pluck( $terms, 'term_taxonomy_id' ) ) ) {
108
- return new WP_Error( 'rest_post_not_in_term', __( 'Invalid taxonomy for post ID.' ), array( 'status' => 404 ) );
109
  }
110
 
111
- $term = $this->terms_controller->prepare_item_for_response( get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ), $request );
112
 
113
  $response = rest_ensure_response( $term );
114
 
@@ -130,14 +132,14 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
130
  return $is_request_valid;
131
  }
132
 
133
- $term = get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy );
134
  $tt_ids = wp_set_object_terms( $post->ID, $term->term_id, $this->taxonomy, true );
135
 
136
  if ( is_wp_error( $tt_ids ) ) {
137
  return $tt_ids;
138
  }
139
 
140
- $term = $this->terms_controller->prepare_item_for_response( get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ), $request );
141
 
142
  $response = rest_ensure_response( $term );
143
  $response->set_status( 201 );
@@ -214,17 +216,17 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
214
  $post = get_post( (int) $request['post_id'] );
215
 
216
  if ( empty( $post ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
217
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
218
  }
219
 
220
  if ( ! $this->posts_controller->check_read_permission( $post ) ) {
221
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => 403 ) );
222
  }
223
 
224
  if ( ! empty( $request['term_id'] ) ) {
225
  $term_id = absint( $request['term_id'] );
226
 
227
- $term = get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy );
228
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
229
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
230
  }
@@ -288,6 +290,7 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
288
  */
289
  public function get_collection_params() {
290
  $query_params = array();
 
291
  $query_params['order'] = array(
292
  'description' => 'Order sort attribute ascending or descending.',
293
  'type' => 'string',
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
+ register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/%s', $base, $tax_base ), 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' => $this->get_collection_params(),
28
  ),
29
  'schema' => array( $this, 'get_public_item_schema' ),
30
  ) );
31
 
32
+ register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/%s/(?P<term_id>[\d]+)', $base, $tax_base ), array(
33
  array(
34
  'methods' => WP_REST_Server::READABLE,
35
  'callback' => array( $this, 'get_item' ),
36
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
37
+ 'args' => array(
38
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
39
+ ),
40
  ),
41
  array(
42
  'methods' => WP_REST_Server::CREATABLE,
106
 
107
  $terms = wp_get_object_terms( $post->ID, $this->taxonomy );
108
 
109
+ if ( ! in_array( $term_id, wp_list_pluck( $terms, 'term_id' ) ) ) {
110
+ return new WP_Error( 'rest_post_not_in_term', __( 'Invalid taxonomy for post id.' ), array( 'status' => 404 ) );
111
  }
112
 
113
+ $term = $this->terms_controller->prepare_item_for_response( get_term( $term_id, $this->taxonomy ), $request );
114
 
115
  $response = rest_ensure_response( $term );
116
 
132
  return $is_request_valid;
133
  }
134
 
135
+ $term = get_term( $term_id, $this->taxonomy );
136
  $tt_ids = wp_set_object_terms( $post->ID, $term->term_id, $this->taxonomy, true );
137
 
138
  if ( is_wp_error( $tt_ids ) ) {
139
  return $tt_ids;
140
  }
141
 
142
+ $term = $this->terms_controller->prepare_item_for_response( get_term( $term_id, $this->taxonomy ), $request );
143
 
144
  $response = rest_ensure_response( $term );
145
  $response->set_status( 201 );
216
  $post = get_post( (int) $request['post_id'] );
217
 
218
  if ( empty( $post ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
219
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
220
  }
221
 
222
  if ( ! $this->posts_controller->check_read_permission( $post ) ) {
223
+ return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
224
  }
225
 
226
  if ( ! empty( $request['term_id'] ) ) {
227
  $term_id = absint( $request['term_id'] );
228
 
229
+ $term = get_term( $term_id, $this->taxonomy );
230
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
231
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
232
  }
290
  */
291
  public function get_collection_params() {
292
  $query_params = array();
293
+ $query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
294
  $query_params['order'] = array(
295
  'description' => 'Order sort attribute ascending or descending.',
296
  'type' => 'string',
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -22,11 +22,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
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' ),
@@ -38,9 +34,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
38
  'callback' => array( $this, 'get_item' ),
39
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
40
  'args' => array(
41
- 'context' => array(
42
- 'default' => 'view',
43
- ),
44
  ),
45
  ),
46
  array(
@@ -64,7 +58,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
64
 
65
  $parent = get_post( $request['parent_id'] );
66
  if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
67
- return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
68
  }
69
 
70
  $revisions = wp_get_post_revisions( $request['parent_id'] );
@@ -91,7 +85,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
91
  }
92
  $parent_post_type_obj = get_post_type_object( $parent->post_type );
93
  if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
94
- return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => 403 ) );
95
  }
96
 
97
  return true;
@@ -107,12 +101,12 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
107
 
108
  $parent = get_post( $request['parent_id'] );
109
  if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
110
- return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
111
  }
112
 
113
  $revision = get_post( $request['id'] );
114
  if ( ! $revision || 'revision' !== $revision->post_type ) {
115
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
116
  }
117
 
118
  $response = $this->prepare_item_for_response( $revision, $request );
@@ -221,7 +215,16 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
221
  $response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
222
  }
223
 
224
- return $response;
 
 
 
 
 
 
 
 
 
225
  }
226
 
227
  /**
@@ -259,7 +262,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
259
  */
260
  'properties' => array(
261
  'author' => array(
262
- 'description' => 'The ID for the author of the object.',
263
  'type' => 'integer',
264
  'context' => array( 'view' ),
265
  ),
@@ -298,7 +301,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
298
  'context' => array( 'view' ),
299
  ),
300
  'parent' => array(
301
- 'description' => 'The ID for the parent of the object.',
302
  'type' => 'integer',
303
  'context' => array( 'view' ),
304
  ),
@@ -349,4 +352,15 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
349
  return $this->add_additional_fields_schema( $schema );
350
  }
351
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
22
  'methods' => WP_REST_Server::READABLE,
23
  'callback' => array( $this, 'get_items' ),
24
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
25
+ 'args' => $this->get_collection_params(),
 
 
 
 
26
  ),
27
 
28
  'schema' => array( $this, 'get_public_item_schema' ),
34
  'callback' => array( $this, 'get_item' ),
35
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
36
  'args' => array(
37
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 
 
38
  ),
39
  ),
40
  array(
58
 
59
  $parent = get_post( $request['parent_id'] );
60
  if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
61
+ return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
62
  }
63
 
64
  $revisions = wp_get_post_revisions( $request['parent_id'] );
85
  }
86
  $parent_post_type_obj = get_post_type_object( $parent->post_type );
87
  if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
88
+ return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
89
  }
90
 
91
  return true;
101
 
102
  $parent = get_post( $request['parent_id'] );
103
  if ( ! $request['parent_id'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
104
+ return new WP_Error( 'rest_post_invalid_parent_id', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
105
  }
106
 
107
  $revision = get_post( $request['id'] );
108
  if ( ! $revision || 'revision' !== $revision->post_type ) {
109
+ return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) );
110
  }
111
 
112
  $response = $this->prepare_item_for_response( $revision, $request );
215
  $response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
216
  }
217
 
218
+ /**
219
+ * Filter a revision returned from the API.
220
+ *
221
+ * Allows modification of the revision right before it is returned.
222
+ *
223
+ * @param WP_REST_Response $response The response object.
224
+ * @param WP_Post $post The original revision object.
225
+ * @param WP_REST_Request $request Request used to generate the response.
226
+ */
227
+ return apply_filters( 'rest_prepare_revision', $response, $post, $request );
228
  }
229
 
230
  /**
262
  */
263
  'properties' => array(
264
  'author' => array(
265
+ 'description' => 'The id for the author of the object.',
266
  'type' => 'integer',
267
  'context' => array( 'view' ),
268
  ),
301
  'context' => array( 'view' ),
302
  ),
303
  'parent' => array(
304
+ 'description' => 'The id for the parent of the object.',
305
  'type' => 'integer',
306
  'context' => array( 'view' ),
307
  ),
352
  return $this->add_additional_fields_schema( $schema );
353
  }
354
 
355
+ /**
356
+ * Get the query params for collections
357
+ *
358
+ * @return array
359
+ */
360
+ public function get_collection_params() {
361
+ return array(
362
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
363
+ );
364
+ }
365
+
366
  }
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -11,11 +11,7 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
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
  ) );
@@ -25,6 +21,9 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
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
  ) );
@@ -37,8 +36,8 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
37
  * @return array
38
  */
39
  public function get_items( $request ) {
40
- if ( ! empty( $request['post_type'] ) ) {
41
- $taxonomies = get_object_taxonomies( $request['post_type'], 'objects' );
42
  } else {
43
  $taxonomies = get_taxonomies( '', 'objects' );
44
  }
@@ -48,7 +47,8 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
48
  if ( is_wp_error( $tax ) ) {
49
  continue;
50
  }
51
- $data[] = $tax;
 
52
  }
53
  return $data;
54
  }
@@ -110,16 +110,29 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
110
  $data = $this->filter_response_by_context( $data, $context );
111
  $data = $this->add_additional_fields_to_object( $data, $request );
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  /**
114
  * Filter a taxonomy returned from the API.
115
  *
116
  * Allows modification of the taxonomy data right before it is returned.
117
  *
118
- * @param array $data Key value array of taxonomy data.
119
- * @param object $item The taxonomy object.
120
- * @param WP_REST_Request $request Request used to generate the response.
121
  */
122
- return apply_filters( 'rest_prepare_taxonomy', $data, $taxonomy, $request );
123
  }
124
 
125
  /**
@@ -173,4 +186,19 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
173
  return $this->add_additional_fields_schema( $schema );
174
  }
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
11
  array(
12
  'methods' => WP_REST_Server::READABLE,
13
  'callback' => array( $this, 'get_items' ),
14
+ 'args' => $this->get_collection_params(),
 
 
 
 
15
  ),
16
  'schema' => array( $this, 'get_public_item_schema' ),
17
  ) );
21
  'methods' => WP_REST_Server::READABLE,
22
  'callback' => array( $this, 'get_item' ),
23
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
24
+ 'args' => array(
25
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
26
+ ),
27
  ),
28
  'schema' => array( $this, 'get_public_item_schema' ),
29
  ) );
36
  * @return array
37
  */
38
  public function get_items( $request ) {
39
+ if ( ! empty( $request['type'] ) ) {
40
+ $taxonomies = get_object_taxonomies( $request['type'], 'objects' );
41
  } else {
42
  $taxonomies = get_taxonomies( '', 'objects' );
43
  }
47
  if ( is_wp_error( $tax ) ) {
48
  continue;
49
  }
50
+ $tax = $this->prepare_response_for_collection( $tax );
51
+ $data[ $tax_type ] = $tax;
52
  }
53
  return $data;
54
  }
110
  $data = $this->filter_response_by_context( $data, $context );
111
  $data = $this->add_additional_fields_to_object( $data, $request );
112
 
113
+ // Wrap the data in a response object.
114
+ $response = rest_ensure_response( $data );
115
+
116
+ $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
117
+ $response->add_links( array(
118
+ 'collection' => array(
119
+ 'href' => rest_url( 'wp/v2/taxonomies' ),
120
+ ),
121
+ 'item' => array(
122
+ 'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
123
+ ),
124
+ ) );
125
+
126
  /**
127
  * Filter a taxonomy returned from the API.
128
  *
129
  * Allows modification of the taxonomy data right before it is returned.
130
  *
131
+ * @param WP_REST_Response $response The response object.
132
+ * @param object $item The original taxonomy object.
133
+ * @param WP_REST_Request $request Request used to generate the response.
134
  */
135
+ return apply_filters( 'rest_prepare_taxonomy', $response, $taxonomy, $request );
136
  }
137
 
138
  /**
186
  return $this->add_additional_fields_schema( $schema );
187
  }
188
 
189
+ /**
190
+ * Get the query params for collections
191
+ *
192
+ * @return array
193
+ */
194
+ public function get_collection_params() {
195
+ $new_params = array();
196
+ $new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
197
+ $new_params['type'] = array(
198
+ 'description' => 'Limit results to taxonomies associated with a specific post type.',
199
+ 'type' => 'string',
200
+ );
201
+ return $new_params;
202
+ }
203
+
204
  }
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -20,13 +20,12 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
20
  public function register_routes() {
21
 
22
  $base = $this->get_taxonomy_base( $this->taxonomy );
23
- $query_params = $this->get_collection_params();
24
- register_rest_route( 'wp/v2', '/terms/' . $base, array(
25
  array(
26
  'methods' => WP_REST_Server::READABLE,
27
  'callback' => array( $this, 'get_items' ),
28
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
29
- 'args' => $query_params,
30
  ),
31
  array(
32
  'methods' => WP_REST_Server::CREATABLE,
@@ -37,11 +36,14 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
37
 
38
  'schema' => array( $this, 'get_public_item_schema' ),
39
  ));
40
- register_rest_route( 'wp/v2', '/terms/' . $base . '/(?P<id>[\d]+)', array(
41
  array(
42
  'methods' => WP_REST_Server::READABLE,
43
  'callback' => array( $this, 'get_item' ),
44
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
 
 
 
45
  ),
46
  array(
47
  'methods' => WP_REST_Server::EDITABLE,
@@ -83,9 +85,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
83
  // Only query top-level terms.
84
  $prepared_args['parent'] = 0;
85
  } else {
86
- $parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
87
- if ( $parent ) {
88
- $prepared_args['parent'] = $parent->term_id;
89
  }
90
  }
91
  }
@@ -111,7 +112,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
111
  $max_pages = ceil( $total_terms / $request['per_page'] );
112
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
113
 
114
- $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/terms/' . $this->get_taxonomy_base( $this->taxonomy ) ) );
115
  if ( $request['page'] > 1 ) {
116
  $prev_page = $request['page'] - 1;
117
  if ( $prev_page > $max_pages ) {
@@ -137,7 +138,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
137
  */
138
  public function get_item( $request ) {
139
 
140
- $term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
141
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
142
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
143
  }
@@ -173,7 +174,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
173
  return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
174
  }
175
 
176
- $parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
177
 
178
  if ( ! $parent ) {
179
  return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 404 ) );
@@ -186,11 +187,11 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
186
  if ( is_wp_error( $term ) ) {
187
 
188
  // If we're going to inform the client that the term exists, give them the identifier
189
- // they can actually use (term_taxonomy_id) -- NOT term_id.
190
 
191
  if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
192
  $existing_term = get_term( $term_id, $this->taxonomy );
193
- $term->add_data( $existing_term->term_taxonomy_id, 'term_exists' );
194
  }
195
 
196
  return $term;
@@ -199,12 +200,12 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
199
  $this->update_additional_fields_for_object( $term, $request );
200
 
201
  $response = $this->get_item( array(
202
- 'id' => $term['term_taxonomy_id'],
203
  ) );
204
 
205
  $response = rest_ensure_response( $response );
206
  $response->set_status( 201 );
207
- $response->header( 'Location', rest_url( '/wp/v2/terms/' . $this->get_taxonomy_base( $this->taxonomy ) . '/' . $term['term_taxonomy_id'] ) );
208
  return $response;
209
  }
210
 
@@ -232,7 +233,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
232
  return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
233
  }
234
 
235
- $parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
236
 
237
  if ( ! $parent ) {
238
  return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 400 ) );
@@ -241,7 +242,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
241
  $prepared_args['parent'] = $parent->term_id;
242
  }
243
 
244
- $term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
245
  if ( ! $term ) {
246
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
247
  }
@@ -254,10 +255,10 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
254
  }
255
  }
256
 
257
- $this->update_additional_fields_for_object( get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy ), $request );
258
 
259
  $response = $this->get_item( array(
260
- 'id' => $term->term_taxonomy_id,
261
  ) );
262
 
263
  return rest_ensure_response( $response );
@@ -272,8 +273,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
272
  public function delete_item( $request ) {
273
 
274
  // Get the actual term_id
275
- $term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
276
- $get_request = new WP_REST_Request( 'GET', rest_url( 'wp/v2/terms/' . $this->get_taxonomy_base( $term->taxonomy ) . '/' . (int) $request['id'] ) );
277
  $get_request->set_param( 'context', 'view' );
278
  $response = $this->prepare_item_for_response( $term, $get_request );
279
 
@@ -390,7 +391,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
390
  return $valid;
391
  }
392
 
393
- $term = get_term_by( 'term_taxonomy_id', (int) $request['id'], $this->taxonomy );
394
  if ( ! $term ) {
395
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
396
  }
@@ -427,16 +428,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
427
  */
428
  public function prepare_item_for_response( $item, $request ) {
429
 
430
- $parent_id = 0;
431
- if ( $item->parent ) {
432
- $parent_term = get_term_by( 'id', (int) $item->parent, $item->taxonomy );
433
- if ( $parent_term ) {
434
- $parent_id = $parent_term->term_taxonomy_id;
435
- }
436
- }
437
-
438
  $data = array(
439
- 'id' => (int) $item->term_taxonomy_id,
440
  'count' => (int) $item->count,
441
  'description' => $item->description,
442
  'link' => get_term_link( $item ),
@@ -446,27 +439,27 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
446
  );
447
  $schema = $this->get_item_schema();
448
  if ( ! empty( $schema['properties']['parent'] ) ) {
449
- $data['parent'] = (int) $parent_id;
450
  }
451
 
452
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
453
  $data = $this->filter_response_by_context( $data, $context );
454
  $data = $this->add_additional_fields_to_object( $data, $request );
455
 
456
- $data = rest_ensure_response( $data );
457
 
458
- $data->add_links( $this->prepare_links( $item ) );
459
 
460
  /**
461
  * Filter a term item returned from the API.
462
  *
463
  * Allows modification of the term data right before it is returned.
464
  *
465
- * @param array $data Key value array of term data.
466
- * @param object $item The term object.
467
- * @param WP_REST_Request $request Request used to generate the response.
468
  */
469
- return apply_filters( 'rest_prepare_term', $data, $item, $request );
470
  }
471
 
472
  /**
@@ -476,21 +469,24 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
476
  * @return array Links for the given term.
477
  */
478
  protected function prepare_links( $term ) {
479
- $base = '/wp/v2/terms/' . $this->get_taxonomy_base( $term->taxonomy );
480
  $links = array(
481
  'self' => array(
482
- 'href' => rest_url( trailingslashit( $base ) . $term->term_taxonomy_id ),
483
  ),
484
  'collection' => array(
485
  'href' => rest_url( $base ),
486
  ),
 
 
 
487
  );
488
 
489
  if ( $term->parent ) {
490
- $parent_term = get_term_by( 'id', (int) $term->parent, $term->taxonomy );
491
  if ( $parent_term ) {
492
  $links['up'] = array(
493
- 'href' => rest_url( sprintf( 'wp/v2/terms/%s/%d', $this->get_taxonomy_base( $parent_term->taxonomy ), $parent_term->term_taxonomy_id ) ),
494
  'embeddable' => true,
495
  );
496
  }
@@ -566,7 +562,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
566
  $taxonomy = get_taxonomy( $this->taxonomy );
567
  if ( $taxonomy->hierarchical ) {
568
  $schema['properties']['parent'] = array(
569
- 'description' => 'The ID for the parent of the object.',
570
  'type' => 'integer',
571
  'context' => array( 'view' ),
572
  );
@@ -581,16 +577,9 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
581
  */
582
  public function get_collection_params() {
583
  $query_params = parent::get_collection_params();
584
- $query_params['context'] = array(
585
- 'description' => 'Change the response format based on request context.',
586
- 'default' => 'view',
587
- 'sanitize_callback' => 'sanitize_key',
588
- 'type' => 'string',
589
- 'enum' => array(
590
- 'embed',
591
- 'view',
592
- ),
593
- );
594
  $query_params['order'] = array(
595
  'description' => 'Order sort attribute ascending or descending.',
596
  'type' => 'string',
20
  public function register_routes() {
21
 
22
  $base = $this->get_taxonomy_base( $this->taxonomy );
23
+ register_rest_route( 'wp/v2', '/' . $base, array(
 
24
  array(
25
  'methods' => WP_REST_Server::READABLE,
26
  'callback' => array( $this, 'get_items' ),
27
  'permission_callback' => array( $this, 'get_items_permissions_check' ),
28
+ 'args' => $this->get_collection_params(),
29
  ),
30
  array(
31
  'methods' => WP_REST_Server::CREATABLE,
36
 
37
  'schema' => array( $this, 'get_public_item_schema' ),
38
  ));
39
+ register_rest_route( 'wp/v2', '/' . $base . '/(?P<id>[\d]+)', array(
40
  array(
41
  'methods' => WP_REST_Server::READABLE,
42
  'callback' => array( $this, 'get_item' ),
43
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
44
+ 'args' => array(
45
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
46
+ ),
47
  ),
48
  array(
49
  'methods' => WP_REST_Server::EDITABLE,
85
  // Only query top-level terms.
86
  $prepared_args['parent'] = 0;
87
  } else {
88
+ if ( $request['parent'] ) {
89
+ $prepared_args['parent'] = $request['parent'];
 
90
  }
91
  }
92
  }
112
  $max_pages = ceil( $total_terms / $request['per_page'] );
113
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
114
 
115
+ $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) ) );
116
  if ( $request['page'] > 1 ) {
117
  $prev_page = $request['page'] - 1;
118
  if ( $prev_page > $max_pages ) {
138
  */
139
  public function get_item( $request ) {
140
 
141
+ $term = get_term( (int) $request['id'], $this->taxonomy );
142
  if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
143
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
144
  }
174
  return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
175
  }
176
 
177
+ $parent = get_term( (int) $request['parent'], $this->taxonomy );
178
 
179
  if ( ! $parent ) {
180
  return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 404 ) );
187
  if ( is_wp_error( $term ) ) {
188
 
189
  // If we're going to inform the client that the term exists, give them the identifier
190
+ // they can actually use.
191
 
192
  if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
193
  $existing_term = get_term( $term_id, $this->taxonomy );
194
+ $term->add_data( $existing_term->term_id, 'term_exists' );
195
  }
196
 
197
  return $term;
200
  $this->update_additional_fields_for_object( $term, $request );
201
 
202
  $response = $this->get_item( array(
203
+ 'id' => $term['term_id'],
204
  ) );
205
 
206
  $response = rest_ensure_response( $response );
207
  $response->set_status( 201 );
208
+ $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) . '/' . $term['term_id'] ) );
209
  return $response;
210
  }
211
 
233
  return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set term parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
234
  }
235
 
236
+ $parent = get_term( (int) $request['parent'], $this->taxonomy );
237
 
238
  if ( ! $parent ) {
239
  return new WP_Error( 'rest_term_invalid', __( "Parent term doesn't exist." ), array( 'status' => 400 ) );
242
  $prepared_args['parent'] = $parent->term_id;
243
  }
244
 
245
+ $term = get_term( (int) $request['id'], $this->taxonomy );
246
  if ( ! $term ) {
247
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
248
  }
255
  }
256
  }
257
 
258
+ $this->update_additional_fields_for_object( get_term( (int) $request['id'], $this->taxonomy ), $request );
259
 
260
  $response = $this->get_item( array(
261
+ 'id' => $term->term_id,
262
  ) );
263
 
264
  return rest_ensure_response( $response );
273
  public function delete_item( $request ) {
274
 
275
  // Get the actual term_id
276
+ $term = get_term( (int) $request['id'], $this->taxonomy );
277
+ $get_request = new WP_REST_Request( 'GET', rest_url( 'wp/v2/' . $this->get_taxonomy_base( $term->taxonomy ) . '/' . (int) $request['id'] ) );
278
  $get_request->set_param( 'context', 'view' );
279
  $response = $this->prepare_item_for_response( $term, $get_request );
280
 
391
  return $valid;
392
  }
393
 
394
+ $term = get_term( (int) $request['id'], $this->taxonomy );
395
  if ( ! $term ) {
396
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
397
  }
428
  */
429
  public function prepare_item_for_response( $item, $request ) {
430
 
 
 
 
 
 
 
 
 
431
  $data = array(
432
+ 'id' => (int) $item->term_id,
433
  'count' => (int) $item->count,
434
  'description' => $item->description,
435
  'link' => get_term_link( $item ),
439
  );
440
  $schema = $this->get_item_schema();
441
  if ( ! empty( $schema['properties']['parent'] ) ) {
442
+ $data['parent'] = (int) $item->parent;
443
  }
444
 
445
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
446
  $data = $this->filter_response_by_context( $data, $context );
447
  $data = $this->add_additional_fields_to_object( $data, $request );
448
 
449
+ $response = rest_ensure_response( $data );
450
 
451
+ $response->add_links( $this->prepare_links( $item ) );
452
 
453
  /**
454
  * Filter a term item returned from the API.
455
  *
456
  * Allows modification of the term data right before it is returned.
457
  *
458
+ * @param WP_REST_Response $response The response object.
459
+ * @param object $item The original term object.
460
+ * @param WP_REST_Request $request Request used to generate the response.
461
  */
462
+ return apply_filters( 'rest_prepare_term', $response, $item, $request );
463
  }
464
 
465
  /**
469
  * @return array Links for the given term.
470
  */
471
  protected function prepare_links( $term ) {
472
+ $base = '/wp/v2/' . $this->get_taxonomy_base( $term->taxonomy );
473
  $links = array(
474
  'self' => array(
475
+ 'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
476
  ),
477
  'collection' => array(
478
  'href' => rest_url( $base ),
479
  ),
480
+ 'about' => array(
481
+ 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
482
+ ),
483
  );
484
 
485
  if ( $term->parent ) {
486
+ $parent_term = get_term( (int) $term->parent, $term->taxonomy );
487
  if ( $parent_term ) {
488
  $links['up'] = array(
489
+ 'href' => rest_url( sprintf( 'wp/v2/%s/%d', $this->get_taxonomy_base( $parent_term->taxonomy ), $parent_term->term_id ) ),
490
  'embeddable' => true,
491
  );
492
  }
562
  $taxonomy = get_taxonomy( $this->taxonomy );
563
  if ( $taxonomy->hierarchical ) {
564
  $schema['properties']['parent'] = array(
565
+ 'description' => 'The id for the parent of the object.',
566
  'type' => 'integer',
567
  'context' => array( 'view' ),
568
  );
577
  */
578
  public function get_collection_params() {
579
  $query_params = parent::get_collection_params();
580
+
581
+ $query_params['context']['default'] = 'view';
582
+
 
 
 
 
 
 
 
583
  $query_params['order'] = array(
584
  'description' => 'Order sort attribute ascending or descending.',
585
  'type' => 'string',
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -10,12 +10,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
10
  */
11
  public function register_routes() {
12
 
13
- $query_params = $this->get_collection_params();
14
  register_rest_route( 'wp/v2', '/users', array(
15
  array(
16
  'methods' => WP_REST_Server::READABLE,
17
  'callback' => array( $this, 'get_items' ),
18
- 'args' => $query_params,
19
  ),
20
  array(
21
  'methods' => WP_REST_Server::CREATABLE,
@@ -36,9 +35,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
36
  'callback' => array( $this, 'get_item' ),
37
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
38
  'args' => array(
39
- 'context' => array(
40
- 'default' => 'embed',
41
- ),
42
  ),
43
  ),
44
  array(
@@ -101,6 +98,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
101
  $request['context'] = 'embed';
102
  }
103
 
 
 
 
 
104
  /**
105
  * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
106
  *
@@ -162,7 +163,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
162
  $user = get_userdata( $id );
163
 
164
  if ( empty( $id ) || empty( $user->ID ) ) {
165
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
166
  }
167
 
168
  $user = $this->prepare_item_for_response( $user, $request );
@@ -271,7 +272,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
271
 
272
  $user = get_userdata( $id );
273
  if ( ! $user ) {
274
- return new WP_Error( 'rest_user_invalid_id', __( 'User ID is invalid.' ), array( 'status' => 400 ) );
275
  }
276
 
277
  if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
@@ -333,12 +334,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
333
 
334
  $user = get_userdata( $id );
335
  if ( ! $user ) {
336
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
337
  }
338
 
339
  if ( ! empty( $reassign ) ) {
340
  if ( $reassign === $id || ! get_userdata( $reassign ) ) {
341
- return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user ID.' ), array( 'status' => 400 ) );
342
  }
343
  }
344
 
@@ -382,7 +383,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
382
  $user = get_userdata( $id );
383
 
384
  if ( empty( $id ) || empty( $user->ID ) ) {
385
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) );
386
  }
387
 
388
  if ( get_current_user_id() === $id ) {
@@ -392,11 +393,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
392
  $context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
393
 
394
  if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
395
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => 403 ) );
396
  } else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
397
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => 403 ) );
398
  } else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
399
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => 403 ) );
400
  }
401
 
402
  return true;
@@ -411,7 +412,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
411
  public function create_item_permissions_check( $request ) {
412
 
413
  if ( ! current_user_can( 'create_users' ) ) {
414
- return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => 403 ) );
415
  }
416
 
417
  return true;
@@ -428,11 +429,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
428
  $id = (int) $request['id'];
429
 
430
  if ( ! current_user_can( 'edit_user', $id ) ) {
431
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => 403 ) );
432
  }
433
 
434
  if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
435
- return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => 403 ) );
436
  }
437
 
438
  return true;
@@ -450,7 +451,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
450
  $reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
451
 
452
  if ( ! current_user_can( 'delete_user', $id ) ) {
453
- return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => 403 ) );
454
  }
455
 
456
  return true;
@@ -489,18 +490,18 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
489
  $data = $this->add_additional_fields_to_object( $data, $request );
490
 
491
  // Wrap the data in a response object
492
- $data = rest_ensure_response( $data );
493
 
494
- $data->add_links( $this->prepare_links( $user ) );
495
 
496
  /**
497
  * Filter user data returned from the REST API.
498
  *
499
- * @param WP_REST_Response $data Response data.
500
- * @param object $user User object used to create response.
501
- * @param WP_REST_Request $request Request object.
502
  */
503
- return apply_filters( 'rest_prepare_user', $data, $user, $request );
504
  }
505
 
506
  /**
@@ -608,7 +609,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
608
  return true;
609
  }
610
 
611
- return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
612
  }
613
 
614
  /**
@@ -766,13 +767,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
766
  */
767
  public function get_collection_params() {
768
  $query_params = parent::get_collection_params();
769
- $query_params['context'] = array(
770
- 'default' => 'view',
771
- 'description' => 'Change the response format based on request context.',
772
- 'enum' => array( 'embed', 'view', 'edit' ),
773
- 'sanitize_callback' => 'sanitize_key',
774
- 'type' => 'string',
775
- );
776
  $query_params['order'] = array(
777
  'default' => 'asc',
778
  'description' => 'Order sort attribute ascending or descending.',
10
  */
11
  public function register_routes() {
12
 
 
13
  register_rest_route( 'wp/v2', '/users', array(
14
  array(
15
  'methods' => WP_REST_Server::READABLE,
16
  'callback' => array( $this, 'get_items' ),
17
+ 'args' => $this->get_collection_params(),
18
  ),
19
  array(
20
  'methods' => WP_REST_Server::CREATABLE,
35
  'callback' => array( $this, 'get_item' ),
36
  'permission_callback' => array( $this, 'get_item_permissions_check' ),
37
  'args' => array(
38
+ 'context' => $this->get_context_param( array( 'default' => 'embed' ) ),
 
 
39
  ),
40
  ),
41
  array(
98
  $request['context'] = 'embed';
99
  }
100
 
101
+ if ( '' !== $prepared_args['search'] ) {
102
+ $prepared_args['search'] = '*' . $prepared_args['search'] . '*';
103
+ }
104
+
105
  /**
106
  * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
107
  *
163
  $user = get_userdata( $id );
164
 
165
  if ( empty( $id ) || empty( $user->ID ) ) {
166
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
167
  }
168
 
169
  $user = $this->prepare_item_for_response( $user, $request );
272
 
273
  $user = get_userdata( $id );
274
  if ( ! $user ) {
275
+ return new WP_Error( 'rest_user_invalid_id', __( 'User id is invalid.' ), array( 'status' => 400 ) );
276
  }
277
 
278
  if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
334
 
335
  $user = get_userdata( $id );
336
  if ( ! $user ) {
337
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 400 ) );
338
  }
339
 
340
  if ( ! empty( $reassign ) ) {
341
  if ( $reassign === $id || ! get_userdata( $reassign ) ) {
342
+ return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid user id.' ), array( 'status' => 400 ) );
343
  }
344
  }
345
 
383
  $user = get_userdata( $id );
384
 
385
  if ( empty( $id ) || empty( $user->ID ) ) {
386
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
387
  }
388
 
389
  if ( get_current_user_id() === $id ) {
393
  $context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
394
 
395
  if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
396
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => rest_authorization_required_code() ) );
397
  } else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
398
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => rest_authorization_required_code() ) );
399
  } else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
400
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => rest_authorization_required_code() ) );
401
  }
402
 
403
  return true;
412
  public function create_item_permissions_check( $request ) {
413
 
414
  if ( ! current_user_can( 'create_users' ) ) {
415
+ return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => rest_authorization_required_code() ) );
416
  }
417
 
418
  return true;
429
  $id = (int) $request['id'];
430
 
431
  if ( ! current_user_can( 'edit_user', $id ) ) {
432
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => rest_authorization_required_code() ) );
433
  }
434
 
435
  if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
436
+ return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => rest_authorization_required_code() ) );
437
  }
438
 
439
  return true;
451
  $reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
452
 
453
  if ( ! current_user_can( 'delete_user', $id ) ) {
454
+ return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) );
455
  }
456
 
457
  return true;
490
  $data = $this->add_additional_fields_to_object( $data, $request );
491
 
492
  // Wrap the data in a response object
493
+ $response = rest_ensure_response( $data );
494
 
495
+ $response->add_links( $this->prepare_links( $user ) );
496
 
497
  /**
498
  * Filter user data returned from the REST API.
499
  *
500
+ * @param WP_REST_Response $response The response object.
501
+ * @param object $user User object used to create response.
502
+ * @param WP_REST_Request $request Request object.
503
  */
504
+ return apply_filters( 'rest_prepare_user', $response, $user, $request );
505
  }
506
 
507
  /**
609
  return true;
610
  }
611
 
612
+ return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => rest_authorization_required_code() ) );
613
  }
614
 
615
  /**
767
  */
768
  public function get_collection_params() {
769
  $query_params = parent::get_collection_params();
770
+
771
+ $query_params['context']['default'] = 'view';
772
+
 
 
 
 
773
  $query_params['order'] = array(
774
  'default' => 'asc',
775
  'description' => 'Order sort attribute ascending or descending.',
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-beta8
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -133,13 +133,13 @@ function _add_extra_api_taxonomy_arguments() {
133
 
134
  if ( isset( $wp_taxonomies['category'] ) ) {
135
  $wp_taxonomies['category']->show_in_rest = true;
136
- $wp_taxonomies['category']->rest_base = 'category';
137
  $wp_taxonomies['category']->rest_controller_class = 'WP_REST_Terms_Controller';
138
  }
139
 
140
  if ( isset( $wp_taxonomies['post_tag'] ) ) {
141
  $wp_taxonomies['post_tag']->show_in_rest = true;
142
- $wp_taxonomies['post_tag']->rest_base = 'tag';
143
  $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
144
  }
145
  }
@@ -220,7 +220,18 @@ function create_initial_rest_routes() {
220
  $controller->register_routes();
221
  }
222
 
223
- if ( ! function_exists( 'register_api_field' ) ) {
 
 
 
 
 
 
 
 
 
 
 
224
  /**
225
  * Registers a new field on an existing WordPress object type.
226
  *
@@ -243,7 +254,7 @@ if ( ! function_exists( 'register_api_field' ) ) {
243
  * this field. Default is 'null', no schema entry will be returned.
244
  * }
245
  */
246
- function register_api_field( $object_type, $attribute, $args = array() ) {
247
  $defaults = array(
248
  'get_callback' => null,
249
  'update_callback' => null,
@@ -261,3 +272,13 @@ if ( ! function_exists( 'register_api_field' ) ) {
261
  }
262
  }
263
  }
 
 
 
 
 
 
 
 
 
 
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-beta9
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
133
 
134
  if ( isset( $wp_taxonomies['category'] ) ) {
135
  $wp_taxonomies['category']->show_in_rest = true;
136
+ $wp_taxonomies['category']->rest_base = 'categories';
137
  $wp_taxonomies['category']->rest_controller_class = 'WP_REST_Terms_Controller';
138
  }
139
 
140
  if ( isset( $wp_taxonomies['post_tag'] ) ) {
141
  $wp_taxonomies['post_tag']->show_in_rest = true;
142
+ $wp_taxonomies['post_tag']->rest_base = 'tags';
143
  $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
144
  }
145
  }
220
  $controller->register_routes();
221
  }
222
 
223
+ if ( ! function_exists( 'rest_authorization_required_code' ) ) {
224
+ /**
225
+ * Returns a contextual HTTP error code for authorization failure.
226
+ *
227
+ * @return integer
228
+ */
229
+ function rest_authorization_required_code() {
230
+ return is_user_logged_in() ? 403 : 401;
231
+ }
232
+ }
233
+
234
+ if ( ! function_exists( 'register_rest_field' ) ) {
235
  /**
236
  * Registers a new field on an existing WordPress object type.
237
  *
254
  * this field. Default is 'null', no schema entry will be returned.
255
  * }
256
  */
257
+ function register_rest_field( $object_type, $attribute, $args = array() ) {
258
  $defaults = array(
259
  'get_callback' => null,
260
  'update_callback' => null,
272
  }
273
  }
274
  }
275
+
276
+ if ( ! function_exists( 'register_api_field' ) ) {
277
+ /**
278
+ * Backwards compat shim
279
+ */
280
+ function register_api_field( $object_type, $attributes, $args = array() ) {
281
+ _deprecated_function( 'register_api_field', 'WPAPI-2.0', 'register_rest_field' );
282
+ register_rest_field( $object_type, $attributes, $args );
283
+ }
284
+ }
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3
5
  Tested up to: 4.4
6
- Stable tag: 2.0-beta8
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,6 +36,120 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 2.0 Beta 8.0 =
40
 
41
  * Prevent fatals when uploading attachment by including admin utilities.
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3
5
  Tested up to: 4.4
6
+ Stable tag: 2.0-beta9
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 9.0 =
40
+
41
+ * BREAKING CHANGE: Move tags and categories to top-level endpoints.
42
+
43
+ Tags are now accessible at `/wp/v2/tags`, and categories accessible at `/wp/v2/categories`. Post terms reside at `/wp/v2/posts/<id>/tags` and `/wp/v2/<id>/categories`.
44
+
45
+ (props @danielbachhuber, [#1802](https://github.com/WP-API/WP-API/pull/1802))
46
+
47
+ * BREAKING CHANGE: Return object for requests to `/wp/v2/taxonomies`.
48
+
49
+ This is consistent with `/wp/v2/types` and `/wp/v2/statuses`.
50
+
51
+ (props @danielbachhuber, [#1825](https://github.com/WP-API/WP-API/pull/1825))
52
+
53
+ * BREAKING CHANGE: Remove `rest_get_timezone()`.
54
+
55
+ `json_get_timezone()` was only ever used in v1. This function causes fatals, and shouldn't be used.
56
+
57
+ (props @danielbachhuber, [#1823](https://github.com/WP-API/WP-API/pull/1823))
58
+
59
+ * BREAKING CHANGE: Rename `register_api_field()` to `register_rest_field()`.
60
+
61
+ Introduces a `register_api_field()` function for backwards compat, which calls `_doing_it_wrong()`. However, `register_api_field()` won't ever be committed to WordPress core, so you should update your function calls.
62
+
63
+ (props @danielbachhuber, [#1824](https://github.com/WP-API/WP-API/pull/1824))
64
+
65
+ * BREAKING CHANGE: Change taxonomies' `post_type` argument to `type`.
66
+
67
+ It's consistent with how we're exposing post types in the API.
68
+
69
+ (props @danielbachhuber, [#1824](https://github.com/WP-API/WP-API/pull/1824))
70
+
71
+ * Sync infrastructure with shipped in WordPress 4.4.
72
+
73
+ * `wp-includes/rest-api/rest-functions.php` is removed, and its functions moved into `wp-includes/rest-api.php`.
74
+ * Send nocache headers for REST requests. [#34832](https://core.trac.wordpress.org/ticket/34832)
75
+ * Fix handling of HEAD requests. [#34837](https://core.trac.wordpress.org/ticket/34837)
76
+ * Mark `WP_REST_Server::get_raw_data()` as static. [#34768](https://core.trac.wordpress.org/ticket/34768)
77
+ * Unabbreviate error string. [#34818](https://core.trac.wordpress.org/ticket/34818)
78
+
79
+ * Change terms endpoints to use `term_id` not `tt_id`.
80
+
81
+ (props @joehoyle, [#1837](https://github.com/WP-API/WP-API/pull/1837))
82
+
83
+ * Standardize declaration of `context` param for `GET` requests across controllers.
84
+
85
+ However, we're still inconsistent in which controllers expose which params. Follow [#1845](https://github.com/WP-API/WP-API/issues/1845) for further discussion.
86
+
87
+ (props @danielbachhuber, [#1795](https://github.com/WP-API/WP-API/pull/1795), [#1835](https://github.com/WP-API/WP-API/pull/1835), [#1838](https://github.com/WP-API/WP-API/pull/1838))
88
+
89
+ * Link types / taxonomies to their collections, and vice versa.
90
+
91
+ Collections link to their type / taxonomy with the `about` relation; types / taxonomies link to their colletion with the `item` relation, which is imperfect and may change in the future.
92
+
93
+ (props @danielbachhuber, [#1814](https://github.com/WP-API/WP-API/pull/1814), [#1817](https://github.com/WP-API/WP-API/pull/1817), [#1829](https://github.com/WP-API/WP-API/pull/1829). [#1846](https://github.com/WP-API/WP-API/pull/1846))
94
+
95
+ * Add missing 'wp/v2' in Location Response header when creating new Post Meta.
96
+
97
+ (props @johanmynhardt, [#1790](https://github.com/WP-API/WP-API/pull/1790))
98
+
99
+ * Expose Post collection query params, including `author`, `order`, `orderby` and `status`.
100
+
101
+ (props @danielbachhuber, [#1793](https://github.com/WP-API/WP-API/pull/1793))
102
+
103
+ * Ignore sticky posts by default.
104
+
105
+ (props @danielbachhuber, [#1801](https://github.com/WP-API/WP-API/pull/1801))
106
+
107
+ * Include `full` image size in attachment `sizes` attribute.
108
+
109
+ (props @danielbachhuber, [#1806](https://github.com/WP-API/WP-API/pull/1806))
110
+
111
+ * In text strings, use `id` instead of `ID`.
112
+
113
+ `ID` is an implementation artifact. Our Resources use `id`.
114
+
115
+ (props @danielbachhuber, [#1803](https://github.com/WP-API/WP-API/pull/1803))
116
+
117
+ * Ensure `attachment.sizes[]` use `mime_type` instead of `mime-type`.
118
+
119
+ (props @danielbachhuber, [#1809](https://github.com/WP-API/WP-API/pull/1809))
120
+
121
+ * Introduce `rest_authorization_required_code()`.
122
+
123
+ Many controllers returned incorrect HTTP codes, which this also fixes.
124
+
125
+ (props @danielbachhuber, [#1808](https://github.com/WP-API/WP-API/pull/1808))
126
+
127
+ * Respect core's `comment_registration` setting.
128
+
129
+ If it's enabled, require users to be logged in to comment.
130
+
131
+ (props @danielbachhuber, [#1826](https://github.com/WP-API/WP-API/pull/1826))
132
+
133
+ * Default to wildcard when searching users.
134
+
135
+ (props @danielbachhuber, [#1827](https://github.com/WP-API/WP-API/pull/1827))
136
+
137
+ * Bring the wp-api.js library up to date for v2 of the REST API.
138
+
139
+ (props @adamsilverstein, [#1828](https://github.com/WP-API/WP-API/pull/1828))
140
+
141
+ * Add `rest_prepare_status` filter.
142
+
143
+ (props @danielbachhuber, [#1830](https://github.com/WP-API/WP-API/pull/1830))
144
+
145
+ * Make `prepare_*` filters more consistent.
146
+
147
+ (props @danielbachhuber, [#1831](https://github.com/WP-API/WP-API/pull/1831))
148
+
149
+ * Add `rest_prepare_post_type` filter for post types.
150
+
151
+ (props @danielbachhuber, [#1833](https://github.com/WP-API/WP-API/pull/1833))
152
+
153
  = 2.0 Beta 8.0 =
154
 
155
  * Prevent fatals when uploading attachment by including admin utilities.
wp-api.js CHANGED
@@ -1,13 +1,25 @@
1
- ( function( WP_API_Settings, Backbone, _, window, undefined ) {
 
2
  'use strict';
3
 
 
 
 
 
 
 
4
  window.wp = window.wp || {};
 
5
 
6
- wp.api = {
7
- models: {},
8
- collections: {},
9
- utils: {}
10
- };
 
 
 
 
11
 
12
  /**
13
  * ECMAScript 5 shim, from MDN.
@@ -75,6 +87,15 @@
75
  return timestamp;
76
  };
77
 
 
 
 
 
 
 
 
 
 
78
  /**
79
  * Array of parseable dates.
80
  *
@@ -184,8 +205,8 @@
184
  /**
185
  * Private Backbone base model for all models.
186
  */
187
- var BaseModel = Backbone.Model.extend(
188
- /** @lends BaseModel.prototype */
189
  {
190
  /**
191
  * Set nonce header before every Backbone sync.
@@ -216,9 +237,13 @@
216
  );
217
 
218
  /**
219
- * Backbone model for single users.
 
 
 
 
220
  */
221
- wp.api.models.User = BaseModel.extend(
222
  /** @lends User.prototype */
223
  {
224
  idAttribute: 'id',
@@ -226,7 +251,7 @@
226
  urlRoot: WP_API_Settings.root + 'wp/v2/users',
227
 
228
  defaults: {
229
- id: null,
230
  avatar_url: {},
231
  capabilities: {},
232
  description: '',
@@ -248,9 +273,12 @@
248
  );
249
 
250
  /**
251
- * Model for Taxonomy.
 
 
 
252
  */
253
- wp.api.models.Taxonomy = BaseModel.extend(
254
  /** @lends Taxonomy.prototype */
255
  {
256
  idAttribute: 'slug',
@@ -270,24 +298,17 @@
270
  );
271
 
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.
282
- *
283
- * @returns {string}
284
- */
285
- url: function() {
286
- var id = this.get( 'id' );
287
- id = id || '';
288
-
289
- return WP_API_Settings.root + 'wp/v2/taxonomies/' + this.get( 'taxonomy' ) + '/terms/' + id;
290
- },
291
 
292
  defaults: {
293
  id: null,
@@ -305,9 +326,12 @@
305
  );
306
 
307
  /**
308
- * Backbone model for single posts.
 
 
 
309
  */
310
- wp.api.models.Post = BaseModel.extend( _.extend(
311
  /** @lends Post.prototype */
312
  {
313
  idAttribute: 'id',
@@ -323,7 +347,6 @@
323
  modified: new Date(),
324
  modified_gmt: new Date(),
325
  password: '',
326
- slug: '',
327
  status: 'draft',
328
  type: 'post',
329
  title: {},
@@ -341,9 +364,12 @@
341
  );
342
 
343
  /**
344
- * Backbone model for pages.
 
 
 
345
  */
346
- wp.api.models.Page = BaseModel.extend( _.extend(
347
  /** @lends Page.prototype */
348
  {
349
  idAttribute: 'id',
@@ -377,24 +403,17 @@
377
  );
378
 
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 + 'wp/v2/posts/' + id + '/revisions';
396
- },
397
-
398
  defaults: {
399
  id: null,
400
  author: null,
@@ -409,15 +428,30 @@
409
  content: {},
410
  excerpt: {},
411
  _links: {}
 
 
 
 
 
 
 
 
 
 
 
 
412
  }
413
 
414
  }, TimeStampedMixin, HierarchicalMixin )
415
  );
416
 
417
  /**
418
- * Backbone model for media items.
 
 
 
419
  */
420
- wp.api.models.Media = BaseModel.extend( _.extend(
421
  /** @lends Media.prototype */
422
  {
423
  idAttribute: 'id',
@@ -448,29 +482,24 @@
448
  post: null,
449
  source_url: '',
450
  _links: {}
451
- },
452
-
453
- /**
454
- * @class Represent a media item.
455
- * @augments Backbone.Model.
456
- * @constructs
457
- */
458
- initialize: function() {
459
-
460
- // Todo: what of the parent model is a page?
461
- this.parentModel = wp.api.models.Post;
462
  }
463
- }, TimeStampedMixin, HierarchicalMixin )
 
464
  );
465
 
466
  /**
467
- * Backbone model for comments.
 
 
 
468
  */
469
- wp.api.models.Comment = BaseModel.extend( _.extend(
470
  /** @lends Comment.prototype */
471
  {
472
  idAttribute: 'id',
473
 
 
 
474
  defaults: {
475
  id: null,
476
  author: null,
@@ -485,38 +514,26 @@
485
  karma: 0,
486
  link: '',
487
  parent: 0,
488
- post: null,
489
  status: 'hold',
490
  type: '',
491
  _links: {}
492
- },
493
-
494
- /**
495
- * Return URL for model.
496
- *
497
- * @returns {string}.
498
- */
499
- url: function() {
500
- var post_id = this.get( 'post' );
501
- post_id = post_id || '';
502
-
503
- var id = this.get( 'id' );
504
- id = id || '';
505
-
506
- return WP_API_Settings.root + 'wp/v2/posts/' + post_id + '/comments/' + id;
507
  }
 
508
  }, TimeStampedMixin, HierarchicalMixin )
509
  );
510
 
511
  /**
512
- * Backbone model for single post types.
 
 
 
513
  */
514
- wp.api.models.PostType = BaseModel.extend(
515
  /** @lends PostType.prototype */
516
  {
517
  idAttribute: 'slug',
518
 
519
- urlRoot: WP_API_Settings.root + 'wp/v2/posts/types',
520
 
521
  defaults: {
522
  slug: null,
@@ -540,21 +557,24 @@
540
  *
541
  * @returns {boolean}.
542
  */
543
- 'delete': function() {
544
  return false;
545
  }
546
  }
547
  );
548
 
549
  /**
550
- * Backbone model for a post status.
 
 
 
551
  */
552
- wp.api.models.PostStatus = BaseModel.extend(
553
  /** @lends PostStatus.prototype */
554
  {
555
  idAttribute: 'slug',
556
 
557
- urlRoot: WP_API_Settings.root + 'wp/v2/posts/statuses',
558
 
559
  defaults: {
560
  slug: null,
@@ -581,12 +601,54 @@
581
  *
582
  * @returns {boolean}.
583
  */
584
- 'delete': function() {
585
  return false;
586
  }
587
  }
588
  );
589
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  /**
591
  * Contains basic collection functionality such as pagination.
592
  */
@@ -917,4 +979,4 @@
917
  * Todo: Handle post meta.
918
  */
919
 
920
- })( WP_API_Settings, Backbone, _, window, ( void 0 ) );
1
+ (function( window, undefined ) {
2
+
3
  'use strict';
4
 
5
+ function WP_API() {
6
+ this.models = {};
7
+ this.collections = {};
8
+ this.views = {};
9
+ }
10
+
11
  window.wp = window.wp || {};
12
+ wp.api = wp.api || new WP_API();
13
 
14
+ })( window );
15
+
16
+ (function( window, undefined ) {
17
+
18
+ 'use strict';
19
+
20
+ window.wp = window.wp || {};
21
+ wp.api = wp.api || {};
22
+ wp.api.utils = wp.api.utils || {};
23
 
24
  /**
25
  * ECMAScript 5 shim, from MDN.
87
  return timestamp;
88
  };
89
 
90
+ })( window );
91
+
92
+ /* global WP_API_Settings:false */
93
+ // Suppress warning about parse function's unused "options" argument:
94
+ /* jshint unused:false */
95
+ (function( wp, WP_API_Settings, Backbone, window, undefined ) {
96
+
97
+ 'use strict';
98
+
99
  /**
100
  * Array of parseable dates.
101
  *
205
  /**
206
  * Private Backbone base model for all models.
207
  */
208
+ var WPApiBaseModel = Backbone.Model.extend(
209
+ /** @lends WPApiBaseModel.prototype */
210
  {
211
  /**
212
  * Set nonce header before every Backbone sync.
237
  );
238
 
239
  /**
240
+ * Backbone model for a single user.
241
+ *
242
+ *
243
+ * @param {Object} attributes
244
+ * @param {int} attributes.id The user id. Optional. Defaults to 'me', fetching the current user.
245
  */
246
+ wp.api.models.User = WPApiBaseModel.extend(
247
  /** @lends User.prototype */
248
  {
249
  idAttribute: 'id',
251
  urlRoot: WP_API_Settings.root + 'wp/v2/users',
252
 
253
  defaults: {
254
+ id: 'me',
255
  avatar_url: {},
256
  capabilities: {},
257
  description: '',
273
  );
274
 
275
  /**
276
+ * Model for a single taxonomy.
277
+ *
278
+ * @param {Object} attributes
279
+ * @param {string} attributes.slug The taxonomy slug.
280
  */
281
+ wp.api.models.Taxonomy = WPApiBaseModel.extend(
282
  /** @lends Taxonomy.prototype */
283
  {
284
  idAttribute: 'slug',
298
  );
299
 
300
  /**
301
+ * Backbone model for a single term.
302
+ *
303
+ * @param {Object} attributes
304
+ * @param {int} id attributesm id.
305
  */
306
+ wp.api.models.Term = WPApiBaseModel.extend(
307
  /** @lends Term.prototype */
308
  {
309
  idAttribute: 'id',
310
 
311
+ urlRoot: WP_API_Settings.root + 'wp/v2/terms/tag',
 
 
 
 
 
 
 
 
 
 
312
 
313
  defaults: {
314
  id: null,
326
  );
327
 
328
  /**
329
+ * Backbone model for a single post.
330
+ *
331
+ * @param {Object} attributes
332
+ * @param {int} attributes.id The post id.
333
  */
334
+ wp.api.models.Post = WPApiBaseModel.extend( _.extend(
335
  /** @lends Post.prototype */
336
  {
337
  idAttribute: 'id',
347
  modified: new Date(),
348
  modified_gmt: new Date(),
349
  password: '',
 
350
  status: 'draft',
351
  type: 'post',
352
  title: {},
364
  );
365
 
366
  /**
367
+ * Backbone model for a single page.
368
+ *
369
+ * @param {Object} attributes
370
+ * @param {int} attributes.id The page id.
371
  */
372
+ wp.api.models.Page = WPApiBaseModel.extend( _.extend(
373
  /** @lends Page.prototype */
374
  {
375
  idAttribute: 'id',
403
  );
404
 
405
  /**
406
+ * Backbone model for a single post revision.
407
+ *
408
+ * @param {Object} attributes
409
+ * @param {int} attributes.parent The id of the post that this revision belongs to.
410
+ * @param {int} attributes.id The revision id.
411
  */
412
+ wp.api.models.PostRevision = WPApiBaseModel.extend( _.extend(
413
+ /** @lends PostRevision.prototype */
414
  {
415
  idAttribute: 'id',
416
 
 
 
 
 
 
 
 
 
 
 
 
417
  defaults: {
418
  id: null,
419
  author: null,
428
  content: {},
429
  excerpt: {},
430
  _links: {}
431
+ },
432
+
433
+ /**
434
+ * Return URL for the model.
435
+ *
436
+ * @returns {string}.
437
+ */
438
+ url: function() {
439
+ var id = this.get( 'id' ) || '',
440
+ parent = this.get( 'parent' ) || '';
441
+
442
+ return WP_API_Settings.root + 'wp/v2/posts/' + parent + '/revisions/' + id;
443
  }
444
 
445
  }, TimeStampedMixin, HierarchicalMixin )
446
  );
447
 
448
  /**
449
+ * Backbone model for a single media item.
450
+ *
451
+ * @param {Object} attributes
452
+ * @param {int} attributes.id The media item id.
453
  */
454
+ wp.api.models.Media = WPApiBaseModel.extend( _.extend(
455
  /** @lends Media.prototype */
456
  {
457
  idAttribute: 'id',
482
  post: null,
483
  source_url: '',
484
  _links: {}
 
 
 
 
 
 
 
 
 
 
 
485
  }
486
+
487
+ }, TimeStampedMixin )
488
  );
489
 
490
  /**
491
+ * Backbone model for a single comment.
492
+ *
493
+ * @param {Object} attributes
494
+ * @param {int} attributes.id The comment id.
495
  */
496
+ wp.api.models.Comment = WPApiBaseModel.extend( _.extend(
497
  /** @lends Comment.prototype */
498
  {
499
  idAttribute: 'id',
500
 
501
+ urlRoot: WP_API_Settings.root + 'wp/v2/comments',
502
+
503
  defaults: {
504
  id: null,
505
  author: null,
514
  karma: 0,
515
  link: '',
516
  parent: 0,
 
517
  status: 'hold',
518
  type: '',
519
  _links: {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  }
521
+
522
  }, TimeStampedMixin, HierarchicalMixin )
523
  );
524
 
525
  /**
526
+ * Backbone model for a single post type.
527
+ *
528
+ * @param {Object} attributes
529
+ * @param {string} attributes.slug The post type slug.
530
  */
531
+ wp.api.models.PostType = WPApiBaseModel.extend(
532
  /** @lends PostType.prototype */
533
  {
534
  idAttribute: 'slug',
535
 
536
+ urlRoot: WP_API_Settings.root + 'wp/v2/types',
537
 
538
  defaults: {
539
  slug: null,
557
  *
558
  * @returns {boolean}.
559
  */
560
+ destroy: function() {
561
  return false;
562
  }
563
  }
564
  );
565
 
566
  /**
567
+ * Backbone model for a a single post status.
568
+ *
569
+ * @param {Object} attributes
570
+ * @param {string} attributes.slug The post status slug.
571
  */
572
+ wp.api.models.PostStatus = WPApiBaseModel.extend(
573
  /** @lends PostStatus.prototype */
574
  {
575
  idAttribute: 'slug',
576
 
577
+ urlRoot: WP_API_Settings.root + 'wp/v2/statuses',
578
 
579
  defaults: {
580
  slug: null,
601
  *
602
  * @returns {boolean}.
603
  */
604
+ destroy: function() {
605
  return false;
606
  }
607
  }
608
  );
609
 
610
+ /**
611
+ * API Schema model. Contains meta information about the API.
612
+ */
613
+ wp.api.models.Schema = WPApiBaseModel.extend(
614
+ /** @lends Shema.prototype */
615
+ {
616
+ url: WP_API_Settings.root + 'wp/v2',
617
+
618
+ defaults: {
619
+ namespace: '',
620
+ _links: '',
621
+ routes: {}
622
+ },
623
+
624
+ /**
625
+ * Prevent model from being saved.
626
+ *
627
+ * @returns {boolean}.
628
+ */
629
+ save: function() {
630
+ return false;
631
+ },
632
+
633
+ /**
634
+ * Prevent model from being deleted.
635
+ *
636
+ * @returns {boolean}.
637
+ */
638
+ destroy: function() {
639
+ return false;
640
+ }
641
+ }
642
+ );
643
+
644
+
645
+ })( wp, WP_API_Settings, Backbone, window );
646
+
647
+ /* global WP_API_Settings:false */
648
+ (function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
649
+
650
+ 'use strict';
651
+
652
  /**
653
  * Contains basic collection functionality such as pagination.
654
  */
979
  * Todo: Handle post meta.
980
  */
981
 
982
+ })( wp, WP_API_Settings, Backbone, _, window );