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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta5 to 2.0-beta6

CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 5.0
4
 
5
  - Load api-core as a compatibility library
1
  # Changelog
2
 
3
+ ## 2.0 Beta 6.0
4
+
5
+ - Remove global inclusion of wp-admin/includes/admin.php
6
+
7
+ For a long time, the REST API loaded wp-admin/includes/admin.php to make use of specific admin utilities. Now, it only loads those admin utilities when it needs them.
8
+
9
+ If your custom endpoints make use of admin utilities, you'll need to make sure to load wp-admin/includes/admin.php before you use them.
10
+
11
+ (props @joehoyle, [#1696](https://github.com/WP-API/WP-API/pull/1696))
12
+
13
+ - Link directly to the featured image in a Post's links.
14
+
15
+ (props @rmccue, [#1563](https://github.com/WP-API/WP-API/pull/1563), [#1711](https://github.com/WP-API/WP-API/pull/1711))
16
+
17
+ - Provide object type as callback argument for custom API fields.
18
+
19
+ (props @jtsternberg, [#1714](https://github.com/WP-API/WP-API/pull/1714))
20
+
21
+ - Change users schema order to be order of importance instead of alpha.
22
+
23
+ (props @rachelbaker, [#1708](https://github.com/WP-API/WP-API/pull/1708))
24
+
25
+ - Clarify documentation for `date` and `modified` attributes.
26
+
27
+ (props @danielbachhuber, [#1715](https://github.com/WP-API/WP-API/pull/1715))
28
+
29
+ - Update the wp-api.js client from the client-js repo.
30
+
31
+ (props @rachelbaker, [#1709](https://github.com/WP-API/WP-API/pull/1709))
32
+
33
+ - Fix the `format` enum to be an array of strings.
34
+
35
+ (props @joehoyle, [#1707](https://github.com/WP-API/WP-API/pull/1707))
36
+
37
+ - Run revisions for collection through `prepare_response_for_collection()`.
38
+
39
+ (props @danielbachhuber, @rachelbaker, [#1671](https://github.com/WP-API/WP-API/pull/1671))
40
+
41
+ - Expose `date_gmt` for `view` context of Posts and Comments.
42
+
43
+ (props @danielbachhuber, [#1690](https://github.com/WP-API/WP-API/pull/1690))
44
+
45
+ - Fix PHP and JS docblock formatting.
46
+
47
+ (props @ahmadawais, [#1699](https://github.com/WP-API/WP-API/pull/1698), [#1699](https://github.com/WP-API/WP-API/pull/1699), [#1701](https://github.com/WP-API/WP-API/pull/1701), [#1700](https://github.com/WP-API/WP-API/pull/1700), [#1702](https://github.com/WP-API/WP-API/pull/1702), [#1703](https://github.com/WP-API/WP-API/pull/1703))
48
+
49
+ - Include `media_details` attribute for attachments in embed context.
50
+
51
+ For image attachments, media_details includes a sizes array of image sizes, which is useful for templating.
52
+
53
+ (props @danielbachhuber, [#1667](https://github.com/WP-API/WP-API/pull/1667))
54
+
55
+ - Make `WP_REST_Controller` error messages more helpful by specifying method to subclass.
56
+
57
+ (props @danielbachhuber, [#1670](https://github.com/WP-API/WP-API/pull/1670))
58
+
59
+ - Expose `slug` in `embed` context for Users.
60
+
61
+ `user_nicename` is a public attribute, used in user URLs, so this is safe data to present.
62
+
63
+ (props @danielbachhuber, [#1666](https://github.com/WP-API/WP-API/pull/1666))
64
+
65
+ - Handle falsy value from `wp_count_terms()`, fixing fatal.
66
+
67
+ (props @joehoyle, [#1641](https://github.com/WP-API/WP-API/pull/1641))
68
+
69
+ - Correct methods in `WP_REST_SERVER::EDITABLE` description.
70
+
71
+ (props @rachelbaker, [#1601](https://github.com/WP-API/WP-API/pull/1601))
72
+
73
+ - Add the embed context to Users collection query params.
74
+
75
+ (props @rachelbaker, [#1591](https://github.com/WP-API/WP-API/pull/1591))
76
+
77
+ - Add Terms Controller collection args details.
78
+
79
+ (props @rachelbaker, [#1603](https://github.com/WP-API/WP-API/pull/1603))
80
+
81
+ - Set comment author details from current user.
82
+
83
+ (props @rmccue, [#1580](https://github.com/WP-API/WP-API/pull/1580))
84
+
85
+ - More hook documentation.
86
+
87
+ (props @adamsilverstein, [#1556](https://github.com/WP-API/WP-API/pull/1556), [#1560](https://github.com/WP-API/WP-API/pull/1560))
88
+
89
+ - Return the trashed status of deleted posts/comments.
90
+
91
+ When a post or a comment is deleted, returns a flag to say whether it's been trashed or properly deleted.
92
+
93
+ (props @pento, [#1499](https://github.com/WP-API/WP-API/pull/1499))
94
+
95
+ - In `WP_REST_Posts_Controller::update_item()`, check the post ID based on the proper post type.
96
+
97
+ (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
98
+
99
  ## 2.0 Beta 5.0
100
 
101
  - Load api-core as a compatibility library
README.md CHANGED
@@ -27,7 +27,7 @@ Retrieving or updating data is as simple as sending a HTTP request.
27
 
28
  Want to get your site's posts? Simply send a `GET` request to `/wp-json/wp/v2/posts`.
29
  Update user with ID 4? Send a `POST` request to `/wp-json/wp/v2/users/4`. Get all
30
- posts with the search term "awesome"? `GET /wp-json/wp/v2/posts?s=awesome`.
31
  It's that easy.
32
 
33
  WP API exposes a simple yet easy interface to WP Query, the posts API, post meta
@@ -50,8 +50,7 @@ There's no fixed timeline for integration into core at this time, but getting cl
50
  Drop this directory in and activate it. You need to be using pretty permalinks
51
  to use the plugin, as it uses custom rewrite rules to power the API.
52
 
53
- Also, be sure to use the `trunk` branch of WordPress Core as there are potentially
54
- recent commits to Core that the REST API relies on.
55
 
56
  ## Issue Tracking
57
 
27
 
28
  Want to get your site's posts? Simply send a `GET` request to `/wp-json/wp/v2/posts`.
29
  Update user with ID 4? Send a `POST` request to `/wp-json/wp/v2/users/4`. Get all
30
+ posts with the search term "awesome"? `GET /wp-json/wp/v2/posts?filter[s]=awesome`.
31
  It's that easy.
32
 
33
  WP API exposes a simple yet easy interface to WP Query, the posts API, post meta
50
  Drop this directory in and activate it. You need to be using pretty permalinks
51
  to use the plugin, as it uses custom rewrite rules to power the API.
52
 
53
+ Also, be sure to use the Subversion `trunk` branch of WordPress Core as there are potentially recent commits to Core that the REST API relies on. See the [WordPress.org website](https://wordpress.org/download/svn/) for simple instructions.
 
54
 
55
  ## Issue Tracking
56
 
compatibility-v1.php DELETED
@@ -1,110 +0,0 @@
1
- <?php
2
-
3
- add_filter( 'json_endpoints', 'json_v1_compatible_routes', 1000 );
4
- add_filter( 'json_dispatch_request', 'json_v1_compatible_dispatch', 10, 3 );
5
-
6
- /**
7
- * Make version 1 routes compatible with v2
8
- *
9
- * @param array $routes API routes
10
- * @return array Filtered routes
11
- */
12
- function json_v1_compatible_routes( $routes ) {
13
- foreach ( $routes as $key => &$route ) {
14
- // Single, with new-style registration
15
- if ( isset( $route['callback'] ) || empty( $route ) ) {
16
- continue;
17
- }
18
-
19
- // Multiple, with new-style registration
20
- $first = reset( $route );
21
- if ( isset( $first['callback'] ) ) {
22
- continue;
23
- }
24
-
25
- // Old-style, map to new-style
26
- if ( count( $route ) <= 2 && isset( $route[1] ) && ! is_array( $route[1] ) ) {
27
- $route = array( $route );
28
- }
29
-
30
- foreach ( $route as &$handler ) {
31
- $methods = isset( $handler[1] ) ? $handler[1] : WP_REST_Server::METHOD_GET;
32
-
33
- $handler = array(
34
- 'callback' => $handler[0],
35
- 'methods' => $methods,
36
- 'v1_compat' => true,
37
- );
38
- }
39
- }
40
-
41
- return $routes;
42
- }
43
-
44
- /**
45
- * Use Reflection to match request parameters to function parameters
46
- *
47
- * @param mixed $result Result to use
48
- * @param WP_JSON_Request $request Request object
49
- * @return mixed
50
- */
51
- function json_v1_compatible_dispatch( $result, $request ) {
52
- // Allow other plugins to hijack too
53
- if ( null !== $result ) {
54
- return $result;
55
- }
56
-
57
- // Do we need the compatibility shim?
58
- $params = $request->get_attributes();
59
- if ( empty( $params['v1_compat'] ) ) {
60
- return $result;
61
- }
62
-
63
- // Build up the arguments, old-style
64
- $args = array_merge( $request->get_url_params(), $request->get_query_params() );
65
- if ( $request->get_method() === 'POST' ) {
66
- $args = array_merge( $args, $request->get_body_params() );
67
- }
68
-
69
- $args = json_v1_sort_callback_params( $params['callback'], $args );
70
- if ( is_wp_error( $args ) ) {
71
- return $args;
72
- }
73
-
74
- return call_user_func_array( $params['callback'], $args );
75
- }
76
-
77
- /**
78
- * Sort parameters by order specified in method declaration
79
- *
80
- * Takes a callback and a list of available params, then filters and sorts
81
- * by the parameters the method actually needs, using the Reflection API
82
- *
83
- * @param callback $callback
84
- * @param array $params
85
- * @return array
86
- */
87
- function json_v1_sort_callback_params( $callback, $provided ) {
88
- if ( is_array( $callback ) ) {
89
- $ref_func = new ReflectionMethod( $callback[0], $callback[1] );
90
- } else {
91
- $ref_func = new ReflectionFunction( $callback );
92
- }
93
-
94
- $wanted = $ref_func->getParameters();
95
- $ordered_parameters = array();
96
-
97
- foreach ( $wanted as $param ) {
98
- if ( isset( $provided[ $param->getName() ] ) ) {
99
- // We have this parameters in the list to choose from
100
- $ordered_parameters[] = $provided[ $param->getName() ];
101
- } elseif ( $param->isDefaultValueAvailable() ) {
102
- // We don't have this parameter, but it's optional
103
- $ordered_parameters[] = $param->getDefaultValue();
104
- } else {
105
- // We don't have this parameter and it wasn't optional, abort!
106
- return new WP_Error( 'json_missing_callback_param', sprintf( __( 'Missing parameter %s' ), $param->getName() ), array( 'status' => 400 ) );
107
- }
108
- }
109
- return $ordered_parameters;
110
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/rest-api.php CHANGED
@@ -16,6 +16,14 @@ define( 'REST_API_VERSION', '2.0' );
16
  /** Compatibility shims for PHP functions */
17
  include_once( dirname( __FILE__ ) . '/wp-includes/compat.php' );
18
 
 
 
 
 
 
 
 
 
19
  /** Main API functions */
20
  include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
21
 
@@ -23,7 +31,7 @@ include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
23
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-server.php' );
24
 
25
  /** WP_HTTP_Response class */
26
- include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-http-response.php' );
27
 
28
  /** WP_REST_Response class */
29
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-response.php' );
16
  /** Compatibility shims for PHP functions */
17
  include_once( dirname( __FILE__ ) . '/wp-includes/compat.php' );
18
 
19
+ /** Core HTTP Request API */
20
+ if ( file_exists( ABSPATH . WPINC . '/class-wp-http-response.php' ) ) {
21
+ include_once( dirname( __FILE__ ) . '/wp-includes/http.php' );
22
+ } else {
23
+ // Compatibility with WP 4.3 and below
24
+ include_once( dirname( __FILE__ ) . '/wp-includes/class-wp-http-response.php' );
25
+ }
26
+
27
  /** Main API functions */
28
  include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
29
 
31
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-server.php' );
32
 
33
  /** WP_HTTP_Response class */
34
+ include_once( dirname( __FILE__ ) . '/wp-includes/class-wp-http-response.php' );
35
 
36
  /** WP_REST_Response class */
37
  include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-response.php' );
core/wp-includes/{rest-api/class-wp-http-response.php → class-wp-http-response.php} RENAMED
@@ -1,9 +1,9 @@
1
  <?php
2
  /**
3
- * REST API: WP_HTTP_Response class
4
  *
5
  * @package WordPress
6
- * @subpackage REST_API
7
  * @since 4.4.0
8
  */
9
 
1
  <?php
2
  /**
3
+ * HTTP API: WP_HTTP_Response class
4
  *
5
  * @package WordPress
6
+ * @subpackage HTTP
7
  * @since 4.4.0
8
  */
9
 
core/wp-includes/http.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Standardizes the HTTP requests for WordPress.
4
+ *
5
+ * REST API compatibility functions located in wp-includes/http.php.
6
+ *
7
+ * @package WordPress
8
+ * @subpackage HTTP
9
+ */
10
+
11
+ /** WP_HTTP_Response class */
12
+ require_once( ABSPATH . WPINC . '/class-wp-http-response.php' );
core/wp-includes/rest-api/class-wp-rest-server.php CHANGED
@@ -7,9 +7,6 @@
7
  * @since 4.4.0
8
  */
9
 
10
- /** Admin bootstrap */
11
- require_once( ABSPATH . 'wp-admin/includes/admin.php' );
12
-
13
  /**
14
  * Core class used to implement the WordPress REST API server.
15
  *
7
  * @since 4.4.0
8
  */
9
 
 
 
 
10
  /**
11
  * Core class used to implement the WordPress REST API server.
12
  *
core/wp-includes/rest-api/rest-functions.php CHANGED
@@ -4,6 +4,7 @@
4
  *
5
  * @package WordPress
6
  * @subpackage REST_API
 
7
  */
8
 
9
  /**
@@ -11,6 +12,8 @@
11
  *
12
  * @since 4.4.0
13
  *
 
 
14
  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
15
  * @param string $route The base URL for route you are adding.
16
  * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
@@ -19,7 +22,6 @@
19
  * false merges (with newer overriding if duplicate keys exist). Default false.
20
  */
21
  function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
22
-
23
  /** @var WP_REST_Server $wp_rest_server */
24
  global $wp_rest_server;
25
 
@@ -67,7 +69,7 @@ function register_rest_route( $namespace, $route, $args = array(), $override = f
67
  * by object type.
68
  *
69
  * @param string|array $object_type Object(s) the field is being registered
70
- * to, "post"|"term"|"comment" etc.
71
  * @param string $attribute The attribute name.
72
  * @param array $args {
73
  * Optional. An array of arguments used to handle the registered field.
@@ -78,12 +80,11 @@ function register_rest_route( $namespace, $route, $args = array(), $override = f
78
  * @type string|array|null $update_callback Optional. The callback function used to set and update the
79
  * field value. Default is 'null', the value cannot be set or
80
  * updated.
81
- * @type string|array|null schema Optional. The callback function used to create the schema for
82
  * this field. Default is 'null', no schema entry will be returned.
83
  * }
84
  */
85
  function register_api_field( $object_type, $attribute, $args = array() ) {
86
-
87
  $defaults = array(
88
  'get_callback' => null,
89
  'update_callback' => null,
@@ -153,6 +154,9 @@ function rest_api_default_filters() {
153
  * Loads the REST API.
154
  *
155
  * @since 4.4.0
 
 
 
156
  */
157
  function rest_api_loaded() {
158
  if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
@@ -162,6 +166,7 @@ function rest_api_loaded() {
162
  /**
163
  * Whether this is a REST Request.
164
  *
 
165
  * @var bool
166
  */
167
  define( 'REST_REQUEST', true );
@@ -230,10 +235,10 @@ function rest_get_url_prefix() {
230
  *
231
  * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
232
  * @param string $path Optional. REST route. Default '/'.
233
- * @param string $scheme Optional. Sanitization scheme. Default 'json'.
234
  * @return string Full URL to the endpoint.
235
  */
236
- function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
237
  if ( empty( $path ) ) {
238
  $path = '/';
239
  }
@@ -249,6 +254,13 @@ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
249
  $url = add_query_arg( 'rest_route', $path, $url );
250
  }
251
 
 
 
 
 
 
 
 
252
  /**
253
  * Filter the REST URL.
254
  *
@@ -258,7 +270,7 @@ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
258
  *
259
  * @param string $url REST URL.
260
  * @param string $path REST route.
261
- * @param int $blod_ig Blog ID.
262
  * @param string $scheme Sanitization scheme.
263
  */
264
  return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
@@ -286,7 +298,7 @@ function rest_url( $path = '', $scheme = 'json' ) {
286
  *
287
  * @since 4.4.0
288
  *
289
- * @global WP_REST_Server $wp_rest_server
290
  *
291
  * @param WP_REST_Request|string $request Request.
292
  * @return WP_REST_Response REST response.
@@ -444,10 +456,9 @@ function rest_handle_options_request( $response, $handler, $request ) {
444
  * @param WP_REST_Response $response Current response being served.
445
  * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
446
  * @param WP_REST_Request $request The request that was used to make current response.
447
- * @return WP_REST_Response Current response being served.
448
  */
449
  function rest_send_allow_header( $response, $server, $request ) {
450
-
451
  $matched_route = $response->get_matched_route();
452
 
453
  if ( ! $matched_route ) {
@@ -571,8 +582,8 @@ function rest_cookie_check_errors( $result ) {
571
  // Determine if there is a nonce.
572
  $nonce = null;
573
 
574
- if ( isset( $_REQUEST['_wp_rest_nonce'] ) ) {
575
- $nonce = $_REQUEST['_wp_rest_nonce'];
576
  } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
577
  $nonce = $_SERVER['HTTP_X_WP_NONCE'];
578
  }
4
  *
5
  * @package WordPress
6
  * @subpackage REST_API
7
+ * @since 4.4.0
8
  */
9
 
10
  /**
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
22
  * false merges (with newer overriding if duplicate keys exist). Default false.
23
  */
24
  function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
 
25
  /** @var WP_REST_Server $wp_rest_server */
26
  global $wp_rest_server;
27
 
69
  * by object type.
70
  *
71
  * @param string|array $object_type Object(s) the field is being registered
72
+ * to, "post"|"term"|"comment" etc.
73
  * @param string $attribute The attribute name.
74
  * @param array $args {
75
  * Optional. An array of arguments used to handle the registered field.
80
  * @type string|array|null $update_callback Optional. The callback function used to set and update the
81
  * field value. Default is 'null', the value cannot be set or
82
  * updated.
83
+ * @type string|array|null $schema Optional. The callback function used to create the schema for
84
  * this field. Default is 'null', no schema entry will be returned.
85
  * }
86
  */
87
  function register_api_field( $object_type, $attribute, $args = array() ) {
 
88
  $defaults = array(
89
  'get_callback' => null,
90
  'update_callback' => null,
154
  * Loads the REST API.
155
  *
156
  * @since 4.4.0
157
+ *
158
+ * @global WP $wp Current WordPress environment instance.
159
+ * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
160
  */
161
  function rest_api_loaded() {
162
  if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
166
  /**
167
  * Whether this is a REST Request.
168
  *
169
+ * @since 4.4.0
170
  * @var bool
171
  */
172
  define( 'REST_REQUEST', true );
235
  *
236
  * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
237
  * @param string $path Optional. REST route. Default '/'.
238
+ * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
239
  * @return string Full URL to the endpoint.
240
  */
241
+ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
242
  if ( empty( $path ) ) {
243
  $path = '/';
244
  }
254
  $url = add_query_arg( 'rest_route', $path, $url );
255
  }
256
 
257
+ if ( is_ssl() ) {
258
+ // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS
259
+ if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
260
+ $url = set_url_scheme( $url, 'https' );
261
+ }
262
+ }
263
+
264
  /**
265
  * Filter the REST URL.
266
  *
270
  *
271
  * @param string $url REST URL.
272
  * @param string $path REST route.
273
+ * @param int $blog_id Blog ID.
274
  * @param string $scheme Sanitization scheme.
275
  */
276
  return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
298
  *
299
  * @since 4.4.0
300
  *
301
+ * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
302
  *
303
  * @param WP_REST_Request|string $request Request.
304
  * @return WP_REST_Response REST response.
456
  * @param WP_REST_Response $response Current response being served.
457
  * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
458
  * @param WP_REST_Request $request The request that was used to make current response.
459
+ * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
460
  */
461
  function rest_send_allow_header( $response, $server, $request ) {
 
462
  $matched_route = $response->get_matched_route();
463
 
464
  if ( ! $matched_route ) {
582
  // Determine if there is a nonce.
583
  $nonce = null;
584
 
585
+ if ( isset( $_REQUEST['_wpnonce'] ) ) {
586
+ $nonce = $_REQUEST['_wpnonce'];
587
  } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
588
  $nonce = $_SERVER['HTTP_X_WP_NONCE'];
589
  }
docs/README.md DELETED
@@ -1,26 +0,0 @@
1
- API Documentation
2
- =================
3
- Learn how the JSON REST API works from the ground up!
4
-
5
- First time interacting with the API? Start with the [Getting Started][] guide,
6
- which will introduce you to the basic concepts for working with the API.
7
-
8
- From there, progress on to other [guides][] to learn in detail about parts of
9
- the API.
10
-
11
- Take a look at more detailed information on [post][post-routes] or
12
- [media][media-routes], or read about [maximizing compatibility][compatibility]
13
- with older clients.
14
-
15
- Dive in deeper into the [schema details][schema] to better understand the little
16
- details, or read about the [philosophy][] behind them. Read about the
17
- [implementation details][implementation] on how the API works internally.
18
-
19
- [Getting Started]: http://wp-api.org/guides/getting-started.html
20
- [guides]: http://wp-api.org/guides.html
21
- [post-routes]: http://wp-api.org/#posts
22
- [media-routes]: http://wp-api.org/#media
23
- [compatibility]: compatibility.md
24
- [schema]: schema.md
25
- [philosophy]: internals/philosophy.md
26
- [implementation]: internals/implementation.md
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/routes/routes.md DELETED
@@ -1,1569 +0,0 @@
1
- Posts
2
- =====
3
-
4
- Create a Post
5
- -------------
6
-
7
- POST /posts
8
-
9
- Requires [authentication](http://wp-api.org/guides/authentication.html)
10
-
11
- ### Input
12
- The `data` parameter consists of the elements of the Post object to be
13
- created. This data can be submitted via a regular HTTP multipart body, with
14
- the Post keys and values set to the `data` parameter, or through a direct JSON
15
- body.
16
-
17
- That is, the following are equivalent:
18
-
19
- ```
20
- Content-Type: application/x-www-form-urlencoded
21
-
22
- data[title]=Hello%20World!&data[content_raw]=Content&data[excerpt_raw]=Excerpt
23
- ```
24
-
25
- ```
26
- Content-Type: application/json
27
-
28
- {"title":"Hello World!","content_raw":"Content","excerpt_raw":"Excerpt"}
29
- ```
30
-
31
- The `data` parameter should be an object containing the following key value
32
- pairs:
33
-
34
- * `title` - Title of the post. (string) __*required*__
35
- * `content_raw` - Full text of the post. (string) __*required*__
36
- * `excerpt_raw` - Text for excerpt of the post. (string) *optional*
37
- * `name` - Slug of the post. (string) *optional*
38
- * `status` - Post status of the post: `draft`, `publish`, `pending`, `future`,
39
- `private`, or any custom registered status. If providing a status of
40
- `future`, you must specify a `date` in order for the post to be published as
41
- expected. Default is `draft`. (string) *optional*
42
- * `type` - Post type of the post: `post`, `page`, `link`, `nav_menu_item`, or
43
- a any custom registered type. Default is `post`. (string) *optional*
44
- * `date` - Date and time the post was, or should be, published in local time.
45
- Date should be an RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
46
- Example: 2014-01-01T12:20:52Z. Default is the local date and time. (string)
47
- *optional*
48
- * `date_gmt` - Date and time the post was, or should be, published in UTC time.
49
- Date should be an [RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
50
- Example: 201401-01T12:20:52Z. Default is the current GMT date and time.
51
- (string) *optional*
52
- * `author` - Author of the post. Author can be provided as a string of the
53
- author's ID or as the User object of the author. Default is current user.
54
- (object \| string) *optional*
55
- * `password` - Password for protecting the post. Default is empty string.
56
- (string) *optional*
57
- * `post_parent` - Post ID of the post parent. Default is 0. (integer)
58
- *optional*
59
- * `post_format` - Format of the post. Default is `standard`. (string)
60
- *optional*
61
- * `menu_order` - The order in which posts specified as the `page` type should
62
- appear in supported menus. Default 0. (integer) *optional*
63
- * `comment_status` - Comment status for the post: `open` or `closed`.
64
- Indicates whether users can submit comments to the post. Default is the
65
- option 'default_comment_status', or 'closed'. (string) *optional*
66
- * `ping_status` - Ping status for the post: `open` or `closed`. Indicates
67
- whether users can submit pingbacks or trackbacks to the post. Default is the
68
- option 'default_ping_status'. (string) *optional*
69
- * `sticky` - Sticky status for the post: `true` or `false`. Default is
70
- `false`. (boolean) *optional*
71
- * `post_meta` - Post meta entries of the post. Post meta should be an array
72
- of one or more Meta objects for each post meta entry. See the Create Meta
73
- for a Post endpoint for the key value pairs. (array) *optional*
74
-
75
-
76
- ### Response
77
- On a successful creation, a 201 Created status is given, indicating that the
78
- post has been created. The post is available canonically from the URL specified
79
- in the Location header.
80
-
81
- The new Post entity is also returned in the body for convienience.
82
-
83
- If the client is not authenticated, a 403 Forbidden response is given.
84
-
85
- Retrieve Posts
86
- --------------
87
- The Posts endpoint returns a Post Collection containing a subset of the site's
88
- posts.
89
-
90
- GET /posts
91
-
92
- ### Input
93
- #### `filter`
94
- The `filter` parameter controls the parameters used to query for posts.
95
-
96
- **Note:** Only "public" query variables are available via the API, as not all
97
- query variables are safe to expose. "Private" query variables are also available
98
- when authenticated as a user with `edit_posts`. Other query variables can be
99
- registered via the `query_vars` filter, or `json_query_vars` for API-specific
100
- query variables.
101
-
102
- Extended documentation on the query variables is available from
103
- [the codex](http://codex.wordpress.org/Class_Reference/WP_Query).
104
-
105
- The following query variables are available to the API:
106
-
107
- * `m`
108
- * `p`
109
- * `posts`
110
- * `w`
111
- * `cat`
112
- * `withcomments`
113
- * `withoutcomments`
114
- * `s`
115
- * `search`
116
- * `exact`
117
- * `sentence`
118
- * `calendar`
119
- * `page`
120
- * `paged`
121
- * `more`
122
- * `tb`
123
- * `pb`
124
- * `author`
125
- * `order`
126
- * `orderby`
127
- * `year`
128
- * `monthnum`
129
- * `day`
130
- * `hour`
131
- * `minute`
132
- * `second`
133
- * `name`
134
- * `category_name`
135
- * `tag`
136
- * `feed`
137
- * `author_name`
138
- * `static`
139
- * `pagename`
140
- * `page_id`
141
- * `error`
142
- * `comments_popup`
143
- * `attachment`
144
- * `attachment_id`
145
- * `subpost`
146
- * `subpost_id`
147
- * `preview`
148
- * `robots`
149
- * `taxonomy`
150
- * `term`
151
- * `cpage`
152
- * `posts_per_page`
153
-
154
- In addition, the following are available when authenticated as a user with
155
- `edit_posts`:
156
-
157
- * `offset`
158
- * `posts_per_archive_page`
159
- * `showposts`
160
- * `nopaging`
161
- * `post_type`
162
- * `post_status`
163
- * `category__in`
164
- * `category__not_in`
165
- * `category__and`
166
- * `tag__in`
167
- * `tag__not_in`
168
- * `tag__and`
169
- * `tag_slug__in`
170
- * `tag_slug__and`
171
- * `tag_id`
172
- * `post_mime_type`
173
- * `perm`
174
- * `comments_per_page`
175
- * `post__in`
176
- * `post__not_in`
177
- * `post_parent`
178
- * `post_parent__in`
179
- * `post_parent__not_in`
180
-
181
- ```
182
- GET /posts?filter[posts_per_page]=8&filter[order]=ASC
183
- ```
184
-
185
- #### `context`
186
- The `context` parameter controls the format of the data to return. See the
187
- Retrieve a Post endpoint for available contexts.
188
-
189
- Default is "view". (string)
190
-
191
-
192
- #### `type`
193
- The `type` parameter specifies the post type to retrieve. This can either be a
194
- string or an array of types.
195
-
196
- Note that arrays are specified using the `[]` URL syntax. e.g.
197
-
198
- ```
199
- GET /posts?type[]=post&type[]=page
200
- ```
201
-
202
- Default is "post". (string)
203
-
204
-
205
- ### Response
206
- The response is a Post Collection document containing the requested Posts if
207
- available.
208
-
209
-
210
- Retrieve a Post
211
- ---------------
212
-
213
- GET /posts/<id>
214
-
215
- ### Input
216
- #### `context`
217
- The `context` parameter controls the format of the data to return. The
218
- following contexts are available:
219
-
220
- * `view`: The default context. Gives the normal User entity.
221
- * `edit`: Context used for extra fields relevant to updating a user. Includes
222
- the `title_raw`, `content_raw`, `guid_raw` and `post_meta` fields, suitable
223
- for editing the post.
224
- * `parent`: Context used when embedding the response inside another (e.g. post
225
- author). This is intended as a minimal subset of the user data to reduce
226
- response size. Returns the `parent` field as an ID, rather than an embedded
227
- post, to ensure we don't traverse the entire post hierarchy.
228
-
229
- ### Response
230
- The response is a Post entity containing the requested Post if available. The
231
- fields available on the Post depend on the `context` parameter.
232
-
233
-
234
- Edit a Post
235
- -----------
236
-
237
- PUT /posts/<id>
238
-
239
- Requires [authentication](http://wp-api.org/guides/authentication.html)
240
-
241
- For compatibility reasons, this endpoint also accepts the POST and PATCH
242
- methods. Both of these methods have the same behaviour as using PUT. It is
243
- recommended to use PUT if available to fit with REST convention.
244
-
245
- ### Input
246
- The `data` parameter consists of Post ID and the elements of the Post object
247
- to be modified. This data can be submitted via a regular HTTP multipart body,
248
- with the Post keys and values set to the `data` parameter, or through a direct
249
- JSON body. See the Create Post endpoint for an example.
250
-
251
- The `data` parameter should be an object containing the following key value
252
- pairs:
253
-
254
- * `ID` - Unique ID of the post. (integer) __*required*__
255
- * `title` - Title of the post. (string) __*required*__
256
- * `content_raw` - Full text of the post. (string) __*required*__
257
- * `excerpt_raw` - Text for excerpt of the post. (string) *optional*
258
- * `name` - Slug of the post. (string) *optional*
259
- * `status` - Post status of the post: `draft`, `publish`, `pending`, `future`,
260
- `private`, or any custom registered status. If providing a status of
261
- `future`, you must specify a `date` in order for the post to be published as
262
- expected. Default is `draft`. (string) *optional*
263
- * `type` - Post type of the post: `post`, `page`, `link`, `nav_menu_item`, or
264
- a any custom registered type. Default is `post`. (string) *optional*
265
- * `date` - Date and time the post was, or should be, published in local time.
266
- Date should be an RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
267
- Example: 2014-01-01T12:20:52Z. Default is the local date and time. (string)
268
- *optional*
269
- * `date_gmt` - Date and time the post was, or should be, published in UTC time.
270
- Date should be an [RFC3339 timestamp](http://tools.ietf.org/html/rfc3339).
271
- Example: 201401-01T12:20:52Z. Default is the current GMT date and time.
272
- (string) *optional*
273
- * `author` - Author of the post. Author can be provided as a string of the
274
- author's ID or as the User object of the author. Default is current user.
275
- (object \| string) *optional*
276
- * `password` - Password for protecting the post. Default is empty string.
277
- (string) *optional*
278
- * `post_parent` - Post ID of the post parent. Default is 0. (integer)
279
- *optional*
280
- * `post_format` - Format of the post. Default is `standard`. (string)
281
- *optional*
282
- * `menu_order` - The order in which posts specified as the `page` type should
283
- appear in supported menus. Default 0. (integer) *optional*
284
- * `comment_status` - Comment status for the post: `open` or `closed`.
285
- Indicates whether users can submit comments to the post. Default is the
286
- option 'default_comment_status', or 'closed'. (string) *optional*
287
- * `ping_status` - Ping status for the post: `open` or `closed`. Indicates
288
- whether users can submit pingbacks or trackbacks to the post. Default is the
289
- option 'default_ping_status'. (string) *optional*
290
- * `sticky` - Sticky status for the post: `true` or `false`. Default is
291
- `false`. (boolean) *optional*
292
- * `post_meta` - Post meta entries of the post. Post meta should be an array
293
- of one or more Meta objects for each post meta entry. See the Edit Meta
294
- for a Post endpoint for the key value pairs. (array) *optional*
295
-
296
-
297
- ### Response
298
- On a successful update, a 200 OK status is given, indicating the post has been
299
- updated. The updated Post entity is returned in the body.
300
-
301
- If the client is not authenticated, a 403 Forbidden response is sent.
302
-
303
- Delete a Post
304
- -------------
305
-
306
- DELETE /posts/<id>
307
-
308
- Requires [authentication](http://wp-api.org/guides/authentication.html)
309
-
310
- ### Input
311
- #### `force`
312
- The `force` parameter controls whether the post is permanently deleted or not.
313
- By default, this is set to false, indicating that the post will be sent to an
314
- intermediate storage (such as the trash) allowing it to be restored later. If
315
- set to true, the post will not be able to be restored by the user.
316
-
317
- Default is false. (boolean)
318
-
319
- ### Response
320
- On successful deletion, a 202 Accepted status code will be returned, indicating
321
- that the post has been moved to the trash for permanent deletion at a
322
- later date.
323
-
324
- If force was set to true, a 200 OK status code will be returned instead,
325
- indicating that the post has been permanently deleted.
326
-
327
- If the client is not authenticated, a 403 Forbidden status code will be returned.
328
-
329
- Retrieve Revisions for a Post
330
- ------------------------
331
-
332
- GET /posts/<id>/revisions
333
-
334
- Requires [authentication](http://wp-api.org/guides/authentication.html)
335
-
336
- ### Response
337
- If successful, returns a 200 OK status code and revisions for the given post.
338
-
339
- If the client is not authenticated, a 403 Forbidden status code will be returned.
340
-
341
-
342
- Create Meta for a Post
343
- ------------------------
344
-
345
- POST /posts/<id>/meta
346
-
347
- Requires [authentication](http://wp-api.org/guides/authentication.html)
348
-
349
- Note that the access rules for metadata apply here (see [Retrieve Meta for
350
- a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ). Any submitted data that violates an access rule (e.g. sending
351
- serialized data) will result in a 403 error.
352
-
353
- ### Input
354
- The supplied data should be a Meta object. This data can be submitted via a
355
- regular HTTP multipart body, with the Meta key and value set with the `data`
356
- parameter, or through a direct JSON body.
357
-
358
- The `data` parameter should be an object containing the following key value
359
- pairs:
360
-
361
- * `key` - The post meta key to be created. (string) *required*
362
- * `value` - The post meta value for the key provided. (string) *required*
363
-
364
- ### Response
365
- On a successful creation, a 201 Created status is given, indicating that the
366
- Meta has been created. The post meta is available canonically from the URL
367
- specified in the Location header.
368
-
369
- The new Meta entity is also returned in the body for convienience.
370
-
371
- If the client is not authenticated, a 403 Forbidden status code will be returned.
372
-
373
- Retrieve Meta for a Post
374
- ------------------------
375
-
376
- GET /posts/<id>/meta
377
-
378
- Requires [authentication](http://wp-api.org/guides/authentication.html)
379
-
380
- WordPress metadata follows some special rules for access:
381
-
382
- * Metadata is only available to authenticated clients, as the fields are "raw"
383
- values from the database. The API cannot ensure that it's not leaking private
384
- data, although we're working on changing WordPress to support this.
385
-
386
- * "Complex" metadata is not available from the API. Only simple values, such as
387
- numbers, strings, and booleans, are available via the meta endpoints. Complex
388
- values, such as arrays and objects do not have a lossless (one-to-one)
389
- representation in JSON. Exposing the serialized value could leak internal
390
- implementation details and pose a security risk.
391
-
392
- * "Protected" metadata is not available from the API. This includes any metadata
393
- with a key prefixed with `_`, as well as any meta marked as protected by
394
- plugins. Protected meta is used to store internal data by many plugins and
395
- cannot be exposed to external clients.
396
-
397
- ### Response
398
- The response is a Meta entity containing all the post_meta for the specified
399
- Post if available.
400
-
401
- Returns a 403 Forbidden status code if the client is not authenticated.
402
-
403
- Retrieve a Meta for a Post
404
- ------------------------
405
-
406
- GET /posts/<id>/meta/<mid>
407
-
408
- Requires [authentication](http://wp-api.org/guides/authentication.html)
409
-
410
- Note that the access rules for metadata apply here (see [Retrieve Meta for
411
- a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ).
412
-
413
- ### Response
414
- The response is a Meta entity containing the post_meta for the specified Meta and
415
- Post if available.
416
-
417
- Returns a 403 Forbidden status code if the client is not authenticated.
418
-
419
- Edit a Meta for a Post
420
- ------------------------
421
-
422
- PUT /posts/<id>/meta/<mid>
423
-
424
- Requires [authentication](http://wp-api.org/guides/authentication.html)
425
-
426
- Note that the access rules for metadata apply here (see [Retrieve Meta for
427
- a Post](http://wp-api.org/#posts_retrieve-meta-for-a-post) ). Any submitted data that violates an access rule (e.g. sending
428
- serialized data) will result in a 403 error.
429
-
430
- ### Input
431
- The supplied data should be a Meta object. This data can be submitted via a
432
- regular HTTP multipart body, with the Meta key and value set with the `data`
433
- parameter, or through a direct JSON body.
434
-
435
- The `data` parameter should be an array containing the following key value pairs:
436
-
437
- * `key` - The post meta key to be updated. (string) *required*
438
- * `value` - The post meta value for the key provided. (string) *required*
439
-
440
- ### Response
441
- On a successful update, a 200 OK status is given, indicating the post_meta has
442
- been updated. The updated Meta entity is returned in the body.
443
-
444
- If the client is not authenticated, a 403 Forbidden status code is returned.
445
-
446
- Delete a Meta for a Post
447
- -------------
448
-
449
- DELETE /posts/<id>/meta/<mid>
450
-
451
- Requires [authentication](http://wp-api.org/guides/authentication.html)
452
-
453
- Note that the access rules for metadata apply here (see Retrieve Meta for
454
- a Post). Attempting to delete data that violates an access rule (e.g. sending
455
- serialized data) will result in a 403 error.
456
-
457
- ### Response
458
- On successful deletion, a 200 OK status code will be returned, indicating
459
- that the post_meta has been permanently deleted.
460
-
461
- If the client is not authenticated, a 403 Forbidden status code is returned.
462
-
463
- Media
464
- =====
465
-
466
-
467
- Create an Attachment
468
- --------------------
469
- The Create Attachment endpoint is used to create the raw data for an attachment.
470
- This is a binary object (blob), such as image data or a video.
471
-
472
- POST /media
473
-
474
- Requires [authentication](http://wp-api.org/guides/authentication.html)
475
-
476
- ### Input
477
- The attachment creation endpoint can accept data in two forms.
478
-
479
- The primary input method accepts raw data POSTed with the corresponding content
480
- type set via the `Content-Type` HTTP header. This is the preferred submission
481
- method.
482
-
483
- The secondary input method accepts data POSTed via `multipart/form-data`, as per
484
- [RFC 2388][]. The uploaded file should be submitted with the name field set to
485
- "file", and the filename field set to the relevant filename for the file.
486
-
487
- In addition, a `Content-MD5` header can be set with the MD5 hash of the file, to
488
- enable the server to check for consistency errors. If the supplied hash does not
489
- match the hash calculated on the server, a 412 Precondition Failed header will
490
- be issued.
491
-
492
- [RFC 2388]: http://tools.ietf.org/html/rfc2388
493
-
494
- ### Response
495
- On a successful creation, a 201 Created status is given, indicating that the
496
- attachment has been created. The attachment is available canonically from the
497
- URL specified in the Location header.
498
-
499
- The new Attachment entity is also returned in the body for convienience.
500
-
501
- Returns a 403 Forbidden status code if the client is not authenticated.
502
-
503
- Get Attachments
504
- ---------------
505
- The Attachments endpoint returns an Attachment collection containing a subset of
506
- the site's attachments.
507
-
508
- This endpoint is an extended version of the Post retrieval endpoint.
509
-
510
- GET /media
511
-
512
- ### Input
513
- #### `fields`
514
- ...
515
-
516
- ### Response
517
- The response is an Attachment entity containing the requested Attachment if
518
- available.
519
-
520
-
521
- Users
522
- =====
523
-
524
-
525
- Create a User
526
- -------------
527
-
528
- POST /users
529
-
530
- Requires [authentication](http://wp-api.org/guides/authentication.html)
531
-
532
- ### Input
533
- The supplied data should be a User object. This data can be submitted via a
534
- regular HTTP multipart body, with User values set as values to the `data`
535
- parameter, or through a direct JSON body.
536
-
537
- That is, the following are equivalent:
538
-
539
- Content-Type: application/x-www-form-urlencoded
540
-
541
- data[username]=newuser&data[name]=New%20User&data[password]=secret
542
-
543
-
544
- Content-Type: application/json
545
-
546
- {"username":"newuser","name":"New User","password":"secret"}
547
-
548
- ### Response
549
- On a successful creation, a 201 Created status is given, indicating that the
550
- user has been created. The user is available canonically from the URL specified
551
- in the Location header.
552
-
553
- The new User entity is also returned in the body for convenience.
554
-
555
- A 403 Forbidden status is returned if the client is not authenticated.
556
-
557
- Retrieve Users
558
- --------------
559
- The Users endpoint returns a User Collection containing a subset of the site's
560
- users.
561
-
562
- GET /users
563
-
564
- Requires [authentication](http://wp-api.org/guides/authentication.html)
565
-
566
-
567
- ### Input
568
- #### `filter`
569
- The `filter` parameter controls the query parameters. It is essentially a subset
570
- of the parameters available to [`WP_User_Query`](http://codex.wordpress.org/Class_Reference/WP_User_Query).
571
-
572
- The parameter should be an array of the following key/value pairs:
573
-
574
- * `number` - Number of users to retrieve, use `-1` for all users. Default
575
- is set by the site. (integer)
576
- * `offset` - Number of users to skip. Default is 0. (integer)
577
- * `orderby` - Parameter to search by, as per [`WP_User_Query`](https://codex.wordpress.org/Class_Reference/WP_User_Query#Order_.26_Orderby_Parameters).
578
- Default is "user_login". (string)
579
- * `order` - Order to sort by. Default is "ASC". (string, "ASC" or "DESC")
580
- * `s` - Keyword to search for. (string)
581
-
582
- ### Response
583
- The response is a User Collection document containing the requested Users if
584
- available.
585
-
586
- A 403 Forbidden status is returned if the client is not authenticated.
587
-
588
-
589
- Retrieve a User
590
- ---------------
591
-
592
- GET /users/<id>
593
-
594
- Requires [authentication](http://wp-api.org/guides/authentication.html)
595
-
596
- ### Input
597
- #### `context`
598
- The `context` parameter controls the format of the data to return. The following
599
- contexts are available:
600
-
601
- * `view`: The default context. Gives the normal User entity.
602
- * `edit`: Context used for extra fields relevant to updating a user. Includes
603
- the `extra_capabilities` field; this field contains the capabilities assigned
604
- to the user themselves, rather than those inherited from their roles. Requires [authentication](http://wp-api.org/guides/authentication.html).
605
- * `embed`: Context used when embedding the response inside another (e.g. post
606
- author). This is intended as a minimal subset of the user data to reduce
607
- response size. Excludes `roles` and `capabilities`.
608
-
609
- Default is "view". (string)
610
-
611
- ### Response
612
- The response is a User entity containing the requested User if available. The
613
- fields available on the User depend on the `context` parameter.
614
-
615
- A 403 Forbidden status is returned if the client is not authenticated.
616
-
617
-
618
- Retrieve Current User
619
- -------------
620
-
621
- GET /users/me
622
-
623
- Requires [authentication](http://wp-api.org/guides/authentication.html)
624
-
625
- This endpoint offers a permalink to get the current user, without needing to
626
- know the user's ID.
627
-
628
- ### Input
629
- #### `context`
630
- The `context` parameter controls the format of the data to return. See the
631
- Retrieve a User endpoint for available contexts.
632
-
633
- Default is "view". (string)
634
-
635
- ### Response
636
- If the client is currently logged in, a 302 Found status is given. The User is
637
- available canonically from the URL specified in the Location header.
638
-
639
- The User entity containing the current User is also returned in the body for
640
- convenience. The fields available on the User depend on the `context` parameter.
641
-
642
- If the client is not logged in, a 403 Forbidden status is given.
643
-
644
-
645
- Edit a User
646
- -----------
647
-
648
- PUT /users/<id>
649
-
650
- Requires [authentication](http://wp-api.org/guides/authentication.html)
651
-
652
- For compatibility reasons, this endpoint also accepts the POST and PATCH
653
- methods. Both of these methods have the same behaviour as using PUT. It is
654
- recommended to use PUT if available to fit with REST convention.
655
-
656
- ### Input
657
- The supplied data should be a User object. This data can be submitted via a
658
- regular HTTP multipart body, with User values set as values to the `data`
659
- parameter, or through a direct JSON body. See the Create User endpoint for an
660
- example.
661
-
662
- ### Response
663
- On a successful update, a 200 OK status is given, indicating the user has been
664
- updated. The updated User entity is returned in the body.
665
-
666
- If the client is not logged in, a 403 Forbidden status is given.
667
-
668
- Delete a User
669
- -------------
670
-
671
- DELETE /users/<id>
672
-
673
- Requires [authentication](http://wp-api.org/guides/authentication.html)
674
-
675
- ### Input
676
- #### `force`
677
- The `force` parameter controls whether the user is permanently deleted or not.
678
- By default, this is set to false, indicating that the user will be sent to an
679
- intermediate storage (such as the trash) allowing it to be restored later. If
680
- set to true, the user will not be able to be restored.
681
-
682
- Default is false. (boolean)
683
-
684
- #### `reassign`
685
- The `reassign` parameter controls whether the deleted user's content is
686
- reassigned to a new User or not. If set to `null`, the deleted user's content
687
- will not be reassigned.
688
-
689
- Default is null. (integer)
690
-
691
-
692
- ### Response
693
- On successful deletion, a 202 Accepted status code will be returned, indicating
694
- that the user has been moved to the trash for permanent deletion at a
695
- later date.
696
-
697
- If force was set to true, a 200 OK status code will be returned instead,
698
- indicating that the user has been permanently deleted.
699
-
700
- If the client is not authenticated, a 403 Forbidden status is given.
701
-
702
- Taxonomies
703
- ==========
704
-
705
-
706
- Retrieve All Taxonomies
707
- -----------------------
708
- The Taxonomies endpoint returns a collection containing objects for each of the
709
- site's registered taxonomies.
710
-
711
- GET /taxonomies
712
-
713
-
714
- ### Response
715
- The response is a collection document containing all registered taxonomies.
716
-
717
-
718
- Retrieve a Taxonomy
719
- -------------------
720
-
721
- GET /taxonomies/<taxonomy>
722
-
723
- ### Response
724
- The response is a Taxonomy entity containing the requested Taxonomy, if available.
725
-
726
-
727
- Retrieve Terms for a Taxonomy
728
- -----------------------------
729
-
730
- GET /taxonomies/<taxonomy>/terms
731
-
732
- ### Response
733
- The response is a collection of taxonomy terms for the specified Taxonomy, if
734
- available.
735
-
736
- Retrieve a Taxonomy Term
737
- ------------------------
738
-
739
- GET /taxonomies/<taxonomy>/terms/<id>
740
-
741
- ### Response
742
- The response is a Taxonomy entity object containing the Taxonomy with the
743
- requested ID, if available.
744
-
745
- SCHEMA
746
- ============
747
- The API is designed around two types of responses: entities, and collections.
748
- Entities are JSON objects representing internal objects, both abstract and
749
- WordPress objects. Collections are JSON arrays of Entities.
750
-
751
- This document is for clients and providers wanting to ensure full compliance
752
- with the specification.
753
-
754
-
755
- Definitions
756
- ==========
757
- The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
758
- "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
759
- interpreted as described in [RFC2119][].
760
-
761
- * Provider: A site making the API available for use
762
- * Consumer: An application accessing and interacting with the API
763
- * slug: A URL-friendly human-readable identifier, usually derived from the title
764
- of the entity.
765
-
766
- [RFC2119]: http://tools.ietf.org/html/rfc2119
767
-
768
-
769
- ### ABNF
770
- Augmented Backus-Naur Form (ABNF) is to be interpreted as described in
771
- [RFC5234][]. In addition, the following basic rules are used to describe basic
772
- parsing constructs above the standard JSON parsing rules.
773
-
774
- token = 1*<any OCTET except CTLs> ; DQUOTE must be escaped with "\"
775
-
776
- Note that as per ABNF, literal strings are case insensitive. That is:
777
-
778
- example-field = "id"
779
- example-field = "ID"
780
-
781
- Providers SHOULD use the capitalisation as per this specification to ensure
782
- maximum compatibility with consumers. Consumers SHOULD ignore the case of
783
- literal strings when parsing data.
784
-
785
- [RFC5234]: http://tools.ietf.org/html/rfc5234
786
-
787
-
788
- Entities
789
- ========
790
-
791
- Index
792
- -----
793
- The Index entity is a JSON object with site properties. The following properties
794
- are defined for the Index entity object.
795
-
796
- ### `name`
797
- The `name` field is a string with the site's name.
798
-
799
- ### `description`
800
- The `description` field is a string with the site's description.
801
-
802
- ### `URL`
803
- The `URL` field is a string with the URL to the site itself.
804
-
805
- ### `routes`
806
- The `routes` field is an object with keys as a route and the values as a route
807
- descriptor.
808
-
809
- The route is a string giving the URL template for the route, relative to the API
810
- root. The template contains URL parts separated by forward slashes, with each
811
- URL part either a static string, or a route variable encased in angle brackets.
812
-
813
- route = ( "/"
814
- / *( "/" ( token / route-variable ) ) )
815
- route-variable = "<" token ">"
816
-
817
- These routes can be converted into URLs by replacing all route variables with
818
- their relevant values, then concatenating the relative URL to the API base.
819
-
820
- The route descriptor is an object with the following defined properties.
821
-
822
- * `supports`: A JSON array of supported HTTP methods (verbs). Possible values
823
- are "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"
824
- * `accepts_json`: A boolean indicating whether data can be passed directly via a
825
- POST request body. Default for missing properties is false.
826
- * `meta`: An Entity Meta entity. Typical `links` values consist of a `self` link
827
- pointing to the route's full URL.
828
-
829
- ### `meta`
830
- The `meta` field is a Entity Meta entity with metadata relating to the entity
831
- representation.
832
-
833
- Typical `links` values for the meta object consist of a `help` key with the
834
- value indicating a human-readable documentation page about the API.
835
-
836
- ### Example
837
-
838
- {
839
- "name": "My WordPress Site",
840
- "description": "Just another WordPress site",
841
- "URL": "http:\/\/example.com",
842
- "routes": {
843
- "\/": {
844
- "supports": [
845
- "HEAD",
846
- "GET"
847
- ],
848
- "meta": {
849
- "self": "http:\/\/example.com\/wp-json\/"
850
- }
851
- },
852
- "\/posts": {
853
- "supports": [
854
- "HEAD",
855
- "GET",
856
- "POST"
857
- ],
858
- "meta": {
859
- "self": "http:\/\/example.com\/wp-json\/posts"
860
- },
861
- "accepts_json": true
862
- },
863
- "\/posts\/<id>": {
864
- "supports": [
865
- "HEAD",
866
- "GET",
867
- "POST",
868
- "PUT",
869
- "PATCH",
870
- "DELETE"
871
- ],
872
- "accepts_json": true
873
- },
874
- "\/posts\/<id>\/revisions": {
875
- "supports": [
876
- "HEAD",
877
- "GET"
878
- ]
879
- },
880
- "\/posts\/<id>\/comments": {
881
- "supports": [
882
- "HEAD",
883
- "GET",
884
- "POST"
885
- ],
886
- "accepts_json": true
887
- },
888
- "\/posts\/<id>\/comments\/<comment>": {
889
- "supports": [
890
- "HEAD",
891
- "GET",
892
- "POST",
893
- "PUT",
894
- "PATCH",
895
- "DELETE"
896
- ],
897
- "accepts_json": true
898
- },
899
- },
900
- "meta": {
901
- "links": {
902
- "help": "https:\/\/github.com\/WP-API\/WP-API",
903
- "profile": "https:\/\/raw.github.com\/WP-API\/WP-API\/master\/docs\/schema.json"
904
- }
905
- }
906
- }
907
-
908
- Post
909
- ----
910
- The Post entity is a JSON object of post properties. Unless otherwise defined,
911
- properties are available in all contexts. The following properties are defined
912
- for the Post entity object:
913
-
914
- ### `title`
915
- The `title` field is a string with the post's title.
916
-
917
- ### `date`, `date_gmt`
918
- The `date` and `date_gmt` fields are strings with the post's creation date and
919
- time in the local time and UTC respectively. These fields follow the [RFC3339][]
920
- Section 5.6 datetime representation.
921
-
922
- date = date-time
923
- date_gmt = date-time
924
-
925
- [RFC3339]: http://tools.ietf.org/html/rfc3339
926
-
927
- ### `modified`, `modified_gmt`
928
- The `modified` and `modified_gmt` fields are strings with the post's last
929
- modification date and time in the local time and UTC respectively. These fields
930
- follow the [RFC3339][] Section 5.6 datetime representation.
931
-
932
- modified = date-time
933
- modified_gmt = date-time
934
-
935
- ### `date_tz`, `modified_tz`
936
- The `date_tz` and `modified_tz` fields are strings with the timezone applying to
937
- the `date` and `modified` fields respectively. The timezone is a [Olsen zoneinfo
938
- database][] identifier. While the `date` and `modified` fields include timezone
939
- offset information, the `date_tz` and `modified_tz` fields allow proper data
940
- operations across Daylight Savings Time boundaries.
941
-
942
- Note that in addition to the normal Olsen timezones, manual offsets may be
943
- given. These manual offsets use the deprecated `Etc/GMT+...` zones and specify
944
- an integer offset in hours from UTC.
945
-
946
- timezone = Olsen-timezone / manual-offset
947
- manual-offset = "Etc/GMT" ("-" / "+") 1*2( DIGIT )
948
-
949
- Consumers SHOULD use the fields if they perform mathematical operations on the
950
- `date` and `modified` fields (such as adding an hour to the last modification
951
- date) rather than relying on the `time-offset` in the `date` or
952
- `modified` fields.
953
-
954
- [Olsen zoneinfo database]: https://en.wikipedia.org/wiki/Tz_database
955
-
956
- ### `status`
957
- The `status` field is a string with the post's status. This status relates to
958
- where the post is in the editorial process. These are usually set values, but
959
- some providers may have extra post statuses.
960
-
961
- post-status = "draft" / "pending" / "private" / "publish" / "trash" / token
962
-
963
- Consumers who encounter an unknown or missing post status SHOULD treat it the
964
- same as a "draft" status.
965
-
966
- ### `type`
967
- The `type` field is a string with the post's type. This field is specific to
968
- providers, with the most basic representation being "post". The type of the
969
- post usually relates to the fields in the Post entity, with other types having
970
- additional fields specific to the type.
971
-
972
- post-type = "post" / token
973
-
974
- Consumers who encounter an unknown or missing post type SHOULD treat it the same
975
- as a "post" type.
976
-
977
- ### `name`
978
- The `name` field is a string with the post's slug.
979
-
980
- ### `author`
981
- The `author` field is a User entity with the user who created the post.
982
-
983
- ### `password`
984
- The `password` field is a string with the post's password. A zero-length
985
- password indicates that the post does not have a password.
986
-
987
- Consumers who encounter a missing password MUST treat it the same as a
988
- zero-length password.
989
-
990
- ### `content`
991
- The `content` field is a string with the post's content.
992
-
993
- ### `excerpt`
994
- The `excerpt` field is a string with the post's excerpt. This is usually a
995
- shortened version of the post content, suitable for displaying in
996
- collection views.
997
-
998
- Consumers who encounter a missing excerpt MAY present a shortened version of the
999
- `content` field instead.
1000
-
1001
- ### `content_raw`, `excerpt_raw`
1002
- The `content_raw` and `excerpt_raw` fields are strings with the post's content
1003
- and excerpt respectively. Unlike the `content` and `excerpt` fields, the value
1004
- has not been passed through internal filtering, and is suitable for editing.
1005
-
1006
- (Context Availability: `edit`)
1007
-
1008
- ### `parent`
1009
- The `parent` field is an integer or JSON object with the post's parent
1010
- post ID. A literal zero indicates that the post does not have a parent
1011
- post.
1012
-
1013
- post-parent = "0" / 1*DIGIT
1014
-
1015
- Consumers who encounter a missing parent ID MUST treat it the same as a parent
1016
- post ID of 0.
1017
-
1018
- Parent fields will be expanded into a full Post entity in the `view` or `edit`
1019
- contexts, but only one level deep. The embedded Post entity will be rendered
1020
- using the `parent` context.
1021
-
1022
- In the `parent` context, the field will contain an integer with the post's
1023
- parent post ID as above.
1024
-
1025
- ### `link`
1026
- The `link` field is a string with the full URL to the post's canonical view.
1027
- This is typically the human-readable location of the entity.
1028
-
1029
- ### `guid`
1030
- The `guid` field is a string with the post's globally unique identifier (GUID).
1031
-
1032
- The GUID is typically in URL form, as this is a relatively easy way of ensuring
1033
- that the GUID is globally unique. However, consumers MUST NOT treat the GUID as
1034
- a URL, and MUST treat the GUID as a string of arbitrary characters.
1035
-
1036
- ### `menu_order`
1037
- The `menu_order` field is an integer with the post's sorting position. This is
1038
- typically used to affect sorting when displaying the post in menus or lists.
1039
- Larger integers should be treated as sorting before smaller integers.
1040
-
1041
- menu-order = 1*DIGIT / "-" 1*DIGIT
1042
-
1043
- Consumers who encounter a missing sorting position MUST treat it the same as a
1044
- sorting position of 0.
1045
-
1046
- ### `comment_status`
1047
- The `comment_status` field is a string with the post's current commenting
1048
- status. This field indicates whether users can submit comments to the post.
1049
-
1050
- post-comment-status = "open" / "closed" / token
1051
-
1052
- Providers MAY use statuses other than "open" or "closed" to indicate other
1053
- statuses. Consumers who encounter an unknown or missing comment status SHOULD
1054
- treat it as "closed".
1055
-
1056
- ### `ping_status`
1057
- The `ping_status` field is a string with the post's current pingback/trackback
1058
- status. This field indicates whether users can submit pingbacks or trackbacks
1059
- to the post.
1060
-
1061
- ping-status = "open" / "closed" / token
1062
-
1063
- Providers MAY use statuses other than "open" or "closed" to indicate other
1064
- statuses. Consumers who encounter an unknown or missing ping status SHOULD treat
1065
- it as "closed".
1066
-
1067
- ### `sticky`
1068
- The `sticky` field is a boolean indicating whether the post is marked as a
1069
- sticky post. Consumers typically display sticky posts before other posts in
1070
- collection views.
1071
-
1072
- ### `post_thumbnail`
1073
- The `post_thumbnail` field is a Media entity.
1074
-
1075
- ### `post_format`
1076
- The `post_format` field is a string with the post format. The post format
1077
- indicates how some meta fields should be displayed. For example, posts with the
1078
- "link" format may wish to display an extra link to a URL specified in a meta
1079
- field or emphasise a link in the post content.
1080
-
1081
- post-format = "standard" / "aside" / "gallery" / "image" / "link" / "status" / "quote" / "video" / "audio" / "chat"
1082
-
1083
- Providers MUST NOT use post formats not specified by this specification, unless
1084
- specified in a subsequent version of the specification. Consumers MUST treat
1085
- unknown post formats as "standard".
1086
-
1087
- ### `terms`
1088
- The `terms` field is a Term collection.
1089
-
1090
- ### `post_meta`
1091
- The `meta` field is a Metadata entity with metadata relating to the post.
1092
-
1093
- ### `meta`
1094
- The `meta` field is a Entity Meta entity with metadata relating to the entity
1095
- representation.
1096
-
1097
- ### Example
1098
-
1099
- {
1100
- "ID": 1,
1101
- "title": "Hello world!q",
1102
- "status": "publish",
1103
- "type": "post",
1104
- "author": {
1105
- "ID": 1,
1106
- "name": "admin",
1107
- "slug": "admin",
1108
- "URL": "",
1109
- "avatar": "http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G",
1110
- "meta": {
1111
- "links": {
1112
- "self": "http:\/\/example.com\/wp-json\/users\/1",
1113
- "archives": "http:\/\/example.com\/wp-json\/users\/1\/posts"
1114
- }
1115
- },
1116
- "first_name": "",
1117
- "last_name": ""
1118
- },
1119
- "content": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!<\/p>\n",
1120
- "parent": 0,
1121
- "link": "http:\/\/example.com\/2013\/06\/02\/hello-world\/",
1122
- "date": "2013-06-02T05:28:00+10:00",
1123
- "modified": "2013-06-30T13:56:57+10:00",
1124
- "format": "standard",
1125
- "slug": "hello-world",
1126
- "guid": "http:\/\/example.com\/?p=1",
1127
- "excerpt": "",
1128
- "menu_order": 0,
1129
- "comment_status": "open",
1130
- "ping_status": "open",
1131
- "sticky": false,
1132
- "date_tz": "Australia\/Brisbane",
1133
- "date_gmt": "2013-06-02T05:28:00+00:00",
1134
- "modified_tz": "Australia\/Brisbane",
1135
- "modified_gmt": "2013-06-30T03:56:57+00:00",
1136
- "password": "",
1137
- "post_meta": [
1138
- ],
1139
- "meta": {
1140
- "links": {
1141
- "self": "http:\/\/example.com\/wp-json\/posts\/1",
1142
- "author": "http:\/\/example.com\/wp-json\/users\/1",
1143
- "collection": "http:\/\/example.com\/wp-json\/posts",
1144
- "replies": "http:\/\/example.com\/wp-json\/posts\/1\/comments",
1145
- "version-history": "http:\/\/example.com\/wp-json\/posts\/1\/revisions"
1146
- }
1147
- },
1148
- "featured_image": null,
1149
- "terms": {
1150
- "category": {
1151
- "ID": 1,
1152
- "name": "Uncategorized",
1153
- "slug": "uncategorized",
1154
- "parent": null,
1155
- "count": 7,
1156
- "meta": {
1157
- "links": {
1158
- "collection": "http:\/\/example.com\/wp-json\/taxonomies\/category\/terms",
1159
- "self": "http:\/\/example.com\/wp-json\/taxonomies\/category\/terms\/1"
1160
- }
1161
- }
1162
- }
1163
- }
1164
- }
1165
-
1166
-
1167
-
1168
- Entity Meta
1169
- -----------
1170
- The Entity Meta entity is a JSON object with custom metadata relating to the
1171
- representation of the parent entity.
1172
-
1173
- The following properties are defined for the Entity Meta entity object:
1174
-
1175
- ### `links`
1176
- The `links` field is a JSON object with hyperlinks to related entities. Each
1177
- item's key is a link relation as per the [IANA Link Relations registry][] with
1178
- the value of the item being the corresponding link URL.
1179
-
1180
- Typical link relations are:
1181
-
1182
- * `self`: A URL pointing to the current entity's location.
1183
- * `up`: A URL pointing to the parent entity's location.
1184
- * `collection`: A URL pointing to a collection that the entity is a member of.
1185
-
1186
- [IANA Link Relations registry]: http://www.iana.org/assignments/link-relations/link-relations.xml
1187
-
1188
-
1189
- User
1190
- ----
1191
- The User entity is a JSON object with user properties. The following properties
1192
- are defined for the User entity object:
1193
-
1194
- ### `ID`
1195
- The `ID` field is an integer with the user's ID.
1196
-
1197
- ### `name`
1198
- The `name` field is a string with the user's display name.
1199
-
1200
- ### `slug`
1201
- The `slug` field is a string with the user's slug.
1202
-
1203
- ### `URL`
1204
- The `URL` field is a string with the URL to the author's site. This is typically
1205
- an external link of the author's choice.
1206
-
1207
- ### `avatar`
1208
- The `avatar` field is a string with the URL to the author's avatar image.
1209
-
1210
- Providers SHOULD ensure that for users without an avatar image, this field is
1211
- either zero-length or the URL returns a HTTP 404 error code on access. Consumers
1212
- MAY display a default avatar instead of a zero-length or URL which returns
1213
- a HTTP 404 error code.
1214
-
1215
- ### `meta`
1216
- The `meta` field is a Entity Meta entity with metadata relating to the entity
1217
- representation.
1218
-
1219
-
1220
- Metadata
1221
- --------
1222
- The Metadata entity is a JSON array with metadata fields. Each metadata field is
1223
- a JSON object with `id`, `key` and `value` fields.
1224
-
1225
- ### `id`
1226
- The `id` field of the metadata field is a positive integer with the internal
1227
- metadata ID.
1228
-
1229
- ### `key`
1230
- The `key` field of the metadata field is a string with the metadata field name.
1231
-
1232
- ### `value`
1233
- The `value` field of the metadata field is a string with the metadata
1234
- field value.
1235
-
1236
-
1237
- Comment
1238
- -------
1239
- The Comment entity is a JSON object with comment properties. The following
1240
- properties are defined for the Comment entity object:
1241
-
1242
- ### `ID`
1243
- The `ID` field is an integer with the comment's ID.
1244
-
1245
- ### `content`
1246
- The `content` field is a string with the comment's content.
1247
-
1248
- ### `status`
1249
- The `status` field is a string with the comment's status. This field indicates
1250
- whether the comment is in the publishing process, or if it has been deleted or
1251
- marked as spam.
1252
-
1253
- comment-status = "hold" / "approved" / "spam" / "trash" / token
1254
-
1255
- Providers MAY use other values to indicate other statuses. Consumers who
1256
- encounter an unknown or missing status SHOULD treat it as "hold".
1257
-
1258
- ### `type`
1259
- The `type` field is a string with the comment's type. This is usually one of the
1260
- following, but providers may provide additional values.
1261
-
1262
- comment-type = "comment" / "trackback" / "pingback" / token
1263
-
1264
- Providers MAY use other values to indicate other types. Consumers who encounter
1265
- an unknown or missing status SHOULD treat it as "comment".
1266
-
1267
- ### `post`
1268
- The `post` field is an integer with the parent post for the comment, or a Post
1269
- entity describing the parent post. A literal zero indicates that the comment
1270
- does not have a parent post.
1271
-
1272
- comment-post-parent = "0" / 1*DIGIT
1273
-
1274
- Consumers who encounter a missing post ID MUST treat it the same as a parent
1275
- post ID of 0.
1276
-
1277
- ### `parent`
1278
- The `post` field is an integer with the parent comment, or a Comment entity
1279
- describing the parent comment. A literal zero indicates that the comment does
1280
- not have a parent comment.
1281
-
1282
- comment-parent = "0" / 1*DIGIT
1283
-
1284
- Consumers who encounter a missing parent ID MUST treat it the same as a parent
1285
- comment ID of 0.
1286
-
1287
- ### `author`
1288
- The `author` field is a User entity with the comment author's data, or a
1289
- User-like object for anonymous authors. The User-like object contains the
1290
- following properties:
1291
-
1292
- #### `ID`
1293
- The `ID` property on the User-like object is always set to `0` for anonymous
1294
- authors.
1295
-
1296
- #### `name`
1297
- The `name` property on the User-like object is a string with the author's name.
1298
-
1299
- #### `URL`
1300
- The `URL` property on the User-like object is a string with the author's URL.
1301
-
1302
- #### `avatar`
1303
- The `avatar` property on the User-like object is a string with the URL to the
1304
- author's avatar image.
1305
-
1306
- This property should be treated the same as the avatar property on the
1307
- User entity.
1308
-
1309
-
1310
- ### `date`, `date_gmt`
1311
- The `date` and `date_gmt` fields are strings with the post's creation date and
1312
- time in the local time and UTC respectively. These fields follow the [RFC3339][]
1313
- Section 5.6 datetime representation.
1314
-
1315
- date = date-time
1316
- date_gmt = date-time
1317
-
1318
- This field should be treated the same as the `date` and `date_gmt` properties on
1319
- a Post entity.
1320
-
1321
- [RFC3339]: http://tools.ietf.org/html/rfc3339
1322
-
1323
- ### `date_tz`, `modified_tz`
1324
- The `date_tz` and `modified_tz` fields are strings with the timezone applying to
1325
- the `date` and `modified` fields respectively. The timezone is a [Olsen zoneinfo
1326
- database][] identifier. While the `date` field includes timezone offset
1327
- information, the `date_tz` field allows proper data operations across Daylight
1328
- Savings Time boundaries.
1329
-
1330
- This field should be treated the same as the `date_tz` property on a
1331
- Post entity.
1332
-
1333
-
1334
- Media
1335
- -----
1336
- The Media entity is a JSON object based on the Post entity. It contains all
1337
- properties of the Post entity, with the following additional properties defined:
1338
-
1339
- ### `source`
1340
- The `source` field is a string with the URL of the entity's original file. For
1341
- image media, this is the source file that intermediate representations are
1342
- generated from. For non-image media, this is the attached media file itself.
1343
-
1344
- ### `is_image`
1345
- The `is_image` field is a boolean which indicates whether the entity's
1346
- associated file should be handled as an image.
1347
-
1348
- ### `attachment_meta`
1349
- The `attachment_meta` field is a Media Meta entity. If the file is not an image
1350
- (as indicated by the `is_image` field), this is an empty JSON object.
1351
-
1352
-
1353
- Media Meta
1354
- ----------
1355
- The Media Meta entity is a JSON object with properties relating to the
1356
- associated Media entity. The following properties are defined for the entity:
1357
-
1358
- ### `width`
1359
- The `width` field is an integer with the original file's width in pixels.
1360
-
1361
- ### `height`
1362
- The `height` field is an integer with the original file's height in pixels.
1363
-
1364
- ### `file`
1365
- The `file` field is a string with the path to the original file, relative to the
1366
- site's upload directory.
1367
-
1368
- ### `sizes`
1369
- The `sizes` field is a JSON object mapping intermediate image sizes to image
1370
- data objects. The key of each item is the size of the intermediate image as an
1371
- internal string representation. The value of each item has the following
1372
- properties defined.
1373
-
1374
- * `file`: The filename of the intermediate file, relative to the directory of
1375
- the original file.
1376
- * `width`: The width of the intermediate file in pixels.
1377
- * `height`: The height of the intermediate file in pixels.
1378
- * `mime-type`: The MIME type of the intermediate file.
1379
- * `url`: The full URL to the intermediate file.
1380
-
1381
- ### `image_meta`
1382
- The `image_meta` field is a JSON object mapping image meta properties to their
1383
- values. This data is taken from the EXIF data on the original image. The
1384
- following properties are defined.
1385
-
1386
- * `aperture`: The aperture used to create the original image as a decimal number
1387
- (with two decimal places).
1388
- * `credit`: Credit for the original image.
1389
- * `camera`: The camera used to create the original image.
1390
- * `created_timestamp`: When the file was created, as a Unix timestamp.
1391
- * `copyright`: Copyright for the original image.
1392
- * `focal_length`: The focal length used to create the original image as a
1393
- decimal string.
1394
- * `iso`: The ISO used to create the original image.
1395
- * `shutter_speed`: The shutter speed used to create the original image, as a
1396
- decimal string.
1397
- * `title`: The original title of the image.
1398
-
1399
-
1400
- Documents
1401
- =========
1402
-
1403
- Index
1404
- -----
1405
- The Index document is the root endpoint for the API server and describes the
1406
- contents and abilities of the API server.
1407
-
1408
- ### Body
1409
- The body of an Index document is an Index entity.
1410
-
1411
- ### Example
1412
-
1413
- {
1414
- "name":"My WordPress Site",
1415
- "description":"Just another WordPress site",
1416
- "URL":"http:\/\/example.com",
1417
- "routes": {
1418
- "\/": {
1419
- "supports": [ "HEAD", "GET" ]
1420
- },
1421
- "\/posts": {
1422
- "supports": [ "HEAD", "GET", "POST" ],
1423
- "accepts_json": true
1424
- },
1425
- "\/posts\/<id>": {
1426
- "supports": [ "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" ]
1427
- },
1428
- "\/posts\/<id>\/revisions": {
1429
- "supports": [ "HEAD", "GET" ]
1430
- },
1431
- "\/posts\/<id>\/comments": {
1432
- "supports": [ "HEAD", "GET", "POST" ],
1433
- "accepts_json":true
1434
- }
1435
- },
1436
- "meta": {
1437
- "links": {
1438
- "help":"http:\/\/codex.wordpress.org\/JSON_API"
1439
- }
1440
- }
1441
- }
1442
-
1443
-
1444
- Post
1445
- ----
1446
- A Post document is defined as the representation of a post item, analogous to an
1447
- Atom item.
1448
-
1449
- ### Headers
1450
- The following headers are sent when a Post is the main entity:
1451
-
1452
- * `Link`:
1453
- * `rel="alternate"; type=text/html`: The permalink for the Post
1454
- * `rel="collection"`: The endpoint of the Post Collection the Post is
1455
- contained in
1456
- * `rel="replies"`: The endpoint of the associated Comment Collection
1457
- * `rel="version-history"`: The endpoint of the Post Collection containing
1458
- the revisions of the Post
1459
-
1460
-
1461
- ### Body
1462
- The body of a Post document is a Post entity.
1463
-
1464
-
1465
- ### Example
1466
-
1467
- HTTP/1.1 200 OK
1468
- Date: Mon, 07 Jan 2013 03:35:14 GMT
1469
- Last-Modified: Mon, 07 Jan 2013 03:35:14 GMT
1470
- Link: <http://localhost/wptrunk/?p=1>; rel="alternate"; type=text/html
1471
- Link: <http://localhost/wptrunk/wp-json/users/1>; rel="author"
1472
- Link: <http://localhost/wptrunk/wp-json/posts>; rel="collection"
1473
- Link: <http://localhost/wptrunk/wp-json/posts/158/comments>; rel="replies"
1474
- Link: <http://localhost/wptrunk/wp-json/posts/158/revisions>; rel="version-history"
1475
- Content-Type: application/json; charset=UTF-8
1476
-
1477
- {
1478
- "ID":158,
1479
- "title":"This is a test!",
1480
- "status":"publish",
1481
- "type":"post",
1482
- "author":{
1483
- "ID":1,
1484
- "name":"admin",
1485
- "slug":"admin",
1486
- "URL":"",
1487
- "avatar":"http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G",
1488
- "meta":{
1489
- "links":{
1490
- "self":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1",
1491
- "archives":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1\/posts"
1492
- }
1493
- }
1494
- },
1495
- "content":"Hello.\r\n\r\nHah.",
1496
- "parent":0,
1497
- "link":"http:\/\/localhost\/wptrunk\/158\/this-is-a-test\/",
1498
- "date":"2013-01-07T13:35:14+10:00",
1499
- "modified":"2013-01-07T13:49:40+10:00",
1500
- "format":"standard",
1501
- "slug":"this-is-a-test",
1502
- "guid":"http:\/\/localhost\/wptrunk\/?p=158",
1503
- "excerpt":"",
1504
- "menu_order":0,
1505
- "comment_status":"open",
1506
- "ping_status":"open",
1507
- "sticky":false,
1508
- "date_tz":"Australia\/Brisbane",
1509
- "date_gmt":"2013-01-07T03:35:14+00:00",
1510
- "modified_tz":"Australia\/Brisbane",
1511
- "modified_gmt":"2013-01-07T03:49:40+00:00",
1512
- "post_thumbnail":[],
1513
- "terms":{
1514
- "category":{
1515
- "ID":1,
1516
- "name":"Uncategorized",
1517
- "slug":"uncategorized",
1518
- "group":0,
1519
- "parent":0,
1520
- "count":4,
1521
- "meta":{
1522
- "links":{
1523
- "collection":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category",
1524
- "self":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category\/terms\/1"
1525
- }
1526
- }
1527
- }
1528
- },
1529
- "post_meta":[],
1530
- "meta":{
1531
- "links":{
1532
- "self":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158",
1533
- "author":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1",
1534
- "collection":"http:\/\/localhost\/wptrunk\/wp-json\/posts",
1535
- "replies":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/comments",
1536
- "version-history":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/revisions"
1537
- }
1538
- }
1539
- }
1540
-
1541
-
1542
- Post Collection
1543
- ---------------
1544
- A Post Collection document is defined as a collection of Post entities.
1545
-
1546
- ### Headers
1547
- The following headers are sent when a Post Collection is the main entity:
1548
-
1549
- * `Link`:
1550
- * `rel="item"` - Each item in the collection has a corresponding Link header
1551
- containing the location of the endpoint for that resource.
1552
-
1553
-
1554
- ### Body
1555
- The Post Collection document is a JSON array of Post entities.
1556
-
1557
-
1558
- User
1559
- ----
1560
- The User document describes a member of the site.
1561
-
1562
- ### Body
1563
- The body of a User document is a User entity.
1564
-
1565
-
1566
- Appendix A: JSON Schema
1567
- =======================
1568
- The JSON Schema describing the entities in this document is available in
1569
- schema.json.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
extras.php CHANGED
@@ -1,12 +1,11 @@
1
  <?php
2
-
3
  /**
4
- * Extra File where a lot of the extra functions from plugin.php go.
 
 
5
  *
6
  * @package WordPress
7
  * @subpackage JSON API
8
- *
9
- * @TODO fix this doc block (Make it better maybe?)
10
  */
11
 
12
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
1
  <?php
 
2
  /**
3
+ * Extra File
4
+ *
5
+ * Contains extra functions from plugin.php go.
6
  *
7
  * @package WordPress
8
  * @subpackage JSON API
 
 
9
  */
10
 
11
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -71,6 +71,9 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
71
  return $id;
72
  }
73
 
 
 
 
74
  wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
75
 
76
  if ( isset( $request['alt_text'] ) ) {
@@ -87,6 +90,15 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
87
  $response->set_status( 201 );
88
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
89
 
 
 
 
 
 
 
 
 
 
90
  return $response;
91
 
92
  }
@@ -115,6 +127,9 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
115
  'context' => 'edit',
116
  ));
117
 
 
 
 
118
  return rest_ensure_response( $response );
119
  }
120
 
@@ -190,6 +205,16 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
190
 
191
  $data->add_links( $this->prepare_links( $post ) );
192
 
 
 
 
 
 
 
 
 
 
 
193
  return apply_filters( 'rest_prepare_attachment', $data, $post, $request );
194
  }
195
 
@@ -236,7 +261,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
236
  $schema['properties']['media_details'] = array(
237
  'description' => 'Details about the attachment file, specific to its type.',
238
  'type' => 'object',
239
- 'context' => array( 'view', 'edit' ),
240
  'readonly' => true,
241
  );
242
  $schema['properties']['post'] = array(
@@ -352,8 +377,9 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
352
  }
353
 
354
  // Verify hash, if given
355
- if ( ! empty( $headers['CONTENT_MD5'] ) ) {
356
- $expected = trim( $headers['CONTENT_MD5'] );
 
357
  $actual = md5_file( $files['file']['tmp_name'] );
358
  if ( $expected !== $actual ) {
359
  return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
@@ -369,7 +395,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
369
  $overrides['action'] = 'wp_handle_mock_upload';
370
  }
371
 
372
- $file = wp_handle_upload( $files, $overrides );
373
 
374
  if ( isset( $file['error'] ) ) {
375
  return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
71
  return $id;
72
  }
73
 
74
+ /** Include admin functions to get access to wp_generate_attachment_metadata() */
75
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
76
+
77
  wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
78
 
79
  if ( isset( $request['alt_text'] ) ) {
90
  $response->set_status( 201 );
91
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
92
 
93
+ /**
94
+ * Fires after a single attachment is created or updated via the REST API.
95
+ *
96
+ * @param object $attachment Inserted attachment.
97
+ * @param WP_REST_Request $request The request sent to the API.
98
+ * @param bool $creating True when creating an attachment, false when updating.
99
+ */
100
+ do_action( 'rest_insert_attachment', $attachment, $request, true );
101
+
102
  return $response;
103
 
104
  }
127
  'context' => 'edit',
128
  ));
129
 
130
+ /* This action is documented in lib/endpoints/class-wp-rest-attachments-controller.php */
131
+ do_action( 'rest_insert_attachment', $data, $request, false );
132
+
133
  return rest_ensure_response( $response );
134
  }
135
 
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
 
261
  $schema['properties']['media_details'] = array(
262
  'description' => 'Details about the attachment file, specific to its type.',
263
  'type' => 'object',
264
+ 'context' => array( 'view', 'edit', 'embed' ),
265
  'readonly' => true,
266
  );
267
  $schema['properties']['post'] = array(
377
  }
378
 
379
  // Verify hash, if given
380
+ if ( ! empty( $headers['content_md5'] ) ) {
381
+ $content_md5 = array_shift( $headers['content_md5'] );
382
+ $expected = trim( $content_md5 );
383
  $actual = md5_file( $files['file']['tmp_name'] );
384
  if ( $expected !== $actual ) {
385
  return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
395
  $overrides['action'] = 'wp_handle_mock_upload';
396
  }
397
 
398
+ $file = wp_handle_upload( $files['file'], $overrides );
399
 
400
  if ( isset( $file['error'] ) ) {
401
  return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -22,7 +22,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
22
  'methods' => WP_REST_Server::CREATABLE,
23
  'callback' => array( $this, 'create_item' ),
24
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
25
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
26
  ),
27
 
28
  'schema' => array( $this, 'get_public_item_schema' ),
@@ -43,7 +43,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
43
  'methods' => WP_REST_Server::EDITABLE,
44
  'callback' => array( $this, 'update_item' ),
45
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
46
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
47
  ),
48
  array(
49
  'methods' => WP_REST_Server::DELETABLE,
@@ -158,6 +158,21 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
158
  if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
159
  $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
160
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
162
  $prepared_comment['comment_author_email'] = '';
163
  }
@@ -168,6 +183,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
168
  $prepared_comment['comment_agent'] = '';
169
  $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
170
 
 
 
 
 
 
 
 
 
171
  $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
172
 
173
  $comment_id = wp_insert_comment( $prepared_comment );
@@ -194,6 +217,15 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
194
  $response->set_status( 201 );
195
  $response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment_id ) );
196
 
 
 
 
 
 
 
 
 
 
197
  return $response;
198
  }
199
 
@@ -243,6 +275,9 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
243
  'context' => 'edit',
244
  ) );
245
 
 
 
 
246
  return rest_ensure_response( $response );
247
  }
248
 
@@ -250,7 +285,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
250
  * Delete a comment.
251
  *
252
  * @param WP_REST_Request $request Full details about the request.
253
- * @return WP_Error|array
254
  */
255
  public function delete_item( $request ) {
256
  $id = (int) $request['id'];
@@ -262,12 +297,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
262
  }
263
 
264
  /**
265
- * Filter whether the comment type supports trashing.
 
 
266
  *
267
- * @param boolean $supports_trash Does the comment type support trashing?
268
- * @param stdClass $comment Comment we're attempting to trash.
269
  */
270
- $supports_trash = apply_filters( 'rest_comment_type_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
271
 
272
  $get_request = new WP_REST_Request( 'GET', rest_url( '/wp/v2/comments/' . $id ) );
273
  $get_request->set_param( 'context', 'edit' );
@@ -275,6 +312,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
275
 
276
  if ( $force ) {
277
  $result = wp_delete_comment( $comment->comment_ID, true );
 
278
  } else {
279
  // If we don't support trashing for this type, error out
280
  if ( ! $supports_trash ) {
@@ -282,12 +320,29 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
282
  }
283
 
284
  $result = wp_trash_comment( $comment->comment_ID );
 
285
  }
286
 
 
 
 
 
 
 
 
287
  if ( ! $result ) {
288
  return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
289
  }
290
 
 
 
 
 
 
 
 
 
 
291
  return $response;
292
  }
293
 
@@ -422,7 +477,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
422
  *
423
  * @param object $comment Comment object.
424
  * @param WP_REST_Request $request Request object.
425
- * @return array $fields
426
  */
427
  public function prepare_item_for_response( $comment, $request ) {
428
  $data = array(
@@ -778,7 +833,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
778
  'description' => 'The date the object was published as GMT.',
779
  'type' => 'string',
780
  'format' => 'date-time',
781
- 'context' => array( 'edit' ),
782
  ),
783
  'karma' => array(
784
  'description' => 'Karma for the object.',
22
  'methods' => WP_REST_Server::CREATABLE,
23
  'callback' => array( $this, 'create_item' ),
24
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
25
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
26
  ),
27
 
28
  'schema' => array( $this, 'get_public_item_schema' ),
43
  'methods' => WP_REST_Server::EDITABLE,
44
  'callback' => array( $this, 'update_item' ),
45
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
46
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
47
  ),
48
  array(
49
  'methods' => WP_REST_Server::DELETABLE,
158
  if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
159
  $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
160
  }
161
+
162
+ // Set author data if the user's logged in
163
+ $missing_author = empty( $prepared_comment['user_id'] )
164
+ && empty( $prepared_comment['comment_author'] )
165
+ && empty( $prepared_comment['comment_author_email'] )
166
+ && empty( $prepared_comment['comment_author_url'] );
167
+
168
+ if ( is_user_logged_in() && $missing_author ) {
169
+ $user = wp_get_current_user();
170
+ $prepared_comment['user_id'] = $user->ID;
171
+ $prepared_comment['comment_author'] = $user->display_name;
172
+ $prepared_comment['comment_author_email'] = $user->user_email;
173
+ $prepared_comment['comment_author_url'] = $user->user_url;
174
+ }
175
+
176
  if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
177
  $prepared_comment['comment_author_email'] = '';
178
  }
183
  $prepared_comment['comment_agent'] = '';
184
  $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
185
 
186
+ /**
187
+ * Filter a comment before it is inserted via the REST API.
188
+ *
189
+ * Allows modification of the comment right before it is inserted via `wp_insert_comment`.
190
+ *
191
+ * @param array $prepared_comment The prepared comment data for `wp_insert_comment`.
192
+ * @param WP_REST_Request $request Request used to insert the comment.
193
+ */
194
  $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
195
 
196
  $comment_id = wp_insert_comment( $prepared_comment );
217
  $response->set_status( 201 );
218
  $response->header( 'Location', rest_url( '/wp/v2/comments/' . $comment_id ) );
219
 
220
+ /**
221
+ * Fires after a comment is created or updated via the REST API.
222
+ *
223
+ * @param array $prepared_comment Inserted comment data.
224
+ * @param WP_REST_Request $request The request sent to the API.
225
+ * @param bool $creating True when creating a comment, false when updating.
226
+ */
227
+ do_action( 'rest_insert_comment', $prepared_comment, $request, true );
228
+
229
  return $response;
230
  }
231
 
275
  'context' => 'edit',
276
  ) );
277
 
278
+ /* This action is documented in lib/endpoints/class-wp-rest-comments-controller.php */
279
+ do_action( 'rest_insert_comment', $prepared_args, $request, false );
280
+
281
  return rest_ensure_response( $response );
282
  }
283
 
285
  * Delete a comment.
286
  *
287
  * @param WP_REST_Request $request Full details about the request.
288
+ * @return WP_Error|WP_REST_Response
289
  */
290
  public function delete_item( $request ) {
291
  $id = (int) $request['id'];
297
  }
298
 
299
  /**
300
+ * Filter whether a comment is trashable.
301
+ *
302
+ * Return false to disable trash support for the post.
303
  *
304
+ * @param boolean $supports_trash Whether the post type support trashing.
305
+ * @param WP_Post $comment The comment object being considered for trashing support.
306
  */
307
+ $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
308
 
309
  $get_request = new WP_REST_Request( 'GET', rest_url( '/wp/v2/comments/' . $id ) );
310
  $get_request->set_param( 'context', 'edit' );
312
 
313
  if ( $force ) {
314
  $result = wp_delete_comment( $comment->comment_ID, true );
315
+ $status = 'deleted';
316
  } else {
317
  // If we don't support trashing for this type, error out
318
  if ( ! $supports_trash ) {
320
  }
321
 
322
  $result = wp_trash_comment( $comment->comment_ID );
323
+ $status = 'trashed';
324
  }
325
 
326
+ $data = $response->get_data();
327
+ $data = array(
328
+ 'data' => $data,
329
+ $status => true,
330
+ );
331
+ $response->set_data( $data );
332
+
333
  if ( ! $result ) {
334
  return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
335
  }
336
 
337
+ /**
338
+ * Fires after a comment is deleted via the REST API.
339
+ *
340
+ * @param object $comment The deleted comment data.
341
+ * @param array $data Delete status data.
342
+ * @param WP_REST_Request $request The request sent to the API.
343
+ */
344
+ do_action( 'rest_delete_comment', $comment, $data, $request );
345
+
346
  return $response;
347
  }
348
 
477
  *
478
  * @param object $comment Comment object.
479
  * @param WP_REST_Request $request Request object.
480
+ * @return WP_REST_Response
481
  */
482
  public function prepare_item_for_response( $comment, $request ) {
483
  $data = array(
833
  'description' => 'The date the object was published as GMT.',
834
  'type' => 'string',
835
  'format' => 'date-time',
836
+ 'context' => array( 'view', 'edit' ),
837
  ),
838
  'karma' => array(
839
  'description' => 'Karma for the object.',
lib/endpoints/class-wp-rest-controller.php CHANGED
@@ -11,124 +11,124 @@ abstract class WP_REST_Controller {
11
  }
12
 
13
  /**
14
- * Get a collection of items
15
  *
16
  * @param WP_REST_Request $request Full data about the request.
17
  * @return WP_Error|WP_REST_Response
18
  */
19
  public function get_items( $request ) {
20
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
21
  }
22
 
23
  /**
24
- * Get one item from the collection
25
  *
26
  * @param WP_REST_Request $request Full data about the request.
27
  * @return WP_Error|WP_REST_Response
28
  */
29
  public function get_item( $request ) {
30
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
31
  }
32
 
33
  /**
34
- * Create one item from the collection
35
  *
36
  * @param WP_REST_Request $request Full data about the request.
37
- * @return WP_Error|WP_REST_Request
38
  */
39
  public function create_item( $request ) {
40
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
41
  }
42
 
43
  /**
44
- * Update one item from the collection
45
  *
46
  * @param WP_REST_Request $request Full data about the request.
47
- * @return WP_Error|WP_REST_Request
48
  */
49
  public function update_item( $request ) {
50
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
51
  }
52
 
53
  /**
54
- * Delete one item from the collection
55
  *
56
  * @param WP_REST_Request $request Full data about the request.
57
- * @return WP_Error|WP_REST_Request
58
  */
59
  public function delete_item( $request ) {
60
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
61
  }
62
 
63
  /**
64
- * Check if a given request has access to get items
65
  *
66
  * @param WP_REST_Request $request Full data about the request.
67
  * @return WP_Error|bool
68
  */
69
  public function get_items_permissions_check( $request ) {
70
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
71
  }
72
 
73
  /**
74
- * Check if a given request has access to get a specific item
75
  *
76
  * @param WP_REST_Request $request Full data about the request.
77
  * @return WP_Error|bool
78
  */
79
  public function get_item_permissions_check( $request ) {
80
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
81
  }
82
 
83
  /**
84
- * Check if a given request has access to create items
85
  *
86
  * @param WP_REST_Request $request Full data about the request.
87
  * @return WP_Error|bool
88
  */
89
  public function create_item_permissions_check( $request ) {
90
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
91
  }
92
 
93
  /**
94
- * Check if a given request has access to update a specific item
95
  *
96
  * @param WP_REST_Request $request Full data about the request.
97
  * @return WP_Error|bool
98
  */
99
  public function update_item_permissions_check( $request ) {
100
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
101
  }
102
 
103
  /**
104
- * Check if a given request has access to delete a specific item
105
  *
106
  * @param WP_REST_Request $request Full data about the request.
107
  * @return WP_Error|bool
108
  */
109
  public function delete_item_permissions_check( $request ) {
110
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
111
  }
112
 
113
  /**
114
- * Prepare the item for create or update operation
115
  *
116
- * @param WP_REST_Request $request Request object
117
  * @return WP_Error|object $prepared_item
118
  */
119
  protected function prepare_item_for_database( $request ) {
120
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
121
  }
122
 
123
  /**
124
- * Prepare the item for the REST response
125
  *
126
  * @param mixed $item WordPress representation of the item.
127
  * @param WP_REST_Request $request Request object.
128
  * @return mixed
129
  */
130
  public function prepare_item_for_response( $item, $request ) {
131
- return new WP_Error( 'invalid-method', __( 'Method not implemented. Must be over-ridden in subclass.' ), array( 'status' => 405 ) );
132
  }
133
 
134
  /**
@@ -152,7 +152,7 @@ abstract class WP_REST_Controller {
152
  }
153
 
154
  /**
155
- * Filter a response based on the context defined in the schema
156
  *
157
  * @param array $data
158
  * @param string $context
@@ -186,7 +186,7 @@ abstract class WP_REST_Controller {
186
  }
187
 
188
  /**
189
- * Get the item's schema, conforming to JSON Schema
190
  *
191
  * @return array
192
  */
@@ -213,7 +213,7 @@ abstract class WP_REST_Controller {
213
  }
214
 
215
  /**
216
- * Get the query params for collections
217
  *
218
  * @return array
219
  */
@@ -240,11 +240,11 @@ abstract class WP_REST_Controller {
240
  }
241
 
242
  /**
243
- * Add the values from additional fields to a data object
244
  *
245
  * @param array $object
246
  * @param WP_REST_Request $request
247
- * @return array modified object with additional fields
248
  */
249
  protected function add_additional_fields_to_object( $object, $request ) {
250
 
@@ -256,7 +256,7 @@ abstract class WP_REST_Controller {
256
  continue;
257
  }
258
 
259
- $object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request );
260
  }
261
 
262
  return $object;
@@ -278,21 +278,21 @@ abstract class WP_REST_Controller {
278
  continue;
279
  }
280
 
281
- // Don't run the update callbacks if the data wasn't passed in the request
282
  if ( ! isset( $request[ $field_name ] ) ) {
283
  continue;
284
  }
285
 
286
- $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request );
287
  }
288
  }
289
 
290
  /**
291
- * Add the schema from additional fields to an schema array
292
  *
293
  * The type of object is inferred from the passed schema.
294
  *
295
- * @param array $schema Schema array
296
  */
297
  protected function add_additional_fields_schema( $schema ) {
298
  if ( ! $schema || ! isset( $schema['title'] ) ) {
@@ -300,7 +300,7 @@ abstract class WP_REST_Controller {
300
  }
301
 
302
  /**
303
- * Can't use $this->get_object_type otherwise we cause an inf loop
304
  */
305
  $object_type = $schema['title'];
306
 
@@ -318,7 +318,7 @@ abstract class WP_REST_Controller {
318
  }
319
 
320
  /**
321
- * Get all the registered additional fields for a given object-type
322
  *
323
  * @param string $object_type
324
  * @return array
@@ -360,12 +360,14 @@ abstract class WP_REST_Controller {
360
  /**
361
  * Get an array of endpoint arguments from the item schema for the controller.
362
  *
363
- * @param $add_required_flag Whether to use the 'required' flag from the schema proprties.
364
- * This is because update requests will not have any required params
365
- * Where as create requests will.
366
- * @return array
 
 
367
  */
368
- public function get_endpoint_args_for_item_schema( $add_required_flag = true ) {
369
 
370
  $schema = $this->get_item_schema();
371
  $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
@@ -373,7 +375,7 @@ abstract class WP_REST_Controller {
373
 
374
  foreach ( $schema_properties as $field_id => $params ) {
375
 
376
- // Anything marked as readonly should not be a arg
377
  if ( ! empty( $params['readonly'] ) ) {
378
  continue;
379
  }
@@ -383,16 +385,22 @@ abstract class WP_REST_Controller {
383
  'sanitize_callback' => array( $this, 'sanitize_schema_property' ),
384
  );
385
 
386
- if ( isset( $params['default'] ) ) {
387
  $endpoint_args[ $field_id ]['default'] = $params['default'];
388
  }
389
 
390
- if ( $add_required_flag && ! empty( $params['required'] ) ) {
391
  $endpoint_args[ $field_id ]['required'] = true;
392
  }
393
 
394
- // Merge in any options provided by the schema property
395
  if ( isset( $params['arg_options'] ) ) {
 
 
 
 
 
 
396
  $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
397
  }
398
  }
@@ -412,7 +420,7 @@ abstract class WP_REST_Controller {
412
 
413
  /**
414
  * We don't currently validate against empty values, as lots of checks
415
- * can unintentially fail, as the callback will often handle an empty
416
  * value it's self.
417
  */
418
  if ( ! $value ) {
@@ -437,7 +445,7 @@ abstract class WP_REST_Controller {
437
  return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'integer' ) );
438
  }
439
 
440
- if ( 'string' === $property['type']&& ! is_string( $value ) ) {
441
  return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'string' ) );
442
  }
443
 
@@ -489,7 +497,7 @@ abstract class WP_REST_Controller {
489
 
490
  case 'email' :
491
  // as sanitize_email is very lossy, we just want to
492
- // make sure the string is safe
493
  if ( sanitize_email( $value ) ) {
494
  return sanitize_email( $value );
495
  }
11
  }
12
 
13
  /**
14
+ * Get a collection of items.
15
  *
16
  * @param WP_REST_Request $request Full data about the request.
17
  * @return WP_Error|WP_REST_Response
18
  */
19
  public function get_items( $request ) {
20
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
21
  }
22
 
23
  /**
24
+ * Get one item from the collection.
25
  *
26
  * @param WP_REST_Request $request Full data about the request.
27
  * @return WP_Error|WP_REST_Response
28
  */
29
  public function get_item( $request ) {
30
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
31
  }
32
 
33
  /**
34
+ * Create one item from the collection.
35
  *
36
  * @param WP_REST_Request $request Full data about the request.
37
+ * @return WP_Error|WP_REST_Response
38
  */
39
  public function create_item( $request ) {
40
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
41
  }
42
 
43
  /**
44
+ * Update one item from the collection.
45
  *
46
  * @param WP_REST_Request $request Full data about the request.
47
+ * @return WP_Error|WP_REST_Response
48
  */
49
  public function update_item( $request ) {
50
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
51
  }
52
 
53
  /**
54
+ * Delete one item from the collection.
55
  *
56
  * @param WP_REST_Request $request Full data about the request.
57
+ * @return WP_Error|WP_REST_Response
58
  */
59
  public function delete_item( $request ) {
60
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
61
  }
62
 
63
  /**
64
+ * Check if a given request has access to get items.
65
  *
66
  * @param WP_REST_Request $request Full data about the request.
67
  * @return WP_Error|bool
68
  */
69
  public function get_items_permissions_check( $request ) {
70
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
71
  }
72
 
73
  /**
74
+ * Check if a given request has access to get a specific item.
75
  *
76
  * @param WP_REST_Request $request Full data about the request.
77
  * @return WP_Error|bool
78
  */
79
  public function get_item_permissions_check( $request ) {
80
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
81
  }
82
 
83
  /**
84
+ * Check if a given request has access to create items.
85
  *
86
  * @param WP_REST_Request $request Full data about the request.
87
  * @return WP_Error|bool
88
  */
89
  public function create_item_permissions_check( $request ) {
90
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
91
  }
92
 
93
  /**
94
+ * Check if a given request has access to update a specific item.
95
  *
96
  * @param WP_REST_Request $request Full data about the request.
97
  * @return WP_Error|bool
98
  */
99
  public function update_item_permissions_check( $request ) {
100
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
101
  }
102
 
103
  /**
104
+ * Check if a given request has access to delete a specific item.
105
  *
106
  * @param WP_REST_Request $request Full data about the request.
107
  * @return WP_Error|bool
108
  */
109
  public function delete_item_permissions_check( $request ) {
110
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
111
  }
112
 
113
  /**
114
+ * Prepare the item for create or update operation.
115
  *
116
+ * @param WP_REST_Request $request Request object.
117
  * @return WP_Error|object $prepared_item
118
  */
119
  protected function prepare_item_for_database( $request ) {
120
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
121
  }
122
 
123
  /**
124
+ * Prepare the item for the REST response.
125
  *
126
  * @param mixed $item WordPress representation of the item.
127
  * @param WP_REST_Request $request Request object.
128
  * @return mixed
129
  */
130
  public function prepare_item_for_response( $item, $request ) {
131
+ return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
132
  }
133
 
134
  /**
152
  }
153
 
154
  /**
155
+ * Filter a response based on the context defined in the schema.
156
  *
157
  * @param array $data
158
  * @param string $context
186
  }
187
 
188
  /**
189
+ * Get the item's schema, conforming to JSON Schema.
190
  *
191
  * @return array
192
  */
213
  }
214
 
215
  /**
216
+ * Get the query params for collections.
217
  *
218
  * @return array
219
  */
240
  }
241
 
242
  /**
243
+ * Add the values from additional fields to a data object.
244
  *
245
  * @param array $object
246
  * @param WP_REST_Request $request
247
+ * @return array modified object with additional fields.
248
  */
249
  protected function add_additional_fields_to_object( $object, $request ) {
250
 
256
  continue;
257
  }
258
 
259
+ $object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
260
  }
261
 
262
  return $object;
278
  continue;
279
  }
280
 
281
+ // Don't run the update callbacks if the data wasn't passed in the request.
282
  if ( ! isset( $request[ $field_name ] ) ) {
283
  continue;
284
  }
285
 
286
+ $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
287
  }
288
  }
289
 
290
  /**
291
+ * Add the schema from additional fields to an schema array.
292
  *
293
  * The type of object is inferred from the passed schema.
294
  *
295
+ * @param array $schema Schema array.
296
  */
297
  protected function add_additional_fields_schema( $schema ) {
298
  if ( ! $schema || ! isset( $schema['title'] ) ) {
300
  }
301
 
302
  /**
303
+ * Can't use $this->get_object_type otherwise we cause an inf loop.
304
  */
305
  $object_type = $schema['title'];
306
 
318
  }
319
 
320
  /**
321
+ * Get all the registered additional fields for a given object-type.
322
  *
323
  * @param string $object_type
324
  * @return array
360
  /**
361
  * Get an array of endpoint arguments from the item schema for the controller.
362
  *
363
+ * @param string $method HTTP method of the request. The arguments
364
+ * for `CREATABLE` requests are checked for required
365
+ * values and may fall-back to a given default, this
366
+ * is not done on `EDITABLE` requests. Default is
367
+ * WP_REST_Server::CREATABLE.
368
+ * @return array $endpoint_args
369
  */
370
+ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
371
 
372
  $schema = $this->get_item_schema();
373
  $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
375
 
376
  foreach ( $schema_properties as $field_id => $params ) {
377
 
378
+ // Arguments specified as `readonly` are not allowed to be set.
379
  if ( ! empty( $params['readonly'] ) ) {
380
  continue;
381
  }
385
  'sanitize_callback' => array( $this, 'sanitize_schema_property' ),
386
  );
387
 
388
+ if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
389
  $endpoint_args[ $field_id ]['default'] = $params['default'];
390
  }
391
 
392
+ if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
393
  $endpoint_args[ $field_id ]['required'] = true;
394
  }
395
 
396
+ // Merge in any options provided by the schema property.
397
  if ( isset( $params['arg_options'] ) ) {
398
+
399
+ // Only use required / default from arg_options on CREATABLE endpoints.
400
+ if ( WP_REST_Server::CREATABLE !== $method ) {
401
+ $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
402
+ }
403
+
404
  $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
405
  }
406
  }
420
 
421
  /**
422
  * We don't currently validate against empty values, as lots of checks
423
+ * can unintentionally fail, as the callback will often handle an empty
424
  * value it's self.
425
  */
426
  if ( ! $value ) {
445
  return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'integer' ) );
446
  }
447
 
448
+ if ( 'string' === $property['type'] && ! is_string( $value ) ) {
449
  return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not of type %s' ), $parameter, 'string' ) );
450
  }
451
 
497
 
498
  case 'email' :
499
  // as sanitize_email is very lossy, we just want to
500
+ // make sure the string is safe.
501
  if ( sanitize_email( $value ) ) {
502
  return sanitize_email( $value );
503
  }
lib/endpoints/class-wp-rest-meta-controller.php CHANGED
@@ -50,7 +50,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
50
  'methods' => WP_REST_Server::CREATABLE,
51
  'callback' => array( $this, 'create_item' ),
52
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
53
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
54
  ),
55
 
56
  'schema' => array( $this, 'get_public_item_schema' ),
@@ -76,7 +76,11 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
76
  'methods' => WP_REST_Server::DELETABLE,
77
  'callback' => array( $this, 'delete_item' ),
78
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
79
- 'args' => array(),
 
 
 
 
80
  ),
81
 
82
  'schema' => array( $this, 'get_public_item_schema' ),
@@ -236,6 +240,14 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
236
  $parent_column = $this->get_parent_column();
237
  $response->add_link( 'about', rest_url( 'wp/' . $this->parent_base . '/' . $data->$parent_column ), array( 'embeddable' => true ) );
238
 
 
 
 
 
 
 
 
 
239
  return apply_filters( 'rest_prepare_meta_value', $response, $request );
240
  }
241
 
@@ -316,6 +328,15 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
316
  ) );
317
  $response = $this->get_item( $request );
318
 
 
 
 
 
 
 
 
 
 
319
  return rest_ensure_response( $response );
320
  }
321
 
@@ -380,6 +401,9 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
380
  $data = $response->get_data();
381
  $response->header( 'Location', rest_url( $this->parent_base . '/' . $parent_id . '/meta/' . $data['id'] ) );
382
 
 
 
 
383
  return $response;
384
  }
385
 
@@ -424,6 +448,13 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
424
  return new WP_Error( 'rest_meta_could_not_delete', __( 'Could not delete meta.' ), array( 'status' => 500 ) );
425
  }
426
 
 
 
 
 
 
 
 
427
  return rest_ensure_response( array( 'message' => __( 'Deleted meta' ) ) );
428
  }
429
  }
50
  'methods' => WP_REST_Server::CREATABLE,
51
  'callback' => array( $this, 'create_item' ),
52
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
53
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
54
  ),
55
 
56
  'schema' => array( $this, 'get_public_item_schema' ),
76
  'methods' => WP_REST_Server::DELETABLE,
77
  'callback' => array( $this, 'delete_item' ),
78
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
79
+ 'args' => array(
80
+ 'force' => array(
81
+ 'default' => false,
82
+ ),
83
+ ),
84
  ),
85
 
86
  'schema' => array( $this, 'get_public_item_schema' ),
240
  $parent_column = $this->get_parent_column();
241
  $response->add_link( 'about', rest_url( 'wp/' . $this->parent_base . '/' . $data->$parent_column ), array( 'embeddable' => true ) );
242
 
243
+ /**
244
+ * Filter a meta value returned from the API.
245
+ *
246
+ * Allows modification of the meta value right before it is returned.
247
+ *
248
+ * @param array $response Key value array of meta data: id, key, value.
249
+ * @param WP_REST_Request $request Request used to generate the response.
250
+ */
251
  return apply_filters( 'rest_prepare_meta_value', $response, $request );
252
  }
253
 
328
  ) );
329
  $response = $this->get_item( $request );
330
 
331
+ /**
332
+ * Fires after meta is added to an object or updated via the REST API.
333
+ *
334
+ * @param array $value The inserted meta data.
335
+ * @param WP_REST_Request $request The request sent to the API.
336
+ * @param bool $creating True when adding meta, false when updating.
337
+ */
338
+ do_action( 'rest_insert_meta', $value, $request, false );
339
+
340
  return rest_ensure_response( $response );
341
  }
342
 
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 );
406
+
407
  return $response;
408
  }
409
 
448
  return new WP_Error( 'rest_meta_could_not_delete', __( 'Could not delete meta.' ), array( 'status' => 500 ) );
449
  }
450
 
451
+ /**
452
+ * Fires after a meta value is deleted via the REST API.
453
+ *
454
+ * @param WP_REST_Request $request The request sent to the API.
455
+ */
456
+ do_action( 'rest_delete_meta', $request );
457
+
458
  return rest_ensure_response( array( 'message' => __( 'Deleted meta' ) ) );
459
  }
460
  }
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -41,7 +41,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
41
  'methods' => WP_REST_Server::CREATABLE,
42
  'callback' => array( $this, 'create_item' ),
43
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
44
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
45
  ),
46
 
47
  'schema' => array( $this, 'get_public_item_schema' ),
@@ -61,7 +61,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
61
  'methods' => WP_REST_Server::EDITABLE,
62
  'callback' => array( $this, 'update_item' ),
63
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
64
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
65
  ),
66
  array(
67
  'methods' => WP_REST_Server::DELETABLE,
@@ -79,9 +79,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
79
  }
80
 
81
  /**
82
- * Get a collection of posts
83
  *
84
- * @param WP_REST_Request $request Full details about the request
85
  * @return WP_Error|WP_REST_Response
86
  */
87
  public function get_items( $request ) {
@@ -94,17 +94,17 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
94
  unset( $args['filter'] );
95
  }
96
 
97
- // Force the post_type argument, since it's not a user input variable
98
  $args['post_type'] = $this->post_type;
99
 
100
  /**
101
- * Alter the query arguments for a request.
102
  *
103
- * This allows you to set extra arguments or defaults for a post
104
  * collection request.
105
  *
106
- * @param array $args Map of query var to query value.
107
- * @param WP_REST_Request $request Full details about the request.
108
  */
109
  $args = apply_filters( 'rest_post_query', $args, $request );
110
  $query_args = $this->prepare_items_query( $args );
@@ -150,9 +150,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
150
  }
151
 
152
  /**
153
- * Get a single post
154
  *
155
- * @param WP_REST_Request $request Full details about the request
156
  * @return WP_Error|WP_REST_Response
157
  */
158
  public function get_item( $request ) {
@@ -172,9 +172,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
172
  }
173
 
174
  /**
175
- * Create a single post
176
  *
177
- * @param WP_REST_Request $request Full details about the request
178
  * @return WP_Error|WP_REST_Response
179
  */
180
  public function create_item( $request ) {
@@ -225,6 +225,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
225
 
226
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
227
 
 
 
 
 
 
 
 
228
  do_action( 'rest_insert_post', $post, $request, true );
229
 
230
  $response = $this->get_item( array(
@@ -239,16 +246,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
239
  }
240
 
241
  /**
242
- * Update a single post
243
  *
244
- * @param WP_REST_Request $request Full details about the request
245
  * @return WP_Error|WP_REST_Response
246
  */
247
  public function update_item( $request ) {
248
  $id = (int) $request['id'];
249
  $post = get_post( $id );
250
 
251
- if ( ! $post ) {
252
  return new WP_Error( 'rest_post_invalid_id', __( 'Post ID is invalid.' ), array( 'status' => 400 ) );
253
  }
254
 
@@ -292,12 +299,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
292
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
293
 
294
  /**
295
- * @TODO: Enable rest_insert_post() action after
296
  * Media Controller has been migrated to new style.
297
  *
298
  * do_action( 'rest_insert_post', $post, $request );
299
  */
300
 
 
 
 
301
  return $this->get_item( array(
302
  'id' => $post_id,
303
  'context' => 'edit',
@@ -305,10 +315,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
305
  }
306
 
307
  /**
308
- * Delete a single post
309
  *
310
- * @param WP_REST_Request $request Full details about the request
311
- * @return array|WP_Error
312
  */
313
  public function delete_item( $request ) {
314
  $id = (int) $request['id'];
@@ -326,12 +336,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
326
  }
327
 
328
  /**
329
- * Filter whether the post type supports trashing.
330
  *
331
- * @param boolean $supports_trash Does the post type support trashing?
332
- * @param WP_Post $post Post we're attempting to trash.
 
 
333
  */
334
- $supports_trash = apply_filters( 'rest_post_type_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 ) );
@@ -341,16 +353,17 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
341
  $request->set_param( 'context', 'edit' );
342
  $response = rest_do_request( $request );
343
 
344
- // If we're forcing, then delete permanently
345
  if ( $force ) {
346
  $result = wp_delete_post( $id, true );
 
347
  } else {
348
- // If we don't support trashing for this type, error out
349
  if ( ! $supports_trash ) {
350
  return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing.' ), array( 'status' => 501 ) );
351
  }
352
 
353
- // Otherwise, only trash if we haven't already
354
  if ( 'trash' === $post->post_status ) {
355
  return new WP_Error( 'rest_already_deleted', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
356
  }
@@ -358,17 +371,34 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
358
  // (Note that internally this falls through to `wp_delete_post` if
359
  // the trash is disabled.)
360
  $result = wp_trash_post( $id );
 
361
  }
362
 
363
  if ( ! $result ) {
364
  return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
365
  }
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  return $response;
368
  }
369
 
370
  /**
371
- * Check if a given request has access to read /posts
372
  *
373
  * @param WP_REST_Request $request Full details about the request.
374
  * @return bool|WP_Error
@@ -385,7 +415,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
385
  }
386
 
387
  /**
388
- * Check if a given request has access to read a post
389
  *
390
  * @param WP_REST_Request $request Full details about the request.
391
  * @return bool|WP_Error
@@ -406,7 +436,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
406
  }
407
 
408
  /**
409
- * Check if a given request has access to create a post
410
  *
411
  * @param WP_REST_Request $request Full details about the request.
412
  * @return bool|WP_Error
@@ -431,7 +461,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
431
  }
432
 
433
  /**
434
- * Check if a given request has access to update a post
435
  *
436
  * @param WP_REST_Request $request Full details about the request.
437
  * @return bool|WP_Error
@@ -461,7 +491,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
461
  }
462
 
463
  /**
464
- * Check if a given request has access to delete a post
465
  *
466
  * @param WP_REST_Request $request Full details about the request.
467
  * @return bool|WP_Error
@@ -490,7 +520,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
490
  $query_args = array();
491
  foreach ( $valid_vars as $var => $index ) {
492
  if ( isset( $prepared_args[ $var ] ) ) {
493
- $query_args[ $var ] = apply_filters( 'rest_query_var-' . $var, $prepared_args[ $var ] );
 
 
 
 
 
 
 
 
494
  }
495
  }
496
 
@@ -508,36 +546,49 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
508
  */
509
  protected function get_allowed_query_vars() {
510
  global $wp;
 
 
 
 
 
 
 
 
511
  $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
512
 
513
  if ( current_user_can( 'edit_posts' ) ) {
514
  /**
515
- * Alter allowed query vars for authorized users.
516
  *
517
  * If the user has the `edit_posts` capability, we also allow use of
518
  * private query parameters, which are only undesirable on the
519
  * frontend, but are safe for use in query strings.
520
  *
521
  * To disable anyway, use
522
- * `add_filter('rest_private_query_vars', '__return_empty_array');`
523
  *
524
- * @param array $private List of allowed query vars for authorized users.
 
525
  */
526
  $private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars );
527
  $valid_vars = array_merge( $valid_vars, $private );
528
  }
529
- // Define our own in addition to WP's normal vars
530
  $rest_valid = array( 'posts_per_page', 'ignore_sticky_posts', 'post_parent' );
531
  $valid_vars = array_merge( $valid_vars, $rest_valid );
532
 
533
  /**
534
- * Alter allowed query vars for the REST API.
535
  *
536
- * This filter allows you to add or remove query vars from the allowed
537
  * list for all requests, including unauthenticated ones. To alter the
538
  * vars for editors only, {@see rest_private_query_vars}.
539
  *
540
- * @param array $valid_vars List of allowed query vars.
 
 
 
 
541
  */
542
  $valid_vars = apply_filters( 'rest_query_vars', $valid_vars );
543
 
@@ -545,7 +596,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
545
  }
546
 
547
  /**
548
- * Check the post excerpt and prepare it for single post output
549
  *
550
  * @param string $excerpt
551
  * @return string|null $excerpt
@@ -555,6 +606,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
555
  return __( 'There is no excerpt because this is a protected post.' );
556
  }
557
 
 
558
  $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) );
559
 
560
  if ( empty( $excerpt ) ) {
@@ -600,22 +652,22 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
600
  }
601
 
602
  /**
603
- * Prepare a single post for create or update
604
  *
605
- * @param WP_REST_Request $request Request object
606
- * @return WP_Error|obj $prepared_post Post object
607
  */
608
  protected function prepare_item_for_database( $request ) {
609
  $prepared_post = new stdClass;
610
 
611
- // ID
612
  if ( isset( $request['id'] ) ) {
613
  $prepared_post->ID = absint( $request['id'] );
614
  }
615
 
616
  $schema = $this->get_item_schema();
617
 
618
- // Post title
619
  if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
620
  if ( is_string( $request['title'] ) ) {
621
  $prepared_post->post_title = wp_filter_post_kses( $request['title'] );
@@ -624,7 +676,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
624
  }
625
  }
626
 
627
- // Post content
628
  if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
629
  if ( is_string( $request['content'] ) ) {
630
  $prepared_post->post_content = wp_filter_post_kses( $request['content'] );
@@ -633,7 +685,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
633
  }
634
  }
635
 
636
- // Post excerpt
637
  if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
638
  if ( is_string( $request['excerpt'] ) ) {
639
  $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] );
@@ -642,9 +694,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
642
  }
643
  }
644
 
645
- // Post type
646
  if ( empty( $request['id'] ) ) {
647
- // Creating new post, use default type for the controller
648
  $prepared_post->post_type = $this->post_type;
649
  } else {
650
  // Updating a post, use previous type.
@@ -652,7 +704,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
652
  }
653
  $post_type = get_post_type_object( $prepared_post->post_type );
654
 
655
- // Post status
656
  if ( isset( $request['status'] ) ) {
657
  $status = $this->handle_status_param( $request['status'], $post_type );
658
  if ( is_wp_error( $status ) ) {
@@ -662,7 +714,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
662
  $prepared_post->post_status = $status;
663
  }
664
 
665
- // Post date
666
  if ( ! empty( $request['date'] ) ) {
667
  $date_data = rest_get_date_with_gmt( $request['date'] );
668
 
@@ -680,7 +732,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
680
  return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
681
  }
682
  }
683
- // Post slug
684
  if ( isset( $request['slug'] ) ) {
685
  $prepared_post->post_name = $request['slug'];
686
  }
@@ -695,7 +747,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
695
  $prepared_post->post_author = $author;
696
  }
697
 
698
- // Post password
699
  if ( isset( $request['password'] ) ) {
700
  $prepared_post->post_password = $request['password'];
701
 
@@ -714,7 +766,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
714
  }
715
  }
716
 
717
- // Parent
718
  $post_type_obj = get_post_type_object( $this->post_type );
719
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
720
  $parent = get_post( (int) $request['parent'] );
@@ -725,22 +777,32 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
725
  $prepared_post->post_parent = (int) $parent->ID;
726
  }
727
 
728
- // Menu order
729
  if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
730
  $prepared_post->menu_order = (int) $request['menu_order'];
731
  }
732
 
733
- // Comment status
734
  if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
735
  $prepared_post->comment_status = $request['comment_status'];
736
  }
737
 
738
- // Ping status
739
  if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
740
  $prepared_post->ping_status = $request['ping_status'];
741
  }
 
 
 
 
 
 
 
 
 
 
 
742
 
743
- return apply_filters( 'rest_pre_insert_' . $this->post_type, $prepared_post, $request );
744
  }
745
 
746
  /**
@@ -795,7 +857,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
795
  $post_author = (int) $post_author;
796
  }
797
 
798
- // Only check edit others' posts if we are another user
799
  if ( get_current_user_id() !== $post_author ) {
800
 
801
  $author = get_userdata( $post_author );
@@ -809,7 +871,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
809
  }
810
 
811
  /**
812
- * Determine the featured image based on a request param
813
  *
814
  * @param int $featured_image
815
  * @param int $post_id
@@ -831,13 +893,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
831
  }
832
 
833
  /**
834
- * Set the template for a page
835
  *
836
  * @param string $template
837
  * @param integer $post_id
838
  */
839
  public function handle_template( $template, $post_id ) {
840
- if ( in_array( $template, array_values( get_page_templates() ) ) ) {
841
  update_post_meta( $post_id, '_wp_page_template', $template );
842
  } else {
843
  update_post_meta( $post_id, '_wp_page_template', '' );
@@ -863,11 +925,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
863
  }
864
 
865
  /**
866
- * Check if we can read a post
867
  *
868
  * Correctly handles posts with the inherit status.
869
  *
870
- * @param obj $post Post object
871
  * @return bool Can we read it?
872
  */
873
  public function check_read_permission( $post ) {
@@ -895,7 +957,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
895
  }
896
 
897
  // If we don't have a parent, but the status is set to inherit, assume
898
- // it's published (as per get_post_status())
899
  if ( 'inherit' === $post->post_status ) {
900
  return true;
901
  }
@@ -904,9 +966,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
904
  }
905
 
906
  /**
907
- * Check if we can edit a post
908
  *
909
- * @param obj $post Post object
910
  * @return bool Can we edit it?
911
  */
912
  protected function check_update_permission( $post ) {
@@ -920,10 +982,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
920
  }
921
 
922
  /**
923
- * Check if we can create a post
924
  *
925
- * @param obj $post Post object
926
- * @return bool Can we create it?
927
  */
928
  protected function check_create_permission( $post ) {
929
  $post_type = get_post_type_object( $post->post_type );
@@ -936,9 +998,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
936
  }
937
 
938
  /**
939
- * Check if we can delete a post
940
  *
941
- * @param obj $post Post object
942
  * @return bool Can we delete it?
943
  */
944
  protected function check_delete_permission( $post ) {
@@ -968,22 +1030,23 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
968
  }
969
 
970
  /**
971
- * Prepare a single post output for response
972
  *
973
- * @param WP_Post $post Post object
974
- * @param WP_REST_Request $request Request object
975
  * @return WP_REST_Response $data
976
  */
977
  public function prepare_item_for_response( $post, $request ) {
978
  $GLOBALS['post'] = $post;
979
  setup_postdata( $post );
980
 
981
- // Base fields for every post
982
  $data = array(
983
  'id' => $post->ID,
984
  'date' => $this->prepare_date_response( $post->post_date_gmt, $post->post_date ),
985
  'date_gmt' => $this->prepare_date_response( $post->post_date_gmt ),
986
  'guid' => array(
 
987
  'rendered' => apply_filters( 'get_the_guid', $post->guid ),
988
  'raw' => $post->guid,
989
  ),
@@ -1013,10 +1076,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1013
 
1014
  $data['content'] = array(
1015
  'raw' => $post->post_content,
 
1016
  'rendered' => apply_filters( 'the_content', $post->post_content ),
1017
  );
1018
 
1019
- // Don't leave our cookie lying around: https://github.com/WP-API/WP-API/issues/1055
1020
  if ( ! empty( $post->post_password ) ) {
1021
  $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = '';
1022
  }
@@ -1067,7 +1131,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1067
 
1068
  if ( ! empty( $schema['properties']['format'] ) ) {
1069
  $data['format'] = get_post_format( $post->ID );
1070
- // Fill in blank post format
1071
  if ( empty( $data['format'] ) ) {
1072
  $data['format'] = 'standard';
1073
  }
@@ -1078,11 +1142,21 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1078
 
1079
  $data = $this->add_additional_fields_to_object( $data, $request );
1080
 
1081
- // Wrap the data in a response object
1082
  $data = rest_ensure_response( $data );
1083
 
1084
  $data->add_links( $this->prepare_links( $post ) );
1085
 
 
 
 
 
 
 
 
 
 
 
1086
  return apply_filters( 'rest_prepare_' . $this->post_type, $data, $post, $request );
1087
  }
1088
 
@@ -1115,7 +1189,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1115
 
1116
  if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'comments' ) ) {
1117
  $replies_url = rest_url( '/wp/v2/comments' );
1118
- $replies_url = add_query_arg( 'post_id', $post->ID, $replies_url );
1119
  $links['replies'] = array(
1120
  'href' => $replies_url,
1121
  'embeddable' => true,
@@ -1135,12 +1209,19 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1135
  );
1136
  }
1137
 
 
 
 
 
 
 
 
 
1138
  if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ) ) ) {
1139
  $attachments_url = rest_url( 'wp/v2/media' );
1140
  $attachments_url = add_query_arg( 'post_parent', $post->ID, $attachments_url );
1141
  $links['http://v2.wp-api.org/attachment'] = array(
1142
  'href' => $attachments_url,
1143
- 'embeddable' => true,
1144
  );
1145
  }
1146
 
@@ -1177,7 +1258,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1177
  }
1178
 
1179
  /**
1180
- * Get the Post's schema, conforming to JSON Schema
1181
  *
1182
  * @return array
1183
  */
@@ -1189,11 +1270,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1189
  'title' => $this->post_type,
1190
  'type' => 'object',
1191
  /*
1192
- * Base properties for every Post
1193
  */
1194
  'properties' => array(
1195
  'date' => array(
1196
- 'description' => 'The date the object was published.',
1197
  'type' => 'string',
1198
  'format' => 'date-time',
1199
  'context' => array( 'view', 'edit', 'embed' ),
@@ -1202,7 +1283,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1202
  'description' => 'The date the object was published, as GMT.',
1203
  'type' => 'string',
1204
  'format' => 'date-time',
1205
- 'context' => array( 'edit' ),
1206
  ),
1207
  'guid' => array(
1208
  'description' => 'The globally unique identifier for the object.',
@@ -1236,7 +1317,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1236
  'readonly' => true,
1237
  ),
1238
  'modified' => array(
1239
- 'description' => 'The date the object was last modified.',
1240
  'type' => 'string',
1241
  'format' => 'date-time',
1242
  'context' => array( 'view', 'edit' ),
@@ -1435,7 +1516,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1435
  $schema['properties']['format'] = array(
1436
  'description' => 'The format for the object.',
1437
  'type' => 'string',
1438
- 'enum' => get_post_format_slugs(),
1439
  'context' => array( 'view', 'edit' ),
1440
  );
1441
  break;
@@ -1455,7 +1536,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1455
  $schema['properties']['template'] = array(
1456
  'description' => 'The theme file to use to display the object.',
1457
  'type' => 'string',
1458
- 'enum' => array_values( get_page_templates() ),
1459
  'context' => array( 'view', 'edit' ),
1460
  );
1461
  }
41
  'methods' => WP_REST_Server::CREATABLE,
42
  'callback' => array( $this, 'create_item' ),
43
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
44
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
45
  ),
46
 
47
  'schema' => array( $this, 'get_public_item_schema' ),
61
  'methods' => WP_REST_Server::EDITABLE,
62
  'callback' => array( $this, 'update_item' ),
63
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
64
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
65
  ),
66
  array(
67
  'methods' => WP_REST_Server::DELETABLE,
79
  }
80
 
81
  /**
82
+ * Get a collection of posts.
83
  *
84
+ * @param WP_REST_Request $request Full details about the request.
85
  * @return WP_Error|WP_REST_Response
86
  */
87
  public function get_items( $request ) {
94
  unset( $args['filter'] );
95
  }
96
 
97
+ // Force the post_type argument, since it's not a user input variable.
98
  $args['post_type'] = $this->post_type;
99
 
100
  /**
101
+ * Filter the query arguments for a request.
102
  *
103
+ * Enables adding extra arguments or setting defaults for a post
104
  * collection request.
105
  *
106
+ * @param array $args Key value array of query var to query value.
107
+ * @param WP_REST_Request $request The request used.
108
  */
109
  $args = apply_filters( 'rest_post_query', $args, $request );
110
  $query_args = $this->prepare_items_query( $args );
150
  }
151
 
152
  /**
153
+ * Get a single post.
154
  *
155
+ * @param WP_REST_Request $request Full details about the request.
156
  * @return WP_Error|WP_REST_Response
157
  */
158
  public function get_item( $request ) {
172
  }
173
 
174
  /**
175
+ * Create a single post.
176
  *
177
+ * @param WP_REST_Request $request Full details about the request.
178
  * @return WP_Error|WP_REST_Response
179
  */
180
  public function create_item( $request ) {
225
 
226
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
227
 
228
+ /**
229
+ * Fires after a single post is created or updated via the REST API.
230
+ *
231
+ * @param object $post Inserted Post object (not a WP_Post object).
232
+ * @param WP_REST_Request $request Request object.
233
+ * @param bool $creating True when creating post, false when updating.
234
+ */
235
  do_action( 'rest_insert_post', $post, $request, true );
236
 
237
  $response = $this->get_item( array(
246
  }
247
 
248
  /**
249
+ * Update a single post.
250
  *
251
+ * @param WP_REST_Request $request Full details about the request.
252
  * @return WP_Error|WP_REST_Response
253
  */
254
  public function update_item( $request ) {
255
  $id = (int) $request['id'];
256
  $post = get_post( $id );
257
 
258
+ if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
259
  return new WP_Error( 'rest_post_invalid_id', __( 'Post ID is invalid.' ), array( 'status' => 400 ) );
260
  }
261
 
299
  $this->update_additional_fields_for_object( get_post( $post_id ), $request );
300
 
301
  /**
302
+ * @TODO: Enable rest_insert_post() action after.
303
  * Media Controller has been migrated to new style.
304
  *
305
  * do_action( 'rest_insert_post', $post, $request );
306
  */
307
 
308
+ /* This action is documented in lib/endpoints/class-wp-rest-controller.php */
309
+ do_action( 'rest_insert_post', $post, $request, false );
310
+
311
  return $this->get_item( array(
312
  'id' => $post_id,
313
  'context' => 'edit',
315
  }
316
 
317
  /**
318
+ * Delete a single post.
319
  *
320
+ * @param WP_REST_Request $request Full details about the request.
321
+ * @return WP_REST_Response|WP_Error
322
  */
323
  public function delete_item( $request ) {
324
  $id = (int) $request['id'];
336
  }
337
 
338
  /**
339
+ * Filter whether a post is trashable.
340
  *
341
+ * Return false to disable trash support for the post.
342
+ *
343
+ * @param boolean $supports_trash Whether the post type support trashing.
344
+ * @param WP_Post $post The Post object being considered for trashing support.
345
  */
346
+ $supports_trash = apply_filters( 'rest_post_trashable', $supports_trash, $post );
347
 
348
  if ( ! $this->check_delete_permission( $post ) ) {
349
  return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => 401 ) );
353
  $request->set_param( 'context', 'edit' );
354
  $response = rest_do_request( $request );
355
 
356
+ // If we're forcing, then delete permanently.
357
  if ( $force ) {
358
  $result = wp_delete_post( $id, true );
359
+ $status = 'deleted';
360
  } else {
361
+ // If we don't support trashing for this type, error out.
362
  if ( ! $supports_trash ) {
363
  return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing.' ), array( 'status' => 501 ) );
364
  }
365
 
366
+ // Otherwise, only trash if we haven't already.
367
  if ( 'trash' === $post->post_status ) {
368
  return new WP_Error( 'rest_already_deleted', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
369
  }
371
  // (Note that internally this falls through to `wp_delete_post` if
372
  // the trash is disabled.)
373
  $result = wp_trash_post( $id );
374
+ $status = 'trashed';
375
  }
376
 
377
  if ( ! $result ) {
378
  return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
379
  }
380
 
381
+ $data = $response->get_data();
382
+ $data = array(
383
+ 'data' => $data,
384
+ $status => true,
385
+ );
386
+ $response->set_data( $data );
387
+
388
+ /**
389
+ * Fires after a single post is deleted or trashed via the REST API.
390
+ *
391
+ * @param object $post The deleted or trashed post.
392
+ * @param array $data The response data.
393
+ * @param WP_REST_Request $request The request sent to the API.
394
+ */
395
+ do_action( 'rest_delete_post', $post, $data, $request );
396
+
397
  return $response;
398
  }
399
 
400
  /**
401
+ * Check if a given request has access to read /posts.
402
  *
403
  * @param WP_REST_Request $request Full details about the request.
404
  * @return bool|WP_Error
415
  }
416
 
417
  /**
418
+ * Check if a given request has access to read a post.
419
  *
420
  * @param WP_REST_Request $request Full details about the request.
421
  * @return bool|WP_Error
436
  }
437
 
438
  /**
439
+ * Check if a given request has access to create a post.
440
  *
441
  * @param WP_REST_Request $request Full details about the request.
442
  * @return bool|WP_Error
461
  }
462
 
463
  /**
464
+ * Check if a given request has access to update a post.
465
  *
466
  * @param WP_REST_Request $request Full details about the request.
467
  * @return bool|WP_Error
491
  }
492
 
493
  /**
494
+ * Check if a given request has access to delete a post.
495
  *
496
  * @param WP_REST_Request $request Full details about the request.
497
  * @return bool|WP_Error
520
  $query_args = array();
521
  foreach ( $valid_vars as $var => $index ) {
522
  if ( isset( $prepared_args[ $var ] ) ) {
523
+ /**
524
+ * Filter the query_vars used in `get_items` for the constructed query.
525
+ *
526
+ * The dynamic portion of the hook name, $var, refers to the query_var key.
527
+ *
528
+ * @param mixed $prepared_args[ $var ] The query_var value.
529
+ *
530
+ */
531
+ $query_args[ $var ] = apply_filters( "rest_query_var-{$var}", $prepared_args[ $var ] );
532
  }
533
  }
534
 
546
  */
547
  protected function get_allowed_query_vars() {
548
  global $wp;
549
+
550
+ /**
551
+ * Filter the publicly allowed query vars.
552
+ *
553
+ * Allows adjusting of the default query vars that are made public.
554
+ *
555
+ * @param array Array of allowed WP_Query query vars.
556
+ */
557
  $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
558
 
559
  if ( current_user_can( 'edit_posts' ) ) {
560
  /**
561
+ * Filter the allowed 'private' query vars for authorized users.
562
  *
563
  * If the user has the `edit_posts` capability, we also allow use of
564
  * private query parameters, which are only undesirable on the
565
  * frontend, but are safe for use in query strings.
566
  *
567
  * To disable anyway, use
568
+ * `add_filter( 'rest_private_query_vars', '__return_empty_array' );`
569
  *
570
+ * @param array $private_query_vars Array of allowed query vars for authorized users.
571
+ * }
572
  */
573
  $private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars );
574
  $valid_vars = array_merge( $valid_vars, $private );
575
  }
576
+ // Define our own in addition to WP's normal vars.
577
  $rest_valid = array( 'posts_per_page', 'ignore_sticky_posts', 'post_parent' );
578
  $valid_vars = array_merge( $valid_vars, $rest_valid );
579
 
580
  /**
581
+ * Filter allowed query vars for the REST API.
582
  *
583
+ * This filter allows you to add or remove query vars from the final allowed
584
  * list for all requests, including unauthenticated ones. To alter the
585
  * vars for editors only, {@see rest_private_query_vars}.
586
  *
587
+ * @param array {
588
+ * Array of allowed WP_Query query vars.
589
+ *
590
+ * @param string $allowed_query_var The query var to allow.
591
+ * }
592
  */
593
  $valid_vars = apply_filters( 'rest_query_vars', $valid_vars );
594
 
596
  }
597
 
598
  /**
599
+ * Check the post excerpt and prepare it for single post output.
600
  *
601
  * @param string $excerpt
602
  * @return string|null $excerpt
606
  return __( 'There is no excerpt because this is a protected post.' );
607
  }
608
 
609
+ /** This filter is documented in wp-includes/post-template.php */
610
  $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) );
611
 
612
  if ( empty( $excerpt ) ) {
652
  }
653
 
654
  /**
655
+ * Prepare a single post for create or update.
656
  *
657
+ * @param WP_REST_Request $request Request object.
658
+ * @return WP_Error|obj $prepared_post Post object.
659
  */
660
  protected function prepare_item_for_database( $request ) {
661
  $prepared_post = new stdClass;
662
 
663
+ // ID.
664
  if ( isset( $request['id'] ) ) {
665
  $prepared_post->ID = absint( $request['id'] );
666
  }
667
 
668
  $schema = $this->get_item_schema();
669
 
670
+ // Post title.
671
  if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
672
  if ( is_string( $request['title'] ) ) {
673
  $prepared_post->post_title = wp_filter_post_kses( $request['title'] );
676
  }
677
  }
678
 
679
+ // Post content.
680
  if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
681
  if ( is_string( $request['content'] ) ) {
682
  $prepared_post->post_content = wp_filter_post_kses( $request['content'] );
685
  }
686
  }
687
 
688
+ // Post excerpt.
689
  if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
690
  if ( is_string( $request['excerpt'] ) ) {
691
  $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] );
694
  }
695
  }
696
 
697
+ // Post type.
698
  if ( empty( $request['id'] ) ) {
699
+ // Creating new post, use default type for the controller.
700
  $prepared_post->post_type = $this->post_type;
701
  } else {
702
  // Updating a post, use previous type.
704
  }
705
  $post_type = get_post_type_object( $prepared_post->post_type );
706
 
707
+ // Post status.
708
  if ( isset( $request['status'] ) ) {
709
  $status = $this->handle_status_param( $request['status'], $post_type );
710
  if ( is_wp_error( $status ) ) {
714
  $prepared_post->post_status = $status;
715
  }
716
 
717
+ // Post date.
718
  if ( ! empty( $request['date'] ) ) {
719
  $date_data = rest_get_date_with_gmt( $request['date'] );
720
 
732
  return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ), array( 'status' => 400 ) );
733
  }
734
  }
735
+ // Post slug.
736
  if ( isset( $request['slug'] ) ) {
737
  $prepared_post->post_name = $request['slug'];
738
  }
747
  $prepared_post->post_author = $author;
748
  }
749
 
750
+ // Post password.
751
  if ( isset( $request['password'] ) ) {
752
  $prepared_post->post_password = $request['password'];
753
 
766
  }
767
  }
768
 
769
+ // Parent.
770
  $post_type_obj = get_post_type_object( $this->post_type );
771
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
772
  $parent = get_post( (int) $request['parent'] );
777
  $prepared_post->post_parent = (int) $parent->ID;
778
  }
779
 
780
+ // Menu order.
781
  if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
782
  $prepared_post->menu_order = (int) $request['menu_order'];
783
  }
784
 
785
+ // Comment status.
786
  if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
787
  $prepared_post->comment_status = $request['comment_status'];
788
  }
789
 
790
+ // Ping status.
791
  if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
792
  $prepared_post->ping_status = $request['ping_status'];
793
  }
794
+ /**
795
+ * Filter the query_vars used in `get_items` for the constructed query.
796
+ *
797
+ * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
798
+ * prepared for insertion.
799
+ *
800
+ * @param object $prepared_post An object representing a single post prepared
801
+ * for inserting or updating the database.
802
+ * @param WP_REST_Request $request Request object.
803
+ */
804
+ return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
805
 
 
806
  }
807
 
808
  /**
857
  $post_author = (int) $post_author;
858
  }
859
 
860
+ // Only check edit others' posts if we are another user.
861
  if ( get_current_user_id() !== $post_author ) {
862
 
863
  $author = get_userdata( $post_author );
871
  }
872
 
873
  /**
874
+ * Determine the featured image based on a request param.
875
  *
876
  * @param int $featured_image
877
  * @param int $post_id
893
  }
894
 
895
  /**
896
+ * Set the template for a page.
897
  *
898
  * @param string $template
899
  * @param integer $post_id
900
  */
901
  public function handle_template( $template, $post_id ) {
902
+ if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) ) {
903
  update_post_meta( $post_id, '_wp_page_template', $template );
904
  } else {
905
  update_post_meta( $post_id, '_wp_page_template', '' );
925
  }
926
 
927
  /**
928
+ * Check if we can read a post.
929
  *
930
  * Correctly handles posts with the inherit status.
931
  *
932
+ * @param obj $post Post object.
933
  * @return bool Can we read it?
934
  */
935
  public function check_read_permission( $post ) {
957
  }
958
 
959
  // If we don't have a parent, but the status is set to inherit, assume
960
+ // it's published (as per get_post_status()).
961
  if ( 'inherit' === $post->post_status ) {
962
  return true;
963
  }
966
  }
967
 
968
  /**
969
+ * Check if we can edit a post.
970
  *
971
+ * @param obj $post Post object.
972
  * @return bool Can we edit it?
973
  */
974
  protected function check_update_permission( $post ) {
982
  }
983
 
984
  /**
985
+ * Check if we can create a post.
986
  *
987
+ * @param obj $post Post object.
988
+ * @return bool Can we create it?.
989
  */
990
  protected function check_create_permission( $post ) {
991
  $post_type = get_post_type_object( $post->post_type );
998
  }
999
 
1000
  /**
1001
+ * Check if we can delete a post.
1002
  *
1003
+ * @param obj $post Post object.
1004
  * @return bool Can we delete it?
1005
  */
1006
  protected function check_delete_permission( $post ) {
1030
  }
1031
 
1032
  /**
1033
+ * Prepare a single post output for response.
1034
  *
1035
+ * @param WP_Post $post Post object.
1036
+ * @param WP_REST_Request $request Request object.
1037
  * @return WP_REST_Response $data
1038
  */
1039
  public function prepare_item_for_response( $post, $request ) {
1040
  $GLOBALS['post'] = $post;
1041
  setup_postdata( $post );
1042
 
1043
+ // Base fields for every post.
1044
  $data = array(
1045
  'id' => $post->ID,
1046
  'date' => $this->prepare_date_response( $post->post_date_gmt, $post->post_date ),
1047
  'date_gmt' => $this->prepare_date_response( $post->post_date_gmt ),
1048
  'guid' => array(
1049
+ /** This filter is documented in wp-includes/post-template.php */
1050
  'rendered' => apply_filters( 'get_the_guid', $post->guid ),
1051
  'raw' => $post->guid,
1052
  ),
1076
 
1077
  $data['content'] = array(
1078
  'raw' => $post->post_content,
1079
+ /** This filter is documented in wp-includes/post-template.php */
1080
  'rendered' => apply_filters( 'the_content', $post->post_content ),
1081
  );
1082
 
1083
+ // Don't leave our cookie lying around: https://github.com/WP-API/WP-API/issues/1055.
1084
  if ( ! empty( $post->post_password ) ) {
1085
  $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = '';
1086
  }
1131
 
1132
  if ( ! empty( $schema['properties']['format'] ) ) {
1133
  $data['format'] = get_post_format( $post->ID );
1134
+ // Fill in blank post format.
1135
  if ( empty( $data['format'] ) ) {
1136
  $data['format'] = 'standard';
1137
  }
1142
 
1143
  $data = $this->add_additional_fields_to_object( $data, $request );
1144
 
1145
+ // Wrap the data in a response object.
1146
  $data = rest_ensure_response( $data );
1147
 
1148
  $data->add_links( $this->prepare_links( $post ) );
1149
 
1150
+ /**
1151
+ * Filter the post data for a response.
1152
+ *
1153
+ * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
1154
+ * prepared for the response.
1155
+ *
1156
+ * @param array $data An array of post data, prepared for response.
1157
+ * @param WP_Post $post Post object.
1158
+ * @param WP_REST_Request $request Request object.
1159
+ */
1160
  return apply_filters( 'rest_prepare_' . $this->post_type, $data, $post, $request );
1161
  }
1162
 
1189
 
1190
  if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'comments' ) ) {
1191
  $replies_url = rest_url( '/wp/v2/comments' );
1192
+ $replies_url = add_query_arg( 'post', $post->ID, $replies_url );
1193
  $links['replies'] = array(
1194
  'href' => $replies_url,
1195
  'embeddable' => true,
1209
  );
1210
  }
1211
 
1212
+ // If we have a featured image, add that.
1213
+ if ( $featured_image = get_post_thumbnail_id( $post->ID ) ) {
1214
+ $image_url = rest_url( 'wp/v2/media/' . $featured_image );
1215
+ $links['http://v2.wp-api.org/featuredmedia'] = array(
1216
+ 'href' => $image_url,
1217
+ 'embeddable' => true,
1218
+ );
1219
+ }
1220
  if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ) ) ) {
1221
  $attachments_url = rest_url( 'wp/v2/media' );
1222
  $attachments_url = add_query_arg( 'post_parent', $post->ID, $attachments_url );
1223
  $links['http://v2.wp-api.org/attachment'] = array(
1224
  'href' => $attachments_url,
 
1225
  );
1226
  }
1227
 
1258
  }
1259
 
1260
  /**
1261
+ * Get the Post's schema, conforming to JSON Schema.
1262
  *
1263
  * @return array
1264
  */
1270
  'title' => $this->post_type,
1271
  'type' => 'object',
1272
  /*
1273
+ * Base properties for every Post.
1274
  */
1275
  'properties' => array(
1276
  'date' => array(
1277
+ 'description' => "The date the object was published, in the site's timezone.",
1278
  'type' => 'string',
1279
  'format' => 'date-time',
1280
  'context' => array( 'view', 'edit', 'embed' ),
1283
  'description' => 'The date the object was published, as GMT.',
1284
  'type' => 'string',
1285
  'format' => 'date-time',
1286
+ 'context' => array( 'view', 'edit' ),
1287
  ),
1288
  'guid' => array(
1289
  'description' => 'The globally unique identifier for the object.',
1317
  'readonly' => true,
1318
  ),
1319
  'modified' => array(
1320
+ 'description' => "The date the object was last modified, in the site's timezone.",
1321
  'type' => 'string',
1322
  'format' => 'date-time',
1323
  'context' => array( 'view', 'edit' ),
1516
  $schema['properties']['format'] = array(
1517
  'description' => 'The format for the object.',
1518
  'type' => 'string',
1519
+ 'enum' => array_values( get_post_format_slugs() ),
1520
  'context' => array( 'view', 'edit' ),
1521
  );
1522
  break;
1536
  $schema['properties']['template'] = array(
1537
  'description' => 'The theme file to use to display the object.',
1538
  'type' => 'string',
1539
+ 'enum' => array_keys( wp_get_theme()->get_page_templates() ),
1540
  'context' => array( 'view', 'edit' ),
1541
  );
1542
  }
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -45,6 +45,11 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
45
  'methods' => WP_REST_Server::DELETABLE,
46
  'callback' => array( $this, 'delete_item' ),
47
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
 
 
 
 
 
48
  ),
49
  'schema' => array( $this, 'get_public_item_schema' ),
50
  ) );
@@ -137,6 +142,15 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
137
  $response = rest_ensure_response( $term );
138
  $response->set_status( 201 );
139
 
 
 
 
 
 
 
 
 
 
140
  return $term;
141
  }
142
 
@@ -169,6 +183,15 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
169
  return $remove;
170
  }
171
 
 
 
 
 
 
 
 
 
 
172
  return $previous_item;
173
  }
174
 
@@ -184,17 +207,18 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
184
  /**
185
  * Validate the API request for relationship requests.
186
  *
187
- * @param WP_REST_Request $request
188
  * @return WP_Error|true
189
  */
190
  protected function validate_request( $request ) {
 
191
 
192
- $post_request = new WP_REST_Request();
193
- $post_request->set_param( 'id', $request['post_id'] );
 
194
 
195
- $post_check = $this->posts_controller->get_item( $post_request );
196
- if ( is_wp_error( $post_check ) ) {
197
- return $post_check;
198
  }
199
 
200
  if ( ! empty( $request['term_id'] ) ) {
45
  'methods' => WP_REST_Server::DELETABLE,
46
  'callback' => array( $this, 'delete_item' ),
47
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
48
+ 'args' => array(
49
+ 'force' => array(
50
+ 'default' => false,
51
+ ),
52
+ ),
53
  ),
54
  'schema' => array( $this, 'get_public_item_schema' ),
55
  ) );
142
  $response = rest_ensure_response( $term );
143
  $response->set_status( 201 );
144
 
145
+ /**
146
+ * Fires after a term is added to a post via the REST API.
147
+ *
148
+ * @param array $term The added term data.
149
+ * @param WP_Post $post The post the term was added to.
150
+ * @param WP_REST_Request $request The request sent to the API.
151
+ */
152
+ do_action( 'rest_insert_term', $term, $post, $request );
153
+
154
  return $term;
155
  }
156
 
183
  return $remove;
184
  }
185
 
186
+ /**
187
+ * Fires after a term is removed from a post via the REST API.
188
+ *
189
+ * @param array $previous_item The removed term data.
190
+ * @param WP_Post $post The post the term was removed from.
191
+ * @param WP_REST_Request $request The request sent to the API.
192
+ */
193
+ do_action( 'rest_remove_term', $previous_item, $post, $request );
194
+
195
  return $previous_item;
196
  }
197
 
207
  /**
208
  * Validate the API request for relationship requests.
209
  *
210
+ * @param WP_REST_Request $request Full data about the request.
211
  * @return WP_Error|true
212
  */
213
  protected function validate_request( $request ) {
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'] ) ) {
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -69,11 +69,12 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
69
 
70
  $revisions = wp_get_post_revisions( $request['parent_id'] );
71
 
72
- $struct = array();
73
  foreach ( $revisions as $revision ) {
74
- $struct[] = $this->prepare_item_for_response( $revision, $request );
 
75
  }
76
- return $struct;
77
  }
78
 
79
  /**
@@ -136,6 +137,17 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
136
  */
137
  public function delete_item( $request ) {
138
  $result = wp_delete_post( $request['id'], true );
 
 
 
 
 
 
 
 
 
 
 
139
  if ( $result ) {
140
  return true;
141
  } else {
@@ -164,7 +176,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
164
  /**
165
  * Prepare the revision for the REST response
166
  *
167
- * @param mixed $item WordPress representation of the revision.
168
  * @param WP_REST_Request $request Request object.
169
  * @return array
170
  */
69
 
70
  $revisions = wp_get_post_revisions( $request['parent_id'] );
71
 
72
+ $response = array();
73
  foreach ( $revisions as $revision ) {
74
+ $data = $this->prepare_item_for_response( $revision, $request );
75
+ $response[] = $this->prepare_response_for_collection( $data );
76
  }
77
+ return $response;
78
  }
79
 
80
  /**
137
  */
138
  public function delete_item( $request ) {
139
  $result = wp_delete_post( $request['id'], true );
140
+
141
+ /**
142
+ * Fires after a revision is deleted via the REST API.
143
+ *
144
+ * @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully)
145
+ * or false (failure). If the revision was moved to to the trash, $result represents
146
+ * its new state; if it was deleted, $result represents its state before deletion.
147
+ * @param WP_REST_Request $request The request sent to the API.
148
+ */
149
+ do_action( 'rest_delete_revision', $result, $request );
150
+
151
  if ( $result ) {
152
  return true;
153
  } else {
176
  /**
177
  * Prepare the revision for the REST response
178
  *
179
+ * @param WP_Post $post Post revision object.
180
  * @param WP_REST_Request $request Request object.
181
  * @return array
182
  */
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -110,6 +110,15 @@ 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
  return apply_filters( 'rest_prepare_taxonomy', $data, $taxonomy, $request );
114
  }
115
 
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
 
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -32,7 +32,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
32
  'methods' => WP_REST_Server::CREATABLE,
33
  'callback' => array( $this, 'create_item' ),
34
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
35
- 'args' => $this->get_endpoint_args_for_item_schema( true ),
36
  ),
37
 
38
  'schema' => array( $this, 'get_public_item_schema' ),
@@ -47,7 +47,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
47
  'methods' => WP_REST_Server::EDITABLE,
48
  'callback' => array( $this, 'update_item' ),
49
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
50
- 'args' => $this->get_endpoint_args_for_item_schema( false ),
51
  ),
52
  array(
53
  'methods' => WP_REST_Server::DELETABLE,
@@ -66,19 +66,27 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
66
  * @return WP_REST_Response|WP_Error
67
  */
68
  public function get_items( $request ) {
69
- $prepared_args = array( 'hide_empty' => false );
 
 
 
 
 
 
70
 
71
- $prepared_args['number'] = $request['per_page'];
72
  $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
73
- $prepared_args['search'] = $request['search'];
74
- $prepared_args['order'] = $request['order'];
75
- $prepared_args['orderby'] = $request['orderby'];
76
 
77
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
 
78
  if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
79
- $parent = get_term_by( 'term_taxonomy_id', (int) $request['parent'], $this->taxonomy );
80
- if ( $parent ) {
81
- $prepared_args['parent'] = $parent->term_id;
 
 
 
 
 
82
  }
83
  }
84
 
@@ -93,6 +101,12 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
93
  unset( $prepared_args['number'] );
94
  unset( $prepared_args['offset'] );
95
  $total_terms = wp_count_terms( $this->taxonomy, $prepared_args );
 
 
 
 
 
 
96
  $response->header( 'X-WP-Total', (int) $total_terms );
97
  $max_pages = ceil( $total_terms / $request['per_page'] );
98
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
@@ -250,7 +264,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
250
  * Delete a single term from a taxonomy
251
  *
252
  * @param WP_REST_Request $request Full details about the request
253
- * @return null
254
  */
255
  public function delete_item( $request ) {
256
 
@@ -260,6 +274,13 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
260
  $get_request->set_param( 'context', 'view' );
261
  $response = $this->prepare_item_for_response( $term, $get_request );
262
 
 
 
 
 
 
 
 
263
  $retval = wp_delete_term( $term->term_id, $term->taxonomy );
264
  if ( ! $retval ) {
265
  return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
@@ -433,6 +454,15 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
433
 
434
  $data->add_links( $this->prepare_links( $item ) );
435
 
 
 
 
 
 
 
 
 
 
436
  return apply_filters( 'rest_prepare_term', $data, $item, $request );
437
  }
438
 
@@ -548,28 +578,68 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
548
  */
549
  public function get_collection_params() {
550
  $query_params = parent::get_collection_params();
551
- $query_params['order'] = array(
552
- 'description' => 'Order sort attribute ascending or descending.',
 
 
553
  'type' => 'string',
554
- 'default' => 'asc',
555
- 'enum' => array( 'asc', 'desc' ),
556
- );
557
- $query_params['orderby'] = array(
558
- 'description' => 'Sort collection by object attribute.',
559
- 'type' => 'string',
560
- 'default' => 'name',
561
  'enum' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  'id',
563
  'name',
564
  'slug',
 
 
 
 
565
  ),
566
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  $taxonomy = get_taxonomy( $this->taxonomy );
568
  if ( $taxonomy->hierarchical ) {
569
  $query_params['parent'] = array(
570
  'description' => 'Limit result set to terms assigned to a specific parent term.',
571
  'type' => 'integer',
572
- 'sanitize_callback' => 'absint',
573
  );
574
  }
575
  return $query_params;
32
  'methods' => WP_REST_Server::CREATABLE,
33
  'callback' => array( $this, 'create_item' ),
34
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
35
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
36
  ),
37
 
38
  'schema' => array( $this, 'get_public_item_schema' ),
47
  'methods' => WP_REST_Server::EDITABLE,
48
  'callback' => array( $this, 'update_item' ),
49
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
50
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
51
  ),
52
  array(
53
  'methods' => WP_REST_Server::DELETABLE,
66
  * @return WP_REST_Response|WP_Error
67
  */
68
  public function get_items( $request ) {
69
+ $prepared_args = array(
70
+ 'order' => $request['order'],
71
+ 'orderby' => $request['orderby'],
72
+ 'hide_empty' => $request['hide_empty'],
73
+ 'number' => $request['per_page'],
74
+ 'search' => $request['search'],
75
+ );
76
 
 
77
  $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
 
 
 
78
 
79
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
80
+
81
  if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
82
+ if ( 0 === $request['parent'] ) {
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
  }
92
 
101
  unset( $prepared_args['number'] );
102
  unset( $prepared_args['offset'] );
103
  $total_terms = wp_count_terms( $this->taxonomy, $prepared_args );
104
+
105
+ // wp_count_terms can return a falsy value when the term has no children
106
+ if ( ! $total_terms ) {
107
+ $total_terms = 0;
108
+ }
109
+
110
  $response->header( 'X-WP-Total', (int) $total_terms );
111
  $max_pages = ceil( $total_terms / $request['per_page'] );
112
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
264
  * Delete a single term from a taxonomy
265
  *
266
  * @param WP_REST_Request $request Full details about the request
267
+ * @return WP_REST_Response|WP_Error
268
  */
269
  public function delete_item( $request ) {
270
 
274
  $get_request->set_param( 'context', 'view' );
275
  $response = $this->prepare_item_for_response( $term, $get_request );
276
 
277
+ $data = $response->get_data();
278
+ $data = array(
279
+ 'data' => $data,
280
+ 'deleted' => true,
281
+ );
282
+ $response->set_data( $data );
283
+
284
  $retval = wp_delete_term( $term->term_id, $term->taxonomy );
285
  if ( ! $retval ) {
286
  return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
454
 
455
  $data->add_links( $this->prepare_links( $item ) );
456
 
457
+ /**
458
+ * Filter a term item returned from the API.
459
+ *
460
+ * Allows modification of the term data right before it is returned.
461
+ *
462
+ * @param array $data Key value array of term data.
463
+ * @param object $item The term object.
464
+ * @param WP_REST_Request $request Request used to generate the response.
465
+ */
466
  return apply_filters( 'rest_prepare_term', $data, $item, $request );
467
  }
468
 
578
  */
579
  public function get_collection_params() {
580
  $query_params = parent::get_collection_params();
581
+ $query_params['context'] = array(
582
+ 'description' => 'Change the response format based on request context.',
583
+ 'default' => 'view',
584
+ 'sanitize_callback' => 'sanitize_key',
585
  'type' => 'string',
 
 
 
 
 
 
 
586
  'enum' => array(
587
+ 'embed',
588
+ 'view',
589
+ ),
590
+ );
591
+ $query_params['order'] = array(
592
+ 'description' => 'Order sort attribute ascending or descending.',
593
+ 'type' => 'string',
594
+ 'sanitize_callback' => 'sanitize_key',
595
+ 'default' => 'asc',
596
+ 'enum' => array(
597
+ 'asc',
598
+ 'desc',
599
+ ),
600
+ );
601
+ $query_params['orderby'] = array(
602
+ 'description' => 'Sort collection by object attribute.',
603
+ 'type' => 'string',
604
+ 'sanitize_callback' => 'sanitize_key',
605
+ 'default' => 'name',
606
+ 'enum' => array(
607
  'id',
608
  'name',
609
  'slug',
610
+ 'term_group',
611
+ 'term_id',
612
+ 'description',
613
+ 'count',
614
  ),
615
  );
616
+ $query_params['per_page'] = array(
617
+ 'description' => 'Number of terms to query at a time with pagination.',
618
+ 'type' => 'integer',
619
+ 'sanitize_callback' => 'absint',
620
+ 'default' => 10,
621
+ );
622
+ $query_params['page'] = array(
623
+ 'description' => 'Number of the desired page within the paginated query results.',
624
+ 'type' => 'integer',
625
+ 'sanitize_callback' => 'absint',
626
+ 'default' => 1,
627
+ );
628
+ $query_params['hide_empty'] = array(
629
+ 'description' => 'Whether to hide terms not assigned to any posts.',
630
+ 'type' => 'boolean',
631
+ 'default' => false,
632
+ );
633
+ $query_params['search'] = array(
634
+ 'description' => 'Search keyword.',
635
+ 'type' => 'string',
636
+ 'sanitize_callback' => 'sanitize_text_field',
637
+ );
638
  $taxonomy = get_taxonomy( $this->taxonomy );
639
  if ( $taxonomy->hierarchical ) {
640
  $query_params['parent'] = array(
641
  'description' => 'Limit result set to terms assigned to a specific parent term.',
642
  'type' => 'integer',
 
643
  );
644
  }
645
  return $query_params;
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -21,7 +21,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
21
  'methods' => WP_REST_Server::CREATABLE,
22
  'callback' => array( $this, 'create_item' ),
23
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
24
- 'args' => array_merge( $this->get_endpoint_args_for_item_schema( true ), array(
25
  'password' => array(
26
  'required' => true,
27
  ),
@@ -45,7 +45,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
45
  'methods' => WP_REST_Server::EDITABLE,
46
  'callback' => array( $this, 'update_item' ),
47
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
48
- 'args' => array_merge( $this->get_endpoint_args_for_item_schema( false ), array(
49
  'password' => array(),
50
  ) ),
51
  ),
@@ -54,6 +54,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
54
  'callback' => array( $this, 'delete_item' ),
55
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
56
  'args' => array(
 
 
 
57
  'reassign' => array(),
58
  ),
59
  ),
@@ -99,11 +102,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
99
  }
100
 
101
  /**
102
- * Filter arguments, before passing to WP_User_Query, when querying users via the REST API
103
  *
104
  * @see https://codex.wordpress.org/Class_Reference/WP_User_Query
105
- * @param array $prepared_args Arguments for WP_User_Query
106
- * @param WP_REST_Request $request The current request
 
107
  */
108
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
109
 
@@ -235,13 +239,13 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
235
  $this->update_additional_fields_for_object( $user, $request );
236
 
237
  /**
238
- * Fires after a user is created via the REST API
239
  *
240
- * @param object $user Data used to create user (not a WP_User object)
241
- * @param WP_REST_Request $request Request object.
242
- * @param bool $bool A boolean that is false.
243
  */
244
- do_action( 'rest_insert_user', $user, $request, false );
245
 
246
  $response = $this->get_item( array(
247
  'id' => $user_id,
@@ -299,8 +303,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
299
 
300
  $this->update_additional_fields_for_object( $user, $request );
301
 
 
302
  do_action( 'rest_insert_user', $user, $request, false );
303
-
304
  $response = $this->get_item( array(
305
  'id' => $user_id,
306
  'context' => 'edit',
@@ -322,7 +326,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
322
 
323
  // We don't support trashing for this type, error out
324
  if ( ! $force ) {
325
- return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
326
  }
327
 
328
  $user = get_userdata( $id );
@@ -340,12 +344,27 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
340
  $get_request->set_param( 'context', 'edit' );
341
  $orig_user = $this->prepare_item_for_response( $user, $get_request );
342
 
 
 
 
 
 
 
 
343
  $result = wp_delete_user( $id, $reassign );
344
 
345
  if ( ! $result ) {
346
  return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
347
  }
348
 
 
 
 
 
 
 
 
 
349
  return $orig_user;
350
  }
351
 
@@ -440,7 +459,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
440
  *
441
  * @param object $user User object.
442
  * @param WP_REST_Request $request Request object.
443
- * @return array $data Response data.
444
  */
445
  public function prepare_item_for_response( $user, $request ) {
446
  $data = array(
@@ -473,11 +492,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
473
  $data->add_links( $this->prepare_links( $user ) );
474
 
475
  /**
476
- * Filter user data before returning via the REST API
477
  *
478
- * @param WP_REST_Response $data Response data
479
- * @param object $user User object used to create response
480
- * @param WP_REST_Request $request Request object.
481
  */
482
  return apply_filters( 'rest_prepare_user', $data, $user, $request );
483
  }
@@ -551,10 +570,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
551
  }
552
 
553
  /**
554
- * Filter user data before inserting user via REST API
555
  *
556
- * @param object $prepared_user User object.
557
- * @param WP_REST_Request $request Request object.
558
  */
559
  return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
560
  }
@@ -575,6 +594,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
575
  // Multisite super admins can freely edit their blog roles -- they possess all caps.
576
  if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || get_current_user_id() !== $user_id || $potential_role->has_cap( 'edit_users' ) ) {
577
  // The new role must be editable by the logged-in user.
 
 
 
 
578
  $editable_roles = get_editable_roles();
579
  if ( empty( $editable_roles[ $role ] ) ) {
580
  return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
@@ -610,38 +633,28 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
610
  'title' => 'user',
611
  'type' => 'object',
612
  'properties' => array(
613
- 'avatar_urls' => array(
614
- 'description' => 'Avatar URLs for the object.',
615
- 'type' => 'object',
616
  'context' => array( 'embed', 'view', 'edit' ),
617
  'readonly' => true,
618
- 'properties' => $avatar_properties,
619
  ),
620
- 'capabilities' => array(
621
- 'description' => 'All capabilities assigned to the user.',
622
- 'type' => 'object',
623
- 'context' => array( 'view', 'edit' ),
624
- ),
625
- 'description' => array(
626
- 'description' => 'Description of the object.',
627
  'type' => 'string',
628
- 'context' => array( 'embed', 'view', 'edit' ),
 
629
  'arg_options' => array(
630
- 'sanitize_callback' => 'wp_filter_post_kses',
631
  ),
632
  ),
633
- 'email' => array(
634
- 'description' => 'The email address for the object.',
635
  'type' => 'string',
636
- 'format' => 'email',
637
- 'context' => array( 'view', 'edit' ),
638
- 'required' => true,
639
- ),
640
- 'extra_capabilities' => array(
641
- 'description' => 'Any extra capabilities assigned to the user.',
642
- 'type' => 'object',
643
- 'context' => array( 'edit' ),
644
- 'readonly' => true,
645
  ),
646
  'first_name' => array(
647
  'description' => 'First name for the object.',
@@ -651,12 +664,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
651
  'sanitize_callback' => 'sanitize_text_field',
652
  ),
653
  ),
654
- 'id' => array(
655
- 'description' => 'Unique identifier for the object.',
656
- 'type' => 'integer',
657
- 'context' => array( 'embed', 'view', 'edit' ),
658
- 'readonly' => true,
659
- ),
660
  'last_name' => array(
661
  'description' => 'Last name for the object.',
662
  'type' => 'string',
@@ -665,21 +672,42 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
665
  'sanitize_callback' => 'sanitize_text_field',
666
  ),
667
  ),
668
- 'link' => array(
669
- 'description' => 'Author URL to the object.',
 
 
 
 
 
 
 
670
  'type' => 'string',
671
  'format' => 'uri',
672
  'context' => array( 'embed', 'view', 'edit' ),
673
  'readonly' => true,
674
  ),
675
- 'name' => array(
676
- 'description' => 'Display name for the object.',
677
  'type' => 'string',
678
  'context' => array( 'embed', 'view', 'edit' ),
679
  'arg_options' => array(
680
- 'sanitize_callback' => 'sanitize_text_field',
681
  ),
682
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  'nickname' => array(
684
  'description' => 'The nickname for the object.',
685
  'type' => 'string',
@@ -688,6 +716,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
688
  'sanitize_callback' => 'sanitize_text_field',
689
  ),
690
  ),
 
 
 
 
 
 
 
 
691
  'registered_date' => array(
692
  'description' => 'Registration date for the user.',
693
  'type' => 'date-time',
@@ -705,29 +741,16 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
705
  'type' => 'string',
706
  'enum' => array_keys( $wp_roles->role_objects ),
707
  ),
708
- 'slug' => array(
709
- 'description' => 'An alphanumeric identifier for the object unique to its type.',
710
- 'type' => 'string',
711
  'context' => array( 'view', 'edit' ),
712
- 'arg_options' => array(
713
- 'sanitize_callback' => 'sanitize_title',
714
- ),
715
- ),
716
- 'url' => array(
717
- 'description' => 'URL of the object.',
718
- 'type' => 'string',
719
- 'format' => 'uri',
720
- 'context' => array( 'embed', 'view', 'edit' ),
721
- 'readonly' => true,
722
  ),
723
- 'username' => array(
724
- 'description' => 'Login name for the user.',
725
- 'type' => 'string',
726
  'context' => array( 'edit' ),
727
- 'required' => true,
728
- 'arg_options' => array(
729
- 'sanitize_callback' => 'sanitize_user',
730
- ),
731
  ),
732
  ),
733
  );
@@ -744,7 +767,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
744
  $query_params['context'] = array(
745
  'default' => 'view',
746
  'description' => 'Change the response format based on request context.',
747
- 'enum' => array( 'view', 'edit' ),
748
  'sanitize_callback' => 'sanitize_key',
749
  'type' => 'string',
750
  );
21
  'methods' => WP_REST_Server::CREATABLE,
22
  'callback' => array( $this, 'create_item' ),
23
  'permission_callback' => array( $this, 'create_item_permissions_check' ),
24
+ 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
25
  'password' => array(
26
  'required' => true,
27
  ),
45
  'methods' => WP_REST_Server::EDITABLE,
46
  'callback' => array( $this, 'update_item' ),
47
  'permission_callback' => array( $this, 'update_item_permissions_check' ),
48
+ 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), array(
49
  'password' => array(),
50
  ) ),
51
  ),
54
  'callback' => array( $this, 'delete_item' ),
55
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
56
  'args' => array(
57
+ 'force' => array(
58
+ 'default' => false,
59
+ ),
60
  'reassign' => array(),
61
  ),
62
  ),
102
  }
103
 
104
  /**
105
+ * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
106
  *
107
  * @see https://codex.wordpress.org/Class_Reference/WP_User_Query
108
+ *
109
+ * @param array $prepared_args Array of arguments for WP_User_Query.
110
+ * @param WP_REST_Request $request The current request.
111
  */
112
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
113
 
239
  $this->update_additional_fields_for_object( $user, $request );
240
 
241
  /**
242
+ * Fires after a user is created or updated via the REST API.
243
  *
244
+ * @param object $user Data used to create the user (not a WP_User object).
245
+ * @param WP_REST_Request $request Request object.
246
+ * @param bool $creating True when creating user, false when updating user.
247
  */
248
+ do_action( 'rest_insert_user', $user, $request, true );
249
 
250
  $response = $this->get_item( array(
251
  'id' => $user_id,
303
 
304
  $this->update_additional_fields_for_object( $user, $request );
305
 
306
+ /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
307
  do_action( 'rest_insert_user', $user, $request, false );
 
308
  $response = $this->get_item( array(
309
  'id' => $user_id,
310
  'context' => 'edit',
326
 
327
  // We don't support trashing for this type, error out
328
  if ( ! $force ) {
329
+ return new WP_Error( 'rest_trash_not_supported', __( 'Users do not support trashing.' ), array( 'status' => 501 ) );
330
  }
331
 
332
  $user = get_userdata( $id );
344
  $get_request->set_param( 'context', 'edit' );
345
  $orig_user = $this->prepare_item_for_response( $user, $get_request );
346
 
347
+ $data = $orig_user->get_data();
348
+ $data = array(
349
+ 'data' => $data,
350
+ 'deleted' => true,
351
+ );
352
+ $orig_user->set_data( $data );
353
+
354
  $result = wp_delete_user( $id, $reassign );
355
 
356
  if ( ! $result ) {
357
  return new WP_Error( 'rest_cannot_delete', __( 'The user cannot be deleted.' ), array( 'status' => 500 ) );
358
  }
359
 
360
+ /**
361
+ * Fires after a user is deleted via the REST API.
362
+ *
363
+ * @param WP_User $user The user data.
364
+ * @param WP_REST_Request $request The request sent to the API.
365
+ */
366
+ do_action( 'rest_delete_user', $user, $data, $request );
367
+
368
  return $orig_user;
369
  }
370
 
459
  *
460
  * @param object $user User object.
461
  * @param WP_REST_Request $request Request object.
462
+ * @return WP_REST_Response Response data.
463
  */
464
  public function prepare_item_for_response( $user, $request ) {
465
  $data = array(
492
  $data->add_links( $this->prepare_links( $user ) );
493
 
494
  /**
495
+ * Filter user data returned from the REST API.
496
  *
497
+ * @param WP_REST_Response $data Response data.
498
+ * @param object $user User object used to create response.
499
+ * @param WP_REST_Request $request Request object.
500
  */
501
  return apply_filters( 'rest_prepare_user', $data, $user, $request );
502
  }
570
  }
571
 
572
  /**
573
+ * Filter user data before inserting user via the REST API.
574
  *
575
+ * @param object $prepared_user User object.
576
+ * @param WP_REST_Request $request Request object.
577
  */
578
  return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
579
  }
594
  // Multisite super admins can freely edit their blog roles -- they possess all caps.
595
  if ( ( is_multisite() && current_user_can( 'manage_sites' ) ) || get_current_user_id() !== $user_id || $potential_role->has_cap( 'edit_users' ) ) {
596
  // The new role must be editable by the logged-in user.
597
+
598
+ /** Include admin functions to get access to get_editable_roles() */
599
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
600
+
601
  $editable_roles = get_editable_roles();
602
  if ( empty( $editable_roles[ $role ] ) ) {
603
  return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give users that role.' ), array( 'status' => 403 ) );
633
  'title' => 'user',
634
  'type' => 'object',
635
  'properties' => array(
636
+ 'id' => array(
637
+ 'description' => 'Unique identifier for the object.',
638
+ 'type' => 'integer',
639
  'context' => array( 'embed', 'view', 'edit' ),
640
  'readonly' => true,
 
641
  ),
642
+ 'username' => array(
643
+ 'description' => 'Login name for the user.',
 
 
 
 
 
644
  'type' => 'string',
645
+ 'context' => array( 'edit' ),
646
+ 'required' => true,
647
  'arg_options' => array(
648
+ 'sanitize_callback' => 'sanitize_user',
649
  ),
650
  ),
651
+ 'name' => array(
652
+ 'description' => 'Display name for the object.',
653
  'type' => 'string',
654
+ 'context' => array( 'embed', 'view', 'edit' ),
655
+ 'arg_options' => array(
656
+ 'sanitize_callback' => 'sanitize_text_field',
657
+ ),
 
 
 
 
 
658
  ),
659
  'first_name' => array(
660
  'description' => 'First name for the object.',
664
  'sanitize_callback' => 'sanitize_text_field',
665
  ),
666
  ),
 
 
 
 
 
 
667
  'last_name' => array(
668
  'description' => 'Last name for the object.',
669
  'type' => 'string',
672
  'sanitize_callback' => 'sanitize_text_field',
673
  ),
674
  ),
675
+ 'email' => array(
676
+ 'description' => 'The email address for the object.',
677
+ 'type' => 'string',
678
+ 'format' => 'email',
679
+ 'context' => array( 'view', 'edit' ),
680
+ 'required' => true,
681
+ ),
682
+ 'url' => array(
683
+ 'description' => 'URL of the object.',
684
  'type' => 'string',
685
  'format' => 'uri',
686
  'context' => array( 'embed', 'view', 'edit' ),
687
  'readonly' => true,
688
  ),
689
+ 'description' => array(
690
+ 'description' => 'Description of the object.',
691
  'type' => 'string',
692
  'context' => array( 'embed', 'view', 'edit' ),
693
  'arg_options' => array(
694
+ 'sanitize_callback' => 'wp_filter_post_kses',
695
  ),
696
  ),
697
+ 'link' => array(
698
+ 'description' => 'Author URL to the object.',
699
+ 'type' => 'string',
700
+ 'format' => 'uri',
701
+ 'context' => array( 'embed', 'view', 'edit' ),
702
+ 'readonly' => true,
703
+ ),
704
+ 'avatar_urls' => array(
705
+ 'description' => 'Avatar URLs for the object.',
706
+ 'type' => 'object',
707
+ 'context' => array( 'embed', 'view', 'edit' ),
708
+ 'readonly' => true,
709
+ 'properties' => $avatar_properties,
710
+ ),
711
  'nickname' => array(
712
  'description' => 'The nickname for the object.',
713
  'type' => 'string',
716
  'sanitize_callback' => 'sanitize_text_field',
717
  ),
718
  ),
719
+ 'slug' => array(
720
+ 'description' => 'An alphanumeric identifier for the object unique to its type.',
721
+ 'type' => 'string',
722
+ 'context' => array( 'embed', 'view', 'edit' ),
723
+ 'arg_options' => array(
724
+ 'sanitize_callback' => 'sanitize_title',
725
+ ),
726
+ ),
727
  'registered_date' => array(
728
  'description' => 'Registration date for the user.',
729
  'type' => 'date-time',
741
  'type' => 'string',
742
  'enum' => array_keys( $wp_roles->role_objects ),
743
  ),
744
+ 'capabilities' => array(
745
+ 'description' => 'All capabilities assigned to the user.',
746
+ 'type' => 'object',
747
  'context' => array( 'view', 'edit' ),
 
 
 
 
 
 
 
 
 
 
748
  ),
749
+ 'extra_capabilities' => array(
750
+ 'description' => 'Any extra capabilities assigned to the user.',
751
+ 'type' => 'object',
752
  'context' => array( 'edit' ),
753
+ 'readonly' => true,
 
 
 
754
  ),
755
  ),
756
  );
767
  $query_params['context'] = array(
768
  'default' => 'view',
769
  'description' => 'Change the response format based on request context.',
770
+ 'enum' => array( 'embed', 'view', 'edit' ),
771
  'sanitize_callback' => 'sanitize_key',
772
  'type' => 'string',
773
  );
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-beta5
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -14,54 +14,77 @@ if ( ! defined( 'REST_API_VERSION' ) ) {
14
  require_once dirname( __FILE__ ) . '/core/rest-api.php';
15
  }
16
 
17
- /** Include admin functions that are used in the endpoints, such as get_page_templates() */
18
- require_once ABSPATH . 'wp-admin/includes/admin.php';
19
-
20
- /** v1 Compatibility */
21
- include_once( dirname( __FILE__ ) . '/compatibility-v1.php' );
22
-
23
- /** WP_REST_Controller class */
24
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
25
 
26
- /** WP_REST_Posts_Controller class */
 
 
27
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
28
 
29
- /** WP_REST_Attachments_Controller class */
 
 
30
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
31
 
32
- /** WP_REST_Post_Types_Controller class */
 
 
33
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
34
 
35
- /** WP_REST_Post_Statuses_Controller class */
 
 
36
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
37
 
38
- /** WP_REST_Revisions_Controller class */
 
 
39
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
40
 
41
- /** WP_REST_Taxonomies_Controller class */
 
 
42
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
43
 
44
- /** WP_REST_Terms_Controller class */
 
 
45
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
46
 
47
- /** WP_REST_Users_Controller class */
 
 
48
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
49
 
50
- /** WP_REST_Comments_Controller class */
 
 
51
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
52
 
53
- /** WP_REST_Meta_Controller class */
 
 
54
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
55
 
56
- /** WP_REST_Meta_Posts_Controller class */
 
 
57
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
58
 
59
- /** WP_REST_Posts_Terms_Controller class */
 
 
60
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
61
 
62
- /** REST extras */
 
 
63
  include_once( dirname( __FILE__ ) . '/extras.php' );
64
 
 
65
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
66
  add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
67
 
@@ -72,25 +95,29 @@ add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
72
  *
73
  * @since 4.4.0
74
  *
75
- * @global array $wp_post_types Registered post types.
76
  */
77
  function _add_extra_api_post_type_arguments() {
78
  global $wp_post_types;
79
 
80
- $wp_post_types['post']->show_in_rest = true;
81
- $wp_post_types['post']->rest_base = 'posts';
82
- $wp_post_types['post']->rest_controller_class = 'WP_REST_Posts_Controller';
83
-
84
- $wp_post_types['page']->show_in_rest = true;
85
- $wp_post_types['page']->rest_base = 'pages';
86
- $wp_post_types['page']->rest_controller_class = 'WP_REST_Posts_Controller';
87
 
88
- $wp_post_types['attachment']->show_in_rest = true;
89
- $wp_post_types['attachment']->rest_base = 'media';
90
- $wp_post_types['attachment']->rest_controller_class = 'WP_REST_Attachments_Controller';
 
 
91
 
 
 
 
 
 
92
  }
93
- add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
94
 
95
  /**
96
  * Adds extra taxonomy registration arguments.
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-beta6
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
14
  require_once dirname( __FILE__ ) . '/core/rest-api.php';
15
  }
16
 
17
+ /**
18
+ * WP_REST_Controller class.
19
+ */
 
 
 
 
20
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
21
 
22
+ /**
23
+ * WP_REST_Posts_Controller class.
24
+ */
25
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
26
 
27
+ /**
28
+ * WP_REST_Attachments_Controller class.
29
+ */
30
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
31
 
32
+ /**
33
+ * WP_REST_Post_Types_Controller class.
34
+ */
35
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
36
 
37
+ /**
38
+ * WP_REST_Post_Statuses_Controller class.
39
+ */
40
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
41
 
42
+ /**
43
+ * WP_REST_Revisions_Controller class.
44
+ */
45
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
46
 
47
+ /**
48
+ * WP_REST_Taxonomies_Controller class.
49
+ */
50
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
51
 
52
+ /**
53
+ * WP_REST_Terms_Controller class.
54
+ */
55
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
56
 
57
+ /**
58
+ * WP_REST_Users_Controller class.
59
+ */
60
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
61
 
62
+ /**
63
+ * WP_REST_Comments_Controller class.
64
+ */
65
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
66
 
67
+ /**
68
+ * WP_REST_Meta_Controller class.
69
+ */
70
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
71
 
72
+ /**
73
+ * WP_REST_Meta_Posts_Controller class.
74
+ */
75
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
76
 
77
+ /**
78
+ * WP_REST_Posts_Terms_Controller class.
79
+ */
80
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
81
 
82
+ /**
83
+ * REST extras.
84
+ */
85
  include_once( dirname( __FILE__ ) . '/extras.php' );
86
 
87
+ add_filter( 'init', '_add_extra_api_post_type_arguments', 11 );
88
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
89
  add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
90
 
95
  *
96
  * @since 4.4.0
97
  *
98
+ * @global array $wp_taxonomies Registered taxonomies.
99
  */
100
  function _add_extra_api_post_type_arguments() {
101
  global $wp_post_types;
102
 
103
+ if ( isset( $wp_post_types['post'] ) ) {
104
+ $wp_post_types['post']->show_in_rest = true;
105
+ $wp_post_types['post']->rest_base = 'posts';
106
+ $wp_post_types['post']->rest_controller_class = 'WP_REST_Posts_Controller';
107
+ }
 
 
108
 
109
+ if ( isset( $wp_post_types['page'] ) ) {
110
+ $wp_post_types['page']->show_in_rest = true;
111
+ $wp_post_types['page']->rest_base = 'pages';
112
+ $wp_post_types['page']->rest_controller_class = 'WP_REST_Posts_Controller';
113
+ }
114
 
115
+ if ( isset( $wp_post_types['attachment'] ) ) {
116
+ $wp_post_types['attachment']->show_in_rest = true;
117
+ $wp_post_types['attachment']->rest_base = 'media';
118
+ $wp_post_types['attachment']->rest_controller_class = 'WP_REST_Attachments_Controller';
119
+ }
120
  }
 
121
 
122
  /**
123
  * Adds extra taxonomy registration arguments.
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === WordPress REST API (Version 2) ===
2
  Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
- Requires at least: 4.3-alpha
5
- Tested up to: 4.4-beta
6
- Stable tag: 2.0-beta5
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,6 +36,102 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 2.0 Beta 5.0 =
40
 
41
  * Load api-core as a compatibility library
1
  === WordPress REST API (Version 2) ===
2
  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-beta6
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 6.0 =
40
+
41
+ * Remove global inclusion of wp-admin/includes/admin.php
42
+
43
+ For a long time, the REST API loaded wp-admin/includes/admin.php to make use of specific admin utilities. Now, it only loads those admin utilities when it needs them.
44
+
45
+ If your custom endpoints make use of admin utilities, you'll need to make sure to load wp-admin/includes/admin.php before you use them.
46
+
47
+ (props @joehoyle, [#1696](https://github.com/WP-API/WP-API/pull/1696))
48
+
49
+ * Link directly to the featured image in a Post's links.
50
+
51
+ (props @rmccue, [#1563](https://github.com/WP-API/WP-API/pull/1563), [#1711](https://github.com/WP-API/WP-API/pull/1711))
52
+
53
+ * Provide object type as callback argument for custom API fields.
54
+
55
+ (props @jtsternberg, [#1714](https://github.com/WP-API/WP-API/pull/1714))
56
+
57
+ * Change users schema order to be order of importance instead of alpha.
58
+
59
+ (props @rachelbaker, [#1708](https://github.com/WP-API/WP-API/pull/1708))
60
+
61
+ * Clarify documentation for `date` and `modified` attributes.
62
+
63
+ (props @danielbachhuber, [#1715](https://github.com/WP-API/WP-API/pull/1715))
64
+
65
+ * Update the wp-api.js client from the client-js repo.
66
+
67
+ (props @rachelbaker, [#1709](https://github.com/WP-API/WP-API/pull/1709))
68
+
69
+ * Fix the `format` enum to be an array of strings.
70
+
71
+ (props @joehoyle, [#1707](https://github.com/WP-API/WP-API/pull/1707))
72
+
73
+ * Run revisions for collection through `prepare_response_for_collection()`.
74
+
75
+ (props @danielbachhuber, @rachelbaker, [#1671](https://github.com/WP-API/WP-API/pull/1671))
76
+
77
+ * Expose `date_gmt` for `view` context of Posts and Comments.
78
+
79
+ (props @danielbachhuber, [#1690](https://github.com/WP-API/WP-API/pull/1690))
80
+
81
+ * Fix PHP and JS docblock formatting.
82
+
83
+ (props @ahmadawais, [#1699](https://github.com/WP-API/WP-API/pull/1698), [#1699](https://github.com/WP-API/WP-API/pull/1699), [#1701](https://github.com/WP-API/WP-API/pull/1701), [#1700](https://github.com/WP-API/WP-API/pull/1700), [#1702](https://github.com/WP-API/WP-API/pull/1702), [#1703](https://github.com/WP-API/WP-API/pull/1703))
84
+
85
+ * Include `media_details` attribute for attachments in embed context.
86
+
87
+ For image attachments, media_details includes a sizes array of image sizes, which is useful for templating.
88
+
89
+ (props @danielbachhuber, [#1667](https://github.com/WP-API/WP-API/pull/1667))
90
+
91
+ * Make `WP_REST_Controller` error messages more helpful by specifying method to subclass.
92
+
93
+ (props @danielbachhuber, [#1670](https://github.com/WP-API/WP-API/pull/1670))
94
+
95
+ * Expose `slug` in `embed` context for Users.
96
+
97
+ `user_nicename` is a public attribute, used in user URLs, so this is safe data to present.
98
+
99
+ (props @danielbachhuber, [#1666](https://github.com/WP-API/WP-API/pull/1666))
100
+
101
+ * Handle falsy value from `wp_count_terms()`, fixing fatal.
102
+
103
+ (props @joehoyle, [#1641](https://github.com/WP-API/WP-API/pull/1641))
104
+
105
+ * Correct methods in `WP_REST_SERVER::EDITABLE` description.
106
+
107
+ (props @rachelbaker, [#1601](https://github.com/WP-API/WP-API/pull/1601))
108
+
109
+ * Add the embed context to Users collection query params.
110
+
111
+ (props @rachelbaker, [#1591](https://github.com/WP-API/WP-API/pull/1591))
112
+
113
+ * Add Terms Controller collection args details.
114
+
115
+ (props @rachelbaker, [#1603](https://github.com/WP-API/WP-API/pull/1603))
116
+
117
+ * Set comment author details from current user.
118
+
119
+ (props @rmccue, [#1580](https://github.com/WP-API/WP-API/pull/1580))
120
+
121
+ * More hook documentation.
122
+
123
+ (props @adamsilverstein, [#1556](https://github.com/WP-API/WP-API/pull/1556), [#1560](https://github.com/WP-API/WP-API/pull/1560))
124
+
125
+ * Return the trashed status of deleted posts/comments.
126
+
127
+ When a post or a comment is deleted, returns a flag to say whether it's been trashed or properly deleted.
128
+
129
+ (props @pento, [#1499](https://github.com/WP-API/WP-API/pull/1499))
130
+
131
+ * In `WP_REST_Posts_Controller::update_item()`, check the post ID based on the proper post type.
132
+
133
+ (props @rachelbaker, [#1497](https://github.com/WP-API/WP-API/pull/1497))
134
+
135
  = 2.0 Beta 5.0 =
136
 
137
  * Load api-core as a compatibility library
wp-api.js CHANGED
@@ -2,7 +2,7 @@
2
  'use strict';
3
 
4
  window.wp = window.wp || {};
5
- var pad;
6
  wp.api = {
7
  models: {},
8
  collections: {},
@@ -10,15 +10,16 @@
10
  };
11
 
12
  /**
13
- * ECMAScript 5 shim, from MDN
14
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
15
  */
16
  if ( ! Date.prototype.toISOString ) {
17
- pad = function( number ) {
18
  var r = String( number );
19
  if ( r.length === 1 ) {
20
  r = '0' + r;
21
  }
 
22
  return r;
23
  };
24
 
@@ -29,35 +30,32 @@
29
  'T' + pad( this.getUTCHours() ) +
30
  ':' + pad( this.getUTCMinutes() ) +
31
  ':' + pad( this.getUTCSeconds() ) +
32
- '.' + String( ( this.getUTCMilliseconds()/1000 ).toFixed( 3 ) ).slice( 2, 5 ) +
33
  'Z';
34
  };
35
  }
36
 
37
-
38
- var origParse = Date.parse;
39
-
40
  /**
41
- * Parse date into ISO8601 format
42
- *
43
- * @param {Date} date
44
  */
45
  wp.api.utils.parseISO8601 = function( date ) {
46
  var timestamp, struct, i, k,
47
  minutesOffset = 0,
48
- numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];;
49
 
50
  // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
51
  // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
52
- // implementations could be faster
53
  // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
54
- if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
55
- // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
56
- for ( i = 0; ( k = numericKeys[i] ); ++i) {
57
  struct[k] = +struct[k] || 0;
58
  }
59
 
60
- // allow undefined days and months
61
  struct[2] = ( +struct[2] || 1 ) - 1;
62
  struct[3] = +struct[3] || 1;
63
 
@@ -77,30 +75,29 @@
77
  return timestamp;
78
  };
79
 
80
-
81
  /**
82
- * Array of parseable dates
83
  *
84
- * @type {string[]}
85
  */
86
  var parseable_dates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ];
87
 
88
  /**
89
- * Mixin for all content that is time stamped
90
  *
91
- * @type {{toJSON: toJSON, parse: parse}}
92
  */
93
  var TimeStampedMixin = {
94
  /**
95
- * Serialize the entity pre-sync
96
  *
97
- * @returns {*}
98
  */
99
  toJSON: function() {
100
  var attributes = _.clone( this.attributes );
101
 
102
- // Serialize Date objects back into 8601 strings
103
- _.each( parseable_dates, function ( key ) {
104
  if ( key in attributes ) {
105
  attributes[key] = attributes[key].toISOString();
106
  }
@@ -110,13 +107,14 @@
110
  },
111
 
112
  /**
113
- * Unserialize the fetched response
114
  *
115
- * @param {*} response
116
- * @returns {*}
117
  */
118
  parse: function( response ) {
119
- // Parse dates into native Date objects
 
120
  _.each( parseable_dates, function ( key ) {
121
  if ( ! ( key in response ) ) {
122
  return;
@@ -126,7 +124,7 @@
126
  response[key] = new Date( timestamp );
127
  });
128
 
129
- // Parse the author into a User object
130
  if ( 'undefined' !== typeof response.author ) {
131
  response.author = new wp.api.models.User( response.author );
132
  }
@@ -136,13 +134,13 @@
136
  };
137
 
138
  /**
139
- * Mixin for all hierarchical content types such as posts
140
  *
141
- * @type {{parent: parent}}
142
  */
143
  var HierarchicalMixin = {
144
  /**
145
- * Get parent object
146
  *
147
  * @returns {Backbone.Model}
148
  */
@@ -150,7 +148,7 @@
150
 
151
  var object, parent = this.get( 'parent' );
152
 
153
- // Return null if we don't have a parent
154
  if ( parent === 0 ) {
155
  return null;
156
  }
@@ -169,31 +167,33 @@
169
  if ( parentModel.collection ) {
170
  return parentModel.collection.get( parent );
171
  } else {
172
- // Otherwise, get the object directly
 
173
  object = new parentModel.constructor( {
174
  id: parent
175
  });
176
 
177
- // Note that this acts asynchronously
178
  object.fetch();
 
179
  return object;
180
  }
181
  }
182
  };
183
 
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
192
  *
193
- * @param {string} method
194
- * @param {Backbone.Model} model
195
- * @param {{beforeSend}, *} options
196
- * @returns {*}
197
  */
198
  sync: function( method, model, options ) {
199
  options = options || {};
@@ -216,14 +216,14 @@
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',
225
 
226
- urlRoot: WP_API_Settings.root + '/users',
227
 
228
  defaults: {
229
  id: null,
@@ -248,14 +248,14 @@
248
  );
249
 
250
  /**
251
- * Model for Taxonomy
252
  */
253
  wp.api.models.Taxonomy = BaseModel.extend(
254
  /** @lends Taxonomy.prototype */
255
  {
256
  idAttribute: 'slug',
257
 
258
- urlRoot: WP_API_Settings.root + '/taxonomies',
259
 
260
  defaults: {
261
  name: '',
@@ -270,7 +270,7 @@
270
  );
271
 
272
  /**
273
- * Backbone model for term
274
  */
275
  wp.api.models.Term = BaseModel.extend(
276
  /** @lends Term.prototype */
@@ -278,7 +278,7 @@
278
  idAttribute: 'id',
279
 
280
  /**
281
- * Return URL for the model
282
  *
283
  * @returns {string}
284
  */
@@ -286,7 +286,7 @@
286
  var id = this.get( 'id' );
287
  id = id || '';
288
 
289
- return WP_API_Settings.root + '/taxonomies/' + this.get( 'taxonomy' ) + '/terms/' + id;
290
  },
291
 
292
  defaults: {
@@ -305,14 +305,14 @@
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',
314
 
315
- urlRoot: WP_API_Settings.root + '/posts',
316
 
317
  defaults: {
318
  id: null,
@@ -341,14 +341,14 @@
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',
350
 
351
- urlRoot: WP_API_Settings.root + '/pages',
352
 
353
  defaults: {
354
  id: null,
@@ -377,7 +377,7 @@
377
  );
378
 
379
  /**
380
- * Backbone model for revisions
381
  */
382
  wp.api.models.Revision = BaseModel.extend( _.extend(
383
  /** @lends Revision.prototype */
@@ -385,14 +385,14 @@
385
  idAttribute: 'id',
386
 
387
  /**
388
- * Return URL for the model
389
  *
390
- * @returns {string}
391
  */
392
  url: function() {
393
  var id = this.get( 'id' ) || '';
394
 
395
- return WP_API_Settings.root + '/posts/' + id + '/revisions';
396
  },
397
 
398
  defaults: {
@@ -415,14 +415,14 @@
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',
424
 
425
- urlRoot: WP_API_Settings.root + '/media',
426
 
427
  defaults: {
428
  id: null,
@@ -451,11 +451,12 @@
451
  },
452
 
453
  /**
454
- * @class Represent a media item
455
- * @augments Backbone.Model
456
  * @constructs
457
  */
458
  initialize: function() {
 
459
  // Todo: what of the parent model is a page?
460
  this.parentModel = wp.api.models.Post;
461
  }
@@ -463,7 +464,7 @@
463
  );
464
 
465
  /**
466
- * Backbone model for comments
467
  */
468
  wp.api.models.Comment = BaseModel.extend( _.extend(
469
  /** @lends Comment.prototype */
@@ -491,9 +492,9 @@
491
  },
492
 
493
  /**
494
- * Return URL for model
495
  *
496
- * @returns {string}
497
  */
498
  url: function() {
499
  var post_id = this.get( 'post' );
@@ -502,20 +503,20 @@
502
  var id = this.get( 'id' );
503
  id = id || '';
504
 
505
- return WP_API_Settings.root + '/posts/' + post_id + '/comments/' + id;
506
  }
507
  }, TimeStampedMixin, HierarchicalMixin )
508
  );
509
 
510
  /**
511
- * Backbone model for single post types
512
  */
513
  wp.api.models.PostType = BaseModel.extend(
514
  /** @lends PostType.prototype */
515
  {
516
  idAttribute: 'slug',
517
 
518
- urlRoot: WP_API_Settings.root + '/posts/types',
519
 
520
  defaults: {
521
  slug: null,
@@ -526,34 +527,34 @@
526
  },
527
 
528
  /**
529
- * Prevent model from being saved
530
  *
531
- * @returns {boolean}
532
  */
533
- save: function () {
534
  return false;
535
  },
536
 
537
  /**
538
- * Prevent model from being deleted
539
  *
540
- * @returns {boolean}
541
  */
542
- 'delete': function () {
543
  return false;
544
  }
545
  }
546
  );
547
 
548
  /**
549
- * Backbone model for a post status
550
  */
551
  wp.api.models.PostStatus = BaseModel.extend(
552
  /** @lends PostStatus.prototype */
553
  {
554
  idAttribute: 'slug',
555
 
556
- urlRoot: WP_API_Settings.root + '/posts/statuses',
557
 
558
  defaults: {
559
  slug: null,
@@ -567,18 +568,18 @@
567
  },
568
 
569
  /**
570
- * Prevent model from being saved
571
  *
572
- * @returns {boolean}
573
  */
574
  save: function() {
575
  return false;
576
  },
577
 
578
  /**
579
- * Prevent model from being deleted
580
  *
581
- * @returns {boolean}
582
  */
583
  'delete': function() {
584
  return false;
@@ -587,14 +588,14 @@
587
  );
588
 
589
  /**
590
- * Contains basic collection functionality such as pagination
591
  */
592
  var BaseCollection = Backbone.Collection.extend(
593
  /** @lends BaseCollection.prototype */
594
  {
595
 
596
  /**
597
- * Setup default state
598
  */
599
  initialize: function() {
600
  this.state = {
@@ -610,10 +611,10 @@
610
  *
611
  * Set nonce header before every Backbone sync.
612
  *
613
- * @param {string} method
614
- * @param {Backbone.Model} model
615
- * @param {{success}, *} options
616
- * @returns {*}
617
  */
618
  sync: function( method, model, options ) {
619
  options = options || {};
@@ -668,10 +669,10 @@
668
  },
669
 
670
  /**
671
- * Fetches the next page of objects if a new page exists
672
  *
673
- * @param {data: {page}} options
674
- * @returns {*}
675
  */
676
  more: function( options ) {
677
  options = options || {};
@@ -695,9 +696,9 @@
695
  },
696
 
697
  /**
698
- * Returns true if there are more pages of objects available
699
  *
700
- * @returns null|boolean
701
  */
702
  hasMore: function() {
703
  if ( this.state.totalPages === null ||
@@ -712,53 +713,59 @@
712
  );
713
 
714
  /**
715
- * Backbone collection for posts
716
  */
717
  wp.api.collections.Posts = BaseCollection.extend(
718
  /** @lends Posts.prototype */
719
  {
720
- url: WP_API_Settings.root + '/posts',
721
 
722
  model: wp.api.models.Post
723
  }
724
  );
725
 
726
  /**
727
- * Backbone collection for pages
728
  */
729
  wp.api.collections.Pages = BaseCollection.extend(
730
  /** @lends Pages.prototype */
731
  {
732
- url: WP_API_Settings.root + '/pages',
733
 
734
  model: wp.api.models.Page
735
  }
736
  );
737
 
738
  /**
739
- * Backbone users collection
740
  */
741
  wp.api.collections.Users = BaseCollection.extend(
742
  /** @lends Users.prototype */
743
  {
744
- url: WP_API_Settings.root + '/users',
745
 
746
  model: wp.api.models.User
747
  }
748
  );
749
 
750
  /**
751
- * Backbone post statuses collection
752
  */
753
  wp.api.collections.PostStatuses = BaseCollection.extend(
754
  /** @lends PostStatuses.prototype */
755
  {
756
- url: WP_API_Settings.root + '/statuses',
757
 
758
  model: wp.api.models.PostStatus,
759
 
760
  parse: function( response ) {
761
- var responseArray = _.values( response );
 
 
 
 
 
 
762
 
763
  return this.constructor.__super__.parse.call( this, responseArray );
764
  }
@@ -766,31 +773,31 @@
766
  );
767
 
768
  /**
769
- * Backbone media library collection
770
  */
771
  wp.api.collections.MediaLibrary = BaseCollection.extend(
772
  /** @lends MediaLibrary.prototype */
773
  {
774
- url: WP_API_Settings.root + '/media',
775
 
776
  model: wp.api.models.Media
777
  }
778
  );
779
 
780
  /**
781
- * Backbone taxonomy collection
782
  */
783
  wp.api.collections.Taxonomies = BaseCollection.extend(
784
  /** @lends Taxonomies.prototype */
785
  {
786
  model: wp.api.models.Taxonomy,
787
 
788
- url: WP_API_Settings.root + '/taxonomies'
789
  }
790
  );
791
 
792
  /**
793
- * Backbone comment collection
794
  */
795
  wp.api.collections.Comments = BaseCollection.extend(
796
  /** @lends Comments.prototype */
@@ -798,23 +805,23 @@
798
  model: wp.api.models.Comment,
799
 
800
  /**
801
- * Return URL for collection
802
  *
803
- * @returns {string}
804
  */
805
- url: WP_API_Settings.root + '/comments'
806
  }
807
  );
808
 
809
  /**
810
- * Backbone post type collection
811
  */
812
  wp.api.collections.PostTypes = BaseCollection.extend(
813
  /** @lends PostTypes.prototype */
814
  {
815
  model: wp.api.models.PostType,
816
 
817
- url: WP_API_Settings.root + '/types',
818
 
819
  parse: function( response ) {
820
  var responseArray = [];
@@ -831,7 +838,7 @@
831
  );
832
 
833
  /**
834
- * Backbone terms collection
835
  *
836
  * Usage: new wp.api.collections.Terms( {}, { taxonomy: 'taxonomy-slug' } )
837
  */
@@ -843,8 +850,8 @@
843
  taxonomy: 'category',
844
 
845
  /**
846
- * @class Represent an array of terms
847
- * @augments Backbone.Collection
848
  * @constructs
849
  */
850
  initialize: function( models, options ) {
@@ -856,20 +863,20 @@
856
  },
857
 
858
  /**
859
- * Return URL for collection
860
  *
861
- * @returns {string}
862
  */
863
  url: function() {
864
- return WP_API_Settings.root + '/terms/' + this.taxonomy;
865
  }
866
  }
867
  );
868
 
869
  /**
870
- * Backbone revisions collection
871
  *
872
- * Usage: new wp.api.collections.Revisions( {}, { parent: POST_ID } )
873
  */
874
  wp.api.collections.Revisions = BaseCollection.extend(
875
  /** @lends Revisions.prototype */
@@ -879,8 +886,8 @@
879
  parent: null,
880
 
881
  /**
882
- * @class Represent an array of revisions
883
- * @augments Backbone.Collection
884
  * @constructs
885
  */
886
  initialize: function( models, options ) {
@@ -892,22 +899,22 @@
892
  },
893
 
894
  /**
895
- * return URL for collection
896
  *
897
- * @returns {string}
898
  */
899
  url: function() {
900
- return WP_API_Settings.root + '/posts/' + this.parent + '/revisions';
901
  }
902
  }
903
  );
904
 
905
  /**
906
- * Todo: Handle schema endpoints
907
  */
908
 
909
  /**
910
- * Todo: Handle post meta
911
  */
912
 
913
  })( WP_API_Settings, Backbone, _, window, ( void 0 ) );
2
  'use strict';
3
 
4
  window.wp = window.wp || {};
5
+
6
  wp.api = {
7
  models: {},
8
  collections: {},
10
  };
11
 
12
  /**
13
+ * ECMAScript 5 shim, from MDN.
14
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
15
  */
16
  if ( ! Date.prototype.toISOString ) {
17
+ var pad = function( number ) {
18
  var r = String( number );
19
  if ( r.length === 1 ) {
20
  r = '0' + r;
21
  }
22
+
23
  return r;
24
  };
25
 
30
  'T' + pad( this.getUTCHours() ) +
31
  ':' + pad( this.getUTCMinutes() ) +
32
  ':' + pad( this.getUTCSeconds() ) +
33
+ '.' + String( ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ) ).slice( 2, 5 ) +
34
  'Z';
35
  };
36
  }
37
 
 
 
 
38
  /**
39
+ * Parse date into ISO8601 format.
40
+ *
41
+ * @param {Date} date.
42
  */
43
  wp.api.utils.parseISO8601 = function( date ) {
44
  var timestamp, struct, i, k,
45
  minutesOffset = 0,
46
+ numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
47
 
48
  // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
49
  // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
50
+ // implementations could be faster.
51
  // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
52
+ if ( ( struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec( date ) ) ) {
53
+ // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC.
54
+ for ( i = 0; ( k = numericKeys[i] ); ++i ) {
55
  struct[k] = +struct[k] || 0;
56
  }
57
 
58
+ // Allow undefined days and months.
59
  struct[2] = ( +struct[2] || 1 ) - 1;
60
  struct[3] = +struct[3] || 1;
61
 
75
  return timestamp;
76
  };
77
 
 
78
  /**
79
+ * Array of parseable dates.
80
  *
81
+ * @type {string[]}.
82
  */
83
  var parseable_dates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ];
84
 
85
  /**
86
+ * Mixin for all content that is time stamped.
87
  *
88
+ * @type {{toJSON: toJSON, parse: parse}}.
89
  */
90
  var TimeStampedMixin = {
91
  /**
92
+ * Serialize the entity pre-sync.
93
  *
94
+ * @returns {*}.
95
  */
96
  toJSON: function() {
97
  var attributes = _.clone( this.attributes );
98
 
99
+ // Serialize Date objects back into 8601 strings.
100
+ _.each( parseable_dates, function( key ) {
101
  if ( key in attributes ) {
102
  attributes[key] = attributes[key].toISOString();
103
  }
107
  },
108
 
109
  /**
110
+ * Unserialize the fetched response.
111
  *
112
+ * @param {*} response.
113
+ * @returns {*}.
114
  */
115
  parse: function( response ) {
116
+
117
+ // Parse dates into native Date objects.
118
  _.each( parseable_dates, function ( key ) {
119
  if ( ! ( key in response ) ) {
120
  return;
124
  response[key] = new Date( timestamp );
125
  });
126
 
127
+ // Parse the author into a User object.
128
  if ( 'undefined' !== typeof response.author ) {
129
  response.author = new wp.api.models.User( response.author );
130
  }
134
  };
135
 
136
  /**
137
+ * Mixin for all hierarchical content types such as posts.
138
  *
139
+ * @type {{parent: parent}}.
140
  */
141
  var HierarchicalMixin = {
142
  /**
143
+ * Get parent object.
144
  *
145
  * @returns {Backbone.Model}
146
  */
148
 
149
  var object, parent = this.get( 'parent' );
150
 
151
+ // Return null if we don't have a parent.
152
  if ( parent === 0 ) {
153
  return null;
154
  }
167
  if ( parentModel.collection ) {
168
  return parentModel.collection.get( parent );
169
  } else {
170
+
171
+ // Otherwise, get the object directly.
172
  object = new parentModel.constructor( {
173
  id: parent
174
  });
175
 
176
+ // Note that this acts asynchronously.
177
  object.fetch();
178
+
179
  return object;
180
  }
181
  }
182
  };
183
 
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.
192
  *
193
+ * @param {string} method.
194
+ * @param {Backbone.Model} model.
195
+ * @param {{beforeSend}, *} options.
196
+ * @returns {*}.
197
  */
198
  sync: function( method, model, options ) {
199
  options = options || {};
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',
225
 
226
+ urlRoot: WP_API_Settings.root + 'wp/v2/users',
227
 
228
  defaults: {
229
  id: null,
248
  );
249
 
250
  /**
251
+ * Model for Taxonomy.
252
  */
253
  wp.api.models.Taxonomy = BaseModel.extend(
254
  /** @lends Taxonomy.prototype */
255
  {
256
  idAttribute: 'slug',
257
 
258
+ urlRoot: WP_API_Settings.root + 'wp/v2/taxonomies',
259
 
260
  defaults: {
261
  name: '',
270
  );
271
 
272
  /**
273
+ * Backbone model for term.
274
  */
275
  wp.api.models.Term = BaseModel.extend(
276
  /** @lends Term.prototype */
278
  idAttribute: 'id',
279
 
280
  /**
281
+ * Return URL for the model.
282
  *
283
  * @returns {string}
284
  */
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: {
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',
314
 
315
+ urlRoot: WP_API_Settings.root + 'wp/v2/posts',
316
 
317
  defaults: {
318
  id: null,
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',
350
 
351
+ urlRoot: WP_API_Settings.root + 'wp/v2/pages',
352
 
353
  defaults: {
354
  id: null,
377
  );
378
 
379
  /**
380
+ * Backbone model for revisions.
381
  */
382
  wp.api.models.Revision = BaseModel.extend( _.extend(
383
  /** @lends Revision.prototype */
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: {
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',
424
 
425
+ urlRoot: WP_API_Settings.root + 'wp/v2/media',
426
 
427
  defaults: {
428
  id: null,
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
  }
464
  );
465
 
466
  /**
467
+ * Backbone model for comments.
468
  */
469
  wp.api.models.Comment = BaseModel.extend( _.extend(
470
  /** @lends Comment.prototype */
492
  },
493
 
494
  /**
495
+ * Return URL for model.
496
  *
497
+ * @returns {string}.
498
  */
499
  url: function() {
500
  var post_id = this.get( 'post' );
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,
527
  },
528
 
529
  /**
530
+ * Prevent model from being saved.
531
  *
532
+ * @returns {boolean}.
533
  */
534
+ save: function() {
535
  return false;
536
  },
537
 
538
  /**
539
+ * Prevent model from being deleted.
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,
568
  },
569
 
570
  /**
571
+ * Prevent model from being saved.
572
  *
573
+ * @returns {boolean}.
574
  */
575
  save: function() {
576
  return false;
577
  },
578
 
579
  /**
580
+ * Prevent model from being deleted.
581
  *
582
+ * @returns {boolean}.
583
  */
584
  'delete': function() {
585
  return false;
588
  );
589
 
590
  /**
591
+ * Contains basic collection functionality such as pagination.
592
  */
593
  var BaseCollection = Backbone.Collection.extend(
594
  /** @lends BaseCollection.prototype */
595
  {
596
 
597
  /**
598
+ * Setup default state.
599
  */
600
  initialize: function() {
601
  this.state = {
611
  *
612
  * Set nonce header before every Backbone sync.
613
  *
614
+ * @param {string} method.
615
+ * @param {Backbone.Model} model.
616
+ * @param {{success}, *} options.
617
+ * @returns {*}.
618
  */
619
  sync: function( method, model, options ) {
620
  options = options || {};
669
  },
670
 
671
  /**
672
+ * Fetches the next page of objects if a new page exists.
673
  *
674
+ * @param {data: {page}} options.
675
+ * @returns {*}.
676
  */
677
  more: function( options ) {
678
  options = options || {};
696
  },
697
 
698
  /**
699
+ * Returns true if there are more pages of objects available.
700
  *
701
+ * @returns null|boolean.
702
  */
703
  hasMore: function() {
704
  if ( this.state.totalPages === null ||
713
  );
714
 
715
  /**
716
+ * Backbone collection for posts.
717
  */
718
  wp.api.collections.Posts = BaseCollection.extend(
719
  /** @lends Posts.prototype */
720
  {
721
+ url: WP_API_Settings.root + 'wp/v2/posts',
722
 
723
  model: wp.api.models.Post
724
  }
725
  );
726
 
727
  /**
728
+ * Backbone collection for pages.
729
  */
730
  wp.api.collections.Pages = BaseCollection.extend(
731
  /** @lends Pages.prototype */
732
  {
733
+ url: WP_API_Settings.root + 'wp/v2/pages',
734
 
735
  model: wp.api.models.Page
736
  }
737
  );
738
 
739
  /**
740
+ * Backbone users collection.
741
  */
742
  wp.api.collections.Users = BaseCollection.extend(
743
  /** @lends Users.prototype */
744
  {
745
+ url: WP_API_Settings.root + 'wp/v2/users',
746
 
747
  model: wp.api.models.User
748
  }
749
  );
750
 
751
  /**
752
+ * Backbone post statuses collection.
753
  */
754
  wp.api.collections.PostStatuses = BaseCollection.extend(
755
  /** @lends PostStatuses.prototype */
756
  {
757
+ url: WP_API_Settings.root + 'wp/v2/statuses',
758
 
759
  model: wp.api.models.PostStatus,
760
 
761
  parse: function( response ) {
762
+ var responseArray = [];
763
+
764
+ for ( var property in response ) {
765
+ if ( response.hasOwnProperty( property ) ) {
766
+ responseArray.push( response[property] );
767
+ }
768
+ }
769
 
770
  return this.constructor.__super__.parse.call( this, responseArray );
771
  }
773
  );
774
 
775
  /**
776
+ * Backbone media library collection.
777
  */
778
  wp.api.collections.MediaLibrary = BaseCollection.extend(
779
  /** @lends MediaLibrary.prototype */
780
  {
781
+ url: WP_API_Settings.root + 'wp/v2/media',
782
 
783
  model: wp.api.models.Media
784
  }
785
  );
786
 
787
  /**
788
+ * Backbone taxonomy collection.
789
  */
790
  wp.api.collections.Taxonomies = BaseCollection.extend(
791
  /** @lends Taxonomies.prototype */
792
  {
793
  model: wp.api.models.Taxonomy,
794
 
795
+ url: WP_API_Settings.root + 'wp/v2/taxonomies'
796
  }
797
  );
798
 
799
  /**
800
+ * Backbone comment collection.
801
  */
802
  wp.api.collections.Comments = BaseCollection.extend(
803
  /** @lends Comments.prototype */
805
  model: wp.api.models.Comment,
806
 
807
  /**
808
+ * Return URL for collection.
809
  *
810
+ * @returns {string}.
811
  */
812
+ url: WP_API_Settings.root + 'wp/v2/comments'
813
  }
814
  );
815
 
816
  /**
817
+ * Backbone post type collection.
818
  */
819
  wp.api.collections.PostTypes = BaseCollection.extend(
820
  /** @lends PostTypes.prototype */
821
  {
822
  model: wp.api.models.PostType,
823
 
824
+ url: WP_API_Settings.root + 'wp/v2/types',
825
 
826
  parse: function( response ) {
827
  var responseArray = [];
838
  );
839
 
840
  /**
841
+ * Backbone terms collection.
842
  *
843
  * Usage: new wp.api.collections.Terms( {}, { taxonomy: 'taxonomy-slug' } )
844
  */
850
  taxonomy: 'category',
851
 
852
  /**
853
+ * @class Represent an array of terms.
854
+ * @augments Backbone.Collection.
855
  * @constructs
856
  */
857
  initialize: function( models, options ) {
863
  },
864
 
865
  /**
866
+ * Return URL for collection.
867
  *
868
+ * @returns {string}.
869
  */
870
  url: function() {
871
+ return WP_API_Settings.root + 'wp/v2/terms/' + this.taxonomy;
872
  }
873
  }
874
  );
875
 
876
  /**
877
+ * Backbone revisions collection.
878
  *
879
+ * Usage: new wp.api.collections.Revisions( {}, { parent: POST_ID } ).
880
  */
881
  wp.api.collections.Revisions = BaseCollection.extend(
882
  /** @lends Revisions.prototype */
886
  parent: null,
887
 
888
  /**
889
+ * @class Represent an array of revisions.
890
+ * @augments Backbone.Collection.
891
  * @constructs
892
  */
893
  initialize: function( models, options ) {
899
  },
900
 
901
  /**
902
+ * return URL for collection.
903
  *
904
+ * @returns {string}.
905
  */
906
  url: function() {
907
+ return WP_API_Settings.root + 'wp/v2/posts/' + this.parent + '/revisions';
908
  }
909
  }
910
  );
911
 
912
  /**
913
+ * Todo: Handle schema endpoints.
914
  */
915
 
916
  /**
917
+ * Todo: Handle post meta.
918
  */
919
 
920
  })( WP_API_Settings, Backbone, _, window, ( void 0 ) );