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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta10 to 2.0-beta11

CHANGELOG.md CHANGED
@@ -1,5 +1,152 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 10.0
4
 
5
  - SECURITY: Ensure media of private posts are private too.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 11.0
4
+
5
+ - BREAKING CHANGE: Moves Post->Term relations to the Post Resource
6
+
7
+ Previously, a client would fetch a Post's Tags with `GET /wp/v2/posts/<id>/tags`.
8
+
9
+ In Beta 11, an array of term ids is included on the Post resource.
10
+
11
+ The collection of terms for a Post can be fetched with `GET /wp/v2/tags?post=<id>`.
12
+
13
+ The `WP_REST_Posts_Terms_Controller` class no longer exists.
14
+
15
+ (props @joehoyle, [#2063](https://github.com/WP-API/WP-API/pull/2063))
16
+
17
+ - BREAKING CHANGE: Adds latest JS client including a minified version.
18
+
19
+ See pull request for a summarized changelog.
20
+
21
+ (props @adamsilverstein, [#1981](https://github.com/WP-API/WP-API/pull/1981))
22
+
23
+ - BREAKING CHANGE: Changes `featured_image` attribute on Posts to `featured_media`.
24
+
25
+ While featuring other attachment types isn't yet officially supported, this makes it easier for us to introduce the possibility in the future.
26
+
27
+ (props @danielbachhuber, [#2044](https://github.com/WP-API/WP-API/pull/2044))
28
+
29
+ - BREAKING CHANGE: Uses discrete schema title for categories and tags.
30
+
31
+ If you've used `register_rest_field( 'term' )`, you'll need to change `'term'` to `'tag'` and/or `'category'`.
32
+
33
+ (props @danielbachhuber, [#2005](https://github.com/WP-API/WP-API/pull/2005))
34
+
35
+ - BREAKING CHANGE: Makes many filters dynamic based on the controller type.
36
+
37
+ If you were using the `rest_prepare_term` filter, you'll need to change it to `rest_prepare_post_tag` or `rest_prepare_category`.
38
+
39
+ If you were using `rest_post_query` or `rest_terms_query`, you'll need update your use to `rest_page_query`, etc.
40
+
41
+ If you were using `rest_post_trashable`, `rest_insert_post` or `rest_delete_post`, they are now dynamic based on the post type slug.
42
+
43
+ (props @danielbachhuber, [#2008](https://github.com/WP-API/WP-API/pull/2008), [#2010](https://github.com/WP-API/WP-API/pull/2010), [#2057](https://github.com/WP-API/WP-API/pull/2057), [#2058](https://github.com/WP-API/WP-API/pull/2058))
44
+
45
+ - Renames `GET /wp/v2/comments` `user` param to `author` to match resource attribute.
46
+
47
+ Not a breaking change, because it didn't work in the first place.
48
+
49
+ (props @danielbachhuber, [#2105](https://github.com/WP-API/WP-API/pull/2105))
50
+
51
+ - Adds support for `GET /wp/v2/pages parent=1,2,3`.
52
+
53
+ (props @danielbachhuber, [#2101](https://github.com/WP-API/WP-API/pull/2101))
54
+
55
+ - Persists image metadata title and caption when not present in the request.
56
+
57
+ (props @danielbachhuber, [#2079](https://github.com/WP-API/WP-API/pull/2079))
58
+
59
+ - Add `parent_exclude` param to `GET /wp/v2/posts`.
60
+
61
+ (props @danielbachhuber, [#2077](https://github.com/WP-API/WP-API/pull/2077))
62
+
63
+ - Adds `slug` param support for collections of Posts, Users, and Taxonomy Terms.
64
+
65
+ (props @danielbachhuber, [#2071](https://github.com/WP-API/WP-API/pull/2071), [#2072](https://github.com/WP-API/WP-API/pull/2072), [#2103](https://github.com/WP-API/WP-API/pull/2103))
66
+
67
+ - When a comment is already trashed, returns `410:rest_already_trashed`.
68
+
69
+ (props @danielbachhuber, [#2069](https://github.com/WP-API/WP-API/pull/2069))
70
+
71
+ - Filter the responses by context after processing additional fields.
72
+
73
+ (props @danielbachhuber, [#2067](https://github.com/WP-API/WP-API/pull/2067))
74
+
75
+ - Adds `offset` param support for collections of Posts, Users, Comments, and Taxonomy Terms.
76
+
77
+ (props @danielbachhuber, [#2061](https://github.com/WP-API/WP-API/pull/2061), [#2062](https://github.com/WP-API/WP-API/pull/2062), [#2064](https://github.com/WP-API/WP-API/pull/2064), [#2076](https://github.com/WP-API/WP-API/pull/2076))
78
+
79
+ - Adds `rest_insert_{$taxonomy}` and `rest_delete_{$taxonomy}` actions.
80
+
81
+ (props @danielbachhuber, [#2060](https://github.com/WP-API/WP-API/pull/2060))
82
+
83
+ - Provides more helpful error message/code on Post Create/Update fail.
84
+
85
+ (props @danielbachhuber, [#2053](https://github.com/WP-API/WP-API/pull/2053))
86
+
87
+ - Forces `GET /wp/v2/media` to be limited to `'status' => [ inherit, private, trash ]`
88
+
89
+ (props @danielbachhuber, [#2026](https://github.com/WP-API/WP-API/pull/2026))
90
+
91
+ - Uses more correct error code for `Comment::delete` permission check.
92
+
93
+ (props @danielbachhuber, [#2054](https://github.com/WP-API/WP-API/pull/2054))
94
+
95
+ - Calls `prepare_item_for_response()` directly in create and update methods.
96
+
97
+ This lets us pass the original request through, giving the method and its filter genuine context, and avoids an
98
+ unnecessary call to `get_item()`.
99
+
100
+ (props @danielbachhuber, [#2038](https://github.com/WP-API/WP-API/pull/2038), [#2040](https://github.com/WP-API/WP-API/pull/2040), [#2041](https://github.com/WP-API/WP-API/pull/2041), [#2043](https://github.com/WP-API/WP-API/pull/2043), [#2042](https://github.com/WP-API/WP-API/pull/2042))
101
+
102
+ - Moves permission check methods across controllers.
103
+
104
+ Placing them above the method they're supposed to check makes the code more readable.
105
+
106
+ (props @danielbachhuber, [#2030](https://github.com/WP-API/WP-API/pull/2030), [#2029](https://github.com/WP-API/WP-API/pull/2029), [#2034](https://github.com/WP-API/WP-API/pull/2034), [#2036](https://github.com/WP-API/WP-API/pull/2036), [#2037](https://github.com/WP-API/WP-API/pull/2037), [#2035](https://github.com/WP-API/WP-API/pull/2035), [#2039](https://github.com/WP-API/WP-API/pull/2039))
107
+
108
+ - Requires `force` argument for `DELETE /wp/v2/<taxonomy>/<id>`.
109
+
110
+ (props @danielbachhuber, [#2028](https://github.com/WP-API/WP-API/pull/2028))
111
+
112
+ - Conditionally requires and defines REST API classes and functions.
113
+
114
+ (props @danielbachhuber, [#2023](https://github.com/WP-API/WP-API/pull/2023), [#2024](https://github.com/WP-API/WP-API/pull/2024))
115
+
116
+ - Avoid a duplicate query for the comment count.
117
+
118
+ (props @rmccue, [#2015](https://github.com/WP-API/WP-API/pull/2015))
119
+
120
+ - Parses `$date` if available in `prepare_date_response()`
121
+
122
+ (props @adamsilverstein, [#1951](https://github.com/WP-API/WP-API/pull/1951))
123
+
124
+ - Abstracts `POST /wp/v2/media` permissions check.
125
+
126
+ (props @danielbachhuber, [#2003](https://github.com/WP-API/WP-API/pull/2003))
127
+
128
+ - Adds `exclude` param to getting collections of Posts, Users, Comments, and Taxonomy Terms.
129
+
130
+ (props @danielbachhuber, [#1998](https://github.com/WP-API/WP-API/pull/1998), [#1999](https://github.com/WP-API/WP-API/pull/1999), [#2000](https://github.com/WP-API/WP-API/pull/2000), [#2002](https://github.com/WP-API/WP-API/pull/2002))
131
+
132
+ - Adds `rest_comment_query` for filtering `GET /wp/v2/comments`.
133
+
134
+ (props @danielbachhuber, [#2007](https://github.com/WP-API/WP-API/pull/2007))
135
+
136
+ - Uses HTTP status code `500` for `db_update_error` when creating an attachment.
137
+
138
+ (props @danielbachhuber, [#1993](https://github.com/WP-API/WP-API/pull/1993))
139
+
140
+ - Adds helpful description to `force` param across all `DELETE` registrations
141
+
142
+ (props @danielbachhuber, [#2004](https://github.com/WP-API/WP-API/pull/2004), [#2027](https://github.com/WP-API/WP-API/pull/2027))
143
+
144
+ - In `GET /wp/v2/<taxonomy>`, drops support for `orderby=>term_id`.
145
+
146
+ Only one `id` is exposed through the REST API.
147
+
148
+ (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
149
+
150
  ## 2.0 Beta 10.0
151
 
152
  - SECURITY: Ensure media of private posts are private too.
extras.php CHANGED
@@ -11,61 +11,75 @@
11
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
12
  add_action( 'admin_enqueue_scripts', 'rest_register_scripts', -100 );
13
 
14
- /**
15
- * Registers REST API JavaScript helpers.
16
- *
17
- * @since 4.4.0
18
- *
19
- * @see wp_register_scripts()
20
- */
21
- function rest_register_scripts() {
22
- wp_register_script( 'wp-api', plugins_url( 'wp-api.js', __FILE__ ), array( 'jquery', 'backbone', 'underscore' ), '1.1', true );
23
 
24
- $settings = array( 'root' => esc_url_raw( get_rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ) );
25
- wp_localize_script( 'wp-api', 'WP_API_Settings', $settings );
26
- }
27
 
28
- /**
29
- * Retrieves the avatar urls in various sizes based on a given email address.
30
- *
31
- * @since 4.4.0
32
- *
33
- * @see get_avatar_url()
34
- *
35
- * @param string $email Email address.
36
- * @return array $urls Gravatar url for each size.
37
- */
38
- function rest_get_avatar_urls( $email ) {
39
- $avatar_sizes = rest_get_avatar_sizes();
40
 
41
- $urls = array();
42
- foreach ( $avatar_sizes as $size ) {
43
- $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
 
 
 
44
  }
45
-
46
- return $urls;
47
  }
48
 
49
- /**
50
- * Retrieves the pixel sizes for avatars.
51
- *
52
- * @since 4.4.0
53
- *
54
- * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
55
- */
56
- function rest_get_avatar_sizes() {
57
  /**
58
- * Filter the REST avatar sizes.
 
 
 
 
59
  *
60
- * Use this filter to adjust the array of sizes returned by the
61
- * `rest_get_avatar_sizes` function.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  *
63
  * @since 4.4.0
64
  *
65
- * @param array $sizes An array of int values that are the pixel sizes for avatars.
66
- * Default `[ 24, 48, 96 ]`.
67
  */
68
- return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
 
71
  /**
11
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
12
  add_action( 'admin_enqueue_scripts', 'rest_register_scripts', -100 );
13
 
14
+ if ( ! function_exists( 'rest_register_scripts' ) ) {
15
+ /**
16
+ * Registers REST API JavaScript helpers.
17
+ *
18
+ * @since 4.4.0
19
+ *
20
+ * @see wp_register_scripts()
21
+ */
22
+ function rest_register_scripts() {
23
 
24
+ // Use minified scripts if SCRIPT_DEBUG is not on.
25
+ $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
 
26
 
27
+ wp_register_script( 'wp-api', plugins_url( 'wp-api' . $suffix . '.js', __FILE__ ), array( 'jquery', 'backbone', 'underscore' ), '1.1', true );
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ $settings = array(
30
+ 'root' => esc_url_raw( get_rest_url() ),
31
+ 'nonce' => wp_create_nonce( 'wp_rest' ),
32
+ 'versionString' => 'wp/v2/',
33
+ );
34
+ wp_localize_script( 'wp-api', 'wpApiSettings', $settings );
35
  }
 
 
36
  }
37
 
38
+ if ( ! function_exists( 'rest_get_avatar_urls' ) ) {
 
 
 
 
 
 
 
39
  /**
40
+ * Retrieves the avatar urls in various sizes based on a given email address.
41
+ *
42
+ * @since 4.4.0
43
+ *
44
+ * @see get_avatar_url()
45
  *
46
+ * @param string $email Email address.
47
+ * @return array $urls Gravatar url for each size.
48
+ */
49
+ function rest_get_avatar_urls( $email ) {
50
+ $avatar_sizes = rest_get_avatar_sizes();
51
+
52
+ $urls = array();
53
+ foreach ( $avatar_sizes as $size ) {
54
+ $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
55
+ }
56
+
57
+ return $urls;
58
+ }
59
+ }
60
+
61
+ if ( ! function_exists( 'rest_get_avatar_sizes' ) ) {
62
+ /**
63
+ * Retrieves the pixel sizes for avatars.
64
  *
65
  * @since 4.4.0
66
  *
67
+ * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
 
68
  */
69
+ function rest_get_avatar_sizes() {
70
+ /**
71
+ * Filter the REST avatar sizes.
72
+ *
73
+ * Use this filter to adjust the array of sizes returned by the
74
+ * `rest_get_avatar_sizes` function.
75
+ *
76
+ * @since 4.4.0
77
+ *
78
+ * @param array $sizes An array of int values that are the pixel sizes for avatars.
79
+ * Default `[ 24, 48, 96 ]`.
80
+ */
81
+ return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
82
+ }
83
  }
84
 
85
  /**
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -3,28 +3,58 @@
3
  class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
4
 
5
  /**
6
- * Create a single attachment
 
7
  *
8
- * @param WP_REST_Request $request Full details about the request
9
- * @return WP_Error|WP_REST_Response
10
  */
11
- public function create_item( $request ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- // Permissions check - Note: "upload_files" cap is returned for an attachment by $post_type_obj->cap->create_posts
14
  $post_type_obj = get_post_type_object( $this->post_type );
15
  if ( ! current_user_can( $post_type_obj->cap->create_posts ) || ! current_user_can( $post_type_obj->cap->edit_posts ) ) {
16
- return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to post on this site.' ), array( 'status' => 400 ) );
17
  }
18
 
19
- // If a user is trying to attach to a post make sure they have permissions. Bail early if post_id is not being passed
20
  if ( ! empty( $request['post'] ) ) {
21
  $parent = get_post( (int) $request['post'] );
22
  $post_parent_type = get_post_type_object( $parent->post_type );
23
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
24
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) );
25
  }
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  // Get the file via $_FILES or raw data
29
  $files = $request->get_file_params();
30
  $headers = $request->get_headers();
@@ -45,8 +75,6 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
45
  $url = $file['url'];
46
  $type = $file['type'];
47
  $file = $file['file'];
48
- $title = $name;
49
- $caption = '';
50
 
51
  // use image exif/iptc data for title and caption defaults if possible
52
  // @codingStandardsIgnoreStart
@@ -54,11 +82,11 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
54
  // @codingStandardsIgnoreEnd
55
  if ( ! empty( $image_meta ) ) {
56
  if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
57
- $title = $image_meta['title'];
58
  }
59
 
60
  if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
61
- $caption = $image_meta['caption'];
62
  }
63
  }
64
 
@@ -68,8 +96,14 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
68
  $attachment->guid = $url;
69
  $id = wp_insert_post( $attachment, true );
70
  if ( is_wp_error( $id ) ) {
 
 
 
 
 
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';
@@ -82,10 +116,8 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
82
 
83
  $this->update_additional_fields_for_object( $attachment, $request );
84
 
85
- $get_request = new WP_REST_Request;
86
- $get_request->set_param( 'id', $id );
87
- $get_request->set_param( 'context', 'edit' );
88
- $response = $this->get_item( $get_request );
89
  $response = rest_ensure_response( $response );
90
  $response->set_status( 201 );
91
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
@@ -95,7 +127,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
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
 
@@ -122,15 +154,15 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
122
  update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
123
  }
124
 
125
- $get_request = new WP_REST_Request;
126
- $get_request->set_param( 'id', $data['id'] );
127
- $get_request->set_param( 'context', 'edit' );
128
- $response = $this->get_item( $get_request );
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
 
136
  /**
@@ -162,7 +194,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
162
  *
163
  * @param WP_Post $post Post object
164
  * @param WP_REST_Request $request Request object
165
- * @return array $response
166
  */
167
  public function prepare_item_for_response( $post, $request ) {
168
  $response = parent::prepare_item_for_response( $post, $request );
@@ -389,15 +421,26 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
389
  */
390
  public function get_collection_params() {
391
  $params = parent::get_collection_params();
392
- $params['parent'] = array(
393
- 'description' => __( 'Limit results to attachments from a specified parent.' ),
394
- 'type' => 'integer',
395
- 'default' => null,
396
- 'sanitize_callback' => 'absint',
397
- );
398
  return $params;
399
  }
400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  /**
402
  * Handle an upload via multipart/form-data ($_FILES)
403
  *
3
  class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
4
 
5
  /**
6
+ * Determine the allowed query_vars for a get_items() response and
7
+ * prepare for WP_Query.
8
  *
9
+ * @param array $prepared_args
10
+ * @return array $query_args
11
  */
12
+ protected function prepare_items_query( $prepared_args = array() ) {
13
+ $query_args = parent::prepare_items_query( $prepared_args );
14
+ if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ) ) ) {
15
+ $query_args['post_status'] = 'inherit';
16
+ }
17
+ return $query_args;
18
+ }
19
+
20
+ /**
21
+ * Check if a given request has access to create an attachment.
22
+ *
23
+ * @param WP_REST_Request $request Full details about the request.
24
+ * @return WP_Error|boolean
25
+ */
26
+ public function create_item_permissions_check( $request ) {
27
+ $ret = parent::create_item_permissions_check( $request );
28
+ if ( ! $ret || is_wp_error( $ret ) ) {
29
+ return $ret;
30
+ }
31
 
32
+ // "upload_files" cap is returned for an attachment by $post_type_obj->cap->create_posts
33
  $post_type_obj = get_post_type_object( $this->post_type );
34
  if ( ! current_user_can( $post_type_obj->cap->create_posts ) || ! current_user_can( $post_type_obj->cap->edit_posts ) ) {
35
+ return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
36
  }
37
 
38
+ // Attaching media to a post requires ability to edit said post
39
  if ( ! empty( $request['post'] ) ) {
40
  $parent = get_post( (int) $request['post'] );
41
  $post_parent_type = get_post_type_object( $parent->post_type );
42
  if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
43
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) );
44
  }
45
  }
46
 
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Create a single attachment
52
+ *
53
+ * @param WP_REST_Request $request Full details about the request
54
+ * @return WP_Error|WP_REST_Response
55
+ */
56
+ public function create_item( $request ) {
57
+
58
  // Get the file via $_FILES or raw data
59
  $files = $request->get_file_params();
60
  $headers = $request->get_headers();
75
  $url = $file['url'];
76
  $type = $file['type'];
77
  $file = $file['file'];
 
 
78
 
79
  // use image exif/iptc data for title and caption defaults if possible
80
  // @codingStandardsIgnoreStart
82
  // @codingStandardsIgnoreEnd
83
  if ( ! empty( $image_meta ) ) {
84
  if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
85
+ $request['title'] = $image_meta['title'];
86
  }
87
 
88
  if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
89
+ $request['caption'] = $image_meta['caption'];
90
  }
91
  }
92
 
96
  $attachment->guid = $url;
97
  $id = wp_insert_post( $attachment, true );
98
  if ( is_wp_error( $id ) ) {
99
+ if ( in_array( $id->get_error_code(), array( 'db_update_error' ) ) ) {
100
+ $id->add_data( array( 'status' => 500 ) );
101
+ } else {
102
+ $id->add_data( array( 'status' => 400 ) );
103
+ }
104
  return $id;
105
  }
106
+ $attachment = get_post( $id );
107
 
108
  /** Include admin functions to get access to wp_generate_attachment_metadata() */
109
  require_once ABSPATH . 'wp-admin/includes/admin.php';
116
 
117
  $this->update_additional_fields_for_object( $attachment, $request );
118
 
119
+ $request->set_param( 'context', 'edit' );
120
+ $response = $this->prepare_item_for_response( $attachment, $request );
 
 
121
  $response = rest_ensure_response( $response );
122
  $response->set_status( 201 );
123
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $attachment->post_type ) . '/' . $id ) );
127
  *
128
  * @param object $attachment Inserted attachment.
129
  * @param WP_REST_Request $request The request sent to the API.
130
+ * @param boolean $creating True when creating an attachment, false when updating.
131
  */
132
  do_action( 'rest_insert_attachment', $attachment, $request, true );
133
 
154
  update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
155
  }
156
 
157
+ $attachment = get_post( $request['id'] );
158
+ $request->set_param( 'context', 'edit' );
159
+ $response = $this->prepare_item_for_response( $attachment, $request );
160
+ $response = rest_ensure_response( $response );
161
 
162
  /* This action is documented in lib/endpoints/class-wp-rest-attachments-controller.php */
163
  do_action( 'rest_insert_attachment', $data, $request, false );
164
 
165
+ return $response;
166
  }
167
 
168
  /**
194
  *
195
  * @param WP_Post $post Post object
196
  * @param WP_REST_Request $request Request object
197
+ * @return WP_REST_Response $response
198
  */
199
  public function prepare_item_for_response( $post, $request ) {
200
  $response = parent::prepare_item_for_response( $post, $request );
421
  */
422
  public function get_collection_params() {
423
  $params = parent::get_collection_params();
424
+ $params['status']['default'] = 'inherit';
425
+ $params['status']['enum'] = array( 'inherit', 'private', 'trash' );
 
 
 
 
426
  return $params;
427
  }
428
 
429
+ /**
430
+ * Validate whether the user can query private statuses
431
+ *
432
+ * @param mixed $value
433
+ * @param WP_REST_Request $request
434
+ * @param string $parameter
435
+ * @return WP_Error|boolean
436
+ */
437
+ public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
438
+ if ( 'inherit' === $value ) {
439
+ return true;
440
+ }
441
+ return parent::validate_user_can_query_private_statuses( $value, $request, $parameter );
442
+ }
443
+
444
  /**
445
  * Handle an upload via multipart/form-data ($_FILES)
446
  *
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -47,7 +47,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
47
  'callback' => array( $this, 'delete_item' ),
48
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
49
  'args' => array(
50
- 'force' => array(),
 
 
 
51
  ),
52
  ),
53
 
@@ -55,6 +58,30 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
55
  ) );
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  /**
59
  * Get a list of comments.
60
  *
@@ -62,7 +89,51 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
62
  * @return WP_Error|WP_REST_Response
63
  */
64
  public function get_items( $request ) {
65
- $prepared_args = $this->prepare_items_query( $request );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  $query = new WP_Comment_Query;
68
  $query_result = $query->query( $prepared_args );
@@ -71,7 +142,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
71
  foreach ( $query_result as $comment ) {
72
  $post = get_post( $comment->comment_post_ID );
73
  if ( ! $this->check_read_post_permission( $post ) || ! $this->check_read_permission( $comment ) ) {
74
-
75
  continue;
76
  }
77
 
@@ -79,15 +149,22 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
79
  $comments[] = $this->prepare_response_for_collection( $data );
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  $response = rest_ensure_response( $comments );
83
- unset( $prepared_args['number'] );
84
- unset( $prepared_args['offset'] );
85
- $query = new WP_Comment_Query;
86
- $prepared_args['count'] = true;
87
- $total_comments = $query->query( $prepared_args );
88
- $response->header( 'X-WP-Total', (int) $total_comments );
89
- $max_pages = ceil( $total_comments / $request['per_page'] );
90
- $response->header( 'X-WP-TotalPages', (int) $max_pages );
91
 
92
  $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/comments' ) );
93
  if ( $request['page'] > 1 ) {
@@ -107,6 +184,38 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
107
  return $response;
108
  }
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  /**
111
  * Get a comment.
112
  *
@@ -134,6 +243,43 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
134
  return $response;
135
  }
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  /**
138
  * Create a comment.
139
  *
@@ -216,13 +362,32 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
216
  *
217
  * @param array $prepared_comment Inserted comment data.
218
  * @param WP_REST_Request $request The request sent to the API.
219
- * @param bool $creating True when creating a comment, false when updating.
220
  */
221
  do_action( 'rest_insert_comment', $prepared_comment, $request, true );
222
 
223
  return $response;
224
  }
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  /**
227
  * Edit a comment
228
  *
@@ -262,12 +427,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
262
  }
263
  }
264
 
265
- $this->update_additional_fields_for_object( get_comment( $id ), $request );
 
266
 
267
- $get_request = new WP_REST_Request;
268
- $get_request->set_param( 'id', $id );
269
- $get_request->set_param( 'context', 'edit' );
270
- $response = $this->get_item( $get_request );
271
 
272
  /* This action is documented in lib/endpoints/class-wp-rest-comments-controller.php */
273
  do_action( 'rest_insert_comment', $prepared_args, $request, false );
@@ -275,6 +439,24 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
275
  return rest_ensure_response( $response );
276
  }
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  /**
279
  * Delete a comment.
280
  *
@@ -314,6 +496,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
314
  return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing.' ), array( 'status' => 501 ) );
315
  }
316
 
 
 
 
 
317
  $result = wp_trash_comment( $comment->comment_ID );
318
  $status = 'trashed';
319
  }
@@ -341,135 +527,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
341
  return $response;
342
  }
343
 
344
-
345
- /**
346
- * Check if a given request has access to read comments
347
- *
348
- * @param WP_REST_Request $request Full details about the request.
349
- * @return bool|WP_Error
350
- */
351
- public function get_items_permissions_check( $request ) {
352
-
353
- // If the post id is specified, check that we can read the post
354
- if ( isset( $request['post'] ) ) {
355
- $post = get_post( (int) $request['post'] );
356
-
357
- if ( $post && ! $this->check_read_post_permission( $post ) ) {
358
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
359
- }
360
- }
361
-
362
- if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
363
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
364
- }
365
-
366
- return true;
367
- }
368
-
369
- /**
370
- * Check if a given request has access to read the comment
371
- *
372
- * @param WP_REST_Request $request Full details about the request.
373
- * @return bool|WP_Error
374
- */
375
- public function get_item_permissions_check( $request ) {
376
- $id = (int) $request['id'];
377
-
378
- $comment = get_comment( $id );
379
-
380
- if ( ! $comment ) {
381
- return true;
382
- }
383
-
384
- if ( ! $this->check_read_permission( $comment ) ) {
385
- return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => rest_authorization_required_code() ) );
386
- }
387
-
388
- $post = get_post( $comment->comment_post_ID );
389
-
390
- if ( $post && ! $this->check_read_post_permission( $post ) ) {
391
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
392
- }
393
-
394
- if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
395
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
396
- }
397
-
398
- return true;
399
- }
400
-
401
- /**
402
- * Check if a given request has access to create a comment
403
- *
404
- * @param WP_REST_Request $request Full details about the request.
405
- * @return bool|WP_Error
406
- */
407
- public function create_item_permissions_check( $request ) {
408
-
409
- if ( ! is_user_logged_in() && get_option( 'comment_registration' ) ) {
410
- return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
411
- }
412
-
413
- // Limit who can set comment `author`, `karma` or `status` to anything other than the default.
414
- if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
415
- return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => rest_authorization_required_code() ) );
416
- }
417
- if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
418
- return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => rest_authorization_required_code() ) );
419
- }
420
- if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
421
- return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => rest_authorization_required_code() ) );
422
- }
423
-
424
- if ( ! empty( $request['post'] ) && $post = get_post( (int) $request['post'] ) ) {
425
-
426
- if ( ! $this->check_read_post_permission( $post ) ) {
427
- return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
428
- }
429
-
430
- if ( ! comments_open( $post->ID ) ) {
431
- return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed on this post.' ), array( 'status' => 403 ) );
432
- }
433
- }
434
-
435
- return true;
436
- }
437
-
438
- /**
439
- * Check if a given request has access to update a comment
440
- *
441
- * @param WP_REST_Request $request Full details about the request.
442
- * @return bool|WP_Error
443
- */
444
- public function update_item_permissions_check( $request ) {
445
-
446
- $id = (int) $request['id'];
447
-
448
- $comment = get_comment( $id );
449
-
450
- if ( $comment && ! $this->check_edit_permission( $comment ) ) {
451
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => rest_authorization_required_code() ) );
452
- }
453
-
454
- return true;
455
- }
456
-
457
- /**
458
- * Check if a given request has access to delete a comment
459
- *
460
- * @param WP_REST_Request $request Full details about the request.
461
- * @return bool|WP_Error
462
- */
463
- public function delete_item_permissions_check( $request ) {
464
- return $this->update_item_permissions_check( $request );
465
- }
466
-
467
  /**
468
  * Prepare a single comment output for response.
469
  *
470
  * @param object $comment Comment object.
471
  * @param WP_REST_Request $request Request object.
472
- * @return WP_REST_Response
473
  */
474
  public function prepare_item_for_response( $comment, $request ) {
475
  $data = array(
@@ -496,8 +559,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
496
  );
497
 
498
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
499
- $data = $this->filter_response_by_context( $data, $context );
500
  $data = $this->add_additional_fields_to_object( $data, $request );
 
501
 
502
  // Wrap the data in a response object
503
  $response = rest_ensure_response( $data );
@@ -563,50 +626,6 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
563
  return $links;
564
  }
565
 
566
- /**
567
- * Filter query parameters for comments collection endpoint.
568
- *
569
- * Prepares arguments before passing them along to WP_Comment_Query.
570
- *
571
- * @param WP_REST_Request $request Request object.
572
- * @return array $prepared_args
573
- */
574
- protected function prepare_items_query( $request ) {
575
-
576
- $prepared_args = array(
577
- 'comment__in' => $request['include'],
578
- 'number' => $request['per_page'],
579
- 'post_id' => $request['post'] ? $request['post'] : '',
580
- 'parent' => isset( $request['parent'] ) ? $request['parent'] : '',
581
- 'search' => $request['search'],
582
- 'orderby' => $this->normalize_query_param( $request['orderby'] ),
583
- 'order' => $request['order'],
584
- 'status' => 'approve',
585
- 'type' => 'comment',
586
- );
587
-
588
- $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
589
-
590
- if ( current_user_can( 'edit_posts' ) ) {
591
- $protected_args = array(
592
- 'user' => $request['user'] ? $request['user'] : '',
593
- 'status' => $request['status'],
594
- 'type' => isset( $request['type'] ) ? $request['type'] : '',
595
- 'author_email' => isset( $request['author_email'] ) ? $request['author_email'] : '',
596
- 'karma' => isset( $request['karma'] ) ? $request['karma'] : '',
597
- 'post_author' => isset( $request['post_author'] ) ? $request['post_author'] : '',
598
- 'post_name' => isset( $request['post_slug'] ) ? $request['post_slug'] : '',
599
- 'post_parent' => isset( $request['post_parent'] ) ? $request['post_parent'] : '',
600
- 'post_status' => isset( $request['post_status'] ) ? $request['post_status'] : '',
601
- 'post_type' => isset( $request['post_type'] ) ? $request['post_type'] : '',
602
- );
603
-
604
- $prepared_args = array_merge( $prepared_args, $protected_args );
605
- }
606
-
607
- return $prepared_args;
608
- }
609
-
610
  /**
611
  * Prepend internal property prefix to query parameters to match our response fields.
612
  *
@@ -902,6 +921,12 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
902
  'sanitize_callback' => 'sanitize_email',
903
  'type' => 'string',
904
  );
 
 
 
 
 
 
905
  $query_params['include'] = array(
906
  'description' => __( 'Limit result set to specific ids.' ),
907
  'type' => 'array',
@@ -914,6 +939,11 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
914
  'sanitize_callback' => 'absint',
915
  'type' => 'integer',
916
  );
 
 
 
 
 
917
  $query_params['order'] = array(
918
  'description' => __( 'Order sort attribute ascending or descending.' ),
919
  'type' => 'string',
@@ -929,6 +959,15 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
929
  'type' => 'string',
930
  'sanitize_callback' => 'sanitize_key',
931
  'default' => 'date_gmt',
 
 
 
 
 
 
 
 
 
932
  );
933
  $query_params['parent'] = array(
934
  'default' => null,
@@ -984,8 +1023,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
984
  'sanitize_callback' => 'sanitize_key',
985
  'type' => 'string',
986
  );
987
- $query_params['user'] = array(
988
- 'default' => null,
989
  'description' => __( 'Limit result set to comments assigned to a specific user id.' ),
990
  'sanitize_callback' => 'absint',
991
  'type' => 'integer',
47
  'callback' => array( $this, 'delete_item' ),
48
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
49
  'args' => array(
50
+ 'force' => array(
51
+ 'default' => false,
52
+ 'description' => __( 'Whether to bypass trash and force deletion.' ),
53
+ ),
54
  ),
55
  ),
56
 
58
  ) );
59
  }
60
 
61
+ /**
62
+ * Check if a given request has access to read comments
63
+ *
64
+ * @param WP_REST_Request $request Full details about the request.
65
+ * @return WP_Error|boolean
66
+ */
67
+ public function get_items_permissions_check( $request ) {
68
+
69
+ // If the post id is specified, check that we can read the post
70
+ if ( isset( $request['post'] ) ) {
71
+ $post = get_post( (int) $request['post'] );
72
+
73
+ if ( $post && ! $this->check_read_post_permission( $post ) ) {
74
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
75
+ }
76
+ }
77
+
78
+ if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
79
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
80
+ }
81
+
82
+ return true;
83
+ }
84
+
85
  /**
86
  * Get a list of comments.
87
  *
89
  * @return WP_Error|WP_REST_Response
90
  */
91
  public function get_items( $request ) {
92
+ $prepared_args = array(
93
+ 'comment__in' => $request['include'],
94
+ 'comment__not_in' => $request['exclude'],
95
+ 'number' => $request['per_page'],
96
+ 'post_id' => $request['post'] ? $request['post'] : '',
97
+ 'parent' => isset( $request['parent'] ) ? $request['parent'] : '',
98
+ 'search' => $request['search'],
99
+ 'offset' => $request['offset'],
100
+ 'orderby' => $this->normalize_query_param( $request['orderby'] ),
101
+ 'order' => $request['order'],
102
+ 'status' => 'approve',
103
+ 'type' => 'comment',
104
+ 'no_found_rows' => false,
105
+ );
106
+
107
+ if ( empty( $request['offset'] ) ) {
108
+ $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
109
+ }
110
+
111
+ if ( current_user_can( 'edit_posts' ) ) {
112
+ $protected_args = array(
113
+ 'user_id' => $request['author'] ? $request['author'] : '',
114
+ 'status' => $request['status'],
115
+ 'type' => isset( $request['type'] ) ? $request['type'] : '',
116
+ 'author_email' => isset( $request['author_email'] ) ? $request['author_email'] : '',
117
+ 'karma' => isset( $request['karma'] ) ? $request['karma'] : '',
118
+ 'post_author' => isset( $request['post_author'] ) ? $request['post_author'] : '',
119
+ 'post_name' => isset( $request['post_slug'] ) ? $request['post_slug'] : '',
120
+ 'post_parent' => isset( $request['post_parent'] ) ? $request['post_parent'] : '',
121
+ 'post_status' => isset( $request['post_status'] ) ? $request['post_status'] : '',
122
+ 'post_type' => isset( $request['post_type'] ) ? $request['post_type'] : '',
123
+ );
124
+
125
+ $prepared_args = array_merge( $prepared_args, $protected_args );
126
+ }
127
+
128
+ /**
129
+ * Filter arguments, before passing to WP_Comment_Query, when querying comments via the REST API.
130
+ *
131
+ * @see https://developer.wordpress.org/reference/classes/wp_comment_query/
132
+ *
133
+ * @param array $prepared_args Array of arguments for WP_Comment_Query.
134
+ * @param WP_REST_Request $request The current request.
135
+ */
136
+ $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request );
137
 
138
  $query = new WP_Comment_Query;
139
  $query_result = $query->query( $prepared_args );
142
  foreach ( $query_result as $comment ) {
143
  $post = get_post( $comment->comment_post_ID );
144
  if ( ! $this->check_read_post_permission( $post ) || ! $this->check_read_permission( $comment ) ) {
 
145
  continue;
146
  }
147
 
149
  $comments[] = $this->prepare_response_for_collection( $data );
150
  }
151
 
152
+ $total_comments = (int) $query->found_comments;
153
+ $max_pages = (int) $query->max_num_pages;
154
+ if ( $total_comments < 1 ) {
155
+ // Out-of-bounds, run the query again without LIMIT for total count
156
+ unset( $prepared_args['number'] );
157
+ unset( $prepared_args['offset'] );
158
+ $query = new WP_Comment_Query;
159
+ $prepared_args['count'] = true;
160
+
161
+ $total_comments = $query->query( $prepared_args );
162
+ $max_pages = ceil( $total_comments / $request['per_page'] );
163
+ }
164
+
165
  $response = rest_ensure_response( $comments );
166
+ $response->header( 'X-WP-Total', $total_comments );
167
+ $response->header( 'X-WP-TotalPages', $max_pages );
 
 
 
 
 
 
168
 
169
  $base = add_query_arg( $request->get_query_params(), rest_url( '/wp/v2/comments' ) );
170
  if ( $request['page'] > 1 ) {
184
  return $response;
185
  }
186
 
187
+ /**
188
+ * Check if a given request has access to read the comment
189
+ *
190
+ * @param WP_REST_Request $request Full details about the request.
191
+ * @return WP_Error|boolean
192
+ */
193
+ public function get_item_permissions_check( $request ) {
194
+ $id = (int) $request['id'];
195
+
196
+ $comment = get_comment( $id );
197
+
198
+ if ( ! $comment ) {
199
+ return true;
200
+ }
201
+
202
+ if ( ! $this->check_read_permission( $comment ) ) {
203
+ return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => rest_authorization_required_code() ) );
204
+ }
205
+
206
+ $post = get_post( $comment->comment_post_ID );
207
+
208
+ if ( $post && ! $this->check_read_post_permission( $post ) ) {
209
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
210
+ }
211
+
212
+ if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
213
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
214
+ }
215
+
216
+ return true;
217
+ }
218
+
219
  /**
220
  * Get a comment.
221
  *
243
  return $response;
244
  }
245
 
246
+ /**
247
+ * Check if a given request has access to create a comment
248
+ *
249
+ * @param WP_REST_Request $request Full details about the request.
250
+ * @return WP_Error|boolean
251
+ */
252
+ public function create_item_permissions_check( $request ) {
253
+
254
+ if ( ! is_user_logged_in() && get_option( 'comment_registration' ) ) {
255
+ return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
256
+ }
257
+
258
+ // Limit who can set comment `author`, `karma` or `status` to anything other than the default.
259
+ if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
260
+ return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => rest_authorization_required_code() ) );
261
+ }
262
+ if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
263
+ return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => rest_authorization_required_code() ) );
264
+ }
265
+ if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
266
+ return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => rest_authorization_required_code() ) );
267
+ }
268
+
269
+ if ( ! empty( $request['post'] ) && $post = get_post( (int) $request['post'] ) ) {
270
+
271
+ if ( ! $this->check_read_post_permission( $post ) ) {
272
+ return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
273
+ }
274
+
275
+ if ( ! comments_open( $post->ID ) ) {
276
+ return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed on this post.' ), array( 'status' => 403 ) );
277
+ }
278
+ }
279
+
280
+ return true;
281
+ }
282
+
283
  /**
284
  * Create a comment.
285
  *
362
  *
363
  * @param array $prepared_comment Inserted comment data.
364
  * @param WP_REST_Request $request The request sent to the API.
365
+ * @param boolean $creating True when creating a comment, false when updating.
366
  */
367
  do_action( 'rest_insert_comment', $prepared_comment, $request, true );
368
 
369
  return $response;
370
  }
371
 
372
+ /**
373
+ * Check if a given request has access to update a comment
374
+ *
375
+ * @param WP_REST_Request $request Full details about the request.
376
+ * @return WP_Error|boolean
377
+ */
378
+ public function update_item_permissions_check( $request ) {
379
+
380
+ $id = (int) $request['id'];
381
+
382
+ $comment = get_comment( $id );
383
+
384
+ if ( $comment && ! $this->check_edit_permission( $comment ) ) {
385
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => rest_authorization_required_code() ) );
386
+ }
387
+
388
+ return true;
389
+ }
390
+
391
  /**
392
  * Edit a comment
393
  *
427
  }
428
  }
429
 
430
+ $comment = get_comment( $id );
431
+ $this->update_additional_fields_for_object( $comment, $request );
432
 
433
+ $request->set_param( 'context', 'edit' );
434
+ $response = $this->prepare_item_for_response( $comment, $request );
 
 
435
 
436
  /* This action is documented in lib/endpoints/class-wp-rest-comments-controller.php */
437
  do_action( 'rest_insert_comment', $prepared_args, $request, false );
439
  return rest_ensure_response( $response );
440
  }
441
 
442
+ /**
443
+ * Check if a given request has access to delete a comment
444
+ *
445
+ * @param WP_REST_Request $request Full details about the request.
446
+ * @return WP_Error|boolean
447
+ */
448
+ public function delete_item_permissions_check( $request ) {
449
+ $id = (int) $request['id'];
450
+ $comment = get_comment( $id );
451
+ if ( ! $comment ) {
452
+ return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
453
+ }
454
+ if ( ! $this->check_edit_permission( $comment ) ) {
455
+ return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you can not delete this comment.' ), array( 'status' => rest_authorization_required_code() ) );
456
+ }
457
+ return true;
458
+ }
459
+
460
  /**
461
  * Delete a comment.
462
  *
496
  return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing.' ), array( 'status' => 501 ) );
497
  }
498
 
499
+ if ( 'trash' === $comment->comment_approved ) {
500
+ return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) );
501
+ }
502
+
503
  $result = wp_trash_comment( $comment->comment_ID );
504
  $status = 'trashed';
505
  }
527
  return $response;
528
  }
529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  /**
531
  * Prepare a single comment output for response.
532
  *
533
  * @param object $comment Comment object.
534
  * @param WP_REST_Request $request Request object.
535
+ * @return WP_REST_Response $response
536
  */
537
  public function prepare_item_for_response( $comment, $request ) {
538
  $data = array(
559
  );
560
 
561
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
562
  $data = $this->add_additional_fields_to_object( $data, $request );
563
+ $data = $this->filter_response_by_context( $data, $context );
564
 
565
  // Wrap the data in a response object
566
  $response = rest_ensure_response( $data );
626
  return $links;
627
  }
628
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
  /**
630
  * Prepend internal property prefix to query parameters to match our response fields.
631
  *
921
  'sanitize_callback' => 'sanitize_email',
922
  'type' => 'string',
923
  );
924
+ $query_params['exclude'] = array(
925
+ 'description' => __( 'Ensure result set excludes specific ids.' ),
926
+ 'type' => 'array',
927
+ 'default' => array(),
928
+ 'sanitize_callback' => 'wp_parse_id_list',
929
+ );
930
  $query_params['include'] = array(
931
  'description' => __( 'Limit result set to specific ids.' ),
932
  'type' => 'array',
939
  'sanitize_callback' => 'absint',
940
  'type' => 'integer',
941
  );
942
+ $query_params['offset'] = array(
943
+ 'description' => __( 'Offset the result set by a specific number of comments.' ),
944
+ 'type' => 'integer',
945
+ 'sanitize_callback' => 'absint',
946
+ );
947
  $query_params['order'] = array(
948
  'description' => __( 'Order sort attribute ascending or descending.' ),
949
  'type' => 'string',
959
  'type' => 'string',
960
  'sanitize_callback' => 'sanitize_key',
961
  'default' => 'date_gmt',
962
+ 'enum' => array(
963
+ 'date',
964
+ 'date_gmt',
965
+ 'id',
966
+ 'include',
967
+ 'post',
968
+ 'parent',
969
+ 'type',
970
+ ),
971
  );
972
  $query_params['parent'] = array(
973
  'default' => null,
1023
  'sanitize_callback' => 'sanitize_key',
1024
  'type' => 'string',
1025
  );
1026
+ $query_params['author'] = array(
 
1027
  'description' => __( 'Limit result set to comments assigned to a specific user id.' ),
1028
  'sanitize_callback' => 'absint',
1029
  'type' => 'integer',
lib/endpoints/class-wp-rest-controller.php CHANGED
@@ -11,102 +11,102 @@ 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', 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
 
@@ -125,7 +125,7 @@ abstract class WP_REST_Controller {
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 ) );
@@ -254,7 +254,6 @@ abstract class WP_REST_Controller {
254
  'type' => 'string',
255
  );
256
  $schema = $this->get_item_schema();
257
- $contexts = array();
258
  if ( empty( $schema['properties'] ) ) {
259
  return array_merge( $param_details, $args );
260
  }
@@ -315,7 +314,7 @@ abstract class WP_REST_Controller {
315
  continue;
316
  }
317
 
318
- $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
319
  }
320
  }
321
 
@@ -327,7 +326,7 @@ abstract class WP_REST_Controller {
327
  * @param array $schema Schema array.
328
  */
329
  protected function add_additional_fields_schema( $schema ) {
330
- if ( ! $schema || ! isset( $schema['title'] ) ) {
331
  return $schema;
332
  }
333
 
@@ -446,7 +445,7 @@ abstract class WP_REST_Controller {
446
  * @param mixed $value
447
  * @param WP_REST_Request $request
448
  * @param string $parameter
449
- * @return WP_Error|bool
450
  */
451
  public function validate_schema_property( $value, $request, $parameter ) {
452
 
@@ -506,7 +505,7 @@ abstract class WP_REST_Controller {
506
  * @param mixed $value
507
  * @param WP_REST_Request $request
508
  * @param string $parameter
509
- * @return WP_Error|bool
510
  */
511
  public function sanitize_schema_property( $value, $request, $parameter ) {
512
 
11
  }
12
 
13
  /**
14
+ * Check if a given request has access to get items.
15
  *
16
  * @param WP_REST_Request $request Full data about the request.
17
+ * @return WP_Error|boolean
18
  */
19
+ public function get_items_permissions_check( $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 a collection of items.
25
  *
26
  * @param WP_REST_Request $request Full data about the request.
27
  * @return WP_Error|WP_REST_Response
28
  */
29
+ public function get_items( $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
+ * Check if a given request has access to get a specific item.
35
  *
36
  * @param WP_REST_Request $request Full data about the request.
37
+ * @return WP_Error|boolean
38
  */
39
+ public function get_item_permissions_check( $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
+ * Get 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 get_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
+ * Check if a given request has access to create items.
55
  *
56
  * @param WP_REST_Request $request Full data about the request.
57
+ * @return WP_Error|boolean
58
  */
59
+ public function create_item_permissions_check( $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
+ * Create one item from the collection.
65
  *
66
  * @param WP_REST_Request $request Full data about the request.
67
+ * @return WP_Error|WP_REST_Response
68
  */
69
+ public function create_item( $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 update a specific item.
75
  *
76
  * @param WP_REST_Request $request Full data about the request.
77
+ * @return WP_Error|boolean
78
  */
79
+ public function update_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
+ * Update one item from the collection.
85
  *
86
  * @param WP_REST_Request $request Full data about the request.
87
+ * @return WP_Error|WP_REST_Response
88
  */
89
+ public function update_item( $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 delete a specific item.
95
  *
96
  * @param WP_REST_Request $request Full data about the request.
97
+ * @return WP_Error|boolean
98
  */
99
+ public function delete_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
+ * Delete one item from the collection.
105
  *
106
  * @param WP_REST_Request $request Full data about the request.
107
+ * @return WP_Error|WP_REST_Response
108
  */
109
+ public function delete_item( $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
 
125
  *
126
  * @param mixed $item WordPress representation of the item.
127
  * @param WP_REST_Request $request Request object.
128
+ * @return WP_REST_Response $response
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 ) );
254
  'type' => 'string',
255
  );
256
  $schema = $this->get_item_schema();
 
257
  if ( empty( $schema['properties'] ) ) {
258
  return array_merge( $param_details, $args );
259
  }
314
  continue;
315
  }
316
 
317
+ call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
318
  }
319
  }
320
 
326
  * @param array $schema Schema array.
327
  */
328
  protected function add_additional_fields_schema( $schema ) {
329
+ if ( empty( $schema['title'] ) ) {
330
  return $schema;
331
  }
332
 
445
  * @param mixed $value
446
  * @param WP_REST_Request $request
447
  * @param string $parameter
448
+ * @return WP_Error|boolean
449
  */
450
  public function validate_schema_property( $value, $request, $parameter ) {
451
 
505
  * @param mixed $value
506
  * @param WP_REST_Request $request
507
  * @param string $parameter
508
+ * @return mixed
509
  */
510
  public function sanitize_schema_property( $value, $request, $parameter ) {
511
 
lib/endpoints/class-wp-rest-meta-controller.php CHANGED
@@ -71,8 +71,9 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
71
  'callback' => array( $this, 'delete_item' ),
72
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
73
  'args' => array(
74
- 'force' => array(
75
- 'default' => false,
 
76
  ),
77
  ),
78
  ),
@@ -340,7 +341,7 @@ abstract class WP_REST_Meta_Controller extends WP_REST_Controller {
340
  *
341
  * @param array $value The inserted meta data.
342
  * @param WP_REST_Request $request The request sent to the API.
343
- * @param bool $creating True when adding meta, false when updating.
344
  */
345
  do_action( 'rest_insert_meta', $value, $request, false );
346
 
71
  'callback' => array( $this, 'delete_item' ),
72
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
73
  'args' => array(
74
+ 'force' => array(
75
+ 'default' => false,
76
+ 'description' => __( 'Required to be true, as resource does not support trashing.' ),
77
  ),
78
  ),
79
  ),
341
  *
342
  * @param array $value The inserted meta data.
343
  * @param WP_REST_Request $request The request sent to the API.
344
+ * @param boolean $creating True when adding meta, false when updating.
345
  */
346
  do_action( 'rest_insert_meta', $value, $request, false );
347
 
lib/endpoints/class-wp-rest-post-statuses-controller.php CHANGED
@@ -48,7 +48,7 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
48
  }
49
  $data[ $obj->name ] = $this->prepare_response_for_collection( $status );
50
  }
51
- return $data;
52
  }
53
 
54
  /**
@@ -62,7 +62,8 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
62
  if ( empty( $obj ) ) {
63
  return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) );
64
  }
65
- return $this->prepare_item_for_response( $obj, $request );
 
66
  }
67
 
68
  /**
@@ -88,8 +89,8 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
88
  );
89
 
90
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
91
- $data = $this->filter_response_by_context( $data, $context );
92
  $data = $this->add_additional_fields_to_object( $data, $request );
 
93
 
94
  $response = rest_ensure_response( $data );
95
 
48
  }
49
  $data[ $obj->name ] = $this->prepare_response_for_collection( $status );
50
  }
51
+ return rest_ensure_response( $data );
52
  }
53
 
54
  /**
62
  if ( empty( $obj ) ) {
63
  return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) );
64
  }
65
+ $data = $this->prepare_item_for_response( $obj, $request );
66
+ return rest_ensure_response( $data );
67
  }
68
 
69
  /**
89
  );
90
 
91
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
92
  $data = $this->add_additional_fields_to_object( $data, $request );
93
+ $data = $this->filter_response_by_context( $data, $context );
94
 
95
  $response = rest_ensure_response( $data );
96
 
lib/endpoints/class-wp-rest-post-types-controller.php CHANGED
@@ -43,7 +43,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
43
  $post_type = $this->prepare_item_for_response( $obj, $request );
44
  $data[ $obj->name ] = $this->prepare_response_for_collection( $post_type );
45
  }
46
- return $data;
47
  }
48
 
49
  /**
@@ -63,7 +63,8 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
63
  if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) {
64
  return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this type.' ), array( 'status' => rest_authorization_required_code() ) );
65
  }
66
- return $this->prepare_item_for_response( $obj, $request );
 
67
  }
68
 
69
  /**
@@ -71,7 +72,7 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
71
  *
72
  * @param stdClass $post_type Post type data
73
  * @param WP_REST_Request $request
74
- * @return array Post type data
75
  */
76
  public function prepare_item_for_response( $post_type, $request ) {
77
  $data = array(
@@ -82,8 +83,8 @@ class WP_REST_Post_Types_Controller extends WP_REST_Controller {
82
  'slug' => $post_type->name,
83
  );
84
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
85
- $data = $this->filter_response_by_context( $data, $context );
86
  $data = $this->add_additional_fields_to_object( $data, $request );
 
87
 
88
  // Wrap the data in a response object.
89
  $response = rest_ensure_response( $data );
43
  $post_type = $this->prepare_item_for_response( $obj, $request );
44
  $data[ $obj->name ] = $this->prepare_response_for_collection( $post_type );
45
  }
46
+ return rest_ensure_response( $data );
47
  }
48
 
49
  /**
63
  if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) {
64
  return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this type.' ), array( 'status' => rest_authorization_required_code() ) );
65
  }
66
+ $data = $this->prepare_item_for_response( $obj, $request );
67
+ return rest_ensure_response( $data );
68
  }
69
 
70
  /**
72
  *
73
  * @param stdClass $post_type Post type data
74
  * @param WP_REST_Request $request
75
+ * @return WP_REST_Response $response
76
  */
77
  public function prepare_item_for_response( $post_type, $request ) {
78
  $data = array(
83
  'slug' => $post_type->name,
84
  );
85
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
86
  $data = $this->add_additional_fields_to_object( $data, $request );
87
+ $data = $this->filter_response_by_context( $data, $context );
88
 
89
  // Wrap the data in a response object.
90
  $response = rest_ensure_response( $data );
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -53,6 +53,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
53
  'args' => array(
54
  'force' => array(
55
  'default' => false,
 
56
  ),
57
  ),
58
  ),
@@ -61,6 +62,23 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
61
  ) );
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  /**
65
  * Get a collection of posts.
66
  *
@@ -68,16 +86,20 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
68
  * @return WP_Error|WP_REST_Response
69
  */
70
  public function get_items( $request ) {
71
- $args = array();
72
- $args['author'] = $request['author'];
73
- $args['order'] = $request['order'];
74
- $args['orderby'] = $request['orderby'];
75
- $args['paged'] = $request['page'];
76
- $args['post__in'] = $request['include'];
77
- $args['posts_per_page'] = $request['per_page'];
78
- $args['post_parent'] = $request['parent'];
79
- $args['post_status'] = $request['status'];
80
- $args['s'] = $request['search'];
 
 
 
 
81
 
82
  if ( is_array( $request['filter'] ) ) {
83
  $args = array_merge( $args, $request['filter'] );
@@ -93,10 +115,12 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
93
  * Enables adding extra arguments or setting defaults for a post
94
  * collection request.
95
  *
 
 
96
  * @param array $args Key value array of query var to query value.
97
  * @param WP_REST_Request $request The request used.
98
  */
99
- $args = apply_filters( 'rest_post_query', $args, $request );
100
  $query_args = $this->prepare_items_query( $args );
101
 
102
  $posts_query = new WP_Query();
@@ -112,17 +136,21 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
112
  $posts[] = $this->prepare_response_for_collection( $data );
113
  }
114
 
115
- $response = rest_ensure_response( $posts );
116
- $count_query = new WP_Query();
117
-
118
- // Store paged value for pagination headers then unset for count query.
119
  $page = (int) $query_args['paged'];
120
- unset( $query_args['paged'] );
 
 
 
 
 
 
 
 
121
 
122
- $query_result = $count_query->query( $query_args );
123
- $total_posts = $count_query->found_posts;
124
- $response->header( 'X-WP-Total', (int) $total_posts );
125
  $max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
 
 
 
126
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
127
 
128
  $request_params = $request->get_query_params();
@@ -150,6 +178,27 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
150
  return $response;
151
  }
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  /**
154
  * Get a single post.
155
  *
@@ -172,6 +221,34 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
172
  return $response;
173
  }
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  /**
176
  * Create a single post.
177
  *
@@ -212,8 +289,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
212
  }
213
  }
214
 
215
- if ( ! empty( $schema['properties']['featured_image'] ) && isset( $request['featured_image'] ) ) {
216
- $this->handle_featured_image( $request['featured_image'], $post->ID );
217
  }
218
 
219
  if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
@@ -223,22 +300,25 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
223
  if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
224
  $this->handle_template( $request['template'], $post->ID );
225
  }
 
 
 
 
226
 
227
- $this->update_additional_fields_for_object( get_post( $post_id ), $request );
 
228
 
229
  /**
230
  * Fires after a single post is created or updated via the REST API.
231
  *
232
  * @param object $post Inserted Post object (not a WP_Post object).
233
  * @param WP_REST_Request $request Request object.
234
- * @param bool $creating True when creating post, false when updating.
235
  */
236
- do_action( 'rest_insert_post', $post, $request, true );
237
 
238
- $get_request = new WP_REST_Request;
239
- $get_request->set_param( 'id', $post_id );
240
- $get_request->set_param( 'context', 'edit' );
241
- $response = $this->get_item( $get_request );
242
  $response = rest_ensure_response( $response );
243
  $response->set_status( 201 );
244
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $post->post_type ) . '/' . $post_id ) );
@@ -246,6 +326,36 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
246
  return $response;
247
  }
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  /**
250
  * Update a single post.
251
  *
@@ -281,8 +391,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
281
  set_post_format( $post, $request['format'] );
282
  }
283
 
284
- if ( ! empty( $schema['properties']['featured_image'] ) && isset( $request['featured_image'] ) ) {
285
- $this->handle_featured_image( $request['featured_image'], $post_id );
286
  }
287
 
288
  if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
@@ -297,25 +407,39 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
297
  $this->handle_template( $request['template'], $post->ID );
298
  }
299
 
300
- $this->update_additional_fields_for_object( get_post( $post_id ), $request );
 
 
 
301
 
302
- /**
303
- * @TODO: Enable rest_insert_post() action after.
304
- * Media Controller has been migrated to new style.
305
- *
306
- * do_action( 'rest_insert_post', $post, $request );
307
- */
308
 
309
  /* This action is documented in lib/endpoints/class-wp-rest-controller.php */
310
- do_action( 'rest_insert_post', $post, $request, false );
311
 
312
- $get_request = new WP_REST_Request;
313
- $get_request->set_param( 'id', $post_id );
314
- $get_request->set_param( 'context', 'edit' );
315
- $response = $this->get_item( $get_request );
316
  return rest_ensure_response( $response );
317
  }
318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  /**
320
  * Delete a single post.
321
  *
@@ -345,15 +469,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
345
  * @param boolean $supports_trash Whether the post type support trashing.
346
  * @param WP_Post $post The Post object being considered for trashing support.
347
  */
348
- $supports_trash = apply_filters( 'rest_post_trashable', $supports_trash, $post );
349
 
350
  if ( ! $this->check_delete_permission( $post ) ) {
351
  return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) );
352
  }
353
 
354
- $request = new WP_REST_Request( 'GET', '/wp/v2/' . $this->get_post_type_base( $this->post_type ) . '/' . $post->ID );
355
  $request->set_param( 'context', 'edit' );
356
- $response = rest_do_request( $request );
357
 
358
  // If we're forcing, then delete permanently.
359
  if ( $force ) {
@@ -367,7 +490,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
367
 
368
  // Otherwise, only trash if we haven't already.
369
  if ( 'trash' === $post->post_status ) {
370
- return new WP_Error( 'rest_already_deleted', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
371
  }
372
 
373
  // (Note that internally this falls through to `wp_delete_post` if
@@ -394,121 +517,11 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
394
  * @param array $data The response data.
395
  * @param WP_REST_Request $request The request sent to the API.
396
  */
397
- do_action( 'rest_delete_post', $post, $data, $request );
398
 
399
  return $response;
400
  }
401
 
402
- /**
403
- * Check if a given request has access to read /posts.
404
- *
405
- * @param WP_REST_Request $request Full details about the request.
406
- * @return bool|WP_Error
407
- */
408
- public function get_items_permissions_check( $request ) {
409
-
410
- $post_type = get_post_type_object( $this->post_type );
411
-
412
- if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
413
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
414
- }
415
-
416
- return true;
417
- }
418
-
419
- /**
420
- * Check if a given request has access to read a post.
421
- *
422
- * @param WP_REST_Request $request Full details about the request.
423
- * @return bool|WP_Error
424
- */
425
- public function get_item_permissions_check( $request ) {
426
-
427
- $post = get_post( (int) $request['id'] );
428
-
429
- if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
430
- return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => rest_authorization_required_code() ) );
431
- }
432
-
433
- if ( $post ) {
434
- return $this->check_read_permission( $post );
435
- }
436
-
437
- return true;
438
- }
439
-
440
- /**
441
- * Check if a given request has access to create a post.
442
- *
443
- * @param WP_REST_Request $request Full details about the request.
444
- * @return bool|WP_Error
445
- */
446
- public function create_item_permissions_check( $request ) {
447
-
448
- $post_type = get_post_type_object( $this->post_type );
449
-
450
- if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
451
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
452
- }
453
-
454
- if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
455
- return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
456
- }
457
-
458
- if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
459
- return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
460
- }
461
-
462
- return current_user_can( $post_type->cap->create_posts );
463
- }
464
-
465
- /**
466
- * Check if a given request has access to update a post.
467
- *
468
- * @param WP_REST_Request $request Full details about the request.
469
- * @return bool|WP_Error
470
- */
471
- public function update_item_permissions_check( $request ) {
472
-
473
- $post = get_post( $request['id'] );
474
- $post_type = get_post_type_object( $this->post_type );
475
-
476
- if ( $post && ! $this->check_update_permission( $post ) ) {
477
- return false;
478
- }
479
-
480
- if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
481
- return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
482
- }
483
-
484
- if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
485
- return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
486
- }
487
-
488
- if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
489
- return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
490
- }
491
-
492
- return true;
493
- }
494
-
495
- /**
496
- * Check if a given request has access to delete a post.
497
- *
498
- * @param WP_REST_Request $request Full details about the request.
499
- * @return bool|WP_Error
500
- */
501
- public function delete_item_permissions_check( $request ) {
502
-
503
- $post = get_post( $request['id'] );
504
-
505
- if ( $post && ! $this->check_delete_permission( $post ) ) {
506
- return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => rest_authorization_required_code() ) );
507
- }
508
-
509
- return true;
510
- }
511
-
512
  /**
513
  * Determine the allowed query_vars for a get_items() response and
514
  * prepare for WP_Query.
@@ -534,10 +547,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
534
  }
535
  }
536
 
537
- if ( empty( $query_args['post_status'] ) && 'attachment' === $this->post_type ) {
538
- $query_args['post_status'] = 'inherit';
539
- }
540
-
541
  if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
542
  $query_args['ignore_sticky_posts'] = true;
543
  }
@@ -585,7 +594,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
585
  $valid_vars = array_merge( $valid_vars, $private );
586
  }
587
  // Define our own in addition to WP's normal vars.
588
- $rest_valid = array( 'post__in', 'posts_per_page', 'ignore_sticky_posts', 'post_parent' );
589
  $valid_vars = array_merge( $valid_vars, $rest_valid );
590
 
591
  /**
@@ -636,14 +645,17 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
636
  * @return string|null ISO8601/RFC3339 formatted datetime.
637
  */
638
  protected function prepare_date_response( $date_gmt, $date = null ) {
639
- if ( '0000-00-00 00:00:00' === $date_gmt ) {
640
- return null;
641
- }
642
-
643
  if ( isset( $date ) ) {
644
  return mysql_to_rfc3339( $date );
645
  }
646
 
 
 
 
 
 
 
647
  return mysql_to_rfc3339( $date_gmt );
648
  }
649
 
@@ -746,12 +758,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
746
 
747
  // Author
748
  if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
749
- $author = $this->handle_author_param( $request['author'], $post_type );
750
- if ( is_wp_error( $author ) ) {
751
- return $author;
 
 
 
752
  }
753
-
754
- $prepared_post->post_author = $author;
755
  }
756
 
757
  // Post password.
@@ -774,7 +788,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
774
  }
775
 
776
  // Parent.
777
- $post_type_obj = get_post_type_object( $this->post_type );
778
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
779
  $parent = get_post( (int) $request['parent'] );
780
  if ( empty( $parent ) ) {
@@ -820,7 +833,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
820
  * @return WP_Error|string $post_status
821
  */
822
  protected function handle_status_param( $post_status, $post_type ) {
823
- $post_status = $post_status;
824
 
825
  switch ( $post_status ) {
826
  case 'draft':
@@ -848,50 +860,20 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
848
  }
849
 
850
  /**
851
- * Determine validity and normalize provided author param.
852
- *
853
- * @param object|integer $post_author
854
- * @param object $post_type
855
- * @return WP_Error|integer $post_author
856
- */
857
- protected function handle_author_param( $post_author, $post_type ) {
858
- if ( is_object( $post_author ) ) {
859
- if ( empty( $post_author->id ) ) {
860
- return new WP_Error( 'rest_invalid_author', __( 'Invalid author object.' ), array( 'status' => 400 ) );
861
- }
862
- $post_author = (int) $post_author->id;
863
- } else {
864
- $post_author = (int) $post_author;
865
- }
866
-
867
- // Only check edit others' posts if we are another user.
868
- if ( get_current_user_id() !== $post_author ) {
869
-
870
- $author = get_userdata( $post_author );
871
-
872
- if ( ! $author ) {
873
- return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) );
874
- }
875
- }
876
-
877
- return $post_author;
878
- }
879
-
880
- /**
881
- * Determine the featured image based on a request param.
882
  *
883
- * @param int $featured_image
884
  * @param int $post_id
885
  */
886
- protected function handle_featured_image( $featured_image, $post_id ) {
887
 
888
- $featured_image = (int) $featured_image;
889
- if ( $featured_image ) {
890
- $result = set_post_thumbnail( $post_id, $featured_image );
891
  if ( $result ) {
892
  return true;
893
  } else {
894
- return new WP_Error( 'rest_invalid_featured_image', __( 'Invalid featured image id.' ), array( 'status' => 400 ) );
895
  }
896
  } else {
897
  return delete_post_thumbnail( $post_id );
@@ -913,11 +895,34 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
913
  }
914
  }
915
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916
  /**
917
  * Check if a given post type should be viewed or managed.
918
  *
919
  * @param object|string $post_type
920
- * @return bool Is post type allowed?
921
  */
922
  protected function check_is_post_type_allowed( $post_type ) {
923
  if ( ! is_object( $post_type ) ) {
@@ -937,7 +942,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
937
  * Correctly handles posts with the inherit status.
938
  *
939
  * @param object $post Post object.
940
- * @return bool Can we read it?
941
  */
942
  public function check_read_permission( $post ) {
943
  if ( ! empty( $post->post_password ) && ! $this->check_update_permission( $post ) ) {
@@ -978,7 +983,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
978
  * Check if we can edit a post.
979
  *
980
  * @param object $post Post object.
981
- * @return bool Can we edit it?
982
  */
983
  protected function check_update_permission( $post ) {
984
  $post_type = get_post_type_object( $post->post_type );
@@ -994,7 +999,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
994
  * Check if we can create a post.
995
  *
996
  * @param object $post Post object.
997
- * @return bool Can we create it?.
998
  */
999
  protected function check_create_permission( $post ) {
1000
  $post_type = get_post_type_object( $post->post_type );
@@ -1010,7 +1015,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1010
  * Check if we can delete a post.
1011
  *
1012
  * @param object $post Post object.
1013
- * @return bool Can we delete it?
1014
  */
1015
  protected function check_delete_permission( $post ) {
1016
  $post_type = get_post_type_object( $post->post_type );
@@ -1106,8 +1111,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1106
  $data['author'] = (int) $post->post_author;
1107
  }
1108
 
1109
- if ( ! empty( $schema['properties']['featured_image'] ) ) {
1110
- $data['featured_image'] = (int) get_post_thumbnail_id( $post->ID );
1111
  }
1112
 
1113
  if ( ! empty( $schema['properties']['parent'] ) ) {
@@ -1146,10 +1151,15 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1146
  }
1147
  }
1148
 
1149
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1150
- $data = $this->filter_response_by_context( $data, $context );
 
 
 
1151
 
 
1152
  $data = $this->add_additional_fields_to_object( $data, $request );
 
1153
 
1154
  // Wrap the data in a response object.
1155
  $response = rest_ensure_response( $data );
@@ -1166,7 +1176,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1166
  * @param WP_Post $post Post object.
1167
  * @param WP_REST_Request $request Request object.
1168
  */
1169
- return apply_filters( 'rest_prepare_' . $this->post_type, $response, $post, $request );
1170
  }
1171
 
1172
  /**
@@ -1221,9 +1231,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1221
  );
1222
  }
1223
 
1224
- // If we have a featured image, add that.
1225
- if ( $featured_image = get_post_thumbnail_id( $post->ID ) ) {
1226
- $image_url = rest_url( 'wp/v2/media/' . $featured_image );
1227
  $links['https://api.w.org/featuredmedia'] = array(
1228
  'href' => $image_url,
1229
  'embeddable' => true,
@@ -1244,12 +1254,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1244
  foreach ( $taxonomies as $tax ) {
1245
  $taxonomy_obj = get_taxonomy( $tax );
1246
  // Skip taxonomies that are not public.
1247
- if ( false === $taxonomy_obj->public || 'post_format' === $tax ) {
1248
  continue;
1249
  }
1250
 
1251
  $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1252
- $terms_url = rest_url( trailingslashit( $base ) . $post->ID . '/' . $tax_base );
 
 
 
 
1253
 
1254
  $links['https://api.w.org/term'][] = array(
1255
  'href' => $terms_url,
@@ -1276,7 +1290,6 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1276
  */
1277
  public function get_item_schema() {
1278
 
1279
- $base = $this->get_post_type_base( $this->post_type );
1280
  $schema = array(
1281
  '$schema' => 'http://json-schema.org/draft-04/schema#',
1282
  'title' => $this->post_type,
@@ -1496,8 +1509,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1496
  break;
1497
 
1498
  case 'thumbnail':
1499
- $schema['properties']['featured_image'] = array(
1500
- 'description' => __( 'The id of the featured image for the object.' ),
1501
  'type' => 'integer',
1502
  'context' => array( 'view', 'edit' ),
1503
  );
@@ -1555,6 +1568,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1555
  );
1556
  }
1557
 
 
 
 
 
 
 
 
 
 
 
1558
  return $this->add_additional_fields_schema( $schema );
1559
  }
1560
 
@@ -1576,12 +1599,23 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1576
  'sanitize_callback' => 'absint',
1577
  );
1578
  }
 
 
 
 
 
 
1579
  $params['include'] = array(
1580
  'description' => __( 'Limit result set to specific ids.' ),
1581
  'type' => 'array',
1582
  'default' => array(),
1583
  'sanitize_callback' => 'wp_parse_id_list',
1584
  );
 
 
 
 
 
1585
  $params['order'] = array(
1586
  'description' => __( 'Order sort attribute ascending or descending.' ),
1587
  'type' => 'string',
@@ -1602,17 +1636,27 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1602
  );
1603
 
1604
  $post_type_obj = get_post_type_object( $this->post_type );
1605
- if ( $post_type_obj->hierarchical ) {
1606
  $params['parent'] = array(
1607
- 'description' => _( 'Limit result set to that of a specific parent id.' ),
1608
- 'type' => 'integer',
1609
- 'sanitize_callback' => 'absint',
1610
- 'default' => null,
 
 
 
 
 
 
1611
  );
1612
  }
1613
 
 
 
 
 
1614
  $params['status'] = array(
1615
- 'default' => 'attachment' === $this->post_type ? 'inherit' : 'publish',
1616
  'description' => __( 'Limit result set to posts assigned a specific status.' ),
1617
  'sanitize_callback' => 'sanitize_key',
1618
  'type' => 'string',
@@ -1630,10 +1674,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1630
  * @param mixed $value
1631
  * @param WP_REST_Request $request
1632
  * @param string $parameter
1633
- * @return WP_Error|bool
1634
  */
1635
  public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
1636
- if ( 'publish' === $value || ( 'attachment' === $this->post_type && 'inherit' === $value ) ) {
1637
  return true;
1638
  }
1639
  $post_type_obj = get_post_type_object( $this->post_type );
53
  'args' => array(
54
  'force' => array(
55
  'default' => false,
56
+ 'description' => __( 'Whether to bypass trash and force deletion.' ),
57
  ),
58
  ),
59
  ),
62
  ) );
63
  }
64
 
65
+ /**
66
+ * Check if a given request has access to read /posts.
67
+ *
68
+ * @param WP_REST_Request $request Full details about the request.
69
+ * @return WP_Error|boolean
70
+ */
71
+ public function get_items_permissions_check( $request ) {
72
+
73
+ $post_type = get_post_type_object( $this->post_type );
74
+
75
+ if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
76
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
77
+ }
78
+
79
+ return true;
80
+ }
81
+
82
  /**
83
  * Get a collection of posts.
84
  *
86
  * @return WP_Error|WP_REST_Response
87
  */
88
  public function get_items( $request ) {
89
+ $args = array();
90
+ $args['author'] = $request['author'];
91
+ $args['offset'] = $request['offset'];
92
+ $args['order'] = $request['order'];
93
+ $args['orderby'] = $request['orderby'];
94
+ $args['paged'] = $request['page'];
95
+ $args['post__in'] = $request['include'];
96
+ $args['post__not_in'] = $request['exclude'];
97
+ $args['posts_per_page'] = $request['per_page'];
98
+ $args['name'] = $request['slug'];
99
+ $args['post_parent__in'] = $request['parent'];
100
+ $args['post_parent__not_in'] = $request['parent_exclude'];
101
+ $args['post_status'] = $request['status'];
102
+ $args['s'] = $request['search'];
103
 
104
  if ( is_array( $request['filter'] ) ) {
105
  $args = array_merge( $args, $request['filter'] );
115
  * Enables adding extra arguments or setting defaults for a post
116
  * collection request.
117
  *
118
+ * @see https://developer.wordpress.org/reference/classes/wp_user_query/
119
+ *
120
  * @param array $args Key value array of query var to query value.
121
  * @param WP_REST_Request $request The request used.
122
  */
123
+ $args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
124
  $query_args = $this->prepare_items_query( $args );
125
 
126
  $posts_query = new WP_Query();
136
  $posts[] = $this->prepare_response_for_collection( $data );
137
  }
138
 
 
 
 
 
139
  $page = (int) $query_args['paged'];
140
+ $total_posts = $posts_query->found_posts;
141
+
142
+ if ( $total_posts < 1 ) {
143
+ // Out-of-bounds, run the query again without LIMIT for total count
144
+ unset( $query_args['paged'] );
145
+ $count_query = new WP_Query();
146
+ $count_query->query( $query_args );
147
+ $total_posts = $count_query->found_posts;
148
+ }
149
 
 
 
 
150
  $max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
151
+
152
+ $response = rest_ensure_response( $posts );
153
+ $response->header( 'X-WP-Total', (int) $total_posts );
154
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
155
 
156
  $request_params = $request->get_query_params();
178
  return $response;
179
  }
180
 
181
+ /**
182
+ * Check if a given request has access to read a post.
183
+ *
184
+ * @param WP_REST_Request $request Full details about the request.
185
+ * @return WP_Error|boolean
186
+ */
187
+ public function get_item_permissions_check( $request ) {
188
+
189
+ $post = get_post( (int) $request['id'] );
190
+
191
+ if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
192
+ return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => rest_authorization_required_code() ) );
193
+ }
194
+
195
+ if ( $post ) {
196
+ return $this->check_read_permission( $post );
197
+ }
198
+
199
+ return true;
200
+ }
201
+
202
  /**
203
  * Get a single post.
204
  *
221
  return $response;
222
  }
223
 
224
+ /**
225
+ * Check if a given request has access to create a post.
226
+ *
227
+ * @param WP_REST_Request $request Full details about the request.
228
+ * @return WP_Error|boolean
229
+ */
230
+ public function create_item_permissions_check( $request ) {
231
+
232
+ $post_type = get_post_type_object( $this->post_type );
233
+
234
+ if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
235
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
236
+ }
237
+
238
+ if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
239
+ return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
240
+ }
241
+
242
+ if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
243
+ return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
244
+ }
245
+
246
+ if ( ! current_user_can( $post_type->cap->create_posts ) ) {
247
+ return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create new posts.' ), array( 'status' => rest_authorization_required_code() ) );
248
+ }
249
+ return true;
250
+ }
251
+
252
  /**
253
  * Create a single post.
254
  *
289
  }
290
  }
291
 
292
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
293
+ $this->handle_featured_media( $request['featured_media'], $post->ID );
294
  }
295
 
296
  if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
300
  if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
301
  $this->handle_template( $request['template'], $post->ID );
302
  }
303
+ $terms_update = $this->handle_terms( $post->ID, $request );
304
+ if ( is_wp_error( $terms_update ) ) {
305
+ return $terms_update;
306
+ }
307
 
308
+ $post = get_post( $post_id );
309
+ $this->update_additional_fields_for_object( $post, $request );
310
 
311
  /**
312
  * Fires after a single post is created or updated via the REST API.
313
  *
314
  * @param object $post Inserted Post object (not a WP_Post object).
315
  * @param WP_REST_Request $request Request object.
316
+ * @param boolean $creating True when creating post, false when updating.
317
  */
318
+ do_action( "rest_insert_{$this->post_type}", $post, $request, true );
319
 
320
+ $request->set_param( 'context', 'edit' );
321
+ $response = $this->prepare_item_for_response( $post, $request );
 
 
322
  $response = rest_ensure_response( $response );
323
  $response->set_status( 201 );
324
  $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_post_type_base( $post->post_type ) . '/' . $post_id ) );
326
  return $response;
327
  }
328
 
329
+ /**
330
+ * Check if a given request has access to update a post.
331
+ *
332
+ * @param WP_REST_Request $request Full details about the request.
333
+ * @return WP_Error|boolean
334
+ */
335
+ public function update_item_permissions_check( $request ) {
336
+
337
+ $post = get_post( $request['id'] );
338
+ $post_type = get_post_type_object( $this->post_type );
339
+
340
+ if ( $post && ! $this->check_update_permission( $post ) ) {
341
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to update this post.' ), array( 'status' => rest_authorization_required_code() ) );;
342
+ }
343
+
344
+ if ( ! empty( $request['password'] ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
345
+ return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create password protected posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
346
+ }
347
+
348
+ if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
349
+ return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
350
+ }
351
+
352
+ if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
353
+ return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
354
+ }
355
+
356
+ return true;
357
+ }
358
+
359
  /**
360
  * Update a single post.
361
  *
391
  set_post_format( $post, $request['format'] );
392
  }
393
 
394
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
395
+ $this->handle_featured_media( $request['featured_media'], $post_id );
396
  }
397
 
398
  if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
407
  $this->handle_template( $request['template'], $post->ID );
408
  }
409
 
410
+ $terms_update = $this->handle_terms( $post->ID, $request );
411
+ if ( is_wp_error( $terms_update ) ) {
412
+ return $terms_update;
413
+ }
414
 
415
+ $post = get_post( $post_id );
416
+ $this->update_additional_fields_for_object( $post, $request );
 
 
 
 
417
 
418
  /* This action is documented in lib/endpoints/class-wp-rest-controller.php */
419
+ do_action( "rest_insert_{$this->post_type}", $post, $request, false );
420
 
421
+ $request->set_param( 'context', 'edit' );
422
+ $response = $this->prepare_item_for_response( $post, $request );
 
 
423
  return rest_ensure_response( $response );
424
  }
425
 
426
+ /**
427
+ * Check if a given request has access to delete a post.
428
+ *
429
+ * @param WP_REST_Request $request Full details about the request.
430
+ * @return bool|WP_Error
431
+ */
432
+ public function delete_item_permissions_check( $request ) {
433
+
434
+ $post = get_post( $request['id'] );
435
+
436
+ if ( $post && ! $this->check_delete_permission( $post ) ) {
437
+ return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => rest_authorization_required_code() ) );
438
+ }
439
+
440
+ return true;
441
+ }
442
+
443
  /**
444
  * Delete a single post.
445
  *
469
  * @param boolean $supports_trash Whether the post type support trashing.
470
  * @param WP_Post $post The Post object being considered for trashing support.
471
  */
472
+ $supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post );
473
 
474
  if ( ! $this->check_delete_permission( $post ) ) {
475
  return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) );
476
  }
477
 
 
478
  $request->set_param( 'context', 'edit' );
479
+ $response = $this->prepare_item_for_response( $post, $request );
480
 
481
  // If we're forcing, then delete permanently.
482
  if ( $force ) {
490
 
491
  // Otherwise, only trash if we haven't already.
492
  if ( 'trash' === $post->post_status ) {
493
+ return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
494
  }
495
 
496
  // (Note that internally this falls through to `wp_delete_post` if
517
  * @param array $data The response data.
518
  * @param WP_REST_Request $request The request sent to the API.
519
  */
520
+ do_action( "rest_delete_{$this->post_type}", $post, $data, $request );
521
 
522
  return $response;
523
  }
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  /**
526
  * Determine the allowed query_vars for a get_items() response and
527
  * prepare for WP_Query.
547
  }
548
  }
549
 
 
 
 
 
550
  if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
551
  $query_args['ignore_sticky_posts'] = true;
552
  }
594
  $valid_vars = array_merge( $valid_vars, $private );
595
  }
596
  // Define our own in addition to WP's normal vars.
597
+ $rest_valid = array( 'offset', 'post__in', 'post__not_in', 'posts_per_page', 'ignore_sticky_posts', 'post_parent', 'post_parent__in', 'post_parent__not_in' );
598
  $valid_vars = array_merge( $valid_vars, $rest_valid );
599
 
600
  /**
645
  * @return string|null ISO8601/RFC3339 formatted datetime.
646
  */
647
  protected function prepare_date_response( $date_gmt, $date = null ) {
648
+ // Use the date if passed.
 
 
 
649
  if ( isset( $date ) ) {
650
  return mysql_to_rfc3339( $date );
651
  }
652
 
653
+ // Return null if $date_gmt is empty/zeros.
654
+ if ( '0000-00-00 00:00:00' === $date_gmt ) {
655
+ return null;
656
+ }
657
+
658
+ // Return the formatted datetime.
659
  return mysql_to_rfc3339( $date_gmt );
660
  }
661
 
758
 
759
  // Author
760
  if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
761
+ $post_author = (int) $request['author'];
762
+ if ( get_current_user_id() !== $post_author ) {
763
+ $user_obj = get_userdata( $post_author );
764
+ if ( ! $user_obj ) {
765
+ return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) );
766
+ }
767
  }
768
+ $prepared_post->post_author = $post_author;
 
769
  }
770
 
771
  // Post password.
788
  }
789
 
790
  // Parent.
 
791
  if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
792
  $parent = get_post( (int) $request['parent'] );
793
  if ( empty( $parent ) ) {
833
  * @return WP_Error|string $post_status
834
  */
835
  protected function handle_status_param( $post_status, $post_type ) {
 
836
 
837
  switch ( $post_status ) {
838
  case 'draft':
860
  }
861
 
862
  /**
863
+ * Determine the featured media based on a request param.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
  *
865
+ * @param int $featured_media
866
  * @param int $post_id
867
  */
868
+ protected function handle_featured_media( $featured_media, $post_id ) {
869
 
870
+ $featured_media = (int) $featured_media;
871
+ if ( $featured_media ) {
872
+ $result = set_post_thumbnail( $post_id, $featured_media );
873
  if ( $result ) {
874
  return true;
875
  } else {
876
+ return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media id.' ), array( 'status' => 400 ) );
877
  }
878
  } else {
879
  return delete_post_thumbnail( $post_id );
895
  }
896
  }
897
 
898
+ /**
899
+ * Update the post's terms from a REST request.
900
+ *
901
+ * @param int $post_id The post ID to update the terms form.
902
+ * @param WP_REST_Request $request The request object with post and terms data.
903
+ * @return null|WP_Error WP_Error on an error assigning any of ther terms.
904
+ */
905
+ protected function handle_terms( $post_id, $request ) {
906
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
907
+ foreach ( $taxonomies as $taxonomy ) {
908
+ $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
909
+
910
+ if ( ! isset( $request[ $base ] ) ) {
911
+ continue;
912
+ }
913
+ $terms = array_map( 'absint', $request[ $base ] );
914
+ $result = wp_set_object_terms( $post_id, $terms, $taxonomy->name );
915
+ if ( is_wp_error( $result ) ) {
916
+ return $result;
917
+ }
918
+ }
919
+ }
920
+
921
  /**
922
  * Check if a given post type should be viewed or managed.
923
  *
924
  * @param object|string $post_type
925
+ * @return boolean Is post type allowed?
926
  */
927
  protected function check_is_post_type_allowed( $post_type ) {
928
  if ( ! is_object( $post_type ) ) {
942
  * Correctly handles posts with the inherit status.
943
  *
944
  * @param object $post Post object.
945
+ * @return boolean Can we read it?
946
  */
947
  public function check_read_permission( $post ) {
948
  if ( ! empty( $post->post_password ) && ! $this->check_update_permission( $post ) ) {
983
  * Check if we can edit a post.
984
  *
985
  * @param object $post Post object.
986
+ * @return boolean Can we edit it?
987
  */
988
  protected function check_update_permission( $post ) {
989
  $post_type = get_post_type_object( $post->post_type );
999
  * Check if we can create a post.
1000
  *
1001
  * @param object $post Post object.
1002
+ * @return boolean Can we create it?.
1003
  */
1004
  protected function check_create_permission( $post ) {
1005
  $post_type = get_post_type_object( $post->post_type );
1015
  * Check if we can delete a post.
1016
  *
1017
  * @param object $post Post object.
1018
+ * @return boolean Can we delete it?
1019
  */
1020
  protected function check_delete_permission( $post ) {
1021
  $post_type = get_post_type_object( $post->post_type );
1111
  $data['author'] = (int) $post->post_author;
1112
  }
1113
 
1114
+ if ( ! empty( $schema['properties']['featured_media'] ) ) {
1115
+ $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
1116
  }
1117
 
1118
  if ( ! empty( $schema['properties']['parent'] ) ) {
1151
  }
1152
  }
1153
 
1154
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1155
+ foreach ( $taxonomies as $taxonomy ) {
1156
+ $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1157
+ $data[ $base ] = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
1158
+ }
1159
 
1160
+ $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1161
  $data = $this->add_additional_fields_to_object( $data, $request );
1162
+ $data = $this->filter_response_by_context( $data, $context );
1163
 
1164
  // Wrap the data in a response object.
1165
  $response = rest_ensure_response( $data );
1176
  * @param WP_Post $post Post object.
1177
  * @param WP_REST_Request $request Request object.
1178
  */
1179
+ return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
1180
  }
1181
 
1182
  /**
1231
  );
1232
  }
1233
 
1234
+ // If we have a featured media, add that.
1235
+ if ( $featured_media = get_post_thumbnail_id( $post->ID ) ) {
1236
+ $image_url = rest_url( 'wp/v2/media/' . $featured_media );
1237
  $links['https://api.w.org/featuredmedia'] = array(
1238
  'href' => $image_url,
1239
  'embeddable' => true,
1254
  foreach ( $taxonomies as $tax ) {
1255
  $taxonomy_obj = get_taxonomy( $tax );
1256
  // Skip taxonomies that are not public.
1257
+ if ( empty( $taxonomy_obj->show_in_rest ) ) {
1258
  continue;
1259
  }
1260
 
1261
  $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1262
+ $terms_url = add_query_arg(
1263
+ 'post',
1264
+ $post->ID,
1265
+ rest_url( 'wp/v2/' . $tax_base )
1266
+ );
1267
 
1268
  $links['https://api.w.org/term'][] = array(
1269
  'href' => $terms_url,
1290
  */
1291
  public function get_item_schema() {
1292
 
 
1293
  $schema = array(
1294
  '$schema' => 'http://json-schema.org/draft-04/schema#',
1295
  'title' => $this->post_type,
1509
  break;
1510
 
1511
  case 'thumbnail':
1512
+ $schema['properties']['featured_media'] = array(
1513
+ 'description' => __( 'The id of the featured media for the object.' ),
1514
  'type' => 'integer',
1515
  'context' => array( 'view', 'edit' ),
1516
  );
1568
  );
1569
  }
1570
 
1571
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1572
+ foreach ( $taxonomies as $taxonomy ) {
1573
+ $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1574
+ $schema['properties'][ $base ] = array(
1575
+ 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
1576
+ 'type' => 'array',
1577
+ 'context' => array( 'view', 'edit' ),
1578
+ );
1579
+ }
1580
+
1581
  return $this->add_additional_fields_schema( $schema );
1582
  }
1583
 
1599
  'sanitize_callback' => 'absint',
1600
  );
1601
  }
1602
+ $params['exclude'] = array(
1603
+ 'description' => __( 'Ensure result set excludes specific ids.' ),
1604
+ 'type' => 'array',
1605
+ 'default' => array(),
1606
+ 'sanitize_callback' => 'wp_parse_id_list',
1607
+ );
1608
  $params['include'] = array(
1609
  'description' => __( 'Limit result set to specific ids.' ),
1610
  'type' => 'array',
1611
  'default' => array(),
1612
  'sanitize_callback' => 'wp_parse_id_list',
1613
  );
1614
+ $params['offset'] = array(
1615
+ 'description' => __( 'Offset the result set by a specific number of items.' ),
1616
+ 'type' => 'integer',
1617
+ 'sanitize_callback' => 'absint',
1618
+ );
1619
  $params['order'] = array(
1620
  'description' => __( 'Order sort attribute ascending or descending.' ),
1621
  'type' => 'string',
1636
  );
1637
 
1638
  $post_type_obj = get_post_type_object( $this->post_type );
1639
+ if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) {
1640
  $params['parent'] = array(
1641
+ 'description' => _( 'Limit result set to those of particular parent ids.' ),
1642
+ 'type' => 'array',
1643
+ 'sanitize_callback' => 'wp_parse_id_list',
1644
+ 'default' => array(),
1645
+ );
1646
+ $params['parent_exclude'] = array(
1647
+ 'description' => _( 'Limit result set to all items except those of a particular parent id.' ),
1648
+ 'type' => 'array',
1649
+ 'sanitize_callback' => 'wp_parse_id_list',
1650
+ 'default' => array(),
1651
  );
1652
  }
1653
 
1654
+ $params['slug'] = array(
1655
+ 'description' => __( 'Limit result set to posts with a specific slug.' ),
1656
+ 'type' => 'string',
1657
+ );
1658
  $params['status'] = array(
1659
+ 'default' => 'publish',
1660
  'description' => __( 'Limit result set to posts assigned a specific status.' ),
1661
  'sanitize_callback' => 'sanitize_key',
1662
  'type' => 'string',
1674
  * @param mixed $value
1675
  * @param WP_REST_Request $request
1676
  * @param string $parameter
1677
+ * @return WP_Error|boolean
1678
  */
1679
  public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
1680
+ if ( 'publish' === $value ) {
1681
  return true;
1682
  }
1683
  $post_type_obj = get_post_type_object( $this->post_type );
lib/endpoints/class-wp-rest-posts-terms-controller.php DELETED
@@ -1,319 +0,0 @@
1
- <?php
2
-
3
- class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
4
-
5
- protected $post_type;
6
-
7
- public function __construct( $post_type, $taxonomy ) {
8
- $this->post_type = $post_type;
9
- $this->taxonomy = $taxonomy;
10
- $this->posts_controller = new WP_REST_Posts_Controller( $post_type );
11
- $this->terms_controller = new WP_REST_Terms_Controller( $taxonomy );
12
- }
13
-
14
- /**
15
- * Register the routes for the objects of the controller.
16
- */
17
- public function register_routes() {
18
-
19
- $base = $this->posts_controller->get_post_type_base( $this->post_type );
20
- $tax_base = $this->terms_controller->get_taxonomy_base( $this->taxonomy );
21
-
22
- register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/%s', $base, $tax_base ), array(
23
- array(
24
- 'methods' => WP_REST_Server::READABLE,
25
- 'callback' => array( $this, 'get_items' ),
26
- 'permission_callback' => array( $this, 'get_items_permissions_check' ),
27
- 'args' => $this->get_collection_params(),
28
- ),
29
- 'schema' => array( $this, 'get_public_item_schema' ),
30
- ) );
31
-
32
- register_rest_route( 'wp/v2', sprintf( '/%s/(?P<post_id>[\d]+)/%s/(?P<term_id>[\d]+)', $base, $tax_base ), array(
33
- array(
34
- 'methods' => WP_REST_Server::READABLE,
35
- 'callback' => array( $this, 'get_item' ),
36
- 'permission_callback' => array( $this, 'get_items_permissions_check' ),
37
- 'args' => array(
38
- 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
39
- ),
40
- ),
41
- array(
42
- 'methods' => WP_REST_Server::CREATABLE,
43
- 'callback' => array( $this, 'create_item' ),
44
- 'permission_callback' => array( $this, 'manage_item_permissions_check' ),
45
- ),
46
- array(
47
- 'methods' => WP_REST_Server::DELETABLE,
48
- 'callback' => array( $this, 'delete_item' ),
49
- 'permission_callback' => array( $this, 'manage_item_permissions_check' ),
50
- 'args' => array(
51
- 'force' => array(
52
- 'default' => false,
53
- ),
54
- ),
55
- ),
56
- 'schema' => array( $this, 'get_public_item_schema' ),
57
- ) );
58
- }
59
-
60
- /**
61
- * Get all the terms that are attached to a post
62
- *
63
- * @param WP_REST_Request $request Full details about the request
64
- * @return WP_Error|WP_REST_Response
65
- */
66
- public function get_items( $request ) {
67
-
68
- $post = get_post( absint( $request['post_id'] ) );
69
-
70
- $is_request_valid = $this->validate_request( $request );
71
- if ( is_wp_error( $is_request_valid ) ) {
72
- return $is_request_valid;
73
- }
74
-
75
- $args = array(
76
- 'order' => $request['order'],
77
- 'orderby' => $request['orderby'],
78
- );
79
- $terms = wp_get_object_terms( $post->ID, $this->taxonomy, $args );
80
-
81
- $response = array();
82
- foreach ( $terms as $term ) {
83
- $data = $this->terms_controller->prepare_item_for_response( $term, $request );
84
- $response[] = $this->prepare_response_for_collection( $data );
85
- }
86
-
87
- $response = rest_ensure_response( $response );
88
-
89
- return $response;
90
- }
91
-
92
- /**
93
- * Get a term that is attached to a post
94
- *
95
- * @param WP_REST_Request $request Full details about the request
96
- * @return WP_Error|WP_REST_Response
97
- */
98
- public function get_item( $request ) {
99
- $post = get_post( absint( $request['post_id'] ) );
100
- $term_id = absint( $request['term_id'] );
101
-
102
- $is_request_valid = $this->validate_request( $request );
103
- if ( is_wp_error( $is_request_valid ) ) {
104
- return $is_request_valid;
105
- }
106
-
107
- $terms = wp_get_object_terms( $post->ID, $this->taxonomy );
108
-
109
- if ( ! in_array( $term_id, wp_list_pluck( $terms, 'term_id' ) ) ) {
110
- return new WP_Error( 'rest_post_not_in_term', __( 'Invalid taxonomy for post id.' ), array( 'status' => 404 ) );
111
- }
112
-
113
- $term = $this->terms_controller->prepare_item_for_response( get_term( $term_id, $this->taxonomy ), $request );
114
-
115
- $response = rest_ensure_response( $term );
116
-
117
- return $response;
118
- }
119
-
120
- /**
121
- * Add a term to a post
122
- *
123
- * @param WP_REST_Request $request Full details about the request
124
- * @return WP_Error|WP_REST_Response
125
- */
126
- public function create_item( $request ) {
127
- $post = get_post( $request['post_id'] );
128
- $term_id = absint( $request['term_id'] );
129
-
130
- $is_request_valid = $this->validate_request( $request );
131
- if ( is_wp_error( $is_request_valid ) ) {
132
- return $is_request_valid;
133
- }
134
-
135
- $term = get_term( $term_id, $this->taxonomy );
136
- $tt_ids = wp_set_object_terms( $post->ID, $term->term_id, $this->taxonomy, true );
137
-
138
- if ( is_wp_error( $tt_ids ) ) {
139
- return $tt_ids;
140
- }
141
-
142
- $term = $this->terms_controller->prepare_item_for_response( get_term( $term_id, $this->taxonomy ), $request );
143
-
144
- $response = rest_ensure_response( $term );
145
- $response->set_status( 201 );
146
-
147
- /**
148
- * Fires after a term is added to a post via the REST API.
149
- *
150
- * @param array $term The added term data.
151
- * @param WP_Post $post The post the term was added to.
152
- * @param WP_REST_Request $request The request sent to the API.
153
- */
154
- do_action( 'rest_insert_term', $term, $post, $request );
155
-
156
- return $term;
157
- }
158
-
159
- /**
160
- * Remove a term from a post.
161
- *
162
- * @param WP_REST_Request $request Full details about the request
163
- * @return WP_Error|null
164
- */
165
- public function delete_item( $request ) {
166
- $post = get_post( absint( $request['post_id'] ) );
167
- $term_id = absint( $request['term_id'] );
168
- $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
169
-
170
- // We don't support trashing for this type, error out
171
- if ( ! $force ) {
172
- return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
173
- }
174
-
175
- $is_request_valid = $this->validate_request( $request );
176
- if ( is_wp_error( $is_request_valid ) ) {
177
- return $is_request_valid;
178
- }
179
-
180
- $previous_item = $this->get_item( $request );
181
-
182
- $remove = wp_remove_object_terms( $post->ID, $term_id, $this->taxonomy );
183
-
184
- if ( is_wp_error( $remove ) ) {
185
- return $remove;
186
- }
187
-
188
- /**
189
- * Fires after a term is removed from a post via the REST API.
190
- *
191
- * @param array $previous_item The removed term data.
192
- * @param WP_Post $post The post the term was removed from.
193
- * @param WP_REST_Request $request The request sent to the API.
194
- */
195
- do_action( 'rest_remove_term', $previous_item, $post, $request );
196
-
197
- return $previous_item;
198
- }
199
-
200
- /**
201
- * Get the Term schema, conforming to JSON Schema.
202
- *
203
- * @return array
204
- */
205
- public function get_item_schema() {
206
- return $this->terms_controller->get_item_schema();
207
- }
208
-
209
- /**
210
- * Validate the API request for relationship requests.
211
- *
212
- * @param WP_REST_Request $request Full data about the request.
213
- * @return WP_Error|true
214
- */
215
- protected function validate_request( $request ) {
216
- $post = get_post( (int) $request['post_id'] );
217
-
218
- if ( empty( $post ) || empty( $post->ID ) || $post->post_type !== $this->post_type ) {
219
- return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
220
- }
221
-
222
- if ( ! $this->posts_controller->check_read_permission( $post ) ) {
223
- return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot view this post.' ), array( 'status' => rest_authorization_required_code() ) );
224
- }
225
-
226
- if ( ! empty( $request['term_id'] ) ) {
227
- $term_id = absint( $request['term_id'] );
228
-
229
- $term = get_term( $term_id, $this->taxonomy );
230
- if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
231
- return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
232
- }
233
- }
234
-
235
- return true;
236
- }
237
-
238
- /**
239
- * Check if a given request has access to read a post's term.
240
- *
241
- * @param WP_REST_Request $request Full details about the request.
242
- * @return bool|WP_Error
243
- */
244
- public function get_items_permissions_check( $request ) {
245
-
246
- $post_request = new WP_REST_Request();
247
- $post_request->set_param( 'id', $request['post_id'] );
248
-
249
- $post_check = $this->posts_controller->get_item_permissions_check( $post_request );
250
-
251
- if ( ! $post_check || is_wp_error( $post_check ) ) {
252
- return $post_check;
253
- }
254
-
255
- $term_request = new WP_REST_Request();
256
- $term_request->set_param( 'id', $request['term_id'] );
257
-
258
- $terms_check = $this->terms_controller->get_item_permissions_check( $term_request );
259
-
260
- if ( ! $terms_check || is_wp_error( $terms_check ) ) {
261
- return $terms_check;
262
- }
263
-
264
- return true;
265
- }
266
-
267
- /**
268
- * Check if a given request has access to manage a post/term relationship.
269
- *
270
- * @param WP_REST_Request $request Full details about the request.
271
- * @return bool|WP_Error
272
- */
273
- public function manage_item_permissions_check( $request ) {
274
-
275
- $taxonomy_obj = get_taxonomy( $this->taxonomy );
276
- if ( ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
277
- return new WP_Error( 'rest_cannot_assign', __( 'Sorry, you are not allowed to assign terms.' ), array( 'status' => rest_authorization_required_code() ) );
278
- }
279
-
280
- $post_request = new WP_REST_Request();
281
- $post_request->set_param( 'id', $request['post_id'] );
282
- $post_check = $this->posts_controller->update_item_permissions_check( $post_request );
283
-
284
- if ( ! $post_check || is_wp_error( $post_check ) ) {
285
- return $post_check;
286
- }
287
-
288
- return true;
289
- }
290
-
291
- /**
292
- * Get the query params for collections
293
- *
294
- * @return array
295
- */
296
- public function get_collection_params() {
297
- $query_params = array();
298
- $query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
299
- $query_params['order'] = array(
300
- 'description' => __( 'Order sort attribute ascending or descending.' ),
301
- 'type' => 'string',
302
- 'default' => 'asc',
303
- 'enum' => array( 'asc', 'desc' ),
304
- );
305
- $query_params['orderby'] = array(
306
- 'description' => __( 'Sort collection by object attribute.' ),
307
- 'type' => 'string',
308
- 'default' => 'name',
309
- 'enum' => array(
310
- 'count',
311
- 'name',
312
- 'slug',
313
- 'term_order',
314
- ),
315
- );
316
- return $query_params;
317
- }
318
-
319
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -48,6 +48,26 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
48
 
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  /**
52
  * Get a collection of revisions
53
  *
@@ -68,27 +88,17 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
68
  $data = $this->prepare_item_for_response( $revision, $request );
69
  $response[] = $this->prepare_response_for_collection( $data );
70
  }
71
- return $response;
72
  }
73
 
74
  /**
75
- * Check if a given request has access to get revisions
76
  *
77
  * @param WP_REST_Request $request Full data about the request.
78
- * @return WP_Error|bool
79
  */
80
- public function get_items_permissions_check( $request ) {
81
-
82
- $parent = get_post( $request['parent_id'] );
83
- if ( ! $parent ) {
84
- return true;
85
- }
86
- $parent_post_type_obj = get_post_type_object( $parent->post_type );
87
- if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
88
- return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
89
- }
90
-
91
- return true;
92
  }
93
 
94
  /**
@@ -110,24 +120,32 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
110
  }
111
 
112
  $response = $this->prepare_item_for_response( $revision, $request );
113
- return $response;
114
  }
115
 
116
  /**
117
- * Check if a given request has access to get a specific revision
118
  *
119
- * @param WP_REST_Request $request Full data about the request.
120
- * @return WP_Error|bool
121
  */
122
- public function get_item_permissions_check( $request ) {
123
- return $this->get_items_permissions_check( $request );
 
 
 
 
 
 
 
 
124
  }
125
 
126
  /**
127
  * Delete a single revision
128
  *
129
  * @param WP_REST_Request $request Full details about the request
130
- * @return bool|WP_Error
131
  */
132
  public function delete_item( $request ) {
133
  $result = wp_delete_post( $request['id'], true );
@@ -149,30 +167,12 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
149
  }
150
  }
151
 
152
- /**
153
- * Check if a given request has access to delete a revision
154
- *
155
- * @param WP_REST_Request $request Full details about the request.
156
- * @return bool|WP_Error
157
- */
158
- public function delete_item_permissions_check( $request ) {
159
-
160
- $response = $this->get_items_permissions_check( $request );
161
- if ( ! $response || is_wp_error( $response ) ) {
162
- return $response;
163
- }
164
-
165
- $post = get_post( $request['id'] );
166
- $post_type = get_post_type_object( 'revision' );
167
- return current_user_can( $post_type->cap->delete_post, $post->ID );
168
- }
169
-
170
  /**
171
  * Prepare the revision for the REST response
172
  *
173
  * @param WP_Post $post Post revision object.
174
  * @param WP_REST_Request $request Request object.
175
- * @return array
176
  */
177
  public function prepare_item_for_response( $post, $request ) {
178
 
@@ -204,12 +204,9 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
204
  }
205
 
206
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
207
- $data = $this->filter_response_by_context( $data, $context );
208
  $data = $this->add_additional_fields_to_object( $data, $request );
 
209
  $response = rest_ensure_response( $data );
210
- if ( is_wp_error( $response ) ) {
211
- return $response;
212
- }
213
 
214
  if ( ! empty( $data['parent'] ) ) {
215
  $response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
@@ -255,7 +252,7 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
255
  public function get_item_schema() {
256
  $schema = array(
257
  '$schema' => 'http://json-schema.org/draft-04/schema#',
258
- 'title' => "{$this->parent_base}-revision",
259
  'type' => 'object',
260
  /*
261
  * Base properties for every Revision
48
 
49
  }
50
 
51
+ /**
52
+ * Check if a given request has access to get revisions
53
+ *
54
+ * @param WP_REST_Request $request Full data about the request.
55
+ * @return WP_Error|boolean
56
+ */
57
+ public function get_items_permissions_check( $request ) {
58
+
59
+ $parent = get_post( $request['parent_id'] );
60
+ if ( ! $parent ) {
61
+ return true;
62
+ }
63
+ $parent_post_type_obj = get_post_type_object( $parent->post_type );
64
+ if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
65
+ return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
66
+ }
67
+
68
+ return true;
69
+ }
70
+
71
  /**
72
  * Get a collection of revisions
73
  *
88
  $data = $this->prepare_item_for_response( $revision, $request );
89
  $response[] = $this->prepare_response_for_collection( $data );
90
  }
91
+ return rest_ensure_response( $response );
92
  }
93
 
94
  /**
95
+ * Check if a given request has access to get a specific revision
96
  *
97
  * @param WP_REST_Request $request Full data about the request.
98
+ * @return WP_Error|boolean
99
  */
100
+ public function get_item_permissions_check( $request ) {
101
+ return $this->get_items_permissions_check( $request );
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
  /**
120
  }
121
 
122
  $response = $this->prepare_item_for_response( $revision, $request );
123
+ return rest_ensure_response( $response );
124
  }
125
 
126
  /**
127
+ * Check if a given request has access to delete a revision
128
  *
129
+ * @param WP_REST_Request $request Full details about the request.
130
+ * @return WP_Error|boolean
131
  */
132
+ public function delete_item_permissions_check( $request ) {
133
+
134
+ $response = $this->get_items_permissions_check( $request );
135
+ if ( ! $response || is_wp_error( $response ) ) {
136
+ return $response;
137
+ }
138
+
139
+ $post = get_post( $request['id'] );
140
+ $post_type = get_post_type_object( 'revision' );
141
+ return current_user_can( $post_type->cap->delete_post, $post->ID );
142
  }
143
 
144
  /**
145
  * Delete a single revision
146
  *
147
  * @param WP_REST_Request $request Full details about the request
148
+ * @return WP_Error|boolean
149
  */
150
  public function delete_item( $request ) {
151
  $result = wp_delete_post( $request['id'], true );
167
  }
168
  }
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  /**
171
  * Prepare the revision for the REST response
172
  *
173
  * @param WP_Post $post Post revision object.
174
  * @param WP_REST_Request $request Request object.
175
+ * @return WP_REST_Response $response
176
  */
177
  public function prepare_item_for_response( $post, $request ) {
178
 
204
  }
205
 
206
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
207
  $data = $this->add_additional_fields_to_object( $data, $request );
208
+ $data = $this->filter_response_by_context( $data, $context );
209
  $response = rest_ensure_response( $data );
 
 
 
210
 
211
  if ( ! empty( $data['parent'] ) ) {
212
  $response->add_link( 'parent', rest_url( sprintf( 'wp/%s/%d', $this->parent_base, $data['parent'] ) ) );
252
  public function get_item_schema() {
253
  $schema = array(
254
  '$schema' => 'http://json-schema.org/draft-04/schema#',
255
+ 'title' => "{$this->parent_post_type}-revision",
256
  'type' => 'object',
257
  /*
258
  * Base properties for every Revision
lib/endpoints/class-wp-rest-taxonomies-controller.php CHANGED
@@ -50,28 +50,14 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
50
  $tax = $this->prepare_response_for_collection( $tax );
51
  $data[ $tax_type ] = $tax;
52
  }
53
- return $data;
54
- }
55
-
56
- /**
57
- * Get a specific taxonomy
58
- *
59
- * @param WP_REST_Request $request
60
- * @return array|WP_Error
61
- */
62
- public function get_item( $request ) {
63
- $tax_obj = get_taxonomy( $request['taxonomy'] );
64
- if ( empty( $tax_obj ) ) {
65
- return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) );
66
- }
67
- return $this->prepare_item_for_response( $tax_obj, $request );
68
  }
69
 
70
  /**
71
  * Check if a given request has access a taxonomy
72
  *
73
  * @param WP_REST_Request $request Full details about the request.
74
- * @return bool
75
  */
76
  public function get_item_permissions_check( $request ) {
77
 
@@ -89,12 +75,27 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
89
  return true;
90
  }
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  /**
93
  * Prepare a taxonomy object for serialization
94
  *
95
  * @param stdClass $taxonomy Taxonomy data
96
  * @param WP_REST_Request $request
97
- * @return array Taxonomy data
98
  */
99
  public function prepare_item_for_response( $taxonomy, $request ) {
100
 
@@ -109,8 +110,8 @@ class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
109
  );
110
 
111
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
112
- $data = $this->filter_response_by_context( $data, $context );
113
  $data = $this->add_additional_fields_to_object( $data, $request );
 
114
 
115
  // Wrap the data in a response object.
116
  $response = rest_ensure_response( $data );
50
  $tax = $this->prepare_response_for_collection( $tax );
51
  $data[ $tax_type ] = $tax;
52
  }
53
+ return rest_ensure_response( $data );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
  /**
57
  * Check if a given request has access a taxonomy
58
  *
59
  * @param WP_REST_Request $request Full details about the request.
60
+ * @return WP_Error|boolean
61
  */
62
  public function get_item_permissions_check( $request ) {
63
 
75
  return true;
76
  }
77
 
78
+ /**
79
+ * Get a specific taxonomy
80
+ *
81
+ * @param WP_REST_Request $request
82
+ * @return array|WP_Error
83
+ */
84
+ public function get_item( $request ) {
85
+ $tax_obj = get_taxonomy( $request['taxonomy'] );
86
+ if ( empty( $tax_obj ) ) {
87
+ return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) );
88
+ }
89
+ $data = $this->prepare_item_for_response( $tax_obj, $request );
90
+ return rest_ensure_response( $data );
91
+ }
92
+
93
  /**
94
  * Prepare a taxonomy object for serialization
95
  *
96
  * @param stdClass $taxonomy Taxonomy data
97
  * @param WP_REST_Request $request
98
+ * @return WP_REST_Response $response
99
  */
100
  public function prepare_item_for_response( $taxonomy, $request ) {
101
 
110
  );
111
 
112
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
113
  $data = $this->add_additional_fields_to_object( $data, $request );
114
+ $data = $this->filter_response_by_context( $data, $context );
115
 
116
  // Wrap the data in a response object.
117
  $response = rest_ensure_response( $data );
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -55,12 +55,28 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
55
  'methods' => WP_REST_Server::DELETABLE,
56
  'callback' => array( $this, 'delete_item' ),
57
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
 
 
 
 
 
 
58
  ),
59
 
60
  'schema' => array( $this, 'get_public_item_schema' ),
61
  ) );
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
64
  /**
65
  * Get terms associated with a taxonomy
66
  *
@@ -69,15 +85,22 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
69
  */
70
  public function get_items( $request ) {
71
  $prepared_args = array(
 
72
  'include' => $request['include'],
73
  'order' => $request['order'],
74
  'orderby' => $request['orderby'],
 
75
  'hide_empty' => $request['hide_empty'],
76
  'number' => $request['per_page'],
77
  'search' => $request['search'],
 
78
  );
79
 
80
- $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
 
 
 
 
81
 
82
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
83
 
@@ -104,9 +127,17 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
104
  * passed to get_terms.
105
  * @param WP_REST_Request $request The current request.
106
  */
107
- $prepared_args = apply_filters( 'rest_terms_query', $prepared_args, $request );
108
 
109
- $query_result = get_terms( $this->taxonomy, $prepared_args );
 
 
 
 
 
 
 
 
110
  $response = array();
111
  foreach ( $query_result as $term ) {
112
  $data = $this->prepare_item_for_response( $term, $request );
@@ -150,6 +181,16 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
150
  return $response;
151
  }
152
 
 
 
 
 
 
 
 
 
 
 
153
  /**
154
  * Get a single term from a taxonomy
155
  *
@@ -171,6 +212,26 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
171
  return rest_ensure_response( $response );
172
  }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  /**
175
  * Create a single term for a taxonomy
176
  *
@@ -217,18 +278,51 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
217
  return $term;
218
  }
219
 
220
- $this->update_additional_fields_for_object( $term, $request );
221
 
222
- $get_request = new WP_REST_Request;
223
- $get_request->set_param( 'id', $term['term_id'] );
224
- $response = $this->get_item( $get_request );
 
 
 
 
 
225
 
 
 
 
226
  $response = rest_ensure_response( $response );
227
  $response->set_status( 201 );
228
- $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) . '/' . $term['term_id'] ) );
229
  return $response;
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  /**
233
  * Update a single term from a taxonomy
234
  *
@@ -272,134 +366,79 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
272
  }
273
  }
274
 
275
- $this->update_additional_fields_for_object( get_term( (int) $request['id'], $this->taxonomy ), $request );
276
-
277
- $get_request = new WP_REST_Request;
278
- $get_request->set_param( 'id', (int) $request['id'] );
279
- $response = $this->get_item( $get_request );
280
-
281
- return rest_ensure_response( $response );
282
- }
283
-
284
- /**
285
- * Delete a single term from a taxonomy
286
- *
287
- * @param WP_REST_Request $request Full details about the request
288
- * @return WP_REST_Response|WP_Error
289
- */
290
- public function delete_item( $request ) {
291
-
292
- // Get the actual term_id
293
  $term = get_term( (int) $request['id'], $this->taxonomy );
294
- $get_request = new WP_REST_Request;
295
- $get_request->set_param( 'id', (int) $request['id'] );
296
- $get_request->set_param( 'context', 'view' );
297
- $response = $this->prepare_item_for_response( $term, $get_request );
298
-
299
- $data = $response->get_data();
300
- $data = array(
301
- 'data' => $data,
302
- 'deleted' => true,
303
- );
304
- $response->set_data( $data );
305
-
306
- $retval = wp_delete_term( $term->term_id, $term->taxonomy );
307
- if ( ! $retval ) {
308
- return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
309
- }
310
-
311
- return $response;
312
- }
313
-
314
- /**
315
- * Check if a given request has access to read the terms.
316
- *
317
- * @param WP_REST_Request $request Full details about the request.
318
- * @return bool|WP_Error
319
- */
320
- public function get_items_permissions_check( $request ) {
321
- return $this->check_is_taxonomy_allowed( $this->taxonomy );
322
- }
323
-
324
- /**
325
- * Check if a given request has access to read a term.
326
- *
327
- * @param WP_REST_Request $request Full details about the request.
328
- * @return bool|WP_Error
329
- */
330
- public function get_item_permissions_check( $request ) {
331
- return $this->check_is_taxonomy_allowed( $this->taxonomy );
332
- }
333
-
334
-
335
- /**
336
- * Check if a given request has access to create a term
337
- *
338
- * @param WP_REST_Request $request Full details about the request.
339
- * @return bool|WP_Error
340
- */
341
- public function create_item_permissions_check( $request ) {
342
 
343
- if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
344
- return false;
345
- }
346
 
347
- $taxonomy_obj = get_taxonomy( $this->taxonomy );
348
- if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
349
- return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new terms.' ), array( 'status' => rest_authorization_required_code() ) );
350
- }
351
-
352
- return true;
353
  }
354
 
355
  /**
356
- * Check if a given request has access to update a term
357
  *
358
  * @param WP_REST_Request $request Full details about the request.
359
- * @return bool|WP_Error
360
  */
361
- public function update_item_permissions_check( $request ) {
362
-
363
  if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
364
  return false;
365
  }
366
-
367
  $term = get_term( (int) $request['id'], $this->taxonomy );
368
  if ( ! $term ) {
369
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
370
  }
371
-
372
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
373
- if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
374
- return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update terms.' ), array( 'status' => rest_authorization_required_code() ) );
375
  }
376
-
377
  return true;
378
  }
379
 
380
  /**
381
- * Check if a given request has access to delete a term
382
  *
383
- * @param WP_REST_Request $request Full details about the request.
384
- * @return bool|WP_Error
385
  */
386
- public function delete_item_permissions_check( $request ) {
387
 
388
- if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
389
- return false;
 
 
 
390
  }
391
 
392
  $term = get_term( (int) $request['id'], $this->taxonomy );
393
- if ( ! $term ) {
394
- return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
395
- }
396
 
397
- $taxonomy_obj = get_taxonomy( $this->taxonomy );
398
- if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
399
- return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete terms.' ), array( 'status' => rest_authorization_required_code() ) );
 
 
 
 
 
 
 
400
  }
401
 
402
- return true;
 
 
 
 
 
 
 
 
 
403
  }
404
 
405
  /**
@@ -423,6 +462,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
423
  *
424
  * @param obj $item Term object
425
  * @param WP_REST_Request $request
 
426
  */
427
  public function prepare_item_for_response( $item, $request ) {
428
 
@@ -441,8 +481,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
441
  }
442
 
443
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
444
- $data = $this->filter_response_by_context( $data, $context );
445
  $data = $this->add_additional_fields_to_object( $data, $request );
 
446
 
447
  $response = rest_ensure_response( $data );
448
 
@@ -457,7 +497,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
457
  * @param object $item The original term object.
458
  * @param WP_REST_Request $request Request used to generate the response.
459
  */
460
- return apply_filters( 'rest_prepare_term', $response, $item, $request );
461
  }
462
 
463
  /**
@@ -501,7 +541,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
501
  public function get_item_schema() {
502
  $schema = array(
503
  '$schema' => 'http://json-schema.org/draft-04/schema#',
504
- 'title' => 'term',
505
  'type' => 'object',
506
  'properties' => array(
507
  'id' => array(
@@ -575,15 +615,29 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
575
  */
576
  public function get_collection_params() {
577
  $query_params = parent::get_collection_params();
 
578
 
579
  $query_params['context']['default'] = 'view';
580
 
 
 
 
 
 
 
581
  $query_params['include'] = array(
582
  'description' => __( 'Limit result set to specific ids.' ),
583
  'type' => 'array',
584
  'default' => array(),
585
  'sanitize_callback' => 'wp_parse_id_list',
586
  );
 
 
 
 
 
 
 
587
  $query_params['order'] = array(
588
  'description' => __( 'Order sort attribute ascending or descending.' ),
589
  'type' => 'string',
@@ -605,7 +659,6 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
605
  'name',
606
  'slug',
607
  'term_group',
608
- 'term_id',
609
  'description',
610
  'count',
611
  ),
@@ -615,7 +668,6 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
615
  'type' => 'boolean',
616
  'default' => false,
617
  );
618
- $taxonomy = get_taxonomy( $this->taxonomy );
619
  if ( $taxonomy->hierarchical ) {
620
  $query_params['parent'] = array(
621
  'description' => __( 'Limit result set to terms assigned to a specific parent term.' ),
@@ -623,6 +675,15 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
623
  'sanitize_callback' => 'absint',
624
  );
625
  }
 
 
 
 
 
 
 
 
 
626
  return $query_params;
627
  }
628
 
@@ -630,7 +691,7 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
630
  * Check that the taxonomy is valid
631
  *
632
  * @param string
633
- * @return bool|WP_Error
634
  */
635
  protected function check_is_taxonomy_allowed( $taxonomy ) {
636
  $taxonomy_obj = get_taxonomy( $taxonomy );
55
  'methods' => WP_REST_Server::DELETABLE,
56
  'callback' => array( $this, 'delete_item' ),
57
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
58
+ 'args' => array(
59
+ 'force' => array(
60
+ 'default' => false,
61
+ 'description' => __( 'Required to be true, as resource does not support trashing.' ),
62
+ ),
63
+ ),
64
  ),
65
 
66
  'schema' => array( $this, 'get_public_item_schema' ),
67
  ) );
68
  }
69
 
70
+ /**
71
+ * Check if a given request has access to read the terms.
72
+ *
73
+ * @param WP_REST_Request $request Full details about the request.
74
+ * @return WP_Error|boolean
75
+ */
76
+ public function get_items_permissions_check( $request ) {
77
+ return $this->check_is_taxonomy_allowed( $this->taxonomy );
78
+ }
79
+
80
  /**
81
  * Get terms associated with a taxonomy
82
  *
85
  */
86
  public function get_items( $request ) {
87
  $prepared_args = array(
88
+ 'exclude' => $request['exclude'],
89
  'include' => $request['include'],
90
  'order' => $request['order'],
91
  'orderby' => $request['orderby'],
92
+ 'post' => $request['post'],
93
  'hide_empty' => $request['hide_empty'],
94
  'number' => $request['per_page'],
95
  'search' => $request['search'],
96
+ 'slug' => $request['slug'],
97
  );
98
 
99
+ if ( ! empty( $request['offset'] ) ) {
100
+ $prepared_args['offset'] = $request['offset'];
101
+ } else {
102
+ $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
103
+ }
104
 
105
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
106
 
127
  * passed to get_terms.
128
  * @param WP_REST_Request $request The current request.
129
  */
130
+ $prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
131
 
132
+ if ( ! empty( $prepared_args['post'] ) ) {
133
+ $terms_args = array(
134
+ 'order' => $prepared_args['order'],
135
+ 'orderby' => $prepared_args['orderby'],
136
+ );
137
+ $query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $terms_args );
138
+ } else {
139
+ $query_result = get_terms( $this->taxonomy, $prepared_args );
140
+ }
141
  $response = array();
142
  foreach ( $query_result as $term ) {
143
  $data = $this->prepare_item_for_response( $term, $request );
181
  return $response;
182
  }
183
 
184
+ /**
185
+ * Check if a given request has access to read a term.
186
+ *
187
+ * @param WP_REST_Request $request Full details about the request.
188
+ * @return WP_Error|boolean
189
+ */
190
+ public function get_item_permissions_check( $request ) {
191
+ return $this->check_is_taxonomy_allowed( $this->taxonomy );
192
+ }
193
+
194
  /**
195
  * Get a single term from a taxonomy
196
  *
212
  return rest_ensure_response( $response );
213
  }
214
 
215
+ /**
216
+ * Check if a given request has access to create a term
217
+ *
218
+ * @param WP_REST_Request $request Full details about the request.
219
+ * @return WP_Error|boolean
220
+ */
221
+ public function create_item_permissions_check( $request ) {
222
+
223
+ if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
224
+ return false;
225
+ }
226
+
227
+ $taxonomy_obj = get_taxonomy( $this->taxonomy );
228
+ if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
229
+ return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new terms.' ), array( 'status' => rest_authorization_required_code() ) );
230
+ }
231
+
232
+ return true;
233
+ }
234
+
235
  /**
236
  * Create a single term for a taxonomy
237
  *
278
  return $term;
279
  }
280
 
281
+ $term = get_term( $term['term_id'], $this->taxonomy );
282
 
283
+ /**
284
+ * Fires after a single term is created or updated via the REST API.
285
+ *
286
+ * @param WP_Term $term Inserted Term object.
287
+ * @param WP_REST_Request $request Request object.
288
+ * @param boolean $creating True when creating term, false when updating.
289
+ */
290
+ do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
291
 
292
+ $this->update_additional_fields_for_object( $term, $request );
293
+ $request->set_param( 'context', 'view' );
294
+ $response = $this->prepare_item_for_response( $term, $request );
295
  $response = rest_ensure_response( $response );
296
  $response->set_status( 201 );
297
+ $response->header( 'Location', rest_url( '/wp/v2/' . $this->get_taxonomy_base( $this->taxonomy ) . '/' . $term->term_id ) );
298
  return $response;
299
  }
300
 
301
+ /**
302
+ * Check if a given request has access to update a term
303
+ *
304
+ * @param WP_REST_Request $request Full details about the request.
305
+ * @return WP_Error|boolean
306
+ */
307
+ public function update_item_permissions_check( $request ) {
308
+
309
+ if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
310
+ return false;
311
+ }
312
+
313
+ $term = get_term( (int) $request['id'], $this->taxonomy );
314
+ if ( ! $term ) {
315
+ return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
316
+ }
317
+
318
+ $taxonomy_obj = get_taxonomy( $this->taxonomy );
319
+ if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
320
+ return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update terms.' ), array( 'status' => rest_authorization_required_code() ) );
321
+ }
322
+
323
+ return true;
324
+ }
325
+
326
  /**
327
  * Update a single term from a taxonomy
328
  *
366
  }
367
  }
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  $term = get_term( (int) $request['id'], $this->taxonomy );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
+ /* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
372
+ do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
 
373
 
374
+ $this->update_additional_fields_for_object( $term, $request );
375
+ $request->set_param( 'context', 'view' );
376
+ $response = $this->prepare_item_for_response( $term, $request );
377
+ return rest_ensure_response( $response );
 
 
378
  }
379
 
380
  /**
381
+ * Check if a given request has access to delete a term
382
  *
383
  * @param WP_REST_Request $request Full details about the request.
384
+ * @return WP_Error|boolean
385
  */
386
+ public function delete_item_permissions_check( $request ) {
 
387
  if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
388
  return false;
389
  }
 
390
  $term = get_term( (int) $request['id'], $this->taxonomy );
391
  if ( ! $term ) {
392
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
393
  }
 
394
  $taxonomy_obj = get_taxonomy( $this->taxonomy );
395
+ if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
396
+ return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete terms.' ), array( 'status' => rest_authorization_required_code() ) );
397
  }
 
398
  return true;
399
  }
400
 
401
  /**
402
+ * Delete a single term from a taxonomy
403
  *
404
+ * @param WP_REST_Request $request Full details about the request
405
+ * @return WP_REST_Response|WP_Error
406
  */
407
+ public function delete_item( $request ) {
408
 
409
+ $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
410
+
411
+ // We don't support trashing for this type, error out
412
+ if ( ! $force ) {
413
+ return new WP_Error( 'rest_trash_not_supported', __( 'Terms do not support trashing.' ), array( 'status' => 501 ) );
414
  }
415
 
416
  $term = get_term( (int) $request['id'], $this->taxonomy );
417
+ $request->set_param( 'context', 'view' );
418
+ $response = $this->prepare_item_for_response( $term, $request );
 
419
 
420
+ $data = $response->get_data();
421
+ $data = array(
422
+ 'data' => $data,
423
+ 'deleted' => true,
424
+ );
425
+ $response->set_data( $data );
426
+
427
+ $retval = wp_delete_term( $term->term_id, $term->taxonomy );
428
+ if ( ! $retval ) {
429
+ return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
430
  }
431
 
432
+ /**
433
+ * Fires after a single term is deleted via the REST API.
434
+ *
435
+ * @param WP_Term $term The deleted term.
436
+ * @param array $data The response data.
437
+ * @param WP_REST_Request $request The request sent to the API.
438
+ */
439
+ do_action( "rest_delete_{$this->taxonomy}", $term, $data, $request );
440
+
441
+ return $response;
442
  }
443
 
444
  /**
462
  *
463
  * @param obj $item Term object
464
  * @param WP_REST_Request $request
465
+ * @return WP_REST_Response $response
466
  */
467
  public function prepare_item_for_response( $item, $request ) {
468
 
481
  }
482
 
483
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 
484
  $data = $this->add_additional_fields_to_object( $data, $request );
485
+ $data = $this->filter_response_by_context( $data, $context );
486
 
487
  $response = rest_ensure_response( $data );
488
 
497
  * @param object $item The original term object.
498
  * @param WP_REST_Request $request Request used to generate the response.
499
  */
500
+ return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
501
  }
502
 
503
  /**
541
  public function get_item_schema() {
542
  $schema = array(
543
  '$schema' => 'http://json-schema.org/draft-04/schema#',
544
+ 'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
545
  'type' => 'object',
546
  'properties' => array(
547
  'id' => array(
615
  */
616
  public function get_collection_params() {
617
  $query_params = parent::get_collection_params();
618
+ $taxonomy = get_taxonomy( $this->taxonomy );
619
 
620
  $query_params['context']['default'] = 'view';
621
 
622
+ $query_params['exclude'] = array(
623
+ 'description' => __( 'Ensure result set excludes specific ids.' ),
624
+ 'type' => 'array',
625
+ 'default' => array(),
626
+ 'sanitize_callback' => 'wp_parse_id_list',
627
+ );
628
  $query_params['include'] = array(
629
  'description' => __( 'Limit result set to specific ids.' ),
630
  'type' => 'array',
631
  'default' => array(),
632
  'sanitize_callback' => 'wp_parse_id_list',
633
  );
634
+ if ( ! $taxonomy->hierarchical ) {
635
+ $query_params['offset'] = array(
636
+ 'description' => __( 'Offset the result set by a specific number of items.' ),
637
+ 'type' => 'integer',
638
+ 'sanitize_callback' => 'absint',
639
+ );
640
+ }
641
  $query_params['order'] = array(
642
  'description' => __( 'Order sort attribute ascending or descending.' ),
643
  'type' => 'string',
659
  'name',
660
  'slug',
661
  'term_group',
 
662
  'description',
663
  'count',
664
  ),
668
  'type' => 'boolean',
669
  'default' => false,
670
  );
 
671
  if ( $taxonomy->hierarchical ) {
672
  $query_params['parent'] = array(
673
  'description' => __( 'Limit result set to terms assigned to a specific parent term.' ),
675
  'sanitize_callback' => 'absint',
676
  );
677
  }
678
+ $query_params['post'] = array(
679
+ 'description' => __( 'Limit result set to terms assigned to a specific post.' ),
680
+ 'type' => 'number',
681
+ 'default' => false,
682
+ );
683
+ $query_params['slug'] = array(
684
+ 'description' => __( 'Limit result set to terms with a specific slug.' ),
685
+ 'type' => 'string',
686
+ );
687
  return $query_params;
688
  }
689
 
691
  * Check that the taxonomy is valid
692
  *
693
  * @param string
694
+ * @return WP_Error|boolean
695
  */
696
  protected function check_is_taxonomy_allowed( $taxonomy ) {
697
  $taxonomy_obj = get_taxonomy( $taxonomy );
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -52,7 +52,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
52
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
53
  'args' => array(
54
  'force' => array(
55
- 'default' => false,
 
56
  ),
57
  'reassign' => array(),
58
  ),
@@ -80,10 +81,15 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
80
  public function get_items( $request ) {
81
 
82
  $prepared_args = array();
 
83
  $prepared_args['include'] = $request['include'];
84
  $prepared_args['order'] = $request['order'];
85
  $prepared_args['number'] = $request['per_page'];
86
- $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
 
 
 
 
87
  $orderby_possibles = array(
88
  'id' => 'ID',
89
  'include' => 'include',
@@ -104,10 +110,15 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
104
  $prepared_args['search'] = '*' . $prepared_args['search'] . '*';
105
  }
106
 
 
 
 
 
 
107
  /**
108
  * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
109
  *
110
- * @see https://codex.wordpress.org/Class_Reference/WP_User_Query
111
  *
112
  * @param array $prepared_args Array of arguments for WP_User_Query.
113
  * @param WP_REST_Request $request The current request.
@@ -115,9 +126,6 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
115
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
116
 
117
  $query = new WP_User_Query( $prepared_args );
118
- if ( is_wp_error( $query ) ) {
119
- return $query;
120
- }
121
 
122
  $users = array();
123
  foreach ( $query->results as $user ) {
@@ -130,13 +138,17 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
130
  // Store pagation values for headers then unset for count query.
131
  $per_page = (int) $prepared_args['number'];
132
  $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
133
- unset( $prepared_args['number'] );
134
- unset( $prepared_args['offset'] );
135
 
136
  $prepared_args['fields'] = 'ID';
137
 
138
- $count_query = new WP_User_Query( $prepared_args );
139
- $total_users = $count_query->get_total();
 
 
 
 
 
 
140
  $response->header( 'X-WP-Total', (int) $total_users );
141
  $max_pages = ceil( $total_users / $per_page );
142
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
@@ -159,6 +171,38 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
159
  return $response;
160
  }
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  /**
163
  * Get a single user
164
  *
@@ -191,14 +235,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
191
  return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) );
192
  }
193
 
194
- $get_request = new WP_REST_Request;
195
- $get_request->set_param( 'id', $current_user_id );
196
- $get_request->set_param( 'context', $request['context'] );
197
- $response = $this->get_item( $get_request );
198
- if ( is_wp_error( $response ) ) {
199
- return $response;
200
- }
201
-
202
  $response = rest_ensure_response( $response );
203
  $response->header( 'Location', rest_url( sprintf( '/wp/v2/users/%d', $current_user_id ) ) );
204
  $response->set_status( 302 );
@@ -206,6 +244,21 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
206
  return $response;
207
  }
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  /**
210
  * Create a single user
211
  *
@@ -243,9 +296,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
243
  if ( is_wp_error( $user_id ) ) {
244
  return $user_id;
245
  }
246
- $user->ID = $user_id;
247
  }
248
 
 
249
  $this->update_additional_fields_for_object( $user, $request );
250
 
251
  /**
@@ -253,14 +306,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
253
  *
254
  * @param object $user Data used to create the user (not a WP_User object).
255
  * @param WP_REST_Request $request Request object.
256
- * @param bool $creating True when creating user, false when updating user.
257
  */
258
  do_action( 'rest_insert_user', $user, $request, true );
259
 
260
- $get_request = new WP_REST_Request;
261
- $get_request->set_param( 'id', $user_id );
262
- $get_request->set_param( 'context', 'edit' );
263
- $response = $this->get_item( $get_request );
264
  $response = rest_ensure_response( $response );
265
  $response->set_status( 201 );
266
  $response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
@@ -268,6 +319,27 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
268
  return $response;
269
  }
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  /**
272
  * Update a single user
273
  *
@@ -311,17 +383,33 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
311
  return $user_id;
312
  }
313
 
 
314
  $this->update_additional_fields_for_object( $user, $request );
315
 
316
  /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
317
  do_action( 'rest_insert_user', $user, $request, false );
318
 
319
- $get_request = new WP_REST_Request;
320
- $get_request->set_param( 'id', $user_id );
321
- $get_request->set_param( 'context', 'edit' );
322
- $response = $this->get_item( $get_request );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- return rest_ensure_response( $response );
325
  }
326
 
327
  /**
@@ -351,10 +439,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
351
  }
352
  }
353
 
354
- $get_request = new WP_REST_Request;
355
- $get_request->set_param( 'id', $id );
356
- $get_request->set_param( 'context', 'edit' );
357
- $orig_user = $this->prepare_item_for_response( $user, $get_request );
358
 
359
  $data = $orig_user->get_data();
360
  $data = array(
@@ -383,98 +469,12 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
383
  return $orig_user;
384
  }
385
 
386
- /**
387
- * Check if a given request has access to read a user
388
- *
389
- * @param WP_REST_Request $request Full details about the request.
390
- * @return bool|WP_Error
391
- */
392
- public function get_item_permissions_check( $request ) {
393
-
394
- $id = (int) $request['id'];
395
- $user = get_userdata( $id );
396
-
397
- if ( empty( $id ) || empty( $user->ID ) ) {
398
- return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
399
- }
400
-
401
- if ( get_current_user_id() === $id ) {
402
- return true;
403
- }
404
-
405
- $context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
406
-
407
- if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
408
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => rest_authorization_required_code() ) );
409
- } else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
410
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => rest_authorization_required_code() ) );
411
- } else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
412
- return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => rest_authorization_required_code() ) );
413
- }
414
-
415
- return true;
416
- }
417
-
418
- /**
419
- * Check if a given request has access create users
420
- *
421
- * @param WP_REST_Request $request Full details about the request.
422
- * @return bool
423
- */
424
- public function create_item_permissions_check( $request ) {
425
-
426
- if ( ! current_user_can( 'create_users' ) ) {
427
- return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => rest_authorization_required_code() ) );
428
- }
429
-
430
- return true;
431
- }
432
-
433
- /**
434
- * Check if a given request has access update a user
435
- *
436
- * @param WP_REST_Request $request Full details about the request.
437
- * @return bool
438
- */
439
- public function update_item_permissions_check( $request ) {
440
-
441
- $id = (int) $request['id'];
442
-
443
- if ( ! current_user_can( 'edit_user', $id ) ) {
444
- return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => rest_authorization_required_code() ) );
445
- }
446
-
447
- if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
448
- return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => rest_authorization_required_code() ) );
449
- }
450
-
451
- return true;
452
- }
453
-
454
- /**
455
- * Check if a given request has access delete a user
456
- *
457
- * @param WP_REST_Request $request Full details about the request.
458
- * @return bool
459
- */
460
- public function delete_item_permissions_check( $request ) {
461
-
462
- $id = (int) $request['id'];
463
- $reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
464
-
465
- if ( ! current_user_can( 'delete_user', $id ) ) {
466
- return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) );
467
- }
468
-
469
- return true;
470
- }
471
-
472
  /**
473
  * Prepare a single user output for response
474
  *
475
  * @param object $user User object.
476
  * @param WP_REST_Request $request Request object.
477
- * @return WP_REST_Response Response data.
478
  */
479
  public function prepare_item_for_response( $user, $request ) {
480
  $data = array(
@@ -497,9 +497,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
497
  );
498
 
499
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
500
- $data = $this->filter_response_by_context( $data, $context );
501
-
502
  $data = $this->add_additional_fields_to_object( $data, $request );
 
503
 
504
  // Wrap the data in a response object
505
  $response = rest_ensure_response( $data );
@@ -598,7 +597,7 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
598
  *
599
  * @param integer $user_id
600
  * @param string $role
601
- * @return boolen|WP_Error
602
  */
603
  protected function check_role_update( $user_id, $role ) {
604
  global $wp_roles;
@@ -789,6 +788,11 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
789
  'default' => array(),
790
  'sanitize_callback' => 'wp_parse_id_list',
791
  );
 
 
 
 
 
792
  $query_params['order'] = array(
793
  'default' => 'asc',
794
  'description' => __( 'Order sort attribute ascending or descending.' ),
@@ -808,6 +812,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
808
  'sanitize_callback' => 'sanitize_key',
809
  'type' => 'string',
810
  );
 
 
 
 
811
  return $query_params;
812
  }
813
  }
52
  'permission_callback' => array( $this, 'delete_item_permissions_check' ),
53
  'args' => array(
54
  'force' => array(
55
+ 'default' => false,
56
+ 'description' => __( 'Required to be true, as resource does not support trashing.' ),
57
  ),
58
  'reassign' => array(),
59
  ),
81
  public function get_items( $request ) {
82
 
83
  $prepared_args = array();
84
+ $prepared_args['exclude'] = $request['exclude'];
85
  $prepared_args['include'] = $request['include'];
86
  $prepared_args['order'] = $request['order'];
87
  $prepared_args['number'] = $request['per_page'];
88
+ if ( ! empty( $request['offset'] ) ) {
89
+ $prepared_args['offset'] = $request['offset'];
90
+ } else {
91
+ $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
92
+ }
93
  $orderby_possibles = array(
94
  'id' => 'ID',
95
  'include' => 'include',
110
  $prepared_args['search'] = '*' . $prepared_args['search'] . '*';
111
  }
112
 
113
+ if ( ! empty( $request['slug'] ) ) {
114
+ $prepared_args['search'] = $request['slug'];
115
+ $prepared_args['search_columns'] = array( 'user_nicename' );
116
+ }
117
+
118
  /**
119
  * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
120
  *
121
+ * @see https://developer.wordpress.org/reference/classes/wp_user_query/
122
  *
123
  * @param array $prepared_args Array of arguments for WP_User_Query.
124
  * @param WP_REST_Request $request The current request.
126
  $prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
127
 
128
  $query = new WP_User_Query( $prepared_args );
 
 
 
129
 
130
  $users = array();
131
  foreach ( $query->results as $user ) {
138
  // Store pagation values for headers then unset for count query.
139
  $per_page = (int) $prepared_args['number'];
140
  $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
 
 
141
 
142
  $prepared_args['fields'] = 'ID';
143
 
144
+ $total_users = $query->get_total();
145
+ if ( $total_users < 1 ) {
146
+ // Out-of-bounds, run the query again without LIMIT for total count
147
+ unset( $prepared_args['number'] );
148
+ unset( $prepared_args['offset'] );
149
+ $count_query = new WP_User_Query( $prepared_args );
150
+ $total_users = $count_query->get_total();
151
+ }
152
  $response->header( 'X-WP-Total', (int) $total_users );
153
  $max_pages = ceil( $total_users / $per_page );
154
  $response->header( 'X-WP-TotalPages', (int) $max_pages );
171
  return $response;
172
  }
173
 
174
+ /**
175
+ * Check if a given request has access to read a user
176
+ *
177
+ * @param WP_REST_Request $request Full details about the request.
178
+ * @return WP_Error|boolean
179
+ */
180
+ public function get_item_permissions_check( $request ) {
181
+
182
+ $id = (int) $request['id'];
183
+ $user = get_userdata( $id );
184
+
185
+ if ( empty( $id ) || empty( $user->ID ) ) {
186
+ return new WP_Error( 'rest_user_invalid_id', __( 'Invalid user id.' ), array( 'status' => 404 ) );
187
+ }
188
+
189
+ if ( get_current_user_id() === $id ) {
190
+ return true;
191
+ }
192
+
193
+ $context = ! empty( $request['context'] ) && in_array( $request['context'], array( 'edit', 'view', 'embed' ) ) ? $request['context'] : 'embed';
194
+
195
+ if ( 'edit' === $context && ! current_user_can( 'edit_user', $id ) ) {
196
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with edit context' ), array( 'status' => rest_authorization_required_code() ) );
197
+ } else if ( 'view' === $context && ! current_user_can( 'list_users' ) ) {
198
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user with view context' ), array( 'status' => rest_authorization_required_code() ) );
199
+ } else if ( 'embed' === $context && ! count_user_posts( $id ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
200
+ return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this user' ), array( 'status' => rest_authorization_required_code() ) );
201
+ }
202
+
203
+ return true;
204
+ }
205
+
206
  /**
207
  * Get a single user
208
  *
235
  return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) );
236
  }
237
 
238
+ $user = wp_get_current_user();
239
+ $response = $this->prepare_item_for_response( $user, $request );
 
 
 
 
 
 
240
  $response = rest_ensure_response( $response );
241
  $response->header( 'Location', rest_url( sprintf( '/wp/v2/users/%d', $current_user_id ) ) );
242
  $response->set_status( 302 );
244
  return $response;
245
  }
246
 
247
+ /**
248
+ * Check if a given request has access create users
249
+ *
250
+ * @param WP_REST_Request $request Full details about the request.
251
+ * @return boolean
252
+ */
253
+ public function create_item_permissions_check( $request ) {
254
+
255
+ if ( ! current_user_can( 'create_users' ) ) {
256
+ return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create users.' ), array( 'status' => rest_authorization_required_code() ) );
257
+ }
258
+
259
+ return true;
260
+ }
261
+
262
  /**
263
  * Create a single user
264
  *
296
  if ( is_wp_error( $user_id ) ) {
297
  return $user_id;
298
  }
 
299
  }
300
 
301
+ $user = get_user_by( 'id', $user_id );
302
  $this->update_additional_fields_for_object( $user, $request );
303
 
304
  /**
306
  *
307
  * @param object $user Data used to create the user (not a WP_User object).
308
  * @param WP_REST_Request $request Request object.
309
+ * @param boolean $creating True when creating user, false when updating user.
310
  */
311
  do_action( 'rest_insert_user', $user, $request, true );
312
 
313
+ $request->set_param( 'context', 'edit' );
314
+ $response = $this->prepare_item_for_response( $user, $request );
 
 
315
  $response = rest_ensure_response( $response );
316
  $response->set_status( 201 );
317
  $response->header( 'Location', rest_url( '/wp/v2/users/' . $user_id ) );
319
  return $response;
320
  }
321
 
322
+ /**
323
+ * Check if a given request has access update a user
324
+ *
325
+ * @param WP_REST_Request $request Full details about the request.
326
+ * @return boolean
327
+ */
328
+ public function update_item_permissions_check( $request ) {
329
+
330
+ $id = (int) $request['id'];
331
+
332
+ if ( ! current_user_can( 'edit_user', $id ) ) {
333
+ return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit users.' ), array( 'status' => rest_authorization_required_code() ) );
334
+ }
335
+
336
+ if ( ! empty( $request['role'] ) && ! current_user_can( 'edit_users' ) ) {
337
+ return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of users.' ), array( 'status' => rest_authorization_required_code() ) );
338
+ }
339
+
340
+ return true;
341
+ }
342
+
343
  /**
344
  * Update a single user
345
  *
383
  return $user_id;
384
  }
385
 
386
+ $user = get_user_by( 'id', $id );
387
  $this->update_additional_fields_for_object( $user, $request );
388
 
389
  /* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
390
  do_action( 'rest_insert_user', $user, $request, false );
391
 
392
+ $request->set_param( 'context', 'edit' );
393
+ $response = $this->prepare_item_for_response( $user, $request );
394
+ $response = rest_ensure_response( $response );
395
+ return $response;
396
+ }
397
+
398
+ /**
399
+ * Check if a given request has access delete a user
400
+ *
401
+ * @param WP_REST_Request $request Full details about the request.
402
+ * @return boolean
403
+ */
404
+ public function delete_item_permissions_check( $request ) {
405
+
406
+ $id = (int) $request['id'];
407
+
408
+ if ( ! current_user_can( 'delete_user', $id ) ) {
409
+ return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this user.' ), array( 'status' => rest_authorization_required_code() ) );
410
+ }
411
 
412
+ return true;
413
  }
414
 
415
  /**
439
  }
440
  }
441
 
442
+ $request->set_param( 'context', 'edit' );
443
+ $orig_user = $this->prepare_item_for_response( $user, $request );
 
 
444
 
445
  $data = $orig_user->get_data();
446
  $data = array(
469
  return $orig_user;
470
  }
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  /**
473
  * Prepare a single user output for response
474
  *
475
  * @param object $user User object.
476
  * @param WP_REST_Request $request Request object.
477
+ * @return WP_REST_Response $response Response data.
478
  */
479
  public function prepare_item_for_response( $user, $request ) {
480
  $data = array(
497
  );
498
 
499
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
 
 
500
  $data = $this->add_additional_fields_to_object( $data, $request );
501
+ $data = $this->filter_response_by_context( $data, $context );
502
 
503
  // Wrap the data in a response object
504
  $response = rest_ensure_response( $data );
597
  *
598
  * @param integer $user_id
599
  * @param string $role
600
+ * @return WP_Error|boolean
601
  */
602
  protected function check_role_update( $user_id, $role ) {
603
  global $wp_roles;
788
  'default' => array(),
789
  'sanitize_callback' => 'wp_parse_id_list',
790
  );
791
+ $query_params['offset'] = array(
792
+ 'description' => __( 'Offset the result set by a specific number of items.' ),
793
+ 'type' => 'integer',
794
+ 'sanitize_callback' => 'absint',
795
+ );
796
  $query_params['order'] = array(
797
  'default' => 'asc',
798
  'description' => __( 'Order sort attribute ascending or descending.' ),
812
  'sanitize_callback' => 'sanitize_key',
813
  'type' => 'string',
814
  );
815
+ $query_params['slug'] = array(
816
+ 'description' => __( 'Limit result set to users with a specific slug.' ),
817
+ 'type' => 'string',
818
+ );
819
  return $query_params;
820
  }
821
  }
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-beta10
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -12,67 +12,86 @@
12
  /**
13
  * WP_REST_Controller class.
14
  */
15
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
 
 
16
 
17
  /**
18
  * WP_REST_Posts_Controller class.
19
  */
20
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
 
 
21
 
22
  /**
23
  * WP_REST_Attachments_Controller class.
24
  */
25
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
 
 
26
 
27
  /**
28
  * WP_REST_Post_Types_Controller class.
29
  */
30
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
 
 
31
 
32
  /**
33
  * WP_REST_Post_Statuses_Controller class.
34
  */
35
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
 
 
36
 
37
  /**
38
  * WP_REST_Revisions_Controller class.
39
  */
40
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
 
 
41
 
42
  /**
43
  * WP_REST_Taxonomies_Controller class.
44
  */
45
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
 
 
46
 
47
  /**
48
  * WP_REST_Terms_Controller class.
49
  */
50
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
 
 
51
 
52
  /**
53
  * WP_REST_Users_Controller class.
54
  */
55
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
 
 
56
 
57
  /**
58
  * WP_REST_Comments_Controller class.
59
  */
60
- require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
 
 
61
 
62
  /**
63
  * WP_REST_Meta_Controller class.
64
  */
65
- include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
 
 
66
 
67
  /**
68
  * WP_REST_Meta_Posts_Controller class.
69
  */
70
- include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
71
-
72
- /**
73
- * WP_REST_Posts_Terms_Controller class.
74
- */
75
- include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
76
 
77
  /**
78
  * REST extras.
@@ -139,80 +158,72 @@ function _add_extra_api_taxonomy_arguments() {
139
  }
140
  }
141
 
142
- /**
143
- * Registers default REST API routes.
144
- *
145
- * @since 4.4.0
146
- */
147
- function create_initial_rest_routes() {
 
148
 
149
- foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
150
- $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
151
 
152
- if ( ! class_exists( $class ) ) {
153
- continue;
154
- }
155
- $controller = new $class( $post_type->name );
156
- if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
157
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
158
  }
159
 
 
 
160
  $controller->register_routes();
161
 
162
- if ( post_type_supports( $post_type->name, 'custom-fields' ) ) {
163
- $meta_controller = new WP_REST_Meta_Posts_Controller( $post_type->name );
164
- $meta_controller->register_routes();
165
- }
166
- if ( post_type_supports( $post_type->name, 'revisions' ) ) {
167
- $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
168
- $revisions_controller->register_routes();
169
- }
170
 
171
- foreach ( get_object_taxonomies( $post_type->name, 'objects' ) as $taxonomy ) {
 
 
172
 
173
- if ( empty( $taxonomy->show_in_rest ) ) {
 
 
 
 
174
  continue;
175
  }
176
 
177
- $posts_terms_controller = new WP_REST_Posts_Terms_Controller( $post_type->name, $taxonomy->name );
178
- $posts_terms_controller->register_routes();
179
  }
180
- }
181
-
182
- // Post types.
183
- $controller = new WP_REST_Post_Types_Controller;
184
- $controller->register_routes();
185
-
186
- // Post statuses.
187
- $controller = new WP_REST_Post_Statuses_Controller;
188
- $controller->register_routes();
189
-
190
- // Taxonomies.
191
- $controller = new WP_REST_Taxonomies_Controller;
192
- $controller->register_routes();
193
 
194
- // Terms.
195
- foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
196
- $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
197
-
198
- if ( ! class_exists( $class ) ) {
199
- continue;
200
- }
201
- $controller = new $class( $taxonomy->name );
202
- if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
203
- continue;
204
- }
205
 
 
 
206
  $controller->register_routes();
207
  }
208
-
209
- // Users.
210
- $controller = new WP_REST_Users_Controller;
211
- $controller->register_routes();
212
-
213
- // Comments.
214
- $controller = new WP_REST_Comments_Controller;
215
- $controller->register_routes();
216
  }
217
 
218
  if ( ! function_exists( 'rest_authorization_required_code' ) ) {
4
  * Description: JSON-based REST API for WordPress, developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://wp-api.org
7
+ * Version: 2.0-beta11
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
12
  /**
13
  * WP_REST_Controller class.
14
  */
15
+ if ( ! class_exists( 'WP_REST_Controller' ) ) {
16
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
17
+ }
18
 
19
  /**
20
  * WP_REST_Posts_Controller class.
21
  */
22
+ if ( ! class_exists( 'WP_REST_Posts_Controller' ) ) {
23
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
24
+ }
25
 
26
  /**
27
  * WP_REST_Attachments_Controller class.
28
  */
29
+ if ( ! class_exists( 'WP_REST_Attachments_Controller' ) ) {
30
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
31
+ }
32
 
33
  /**
34
  * WP_REST_Post_Types_Controller class.
35
  */
36
+ if ( ! class_exists( 'WP_REST_Post_Types_Controller' ) ) {
37
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
38
+ }
39
 
40
  /**
41
  * WP_REST_Post_Statuses_Controller class.
42
  */
43
+ if ( ! class_exists( 'WP_REST_Post_Statuses_Controller' ) ) {
44
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
45
+ }
46
 
47
  /**
48
  * WP_REST_Revisions_Controller class.
49
  */
50
+ if ( ! class_exists( 'WP_REST_Revisions_Controller' ) ) {
51
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
52
+ }
53
 
54
  /**
55
  * WP_REST_Taxonomies_Controller class.
56
  */
57
+ if ( ! class_exists( 'WP_REST_Taxonomies_Controller' ) ) {
58
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
59
+ }
60
 
61
  /**
62
  * WP_REST_Terms_Controller class.
63
  */
64
+ if ( ! class_exists( 'WP_REST_Terms_Controller' ) ) {
65
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
66
+ }
67
 
68
  /**
69
  * WP_REST_Users_Controller class.
70
  */
71
+ if ( ! class_exists( 'WP_REST_Users_Controller' ) ) {
72
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
73
+ }
74
 
75
  /**
76
  * WP_REST_Comments_Controller class.
77
  */
78
+ if ( ! class_exists( 'WP_REST_Comments_Controller' ) ) {
79
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
80
+ }
81
 
82
  /**
83
  * WP_REST_Meta_Controller class.
84
  */
85
+ if ( ! class_exists( 'WP_REST_Meta_Controller' ) ) {
86
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
87
+ }
88
 
89
  /**
90
  * WP_REST_Meta_Posts_Controller class.
91
  */
92
+ if ( ! class_exists( 'WP_REST_Meta_Posts_Controller' ) ) {
93
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
94
+ }
 
 
 
95
 
96
  /**
97
  * REST extras.
158
  }
159
  }
160
 
161
+ if ( ! function_exists( 'create_initial_rest_routes' ) ) {
162
+ /**
163
+ * Registers default REST API routes.
164
+ *
165
+ * @since 4.4.0
166
+ */
167
+ function create_initial_rest_routes() {
168
 
169
+ foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
170
+ $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
171
 
172
+ if ( ! class_exists( $class ) ) {
173
+ continue;
174
+ }
175
+ $controller = new $class( $post_type->name );
176
+ if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
177
+ continue;
178
+ }
179
+
180
+ $controller->register_routes();
181
+
182
+ if ( post_type_supports( $post_type->name, 'custom-fields' ) ) {
183
+ $meta_controller = new WP_REST_Meta_Posts_Controller( $post_type->name );
184
+ $meta_controller->register_routes();
185
+ }
186
+ if ( post_type_supports( $post_type->name, 'revisions' ) ) {
187
+ $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
188
+ $revisions_controller->register_routes();
189
+ }
190
  }
191
 
192
+ // Post types.
193
+ $controller = new WP_REST_Post_Types_Controller;
194
  $controller->register_routes();
195
 
196
+ // Post statuses.
197
+ $controller = new WP_REST_Post_Statuses_Controller;
198
+ $controller->register_routes();
199
+
200
+ // Taxonomies.
201
+ $controller = new WP_REST_Taxonomies_Controller;
202
+ $controller->register_routes();
 
203
 
204
+ // Terms.
205
+ foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
206
+ $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
207
 
208
+ if ( ! class_exists( $class ) ) {
209
+ continue;
210
+ }
211
+ $controller = new $class( $taxonomy->name );
212
+ if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
213
  continue;
214
  }
215
 
216
+ $controller->register_routes();
 
217
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ // Users.
220
+ $controller = new WP_REST_Users_Controller;
221
+ $controller->register_routes();
 
 
 
 
 
 
 
 
222
 
223
+ // Comments.
224
+ $controller = new WP_REST_Comments_Controller;
225
  $controller->register_routes();
226
  }
 
 
 
 
 
 
 
 
227
  }
228
 
229
  if ( ! function_exists( 'rest_authorization_required_code' ) ) {
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.4
5
  Tested up to: 4.5-alpha
6
- Stable tag: 2.0-beta10
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,6 +36,153 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 2.0 Beta 10.0 =
40
 
41
  * SECURITY: Ensure media of private posts are private too.
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.4
5
  Tested up to: 4.5-alpha
6
+ Stable tag: 2.0-beta11
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
36
 
37
  == Changelog ==
38
 
39
+ = 2.0 Beta 11.0 =
40
+
41
+ * BREAKING CHANGE: Moves Post->Term relations to the Post Resource
42
+
43
+ Previously, a client would fetch a Post's Tags with `GET /wp/v2/posts/<id>/tags`.
44
+
45
+ In Beta 11, an array of term ids is included on the Post resource.
46
+
47
+ The collection of terms for a Post can be fetched with `GET /wp/v2/tags?post=<id>`.
48
+
49
+ The `WP_REST_Posts_Terms_Controller` class no longer exists.
50
+
51
+ (props @joehoyle, [#2063](https://github.com/WP-API/WP-API/pull/2063))
52
+
53
+ * BREAKING CHANGE: Adds latest JS client including a minified version.
54
+
55
+ See pull request for a summarized changelog.
56
+
57
+ (props @adamsilverstein, [#1981](https://github.com/WP-API/WP-API/pull/1981))
58
+
59
+ * BREAKING CHANGE: Changes `featured_image` attribute on Posts to `featured_media`.
60
+
61
+ While featuring other attachment types isn't yet officially supported, this makes it easier for us to introduce the possibility in the future.
62
+
63
+ (props @danielbachhuber, [#2044](https://github.com/WP-API/WP-API/pull/2044))
64
+
65
+ * BREAKING CHANGE: Uses discrete schema title for categories and tags.
66
+
67
+ If you've used `register_rest_field( 'term' )`, you'll need to change `'term'` to `'tag'` and/or `'category'`.
68
+
69
+ (props @danielbachhuber, [#2005](https://github.com/WP-API/WP-API/pull/2005))
70
+
71
+ * BREAKING CHANGE: Makes many filters dynamic based on the controller type.
72
+
73
+ If you were using the `rest_prepare_term` filter, you'll need to change it to `rest_prepare_post_tag` or `rest_prepare_category`.
74
+
75
+ If you were using `rest_post_query` or `rest_terms_query`, you'll need update your use to `rest_page_query`, etc.
76
+
77
+ If you were using `rest_post_trashable`, `rest_insert_post` or `rest_delete_post`, they are now dynamic based on the post type slug.
78
+
79
+ (props @danielbachhuber, [#2008](https://github.com/WP-API/WP-API/pull/2008), [#2010](https://github.com/WP-API/WP-API/pull/2010), [#2057](https://github.com/WP-API/WP-API/pull/2057), [#2058](https://github.com/WP-API/WP-API/pull/2058))
80
+
81
+ * Renames `GET /wp/v2/comments` `user` param to `author` to match resource attribute.
82
+
83
+ Not a breaking change, because it didn't work in the first place.
84
+
85
+ (props @danielbachhuber, [#2105](https://github.com/WP-API/WP-API/pull/2105))
86
+
87
+ * Adds support for `GET /wp/v2/pages parent=1,2,3`.
88
+
89
+ (props @danielbachhuber, [#2101](https://github.com/WP-API/WP-API/pull/2101))
90
+
91
+ * Persists image metadata title and caption when not present in the request.
92
+
93
+ (props @danielbachhuber, [#2079](https://github.com/WP-API/WP-API/pull/2079))
94
+
95
+ * Add `parent_exclude` param to `GET /wp/v2/posts`.
96
+
97
+ (props @danielbachhuber, [#2077](https://github.com/WP-API/WP-API/pull/2077))
98
+
99
+ * Adds `slug` param support for collections of Posts, Users, and Taxonomy Terms.
100
+
101
+ (props @danielbachhuber, [#2071](https://github.com/WP-API/WP-API/pull/2071), [#2072](https://github.com/WP-API/WP-API/pull/2072), [#2103](https://github.com/WP-API/WP-API/pull/2103))
102
+
103
+ * When a comment is already trashed, returns `410:rest_already_trashed`.
104
+
105
+ (props @danielbachhuber, [#2069](https://github.com/WP-API/WP-API/pull/2069))
106
+
107
+ * Filter the responses by context after processing additional fields.
108
+
109
+ (props @danielbachhuber, [#2067](https://github.com/WP-API/WP-API/pull/2067))
110
+
111
+ * Adds `offset` param support for collections of Posts, Users, Comments, and Taxonomy Terms.
112
+
113
+ (props @danielbachhuber, [#2061](https://github.com/WP-API/WP-API/pull/2061), [#2062](https://github.com/WP-API/WP-API/pull/2062), [#2064](https://github.com/WP-API/WP-API/pull/2064), [#2076](https://github.com/WP-API/WP-API/pull/2076))
114
+
115
+ * Adds `rest_insert_{$taxonomy}` and `rest_delete_{$taxonomy}` actions.
116
+
117
+ (props @danielbachhuber, [#2060](https://github.com/WP-API/WP-API/pull/2060))
118
+
119
+ * Provides more helpful error message/code on Post Create/Update fail.
120
+
121
+ (props @danielbachhuber, [#2053](https://github.com/WP-API/WP-API/pull/2053))
122
+
123
+ * Forces `GET /wp/v2/media` to be limited to `'status' => [ inherit, private, trash ]`
124
+
125
+ (props @danielbachhuber, [#2026](https://github.com/WP-API/WP-API/pull/2026))
126
+
127
+ * Uses more correct error code for `Comment::delete` permission check.
128
+
129
+ (props @danielbachhuber, [#2054](https://github.com/WP-API/WP-API/pull/2054))
130
+
131
+ * Calls `prepare_item_for_response()` directly in create and update methods.
132
+
133
+ This lets us pass the original request through, giving the method and its filter genuine context, and avoids an
134
+ unnecessary call to `get_item()`.
135
+
136
+ (props @danielbachhuber, [#2038](https://github.com/WP-API/WP-API/pull/2038), [#2040](https://github.com/WP-API/WP-API/pull/2040), [#2041](https://github.com/WP-API/WP-API/pull/2041), [#2043](https://github.com/WP-API/WP-API/pull/2043), [#2042](https://github.com/WP-API/WP-API/pull/2042))
137
+
138
+ * Moves permission check methods across controllers.
139
+
140
+ Placing them above the method they're supposed to check makes the code more readable.
141
+
142
+ (props @danielbachhuber, [#2030](https://github.com/WP-API/WP-API/pull/2030), [#2029](https://github.com/WP-API/WP-API/pull/2029), [#2034](https://github.com/WP-API/WP-API/pull/2034), [#2036](https://github.com/WP-API/WP-API/pull/2036), [#2037](https://github.com/WP-API/WP-API/pull/2037), [#2035](https://github.com/WP-API/WP-API/pull/2035), [#2039](https://github.com/WP-API/WP-API/pull/2039))
143
+
144
+ * Requires `force` argument for `DELETE /wp/v2/<taxonomy>/<id>`.
145
+
146
+ (props @danielbachhuber, [#2028](https://github.com/WP-API/WP-API/pull/2028))
147
+
148
+ * Conditionally requires and defines REST API classes and functions.
149
+
150
+ (props @danielbachhuber, [#2023](https://github.com/WP-API/WP-API/pull/2023), [#2024](https://github.com/WP-API/WP-API/pull/2024))
151
+
152
+ * Avoid a duplicate query for the comment count.
153
+
154
+ (props @rmccue, [#2015](https://github.com/WP-API/WP-API/pull/2015))
155
+
156
+ * Parses `$date` if available in `prepare_date_response()`
157
+
158
+ (props @adamsilverstein, [#1951](https://github.com/WP-API/WP-API/pull/1951))
159
+
160
+ * Abstracts `POST /wp/v2/media` permissions check.
161
+
162
+ (props @danielbachhuber, [#2003](https://github.com/WP-API/WP-API/pull/2003))
163
+
164
+ * Adds `exclude` param to getting collections of Posts, Users, Comments, and Taxonomy Terms.
165
+
166
+ (props @danielbachhuber, [#1998](https://github.com/WP-API/WP-API/pull/1998), [#1999](https://github.com/WP-API/WP-API/pull/1999), [#2000](https://github.com/WP-API/WP-API/pull/2000), [#2002](https://github.com/WP-API/WP-API/pull/2002))
167
+
168
+ * Adds `rest_comment_query` for filtering `GET /wp/v2/comments`.
169
+
170
+ (props @danielbachhuber, [#2007](https://github.com/WP-API/WP-API/pull/2007))
171
+
172
+ * Uses HTTP status code `500` for `db_update_error` when creating an attachment.
173
+
174
+ (props @danielbachhuber, [#1993](https://github.com/WP-API/WP-API/pull/1993))
175
+
176
+ * Adds helpful description to `force` param across all `DELETE` registrations
177
+
178
+ (props @danielbachhuber, [#2004](https://github.com/WP-API/WP-API/pull/2004), [#2027](https://github.com/WP-API/WP-API/pull/2027))
179
+
180
+ * In `GET /wp/v2/<taxonomy>`, drops support for `orderby=>term_id`.
181
+
182
+ Only one `id` is exposed through the REST API.
183
+
184
+ (props @danielbachhuber, [#1990](https://github.com/WP-API/WP-API/pull/1990))
185
+
186
  = 2.0 Beta 10.0 =
187
 
188
  * SECURITY: Ensure media of private posts are private too.
wp-api.js CHANGED
@@ -8,8 +8,9 @@
8
  this.views = {};
9
  }
10
 
11
- window.wp = window.wp || {};
12
- wp.api = wp.api || new WP_API();
 
13
 
14
  })( window );
15
 
@@ -17,18 +18,20 @@
17
 
18
  'use strict';
19
 
 
 
20
  window.wp = window.wp || {};
21
  wp.api = wp.api || {};
22
  wp.api.utils = wp.api.utils || {};
23
 
24
  /**
25
- * ECMAScript 5 shim, from MDN.
26
  * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
27
  */
28
  if ( ! Date.prototype.toISOString ) {
29
- var pad = function( number ) {
30
- var r = String( number );
31
- if ( r.length === 1 ) {
32
  r = '0' + r;
33
  }
34
 
@@ -62,6 +65,7 @@
62
  // implementations could be faster.
63
  // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
64
  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 ) ) ) {
 
65
  // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC.
66
  for ( i = 0; ( k = numericKeys[i] ); ++i ) {
67
  struct[k] = +struct[k] || 0;
@@ -71,10 +75,10 @@
71
  struct[2] = ( +struct[2] || 1 ) - 1;
72
  struct[3] = +struct[3] || 1;
73
 
74
- if ( struct[8] !== 'Z' && struct[9] !== undefined ) {
75
  minutesOffset = struct[10] * 60 + struct[11];
76
 
77
- if ( struct[9] === '+' ) {
78
  minutesOffset = 0 - minutesOffset;
79
  }
80
  }
@@ -87,585 +91,667 @@
87
  return timestamp;
88
  };
89
 
90
- })( window );
 
 
 
 
 
 
 
 
91
 
92
- /* global WP_API_Settings:false */
93
- // Suppress warning about parse function's unused "options" argument:
94
- /* jshint unused:false */
95
- (function( wp, WP_API_Settings, Backbone, window, undefined ) {
 
 
 
 
 
96
 
97
- 'use strict';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  /**
100
- * Array of parseable dates.
101
  *
102
- * @type {string[]}.
103
  */
104
- var parseable_dates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ];
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  /**
107
- * Mixin for all content that is time stamped.
108
  *
109
- * @type {{toJSON: toJSON, parse: parse}}.
 
 
110
  */
111
- var TimeStampedMixin = {
 
112
  /**
113
- * Serialize the entity pre-sync.
114
- *
115
- * @returns {*}.
116
  */
117
- toJSON: function() {
118
- var attributes = _.clone( this.attributes );
119
 
120
- // Serialize Date objects back into 8601 strings.
121
- _.each( parseable_dates, function( key ) {
122
- if ( key in attributes ) {
123
- attributes[key] = attributes[key].toISOString();
124
- }
125
- });
126
 
127
- return attributes;
128
- },
129
 
130
- /**
131
- * Unserialize the fetched response.
132
- *
133
- * @param {*} response.
134
- * @returns {*}.
135
- */
136
- parse: function( response ) {
137
 
138
- // Parse dates into native Date objects.
139
- _.each( parseable_dates, function ( key ) {
140
- if ( ! ( key in response ) ) {
141
- return;
142
  }
 
143
 
144
- var timestamp = wp.api.utils.parseISO8601( response[key] );
145
- response[key] = new Date( timestamp );
146
- });
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- // Parse the author into a User object.
149
- if ( 'undefined' !== typeof response.author ) {
150
- response.author = new wp.api.models.User( response.author );
151
  }
152
 
153
- return response;
154
- }
155
  };
156
 
157
  /**
158
- * Mixin for all hierarchical content types such as posts.
159
  *
160
- * @type {{parent: parent}}.
 
 
161
  */
162
- var HierarchicalMixin = {
163
- /**
164
- * Get parent object.
165
- *
166
- * @returns {Backbone.Model}
167
- */
168
- parent: function() {
169
 
170
- var object, parent = this.get( 'parent' );
171
 
172
- // Return null if we don't have a parent.
173
- if ( parent === 0 ) {
174
- return null;
175
- }
176
-
177
- var parentModel = this;
178
 
179
- if ( 'undefined' !== typeof this.parentModel ) {
 
 
 
 
 
 
 
 
 
180
  /**
181
- * Probably a better way to do this. Perhaps grab a cached version of the
182
- * instantiated model?
 
183
  */
184
- parentModel = new this.parentModel();
185
- }
186
 
187
- // Can we get this from its collection?
188
- if ( parentModel.collection ) {
189
- return parentModel.collection.get( parent );
190
- } else {
191
 
192
- // Otherwise, get the object directly.
193
- object = new parentModel.constructor( {
194
- id: parent
195
- });
 
 
196
 
197
- // Note that this acts asynchronously.
198
- object.fetch();
199
 
200
- return object;
201
- }
202
- }
203
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- /**
206
- * Private Backbone base model for all models.
207
- */
208
- var WPApiBaseModel = Backbone.Model.extend(
209
- /** @lends WPApiBaseModel.prototype */
210
- {
211
  /**
212
- * Set nonce header before every Backbone sync.
213
  *
214
- * @param {string} method.
215
- * @param {Backbone.Model} model.
216
- * @param {{beforeSend}, *} options.
217
- * @returns {*}.
 
 
 
218
  */
219
- sync: function( method, model, options ) {
220
- options = options || {};
221
 
222
- if ( 'undefined' !== typeof WP_API_Settings.nonce ) {
223
- var beforeSend = options.beforeSend;
224
 
225
- options.beforeSend = function( xhr ) {
226
- xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
227
-
228
- if ( beforeSend ) {
229
- return beforeSend.apply( this, arguments );
230
- }
231
- };
232
  }
233
 
234
- return Backbone.sync( method, model, options );
235
- }
236
- }
237
- );
238
 
239
- /**
240
- * Backbone model for a single user.
241
- *
242
- *
243
- * @param {Object} attributes
244
- * @param {int} attributes.id The user id. Optional. Defaults to 'me', fetching the current user.
245
- */
246
- wp.api.models.User = WPApiBaseModel.extend(
247
- /** @lends User.prototype */
248
- {
249
- idAttribute: 'id',
250
 
251
- urlRoot: WP_API_Settings.root + 'wp/v2/users',
 
252
 
253
- defaults: {
254
- id: 'me',
255
- avatar_url: {},
256
- capabilities: {},
257
- description: '',
258
- email: '',
259
- extra_capabilities: {},
260
- first_name: '',
261
- last_name: '',
262
- link: '',
263
- name: '',
264
- nickname: '',
265
- registered_date: new Date(),
266
- roles: [],
267
- slug: '',
268
- url: '',
269
- username: '',
270
- _links: {}
271
- }
272
- }
273
- );
274
 
275
- /**
276
- * Model for a single taxonomy.
277
- *
278
- * @param {Object} attributes
279
- * @param {string} attributes.slug The taxonomy slug.
280
- */
281
- wp.api.models.Taxonomy = WPApiBaseModel.extend(
282
- /** @lends Taxonomy.prototype */
283
- {
284
- idAttribute: 'slug',
285
 
286
- urlRoot: WP_API_Settings.root + 'wp/v2/taxonomies',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
- defaults: {
289
- name: '',
290
- slug: null,
291
- description: '',
292
- labels: {},
293
- types: [],
294
- show_cloud: false,
295
- hierarchical: false
296
- }
297
- }
298
- );
299
 
300
- /**
301
- * Backbone model for a single term.
302
- *
303
- * @param {Object} attributes
304
- * @param {int} id attributesm id.
305
- */
306
- wp.api.models.Term = WPApiBaseModel.extend(
307
- /** @lends Term.prototype */
308
- {
309
- idAttribute: 'id',
310
 
311
- urlRoot: WP_API_Settings.root + 'wp/v2/terms/tag',
 
 
312
 
313
- defaults: {
314
- id: null,
315
- name: '',
316
- slug: '',
317
- description: '',
318
- parent: null,
319
- count: 0,
320
- link: '',
321
- taxonomy: '',
322
- _links: {}
323
- }
324
 
325
- }
326
- );
 
327
 
328
- /**
329
- * Backbone model for a single post.
330
- *
331
- * @param {Object} attributes
332
- * @param {int} attributes.id The post id.
333
- */
334
- wp.api.models.Post = WPApiBaseModel.extend( _.extend(
335
- /** @lends Post.prototype */
336
- {
337
- idAttribute: 'id',
338
 
339
- urlRoot: WP_API_Settings.root + 'wp/v2/posts',
 
 
340
 
341
- defaults: {
342
- id: null,
343
- date: new Date(),
344
- date_gmt: new Date(),
345
- guid: {},
346
- link: '',
347
- modified: new Date(),
348
- modified_gmt: new Date(),
349
- password: '',
350
- status: 'draft',
351
- type: 'post',
352
- title: {},
353
- content: {},
354
- author: null,
355
- excerpt: {},
356
- featured_image: null,
357
- comment_status: 'open',
358
- ping_status: 'open',
359
- sticky: false,
360
- format: 'standard',
361
- _links: {}
362
- }
363
- }, TimeStampedMixin, HierarchicalMixin )
364
- );
365
 
366
- /**
367
- * Backbone model for a single page.
368
- *
369
- * @param {Object} attributes
370
- * @param {int} attributes.id The page id.
371
- */
372
- wp.api.models.Page = WPApiBaseModel.extend( _.extend(
373
- /** @lends Page.prototype */
374
- {
375
- idAttribute: 'id',
376
 
377
- urlRoot: WP_API_Settings.root + 'wp/v2/pages',
 
378
 
379
- defaults: {
380
- id: null,
381
- date: new Date(),
382
- date_gmt: new Date(),
383
- guid: {},
384
- link: '',
385
- modified: new Date(),
386
- modified_gmt: new Date(),
387
- password: '',
388
- slug: '',
389
- status: 'draft',
390
- type: 'page',
391
- title: {},
392
- content: {},
393
- author: null,
394
- excerpt: {},
395
- featured_image: null,
396
- comment_status: 'closed',
397
- ping_status: 'closed',
398
- menu_order: null,
399
- template: '',
400
- _links: {}
401
- }
402
- }, TimeStampedMixin, HierarchicalMixin )
403
- );
404
 
405
- /**
406
- * Backbone model for a single post revision.
407
- *
408
- * @param {Object} attributes
409
- * @param {int} attributes.parent The id of the post that this revision belongs to.
410
- * @param {int} attributes.id The revision id.
411
- */
412
- wp.api.models.PostRevision = WPApiBaseModel.extend( _.extend(
413
- /** @lends PostRevision.prototype */
414
- {
415
- idAttribute: 'id',
416
 
417
- defaults: {
418
- id: null,
419
- author: null,
420
- date: new Date(),
421
- date_gmt: new Date(),
422
- guid: {},
423
- modified: new Date(),
424
- modified_gmt: new Date(),
425
- parent: 0,
426
- slug: '',
427
- title: {},
428
- content: {},
429
- excerpt: {},
430
- _links: {}
431
  },
432
 
433
  /**
434
- * Return URL for the model.
435
- *
436
- * @returns {string}.
437
  */
438
- url: function() {
439
- var id = this.get( 'id' ) || '',
440
- parent = this.get( 'parent' ) || '';
 
 
441
 
442
- return WP_API_Settings.root + 'wp/v2/posts/' + parent + '/revisions/' + id;
443
- }
 
 
 
 
 
 
444
 
445
- }, TimeStampedMixin, HierarchicalMixin )
446
- );
 
 
 
 
 
 
 
 
 
 
447
 
448
- /**
449
- * Backbone model for a single media item.
450
- *
451
- * @param {Object} attributes
452
- * @param {int} attributes.id The media item id.
453
- */
454
- wp.api.models.Media = WPApiBaseModel.extend( _.extend(
455
- /** @lends Media.prototype */
456
- {
457
- idAttribute: 'id',
 
 
458
 
459
- urlRoot: WP_API_Settings.root + 'wp/v2/media',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
- defaults: {
462
- id: null,
463
- date: new Date(),
464
- date_gmt: new Date(),
465
- guid: {},
466
- link: '',
467
- modified: new Date(),
468
- modified_gmt: new Date(),
469
- password: '',
470
- slug: '',
471
- status: 'draft',
472
- type: 'attachment',
473
- title: {},
474
- author: null,
475
- comment_status: 'open',
476
- ping_status: 'open',
477
- alt_text: '',
478
- caption: '',
479
- description: '',
480
- media_type: '',
481
- media_details: {},
482
- post: null,
483
- source_url: '',
484
- _links: {}
485
- }
486
 
487
- }, TimeStampedMixin )
488
- );
489
 
490
- /**
491
- * Backbone model for a single comment.
492
- *
493
- * @param {Object} attributes
494
- * @param {int} attributes.id The comment id.
495
- */
496
- wp.api.models.Comment = WPApiBaseModel.extend( _.extend(
497
- /** @lends Comment.prototype */
498
- {
499
- idAttribute: 'id',
500
 
501
- urlRoot: WP_API_Settings.root + 'wp/v2/comments',
 
502
 
503
- defaults: {
504
- id: null,
505
- author: null,
506
- author_email: '',
507
- author_ip: '',
508
- author_name: '',
509
- author_url: '',
510
- author_user_agent: '',
511
- content: {},
512
- date: new Date(),
513
- date_gmt: new Date(),
514
- karma: 0,
515
- link: '',
516
- parent: 0,
517
- status: 'hold',
518
- type: '',
519
- _links: {}
520
- }
521
 
522
- }, TimeStampedMixin, HierarchicalMixin )
523
- );
 
524
 
525
- /**
526
- * Backbone model for a single post type.
527
- *
528
- * @param {Object} attributes
529
- * @param {string} attributes.slug The post type slug.
530
- */
531
- wp.api.models.PostType = WPApiBaseModel.extend(
532
- /** @lends PostType.prototype */
533
- {
534
- idAttribute: 'slug',
535
 
536
- urlRoot: WP_API_Settings.root + 'wp/v2/types',
 
 
537
 
538
- defaults: {
539
- slug: null,
540
- name: '',
541
- description: '',
542
- labels: {},
543
- hierarchical: false
544
  },
545
 
546
  /**
547
- * Prevent model from being saved.
548
- *
549
- * @returns {boolean}.
550
  */
551
- save: function() {
552
- return false;
 
 
553
  },
554
 
555
  /**
556
- * Prevent model from being deleted.
557
- *
558
- * @returns {boolean}.
559
  */
560
- destroy: function() {
561
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
 
 
 
 
 
563
  }
564
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
 
566
  /**
567
- * Backbone model for a a single post status.
568
- *
569
- * @param {Object} attributes
570
- * @param {string} attributes.slug The post status slug.
571
  */
572
- wp.api.models.PostStatus = WPApiBaseModel.extend(
573
- /** @lends PostStatus.prototype */
574
  {
575
- idAttribute: 'slug',
 
 
 
 
 
 
 
 
 
576
 
577
- urlRoot: WP_API_Settings.root + 'wp/v2/statuses',
578
 
579
- defaults: {
580
- slug: null,
581
- name: '',
582
- 'public': true,
583
- 'protected': false,
584
- 'private': false,
585
- queryable: true,
586
- show_in_list: true,
587
- _links: {}
 
 
 
 
 
 
 
 
 
 
 
588
  },
589
 
590
  /**
591
- * Prevent model from being saved.
592
- *
593
- * @returns {boolean}.
594
  */
595
- save: function() {
596
- return false;
 
 
 
 
 
 
 
 
 
 
597
  },
598
 
599
  /**
600
- * Prevent model from being deleted.
601
- *
602
- * @returns {boolean}.
603
  */
604
- destroy: function() {
605
- return false;
 
 
 
 
 
 
 
 
 
 
606
  }
 
607
  }
608
  );
609
 
610
  /**
611
  * API Schema model. Contains meta information about the API.
612
  */
613
- wp.api.models.Schema = WPApiBaseModel.extend(
614
- /** @lends Shema.prototype */
615
  {
616
- url: WP_API_Settings.root + 'wp/v2',
617
-
618
  defaults: {
619
- namespace: '',
620
- _links: '',
621
  routes: {}
622
  },
623
 
624
- /**
625
- * Prevent model from being saved.
626
- *
627
- * @returns {boolean}.
628
- */
629
- save: function() {
630
- return false;
 
631
  },
632
 
633
- /**
634
- * Prevent model from being deleted.
635
- *
636
- * @returns {boolean}.
637
- */
638
- destroy: function() {
639
- return false;
640
  }
641
  }
642
  );
 
643
 
644
-
645
- })( wp, WP_API_Settings, Backbone, window );
646
-
647
- /* global WP_API_Settings:false */
648
- (function( wp, WP_API_Settings, Backbone, _, window, undefined ) {
649
 
650
  'use strict';
651
 
652
  /**
653
  * Contains basic collection functionality such as pagination.
654
  */
655
- var BaseCollection = Backbone.Collection.extend(
656
  /** @lends BaseCollection.prototype */
657
  {
658
 
659
  /**
660
  * Setup default state.
661
  */
662
- initialize: function() {
663
  this.state = {
664
  data: {},
665
  currentPage: null,
666
  totalPages: null,
667
  totalObjects: null
668
  };
 
 
 
 
 
669
  },
670
 
671
  /**
@@ -679,13 +765,15 @@
679
  * @returns {*}.
680
  */
681
  sync: function( method, model, options ) {
682
- options = options || {};
683
- var beforeSend = options.beforeSend,
684
  self = this;
685
 
686
- if ( 'undefined' !== typeof WP_API_Settings.nonce ) {
 
 
 
687
  options.beforeSend = function( xhr ) {
688
- xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
689
 
690
  if ( beforeSend ) {
691
  return beforeSend.apply( self, arguments );
@@ -710,12 +798,12 @@
710
  self.state.currentPage = options.data.page - 1;
711
  }
712
 
713
- var success = options.success;
714
  options.success = function( data, textStatus, request ) {
715
  self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 );
716
  self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 );
717
 
718
- if ( self.state.currentPage === null ) {
719
  self.state.currentPage = 1;
720
  } else {
721
  self.state.currentPage++;
@@ -747,7 +835,7 @@
747
  return false;
748
  }
749
 
750
- if ( this.state.currentPage === null || this.state.currentPage <= 1 ) {
751
  options.data.page = 2;
752
  } else {
753
  options.data.page = this.state.currentPage + 1;
@@ -763,9 +851,9 @@
763
  * @returns null|boolean.
764
  */
765
  hasMore: function() {
766
- if ( this.state.totalPages === null ||
767
- this.state.totalObjects === null ||
768
- this.state.currentPage === null ) {
769
  return null;
770
  } else {
771
  return ( this.state.currentPage < this.state.totalPages );
@@ -774,209 +862,367 @@
774
  }
775
  );
776
 
777
- /**
778
- * Backbone collection for posts.
779
- */
780
- wp.api.collections.Posts = BaseCollection.extend(
781
- /** @lends Posts.prototype */
782
- {
783
- url: WP_API_Settings.root + 'wp/v2/posts',
784
 
785
- model: wp.api.models.Post
786
- }
787
- );
788
 
789
- /**
790
- * Backbone collection for pages.
791
- */
792
- wp.api.collections.Pages = BaseCollection.extend(
793
- /** @lends Pages.prototype */
794
- {
795
- url: WP_API_Settings.root + 'wp/v2/pages',
796
 
797
- model: wp.api.models.Page
798
- }
799
- );
800
 
801
- /**
802
- * Backbone users collection.
803
- */
804
- wp.api.collections.Users = BaseCollection.extend(
805
- /** @lends Users.prototype */
806
- {
807
- url: WP_API_Settings.root + 'wp/v2/users',
808
 
809
- model: wp.api.models.User
810
- }
811
- );
 
 
 
 
 
812
 
813
- /**
814
- * Backbone post statuses collection.
815
- */
816
- wp.api.collections.PostStatuses = BaseCollection.extend(
817
- /** @lends PostStatuses.prototype */
818
- {
819
- url: WP_API_Settings.root + 'wp/v2/statuses',
820
 
821
- model: wp.api.models.PostStatus,
822
 
823
- parse: function( response ) {
824
- var responseArray = [];
825
 
826
- for ( var property in response ) {
827
- if ( response.hasOwnProperty( property ) ) {
828
- responseArray.push( response[property] );
829
- }
830
- }
831
 
832
- return this.constructor.__super__.parse.call( this, responseArray );
833
- }
834
- }
835
- );
836
 
837
- /**
838
- * Backbone media library collection.
839
- */
840
- wp.api.collections.MediaLibrary = BaseCollection.extend(
841
- /** @lends MediaLibrary.prototype */
842
- {
843
- url: WP_API_Settings.root + 'wp/v2/media',
844
 
845
- model: wp.api.models.Media
846
- }
847
- );
848
 
849
- /**
850
- * Backbone taxonomy collection.
851
- */
852
- wp.api.collections.Taxonomies = BaseCollection.extend(
853
- /** @lends Taxonomies.prototype */
854
- {
855
- model: wp.api.models.Taxonomy,
 
 
 
 
 
 
 
 
 
856
 
857
- url: WP_API_Settings.root + 'wp/v2/taxonomies'
858
- }
859
- );
 
 
 
860
 
861
- /**
862
- * Backbone comment collection.
863
- */
864
- wp.api.collections.Comments = BaseCollection.extend(
865
- /** @lends Comments.prototype */
866
- {
867
- model: wp.api.models.Comment,
868
 
869
  /**
870
- * Return URL for collection.
 
 
 
871
  *
872
- * @returns {string}.
873
  */
874
- url: WP_API_Settings.root + 'wp/v2/comments'
875
- }
876
- );
877
-
878
- /**
879
- * Backbone post type collection.
880
- */
881
- wp.api.collections.PostTypes = BaseCollection.extend(
882
- /** @lends PostTypes.prototype */
883
- {
884
- model: wp.api.models.PostType,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
 
886
- url: WP_API_Settings.root + 'wp/v2/types',
 
 
 
 
 
 
 
887
 
888
- parse: function( response ) {
889
- var responseArray = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
890
 
891
- for ( var property in response ) {
892
- if ( response.hasOwnProperty( property ) ) {
893
- responseArray.push( response[property] );
 
894
  }
895
  }
896
-
897
- return this.constructor.__super__.parse.call( this, responseArray );
898
- }
899
- }
900
- );
901
-
902
- /**
903
- * Backbone terms collection.
904
- *
905
- * Usage: new wp.api.collections.Terms( {}, { taxonomy: 'taxonomy-slug' } )
906
- */
907
- wp.api.collections.Terms = BaseCollection.extend(
908
- /** @lends Terms.prototype */
909
- {
910
- model: wp.api.models.Term,
911
-
912
- taxonomy: 'category',
913
 
914
  /**
915
- * @class Represent an array of terms.
916
- * @augments Backbone.Collection.
917
- * @constructs
918
  */
919
- initialize: function( models, options ) {
920
- if ( 'undefined' !== typeof options && options.taxonomy ) {
921
- this.taxonomy = options.taxonomy;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  }
923
 
924
- BaseCollection.prototype.initialize.apply( this, arguments );
925
- },
 
 
926
 
927
  /**
928
- * Return URL for collection.
929
  *
930
- * @returns {string}.
931
  */
932
- url: function() {
933
- return WP_API_Settings.root + 'wp/v2/terms/' + this.taxonomy;
934
- }
935
- }
936
- );
937
 
938
- /**
939
- * Backbone revisions collection.
940
- *
941
- * Usage: new wp.api.collections.Revisions( {}, { parent: POST_ID } ).
942
- */
943
- wp.api.collections.Revisions = BaseCollection.extend(
944
- /** @lends Revisions.prototype */
945
- {
946
- model: wp.api.models.Revision,
947
 
948
- parent: null,
 
949
 
950
- /**
951
- * @class Represent an array of revisions.
952
- * @augments Backbone.Collection.
953
- * @constructs
954
- */
955
- initialize: function( models, options ) {
956
- BaseCollection.prototype.initialize.apply( this, arguments );
957
 
958
- if ( options && options.parent ) {
959
- this.parent = options.parent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
  }
961
- },
962
 
963
- /**
964
- * return URL for collection.
965
- *
966
- * @returns {string}.
967
- */
968
- url: function() {
969
- return WP_API_Settings.root + 'wp/v2/posts/' + this.parent + '/revisions';
970
- }
 
971
  }
972
- );
 
 
 
 
 
973
 
974
  /**
975
- * Todo: Handle schema endpoints.
 
 
 
 
 
976
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
977
 
978
  /**
979
- * Todo: Handle post meta.
980
  */
981
 
982
- })( wp, WP_API_Settings, Backbone, _, window );
 
 
 
8
  this.views = {};
9
  }
10
 
11
+ window.wp = window.wp || {};
12
+ wp.api = wp.api || new WP_API();
13
+ wp.api.versionString = wp.api.versionString || 'wp/v2/';
14
 
15
  })( window );
16
 
18
 
19
  'use strict';
20
 
21
+ var pad, r;
22
+
23
  window.wp = window.wp || {};
24
  wp.api = wp.api || {};
25
  wp.api.utils = wp.api.utils || {};
26
 
27
  /**
28
+ * ECMAScript 5 shim, adapted from MDN.
29
  * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
30
  */
31
  if ( ! Date.prototype.toISOString ) {
32
+ pad = function( number ) {
33
+ r = String( number );
34
+ if ( 1 === r.length ) {
35
  r = '0' + r;
36
  }
37
 
65
  // implementations could be faster.
66
  // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
67
  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 ) ) ) {
68
+
69
  // Avoid NaN timestamps caused by “undefined” values being passed to Date.UTC.
70
  for ( i = 0; ( k = numericKeys[i] ); ++i ) {
71
  struct[k] = +struct[k] || 0;
75
  struct[2] = ( +struct[2] || 1 ) - 1;
76
  struct[3] = +struct[3] || 1;
77
 
78
+ if ( 'Z' !== struct[8] && undefined !== struct[9] ) {
79
  minutesOffset = struct[10] * 60 + struct[11];
80
 
81
+ if ( '+' === struct[9] ) {
82
  minutesOffset = 0 - minutesOffset;
83
  }
84
  }
91
  return timestamp;
92
  };
93
 
94
+ /**
95
+ * Helper function for getting the root URL.
96
+ * @return {[type]} [description]
97
+ */
98
+ wp.api.utils.getRootUrl = function() {
99
+ return window.location.origin ?
100
+ window.location.origin + '/' :
101
+ window.location.protocol + '/' + window.location.host + '/';
102
+ };
103
 
104
+ /**
105
+ * Helper for capitalizing strings.
106
+ */
107
+ wp.api.utils.capitalize = function( str ) {
108
+ if ( _.isUndefined( str ) ) {
109
+ return str;
110
+ }
111
+ return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
112
+ };
113
 
114
+ /**
115
+ * Extract a route part based on negitive index.
116
+ *
117
+ * @param {string} route The endpoint route.
118
+ * @param {int} part The number of parts from the end of the route to retrieve. Default 1.
119
+ * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`.
120
+ */
121
+ wp.api.utils.extractRoutePart = function( route, part ) {
122
+ var routeParts;
123
+
124
+ part = part || 1;
125
+
126
+ // Remove versions string from route to avoid returning it.
127
+ route = route.replace( wp.api.versionString, '' );
128
+ routeParts = route.split( '/' ).reverse();
129
+ if ( _.isUndefined( routeParts[ --part ] ) ) {
130
+ return '';
131
+ }
132
+ return routeParts[ part ];
133
+ };
134
 
135
  /**
136
+ * Extract a parent name from a passed route.
137
  *
138
+ * @param {string} route The route to extract a name from.
139
  */
140
+ wp.api.utils.extractParentName = function( route ) {
141
+ var name,
142
+ lastSlash = route.lastIndexOf( '_id>[\\d]+)/' );
143
+
144
+ if ( lastSlash < 0 ) {
145
+ return '';
146
+ }
147
+ name = route.substr( 0, lastSlash - 1 );
148
+ name = name.split( '/' );
149
+ name.pop();
150
+ name = name.pop();
151
+ return name;
152
+ };
153
 
154
  /**
155
+ * Add defaults to a model from a route's endpoints.
156
  *
157
+ * @param {array} routeEndpoints Array of route endpoints.
158
+ * @param {Object} modelInstance An instance of the model (or collection)
159
+ * to add the defaults to.
160
  */
161
+ wp.api.utils.decorateFromRoute = function( routeEndpoints, modelInstance ) {
162
+
163
  /**
164
+ * Build the defaults based on route endpoint data.
 
 
165
  */
166
+ _.each( routeEndpoints, function( routeEndpoint ) {
 
167
 
168
+ // Add post and edit endpoints as model defaults.
169
+ if ( _.contains( routeEndpoint.methods, 'POST' ) || _.contains( routeEndpoint.methods, 'PUT' ) ) {
 
 
 
 
170
 
171
+ // Add any non empty args, merging them into the defaults object.
172
+ if ( ! _.isEmpty( routeEndpoint.args ) ) {
173
 
174
+ // Set as defauls if no defaults yet.
175
+ if ( _.isEmpty( modelInstance.defaults ) ) {
176
+ modelInstance.defaults = routeEndpoint.args;
177
+ } else {
 
 
 
178
 
179
+ // We already have defaults, merge these new args in.
180
+ modelInstance.defaults = _.union( routeEndpoint.args, modelInstance.defaults );
181
+ }
 
182
  }
183
+ } else {
184
 
185
+ // Add GET method as model options.
186
+ if ( _.contains( routeEndpoint.methods, 'GET' ) ) {
187
+
188
+ // Add any non empty args, merging them into the defaults object.
189
+ if ( ! _.isEmpty( routeEndpoint.args ) ) {
190
+
191
+ // Set as defauls if no defaults yet.
192
+ if ( _.isEmpty( modelInstance.options ) ) {
193
+ modelInstance.options = routeEndpoint.args;
194
+ } else {
195
+
196
+ // We already have options, merge these new args in.
197
+ modelInstance.options = _.union( routeEndpoint.args, modelInstance.options );
198
+ }
199
+ }
200
 
201
+ }
 
 
202
  }
203
 
204
+ } );
205
+
206
  };
207
 
208
  /**
209
+ * Add mixins and helpers to models depending on their defaults.
210
  *
211
+ * @param {Backbone Model} model The model to attach helpers and mixins to.
212
+ * @param {string} modelClassName The classname of the constructed model.
213
+ * @param {Object} loadingObjects An object containing the models and collections we are building.
214
  */
215
+ wp.api.utils.addMixinsAndHelpers = function( model, modelClassName, loadingObjects ) {
 
 
 
 
 
 
216
 
217
+ var hasDate = false,
218
 
219
+ /**
220
+ * Array of parseable dates.
221
+ *
222
+ * @type {string[]}.
223
+ */
224
+ parseableDates = [ 'date', 'modified', 'date_gmt', 'modified_gmt' ],
225
 
226
+ /**
227
+ * Mixin for all content that is time stamped.
228
+ *
229
+ * This mixin converts between mysql timestamps and JavaScript Dates when syncing a model
230
+ * to or from the server. For example, a date stored as `2015-12-27T21:22:24` on the server
231
+ * gets expanded to `Sun Dec 27 2015 14:22:24 GMT-0700 (MST)` when the model is fetched.
232
+ *
233
+ * @type {{toJSON: toJSON, parse: parse}}.
234
+ */
235
+ TimeStampedMixin = {
236
  /**
237
+ * Serialize the entity pre-sync.
238
+ *
239
+ * @returns {*}.
240
  */
241
+ toJSON: function() {
242
+ var attributes = _.clone( this.attributes );
243
 
244
+ // Serialize Date objects back into 8601 strings.
245
+ _.each( parseableDates, function( key ) {
246
+ if ( key in attributes ) {
 
247
 
248
+ // Don't convert null values
249
+ if ( ! _.isNull( attributes[ key ] ) ) {
250
+ attributes[ key ] = attributes[ key ].toISOString();
251
+ }
252
+ }
253
+ } );
254
 
255
+ return attributes;
256
+ },
257
 
258
+ /**
259
+ * Unserialize the fetched response.
260
+ *
261
+ * @param {*} response.
262
+ * @returns {*}.
263
+ */
264
+ parse: function( response ) {
265
+ var timestamp;
266
+
267
+ // Parse dates into native Date objects.
268
+ _.each( parseableDates, function( key ) {
269
+ if ( ! ( key in response ) ) {
270
+ return;
271
+ }
272
+
273
+ // Don't convert null values
274
+ if ( ! _.isNull( response[ key ] ) ) {
275
+ timestamp = wp.api.utils.parseISO8601( response[ key ] );
276
+ response[ key ] = new Date( timestamp );
277
+ }
278
+ });
279
+
280
+ return response;
281
+ }
282
+ },
283
 
 
 
 
 
 
 
284
  /**
285
+ * Build a helper function to retrieve related model.
286
  *
287
+ * @param {string} parentModel The parent model.
288
+ * @param {int} modelId The model ID if the object to request
289
+ * @param {string} modelName The model name to use when constructing the model.
290
+ * @param {string} embedSourcePoint Where to check the embedds object for _embed data.
291
+ * @param {string} embedCheckField Which model field to check to see if the model has data.
292
+ *
293
+ * @return {Deferred.promise} A promise which resolves to the constructed model.
294
  */
295
+ buildModelGetter = function( parentModel, modelId, modelName, embedSourcePoint, embedCheckField ) {
296
+ var getModel, embeddeds, attributes, deferred;
297
 
298
+ deferred = jQuery.Deferred();
299
+ embeddeds = parentModel.get( '_embedded' ) || {};
300
 
301
+ // Verify that we have a valied author id.
302
+ if ( ! _.isNumber( modelId ) ) {
303
+ deferred.reject();
304
+ return deferred;
 
 
 
305
  }
306
 
307
+ // If we have embedded object data, use that when constructing the getModel.
308
+ if ( embeddeds[ embedSourcePoint ] ) {
309
+ attributes = _.findWhere( embeddeds[ embedSourcePoint ], { id: modelId } );
310
+ }
311
 
312
+ // Otherwise use the modelId.
313
+ if ( ! attributes ) {
314
+ attributes = { id: modelId };
315
+ }
 
 
 
 
 
 
 
316
 
317
+ // Create the new getModel model.
318
+ getModel = new wp.api.models[ modelName ]( attributes );
319
 
320
+ // If we didn’t have an embedded getModel, fetch the getModel data.
321
+ if ( ! getModel.get( embedCheckField ) ) {
322
+ getModel.fetch( { success: function( getModel ) {
323
+ deferred.resolve( getModel );
324
+ } } );
325
+ } else {
326
+ deferred.resolve( getModel );
327
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
+ // Return a promise.
330
+ return deferred.promise();
331
+ },
 
 
 
 
 
 
 
332
 
333
+ /**
334
+ * Build a helper to retrieve a collection.
335
+ *
336
+ * @param {string} parentModel The parent model.
337
+ * @param {string} collectionName The name to use when constructing the collection.
338
+ * @param {string} embedSourcePoint Where to check the embedds object for _embed data.
339
+ * @param {string} embedIndex An addiitonal optional index for the _embed data.
340
+ *
341
+ * @return {Deferred.promise} A promise which resolves to the constructed collection.
342
+ */
343
+ buildCollectionGetter = function( parentModel, collectionName, embedSourcePoint, embedIndex ) {
344
+ /**
345
+ * Returns a promise that resolves to the requested collection
346
+ *
347
+ * Uses the embedded data if available, otherwises fetches the
348
+ * data from the server.
349
+ *
350
+ * @return {Deferred.promise} promise Resolves to a wp.api.collections[ collectionName ]
351
+ * collection.
352
+ */
353
+ var postId, embeddeds, getObjects,
354
+ classProperties = '',
355
+ properties = '',
356
+ deferred = jQuery.Deferred();
357
+
358
+ postId = parentModel.get( 'id' );
359
+ embeddeds = parentModel.get( '_embedded' ) || {};
360
+
361
+ // Verify that we have a valied post id.
362
+ if ( ! _.isNumber( postId ) || 0 === postId ) {
363
+ deferred.reject();
364
+ return deferred;
365
+ }
366
 
367
+ // If we have embedded getObjects data, use that when constructing the getObjects.
368
+ if ( ! _.isUndefined( embedSourcePoint ) && ! _.isUndefined( embeddeds[ embedSourcePoint ] ) ) {
 
 
 
 
 
 
 
 
 
369
 
370
+ // Some embeds also include an index offset, check for that.
371
+ if ( _.isUndefined( embedIndex ) ) {
 
 
 
 
 
 
 
 
372
 
373
+ // Use the embed source point directly.
374
+ properties = embeddeds[ embedSourcePoint ];
375
+ } else {
376
 
377
+ // Add the index to the embed source point.
378
+ properties = embeddeds[ embedSourcePoint ][ embedIndex ];
379
+ }
380
+ } else {
 
 
 
 
 
 
 
381
 
382
+ // Otherwise use the postId.
383
+ classProperties = { parent: postId };
384
+ }
385
 
386
+ // Create the new getObjects collection.
387
+ getObjects = new wp.api.collections[ collectionName ]( properties, classProperties );
 
 
 
 
 
 
 
 
388
 
389
+ // If we didn’t have embedded getObjects, fetch the getObjects data.
390
+ if ( _.isUndefined( getObjects.models[0] ) ) {
391
+ getObjects.fetch( { success: function( getObjects ) {
392
 
393
+ // Add a helper 'parent_post' attribute onto the model.
394
+ setHelperParentPost( getObjects, postId );
395
+ deferred.resolve( getObjects );
396
+ } } );
397
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
+ // Add a helper 'parent_post' attribute onto the model.
400
+ setHelperParentPost( getObjects, postId );
401
+ deferred.resolve( getObjects );
402
+ }
 
 
 
 
 
 
403
 
404
+ // Return a promise.
405
+ return deferred.promise();
406
 
407
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
 
409
+ /**
410
+ * Set the model post parent.
411
+ */
412
+ setHelperParentPost = function( collection, postId ) {
 
 
 
 
 
 
 
413
 
414
+ // Attach post_parent id to the collection.
415
+ _.each( collection.models, function( model ) {
416
+ model.set( 'parent_post', postId );
417
+ } );
 
 
 
 
 
 
 
 
 
 
418
  },
419
 
420
  /**
421
+ * Add a helper funtion to handle post Meta.
 
 
422
  */
423
+ MetaMixin = {
424
+ getMeta: function() {
425
+ return buildCollectionGetter( this, 'PostMeta', 'https://api.w.org/meta' );
426
+ }
427
+ },
428
 
429
+ /**
430
+ * Add a helper funtion to handle post Revisions.
431
+ */
432
+ RevisionsMixin = {
433
+ getRevisions: function() {
434
+ return buildCollectionGetter( this, 'PostRevisions' );
435
+ }
436
+ },
437
 
438
+ /**
439
+ * Add a helper funtion to handle post Tags.
440
+ */
441
+ TagsMixin = {
442
+ getTags: function() {
443
+ return buildCollectionGetter( this, 'PostTags', 'https://api.w.org/term', 1 );
444
+ }
445
+ },
446
+ /**
447
+ * Add a helper funtion to handle post Categories.
448
+ */
449
+ CategoriesMixin = {
450
 
451
+ /**
452
+ * Get a PostCategories model for an model's categories.
453
+ *
454
+ * Uses the embedded data if available, otherwises fetches the
455
+ * data from the server.
456
+ *
457
+ * @return {Deferred.promise} promise Resolves to a wp.api.collections.PostCategories
458
+ * collection containing the post categories.
459
+ */
460
+ getCategories: function() {
461
+ return buildCollectionGetter( this, 'PostCategories', 'https://api.w.org/term', 0 );
462
+ },
463
 
464
+ /**
465
+ * Set the categories for a post.
466
+ *
467
+ * Accepts an array of category slugs, or a PostCategories collection.
468
+ *
469
+ * @param {array|Backbone.Collection} categories The categories to set on the post.
470
+ *
471
+ */
472
+ setCategories: function( categories ) {
473
+ var allCategories, newCategory,
474
+ self = this,
475
+ newCategories = [];
476
+
477
+ // If this is an array of slugs, build a collection.
478
+ if ( _.isArray( categories ) ) {
479
+
480
+ // Get all the categories.
481
+ allCategories = new wp.api.collections.Categories();
482
+ allCategories.fetch( {
483
+ success: function( allcats ) {
484
+
485
+ // Find the passed categories and set them up.
486
+ _.each( categories, function( category ) {
487
+ newCategory = new wp.api.models.PostCategories( allcats.findWhere( { slug: category } ) );
488
+
489
+ // Tie the new category to the post.
490
+ newCategory.set( 'parent_post', self.get( 'id' ) );
491
+
492
+ // Add the new category to the collection.
493
+ newCategories.push( newCategory );
494
+ } );
495
+ categories = new wp.api.collections.PostCategories( newCategories );
496
+ self.setCategoriesWithCollection( categories );
497
+ }
498
+ } );
499
 
500
+ } else {
501
+ this.setCategoriesWithCollection( categories );
502
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
 
504
+ },
 
505
 
506
+ /**
507
+ * Set the categories for a post.
508
+ *
509
+ * Accepts PostCategories collection.
510
+ *
511
+ * @param {array|Backbone.Collection} categories The categories to set on the post.
512
+ *
513
+ */
514
+ setCategoriesWithCollection: function( categories ) {
515
+ var removedCategories, addedCategories, categoriesIds, existingCategoriesIds;
516
 
517
+ // Get the existing categories.
518
+ this.getCategories().done( function( existingCategories ) {
519
 
520
+ // Pluck out the category ids.
521
+ categoriesIds = categories.pluck( 'id' );
522
+ existingCategoriesIds = existingCategories.pluck( 'id' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
 
524
+ // Calculate which categories have been removed or added (leave the rest).
525
+ addedCategories = _.difference( categoriesIds, existingCategoriesIds );
526
+ removedCategories = _.difference( existingCategoriesIds, categoriesIds );
527
 
528
+ // Add the added categories.
529
+ _.each( addedCategories, function( addedCategory ) {
 
 
 
 
 
 
 
 
530
 
531
+ // Save the new categories on the post with a 'POST' method, not Backbone's default 'PUT'.
532
+ existingCategories.create( categories.get( addedCategory ), { type: 'POST' } );
533
+ } );
534
 
535
+ // Remove the removed categories.
536
+ _.each( removedCategories, function( removedCategory ) {
537
+ existingCategories.get( removedCategory ).destroy();
538
+ } );
539
+ } );
540
+ }
541
  },
542
 
543
  /**
544
+ * Add a helper function to retrieve the author user model.
 
 
545
  */
546
+ AuthorMixin = {
547
+ getAuthorUser: function() {
548
+ return buildModelGetter( this, this.get( 'author' ), 'User', 'author', 'name' );
549
+ }
550
  },
551
 
552
  /**
553
+ * Add a helper function to retrieve the featured image.
 
 
554
  */
555
+ FeaturedImageMixin = {
556
+ getFeaturedImage: function() {
557
+ return buildModelGetter( this, this.get( 'featured_image' ), 'Media', 'https://api.w.org/featuredmedia', 'source_url' );
558
+ }
559
+ };
560
+
561
+ // Exit if we don't have valid model defaults.
562
+ if ( _.isUndefined( model.defaults ) ) {
563
+ return model;
564
+ }
565
+
566
+ // Go thru the parsable date fields, if our model contains any of them it gets the TimeStampedMixin.
567
+ _.each( parseableDates, function( theDateKey ) {
568
+ if ( ! _.isUndefined( model.defaults[ theDateKey ] ) ) {
569
+ hasDate = true;
570
  }
571
+ } );
572
+
573
+ // Add the TimeStampedMixin for models that contain a date field.
574
+ if ( hasDate ) {
575
+ model = model.extend( TimeStampedMixin );
576
  }
577
+
578
+ // Add the AuthorMixin for models that contain an author.
579
+ if ( ! _.isUndefined( model.defaults.author ) ) {
580
+ model = model.extend( AuthorMixin );
581
+ }
582
+
583
+ // Add the FeaturedImageMixin for models that contain a featured_image.
584
+ if ( ! _.isUndefined( model.defaults.featured_image ) ) {
585
+ model = model.extend( FeaturedImageMixin );
586
+ }
587
+
588
+ // Add the CategoriesMixin for models that support categories collections.
589
+ if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Categories' ] ) ) {
590
+ model = model.extend( CategoriesMixin );
591
+ }
592
+
593
+ // Add the MetaMixin for models that support meta collections.
594
+ if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Meta' ] ) ) {
595
+ model = model.extend( MetaMixin );
596
+ }
597
+
598
+ // Add the TagsMixin for models that support tags collections.
599
+ if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Tags' ] ) ) {
600
+ model = model.extend( TagsMixin );
601
+ }
602
+
603
+ // Add the RevisionsMixin for models that support revisions collections.
604
+ if ( ! _.isUndefined( loadingObjects.collections[ modelClassName + 'Revisions' ] ) ) {
605
+ model = model.extend( RevisionsMixin );
606
+ }
607
+
608
+ return model;
609
+ };
610
+
611
+ })( window );
612
+
613
+ /* global wpApiSettings:false */
614
+
615
+ // Suppress warning about parse function's unused "options" argument:
616
+ /* jshint unused:false */
617
+ (function( wp, wpApiSettings, Backbone, window, undefined ) {
618
+
619
+ 'use strict';
620
 
621
  /**
622
+ * Backbone base model for all models.
 
 
 
623
  */
624
+ wp.api.WPApiBaseModel = Backbone.Model.extend(
625
+ /** @lends WPApiBaseModel.prototype */
626
  {
627
+ /**
628
+ * Set nonce header before every Backbone sync.
629
+ *
630
+ * @param {string} method.
631
+ * @param {Backbone.Model} model.
632
+ * @param {{beforeSend}, *} options.
633
+ * @returns {*}.
634
+ */
635
+ sync: function( method, model, options ) {
636
+ var beforeSend;
637
 
638
+ options = options || {};
639
 
640
+ if ( ! _.isUndefined( wpApiSettings.nonce ) && ! _.isNull( wpApiSettings.nonce ) ) {
641
+ beforeSend = options.beforeSend;
642
+
643
+ // @todo enable option for jsonp endpoints
644
+ // options.dataType = 'jsonp';
645
+
646
+ options.beforeSend = function( xhr ) {
647
+ xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
648
+
649
+ if ( beforeSend ) {
650
+ return beforeSend.apply( this, arguments );
651
+ }
652
+ };
653
+ }
654
+
655
+ // Add '?force=true' to delete method when required.
656
+ if ( this.requireForceForDelete && 'delete' === method ) {
657
+ model.url = model.url() + '?force=true';
658
+ }
659
+ return Backbone.sync( method, model, options );
660
  },
661
 
662
  /**
663
+ * Save is only allowed when the PUT OR POST methods are available for the endpoint.
 
 
664
  */
665
+ save: function( attrs, options ) {
666
+
667
+ // Do we have the put method, then execute the save.
668
+ if ( _.contains( this.methods, 'PUT' ) || _.contains( this.methods, 'POST' ) ) {
669
+
670
+ // Proxy the call to the original save function.
671
+ return Backbone.Model.prototype.save.call( this, attrs, options );
672
+ } else {
673
+
674
+ // Otherwise bail, disallowing action.
675
+ return false;
676
+ }
677
  },
678
 
679
  /**
680
+ * Delete is only allowed when the DELETE method is available for the endpoint.
 
 
681
  */
682
+ destroy: function( options ) {
683
+
684
+ // Do we have the DELETE method, then execute the destroy.
685
+ if ( _.contains( this.methods, 'DELETE' ) ) {
686
+
687
+ // Proxy the call to the original save function.
688
+ return Backbone.Model.prototype.destroy.call( this, options );
689
+ } else {
690
+
691
+ // Otherwise bail, disallowing action.
692
+ return false;
693
+ }
694
  }
695
+
696
  }
697
  );
698
 
699
  /**
700
  * API Schema model. Contains meta information about the API.
701
  */
702
+ wp.api.models.Schema = wp.api.WPApiBaseModel.extend(
703
+ /** @lends Schema.prototype */
704
  {
 
 
705
  defaults: {
706
+ _links: {},
707
+ namespace: null,
708
  routes: {}
709
  },
710
 
711
+ initialize: function( attributes, options ) {
712
+ var model = this;
713
+ options = options || {};
714
+
715
+ wp.api.WPApiBaseModel.prototype.initialize.call( model, attributes, options );
716
+
717
+ model.apiRoot = options.apiRoot || wpApiSettings.root;
718
+ model.versionString = options.versionString || wpApiSettings.versionString;
719
  },
720
 
721
+ url: function() {
722
+ return this.apiRoot + this.versionString;
 
 
 
 
 
723
  }
724
  }
725
  );
726
+ })( wp, wpApiSettings, Backbone, window );
727
 
728
+ /* global wpApiSettings:false */
729
+ (function( wp, wpApiSettings, Backbone, _, window, undefined ) {
 
 
 
730
 
731
  'use strict';
732
 
733
  /**
734
  * Contains basic collection functionality such as pagination.
735
  */
736
+ wp.api.WPApiBaseCollection = Backbone.Collection.extend(
737
  /** @lends BaseCollection.prototype */
738
  {
739
 
740
  /**
741
  * Setup default state.
742
  */
743
+ initialize: function( models, options ) {
744
  this.state = {
745
  data: {},
746
  currentPage: null,
747
  totalPages: null,
748
  totalObjects: null
749
  };
750
+ if ( _.isUndefined( options ) ) {
751
+ this.parent = '';
752
+ } else {
753
+ this.parent = options.parent;
754
+ }
755
  },
756
 
757
  /**
765
  * @returns {*}.
766
  */
767
  sync: function( method, model, options ) {
768
+ var beforeSend, success,
 
769
  self = this;
770
 
771
+ options = options || {};
772
+ beforeSend = options.beforeSend;
773
+
774
+ if ( 'undefined' !== typeof wpApiSettings.nonce ) {
775
  options.beforeSend = function( xhr ) {
776
+ xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
777
 
778
  if ( beforeSend ) {
779
  return beforeSend.apply( self, arguments );
798
  self.state.currentPage = options.data.page - 1;
799
  }
800
 
801
+ success = options.success;
802
  options.success = function( data, textStatus, request ) {
803
  self.state.totalPages = parseInt( request.getResponseHeader( 'x-wp-totalpages' ), 10 );
804
  self.state.totalObjects = parseInt( request.getResponseHeader( 'x-wp-total' ), 10 );
805
 
806
+ if ( null === self.state.currentPage ) {
807
  self.state.currentPage = 1;
808
  } else {
809
  self.state.currentPage++;
835
  return false;
836
  }
837
 
838
+ if ( null === this.state.currentPage || this.state.currentPage <= 1 ) {
839
  options.data.page = 2;
840
  } else {
841
  options.data.page = this.state.currentPage + 1;
851
  * @returns null|boolean.
852
  */
853
  hasMore: function() {
854
+ if ( null === this.state.totalPages ||
855
+ null === this.state.totalObjects ||
856
+ null === this.state.currentPage ) {
857
  return null;
858
  } else {
859
  return ( this.state.currentPage < this.state.totalPages );
862
  }
863
  );
864
 
865
+ })( wp, wpApiSettings, Backbone, _, window );
 
 
 
 
 
 
866
 
867
+ /* global wpApiSettings */
868
+ (function( window, undefined ) {
 
869
 
870
+ 'use strict';
 
 
 
 
 
 
871
 
872
+ var Endpoint, initializedDeferreds = {};
 
 
873
 
874
+ window.wp = window.wp || {};
875
+ wp.api = wp.api || {};
 
 
 
 
 
876
 
877
+ Endpoint = Backbone.Model.extend({
878
+ defaults: {
879
+ apiRoot: wpApiSettings.root,
880
+ versionString: wp.api.versionString,
881
+ schema: null,
882
+ models: {},
883
+ collections: {}
884
+ },
885
 
886
+ initialize: function() {
887
+ var model = this, deferred;
 
 
 
 
 
888
 
889
+ Backbone.Model.prototype.initialize.apply( model, arguments );
890
 
891
+ deferred = jQuery.Deferred();
892
+ model.schemaConstructed = deferred.promise();
893
 
894
+ model.schemaModel = new wp.api.models.Schema( null, {
895
+ apiRoot: model.get( 'apiRoot' ),
896
+ versionString: model.get( 'versionString' )
897
+ });
 
898
 
899
+ model.schemaModel.once( 'change', function() {
900
+ model.constructFromSchema();
901
+ deferred.resolve( model );
902
+ } );
903
 
904
+ if ( model.get( 'schema' ) ) {
 
 
 
 
 
 
905
 
906
+ // Use schema supplied as model attribute.
907
+ model.schemaModel.set( model.schemaModel.parse( model.get( 'schema' ) ) );
908
+ } else if ( ! _.isUndefined( sessionStorage ) && sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) ) {
909
 
910
+ // Used a cached copy of the schema model if available.
911
+ model.schemaModel.set( model.schemaModel.parse( JSON.parse( sessionStorage.getItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ) ) ) ) );
912
+ } else {
913
+ model.schemaModel.fetch({
914
+ /**
915
+ * When the server return the schema model data, store the data in a sessionCache so we don't
916
+ * have to retrieve it again for this session. Then, construct the models and collections based
917
+ * on the schema model data.
918
+ */
919
+ success: function( newSchemaModel ) {
920
+
921
+ // Store a copy of the schema model in the session cache if available.
922
+ if ( ! _.isUndefined( sessionStorage ) ) {
923
+ sessionStorage.setItem( 'wp-api-schema-model' + model.get( 'apiRoot' ) + model.get( 'versionString' ), JSON.stringify( newSchemaModel ) );
924
+ }
925
+ },
926
 
927
+ // @todo Handle the error condition.
928
+ error: function() {
929
+ }
930
+ });
931
+ }
932
+ },
933
 
934
+ constructFromSchema: function() {
935
+ var routeModel = this, modelRoutes, collectionRoutes, schemaRoot, loadingObjects,
 
 
 
 
 
936
 
937
  /**
938
+ * Set up the model and collection name mapping options. As the schema is built, the
939
+ * model and collection names will be adjusted if they are found in the mapping object.
940
+ *
941
+ * Localizing a variable wpApiSettings.mapping will over-ride the default mapping options.
942
  *
 
943
  */
944
+ mapping = wpApiSettings.mapping || {
945
+ models: {
946
+ 'Categories': 'Category',
947
+ 'Comments': 'Comment',
948
+ 'Pages': 'Page',
949
+ 'PagesMeta': 'PageMeta',
950
+ 'PagesRevisions': 'PageRevision',
951
+ 'Posts': 'Post',
952
+ 'PostsCategories': 'PostCategory',
953
+ 'PostsRevisions': 'PostRevision',
954
+ 'PostsTags': 'PostTag',
955
+ 'Schema': 'Schema',
956
+ 'Statuses': 'Status',
957
+ 'Tags': 'Tag',
958
+ 'Taxonomies': 'Taxonomy',
959
+ 'Types': 'Type',
960
+ 'Users': 'User'
961
+ },
962
+ collections: {
963
+ 'PagesMeta': 'PageMeta',
964
+ 'PagesRevisions': 'PageRevisions',
965
+ 'PostsCategories': 'PostCategories',
966
+ 'PostsMeta': 'PostMeta',
967
+ 'PostsRevisions': 'PostRevisions',
968
+ 'PostsTags': 'PostTags'
969
+ }
970
+ };
971
 
972
+ /**
973
+ * Iterate thru the routes, picking up models and collections to build. Builds two arrays,
974
+ * one for models and one for collections.
975
+ */
976
+ modelRoutes = [];
977
+ collectionRoutes = [];
978
+ schemaRoot = routeModel.get( 'apiRoot' ).replace( wp.api.utils.getRootUrl(), '' );
979
+ loadingObjects = {};
980
 
981
+ /**
982
+ * Tracking objects for models and collections.
983
+ */
984
+ loadingObjects.models = routeModel.get( 'models' );
985
+ loadingObjects.collections = routeModel.get( 'collections' );
986
+
987
+ _.each( routeModel.schemaModel.get( 'routes' ), function( route, index ) {
988
+
989
+ // Skip the schema root if included in the schema.
990
+ if ( index !== routeModel.get( ' versionString' ) &&
991
+ index !== schemaRoot &&
992
+ index !== ( '/' + routeModel.get( 'versionString' ).slice( 0, -1 ) )
993
+ ) {
994
+ /**
995
+ * Single item models end with a regex/variable.
996
+ *
997
+ * @todo make model/collection logic more robust.
998
+ */
999
+ if ( index.endsWith( '+)' ) ) {
1000
+ modelRoutes.push( { index: index, route: route } );
1001
+ } else {
1002
 
1003
+ // Collections end in a name.
1004
+ if ( ! index.endsWith( 'me' ) ) {
1005
+ collectionRoutes.push( { index: index, route: route } );
1006
+ }
1007
  }
1008
  }
1009
+ } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1010
 
1011
  /**
1012
+ * Construct the models.
1013
+ *
1014
+ * Base the class name on the route endpoint.
1015
  */
1016
+ _.each( modelRoutes, function( modelRoute ) {
1017
+
1018
+ // Extract the name and any parent from the route.
1019
+ var modelClassName,
1020
+ routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2 ),
1021
+ parentName = wp.api.utils.extractRoutePart( modelRoute.index, 4 );
1022
+
1023
+ // If the model has a parent in its route, add that to its class name.
1024
+ if ( '' !== parentName && parentName !== routeName ) {
1025
+ modelClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName );
1026
+ modelClassName = mapping.models[ modelClassName ] || modelClassName;
1027
+ loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
1028
+
1029
+ // Function that returns a constructed url based on the parent and id.
1030
+ url: function() {
1031
+ var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) +
1032
+ parentName + '/' +
1033
+ ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ?
1034
+ this.get( 'parent_post' ) :
1035
+ this.get( 'parent' ) ) + '/' +
1036
+ routeName;
1037
+ if ( ! _.isUndefined( this.get( 'id' ) ) ) {
1038
+ url += '/' + this.get( 'id' );
1039
+ }
1040
+ return url;
1041
+ },
1042
+
1043
+ // Include a reference to the original route object.
1044
+ route: modelRoute,
1045
+
1046
+ // Include a reference to the original class name.
1047
+ name: modelClassName,
1048
+
1049
+ // Include the array of route methods for easy reference.
1050
+ methods: modelRoute.route.methods,
1051
+
1052
+ initialize: function() {
1053
+ /**
1054
+ * Posts and pages support trashing, other types don't support a trash
1055
+ * and require that you pass ?force=true to actually delete them.
1056
+ *
1057
+ * @todo we should be getting trashability from the Schema, not hard coding types here.
1058
+ */
1059
+ if (
1060
+ 'Posts' !== this.name &&
1061
+ 'Pages' !== this.name &&
1062
+ _.contains( this.methods, 'DELETE' )
1063
+ ) {
1064
+ this.requireForceForDelete = true;
1065
+ }
1066
+ }
1067
+ } );
1068
+ } else {
1069
+
1070
+ // This is a model without a parent in its route
1071
+ modelClassName = wp.api.utils.capitalize( routeName );
1072
+ modelClassName = mapping.models[ modelClassName ] || modelClassName;
1073
+ loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
1074
+
1075
+ // Function that returns a constructed url based on the id.
1076
+ url: function() {
1077
+ var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + routeName;
1078
+ if ( ! _.isUndefined( this.get( 'id' ) ) ) {
1079
+ url += '/' + this.get( 'id' );
1080
+ }
1081
+ return url;
1082
+ },
1083
+
1084
+ // Include a reference to the original route object.
1085
+ route: modelRoute,
1086
+
1087
+ // Include a reference to the original class name.
1088
+ name: modelClassName,
1089
+
1090
+ // Include the array of route methods for easy reference.
1091
+ methods: modelRoute.route.methods
1092
+ } );
1093
  }
1094
 
1095
+ // Add defaults to the new model, pulled form the endpoint
1096
+ wp.api.utils.decorateFromRoute( modelRoute.route.endpoints, loadingObjects.models[ modelClassName ] );
1097
+
1098
+ } );
1099
 
1100
  /**
1101
+ * Construct the collections.
1102
  *
1103
+ * Base the class name on the route endpoint.
1104
  */
1105
+ _.each( collectionRoutes, function( collectionRoute ) {
 
 
 
 
1106
 
1107
+ // Extract the name and any parent from the route.
1108
+ var collectionClassName, modelClassName,
1109
+ routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ),
1110
+ parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 3 );
 
 
 
 
 
1111
 
1112
+ // If the collection has a parent in its route, add that to its class name/
1113
+ if ( '' !== parentName && parentName !== routeName ) {
1114
 
1115
+ collectionClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName );
1116
+ modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
1117
+ collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
1118
+ loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {
 
 
 
1119
 
1120
+ // Function that returns a constructed url passed on the parent.
1121
+ url: function() {
1122
+ return routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) +
1123
+ parentName + '/' + this.parent + '/' +
1124
+ routeName;
1125
+ },
1126
+
1127
+ // Specify the model that this collection contains.
1128
+ model: loadingObjects.models[ modelClassName ],
1129
+
1130
+ // Include a reference to the original class name.
1131
+ name: collectionClassName,
1132
+
1133
+ // Include a reference to the original route object.
1134
+ route: collectionRoute,
1135
+
1136
+ // Include the array of route methods for easy reference.
1137
+ methods: collectionRoute.route.methods
1138
+ } );
1139
+ } else {
1140
+
1141
+ // This is a collection without a parent in its route.
1142
+ collectionClassName = wp.api.utils.capitalize( routeName );
1143
+ modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
1144
+ collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
1145
+ loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {
1146
+
1147
+ // For the url of a root level collection, use a string.
1148
+ url: routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + routeName,
1149
+
1150
+ // Specify the model that this collection contains.
1151
+ model: loadingObjects.models[ modelClassName ],
1152
+
1153
+ // Include a reference to the original class name.
1154
+ name: collectionClassName,
1155
+
1156
+ // Include a reference to the original route object.
1157
+ route: collectionRoute,
1158
+
1159
+ // Include the array of route methods for easy reference.
1160
+ methods: collectionRoute.route.methods
1161
+ } );
1162
  }
 
1163
 
1164
+ // Add defaults to the new model, pulled form the endpoint
1165
+ wp.api.utils.decorateFromRoute( collectionRoute.route.endpoints, loadingObjects.collections[ collectionClassName ] );
1166
+ } );
1167
+
1168
+ // Add mixins and helpers for each of the models.
1169
+ _.each( loadingObjects.models, function( model, index ) {
1170
+ loadingObjects.models[ index ] = wp.api.utils.addMixinsAndHelpers( model, index, loadingObjects );
1171
+ } );
1172
+
1173
  }
1174
+
1175
+ });
1176
+
1177
+ wp.api.endpoints = new Backbone.Collection({
1178
+ model: Endpoint
1179
+ });
1180
 
1181
  /**
1182
+ * Initialize the wp-api, optionally passing the API root.
1183
+ *
1184
+ * @param {object} [args]
1185
+ * @param {string} [args.apiRoot] The api root. Optional, defaults to wpApiSettings.root.
1186
+ * @param {string} [args.versionString] The version string. Optional, defaults to wpApiSettings.root.
1187
+ * @param {object} [args.schema] The schema. Optional, will be fetched from API if not provided.
1188
  */
1189
+ wp.api.init = function( args ) {
1190
+ var endpoint, attributes = {}, deferred, promise;
1191
+
1192
+ args = args || {};
1193
+ attributes.apiRoot = args.apiRoot || wpApiSettings.root;
1194
+ attributes.versionString = args.versionString || wpApiSettings.versionString;
1195
+ attributes.schema = args.schema || null;
1196
+ if ( ! attributes.schema && attributes.apiRoot === wpApiSettings.root && attributes.versionString === wpApiSettings.versionString ) {
1197
+ attributes.schema = wpApiSettings.schema;
1198
+ }
1199
+
1200
+ if ( ! initializedDeferreds[ attributes.apiRoot + attributes.versionString ] ) {
1201
+ endpoint = wp.api.endpoints.findWhere( { apiRoot: attributes.apiRoot, versionString: attributes.versionString } );
1202
+ if ( ! endpoint ) {
1203
+ endpoint = new Endpoint( attributes );
1204
+ wp.api.endpoints.add( endpoint );
1205
+ }
1206
+ deferred = jQuery.Deferred();
1207
+ promise = deferred.promise();
1208
+
1209
+ endpoint.schemaConstructed.done( function( endpoint ) {
1210
+
1211
+ // Map the default endpoints, extending any already present items (including Schema model).
1212
+ wp.api.models = _.extend( endpoint.get( 'models' ), wp.api.models );
1213
+ wp.api.collections = _.extend( endpoint.get( 'collections' ), wp.api.collections );
1214
+ deferred.resolveWith( wp.api, [ endpoint ] );
1215
+ } );
1216
+ initializedDeferreds[ attributes.apiRoot + attributes.versionString ] = promise;
1217
+ }
1218
+ return initializedDeferreds[ attributes.apiRoot + attributes.versionString ];
1219
+ };
1220
 
1221
  /**
1222
+ * Construct the default endpoints and add to an endpoints collection.
1223
  */
1224
 
1225
+ // The wp.api.init function returns a promise that will resolve with the endpoint once it is ready.
1226
+ wp.api.init();
1227
+
1228
+ })( window );
wp-api.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ !function(a,b){"use strict";function c(){this.models={},this.collections={},this.views={}}a.wp=a.wp||{},wp.api=wp.api||new c,wp.api.versionString=wp.api.versionString||"wp/v2/"}(window),function(a,b){"use strict";var c,d;a.wp=a.wp||{},wp.api=wp.api||{},wp.api.utils=wp.api.utils||{},Date.prototype.toISOString||(c=function(a){return d=String(a),1===d.length&&(d="0"+d),d},Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+c(this.getUTCMonth()+1)+"-"+c(this.getUTCDate())+"T"+c(this.getUTCHours())+":"+c(this.getUTCMinutes())+":"+c(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1e3).toFixed(3)).slice(2,5)+"Z"}),wp.api.utils.parseISO8601=function(a){var c,d,e,f,g=0,h=[1,4,5,6,7,10,11];if(d=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(a)){for(e=0;f=h[e];++e)d[f]=+d[f]||0;d[2]=(+d[2]||1)-1,d[3]=+d[3]||1,"Z"!==d[8]&&b!==d[9]&&(g=60*d[10]+d[11],"+"===d[9]&&(g=0-g)),c=Date.UTC(d[1],d[2],d[3],d[4],d[5]+g,d[6],d[7])}else c=Date.parse?Date.parse(a):NaN;return c},wp.api.utils.getRootUrl=function(){return a.location.origin?a.location.origin+"/":a.location.protocol+"/"+a.location.host+"/"},wp.api.utils.capitalize=function(a){return _.isUndefined(a)?a:a.charAt(0).toUpperCase()+a.slice(1)},wp.api.utils.extractRoutePart=function(a,b){var c;return b=b||1,a=a.replace(wp.api.versionString,""),c=a.split("/").reverse(),_.isUndefined(c[--b])?"":c[b]},wp.api.utils.extractParentName=function(a){var b,c=a.lastIndexOf("_id>[\\d]+)/");return 0>c?"":(b=a.substr(0,c-1),b=b.split("/"),b.pop(),b=b.pop())},wp.api.utils.decorateFromRoute=function(a,b){_.each(a,function(a){_.contains(a.methods,"POST")||_.contains(a.methods,"PUT")?_.isEmpty(a.args)||(_.isEmpty(b.defaults)?b.defaults=a.args:b.defaults=_.union(a.args,b.defaults)):_.contains(a.methods,"GET")&&(_.isEmpty(a.args)||(_.isEmpty(b.options)?b.options=a.args:b.options=_.union(a.args,b.options)))})},wp.api.utils.addMixinsAndHelpers=function(a,b,c){var d=!1,e=["date","modified","date_gmt","modified_gmt"],f={toJSON:function(){var a=_.clone(this.attributes);return _.each(e,function(b){b in a&&(_.isNull(a[b])||(a[b]=a[b].toISOString()))}),a},parse:function(a){var b;return _.each(e,function(c){c in a&&(_.isNull(a[c])||(b=wp.api.utils.parseISO8601(a[c]),a[c]=new Date(b)))}),a}},g=function(a,b,c,d,e){var f,g,h,i;return i=jQuery.Deferred(),g=a.get("_embedded")||{},_.isNumber(b)?(g[d]&&(h=_.findWhere(g[d],{id:b})),h||(h={id:b}),f=new wp.api.models[c](h),f.get(e)?i.resolve(f):f.fetch({success:function(a){i.resolve(a)}}),i.promise()):(i.reject(),i)},h=function(a,b,c,d){var e,f,g,h="",j="",k=jQuery.Deferred();return e=a.get("id"),f=a.get("_embedded")||{},_.isNumber(e)&&0!==e?(_.isUndefined(c)||_.isUndefined(f[c])?h={parent:e}:j=_.isUndefined(d)?f[c]:f[c][d],g=new wp.api.collections[b](j,h),_.isUndefined(g.models[0])?g.fetch({success:function(a){i(a,e),k.resolve(a)}}):(i(g,e),k.resolve(g)),k.promise()):(k.reject(),k)},i=function(a,b){_.each(a.models,function(a){a.set("parent_post",b)})},j={getMeta:function(){return h(this,"PostMeta","https://api.w.org/meta")}},k={getRevisions:function(){return h(this,"PostRevisions")}},l={getTags:function(){return h(this,"PostTags","https://api.w.org/term",1)}},m={getCategories:function(){return h(this,"PostCategories","https://api.w.org/term",0)},setCategories:function(a){var b,c,d=this,e=[];_.isArray(a)?(b=new wp.api.collections.Categories,b.fetch({success:function(b){_.each(a,function(a){c=new wp.api.models.PostCategories(b.findWhere({slug:a})),c.set("parent_post",d.get("id")),e.push(c)}),a=new wp.api.collections.PostCategories(e),d.setCategoriesWithCollection(a)}})):this.setCategoriesWithCollection(a)},setCategoriesWithCollection:function(a){var b,c,d,e;this.getCategories().done(function(f){d=a.pluck("id"),e=f.pluck("id"),c=_.difference(d,e),b=_.difference(e,d),_.each(c,function(b){f.create(a.get(b),{type:"POST"})}),_.each(b,function(a){f.get(a).destroy()})})}},n={getAuthorUser:function(){return g(this,this.get("author"),"User","author","name")}},o={getFeaturedImage:function(){return g(this,this.get("featured_image"),"Media","https://api.w.org/featuredmedia","source_url")}};return _.isUndefined(a.defaults)?a:(_.each(e,function(b){_.isUndefined(a.defaults[b])||(d=!0)}),d&&(a=a.extend(f)),_.isUndefined(a.defaults.author)||(a=a.extend(n)),_.isUndefined(a.defaults.featured_image)||(a=a.extend(o)),_.isUndefined(c.collections[b+"Categories"])||(a=a.extend(m)),_.isUndefined(c.collections[b+"Meta"])||(a=a.extend(j)),_.isUndefined(c.collections[b+"Tags"])||(a=a.extend(l)),_.isUndefined(c.collections[b+"Revisions"])||(a=a.extend(k)),a)}}(window),function(a,b,c,d,e){"use strict";a.api.WPApiBaseModel=c.Model.extend({sync:function(a,d,e){var f;return e=e||{},_.isUndefined(b.nonce)||_.isNull(b.nonce)||(f=e.beforeSend,e.beforeSend=function(a){return a.setRequestHeader("X-WP-Nonce",b.nonce),f?f.apply(this,arguments):void 0}),this.requireForceForDelete&&"delete"===a&&(d.url=d.url()+"?force=true"),c.sync(a,d,e)},save:function(a,b){return _.contains(this.methods,"PUT")||_.contains(this.methods,"POST")?c.Model.prototype.save.call(this,a,b):!1},destroy:function(a){return _.contains(this.methods,"DELETE")?c.Model.prototype.destroy.call(this,a):!1}}),a.api.models.Schema=a.api.WPApiBaseModel.extend({defaults:{_links:{},namespace:null,routes:{}},initialize:function(c,d){var e=this;d=d||{},a.api.WPApiBaseModel.prototype.initialize.call(e,c,d),e.apiRoot=d.apiRoot||b.root,e.versionString=d.versionString||b.versionString},url:function(){return this.apiRoot+this.versionString}})}(wp,wpApiSettings,Backbone,window),function(a,b,c,d,e,f){"use strict";a.api.WPApiBaseCollection=c.Collection.extend({initialize:function(a,b){this.state={data:{},currentPage:null,totalPages:null,totalObjects:null},d.isUndefined(b)?this.parent="":this.parent=b.parent},sync:function(a,e,f){var g,h,i=this;return f=f||{},g=f.beforeSend,"undefined"!=typeof b.nonce&&(f.beforeSend=function(a){return a.setRequestHeader("X-WP-Nonce",b.nonce),g?g.apply(i,arguments):void 0}),"read"===a&&(f.data?(i.state.data=d.clone(f.data),delete i.state.data.page):i.state.data=f.data={},"undefined"==typeof f.data.page?(i.state.currentPage=null,i.state.totalPages=null,i.state.totalObjects=null):i.state.currentPage=f.data.page-1,h=f.success,f.success=function(a,b,c){return i.state.totalPages=parseInt(c.getResponseHeader("x-wp-totalpages"),10),i.state.totalObjects=parseInt(c.getResponseHeader("x-wp-total"),10),null===i.state.currentPage?i.state.currentPage=1:i.state.currentPage++,h?h.apply(this,arguments):void 0}),c.sync(a,e,f)},more:function(a){if(a=a||{},a.data=a.data||{},d.extend(a.data,this.state.data),"undefined"==typeof a.data.page){if(!this.hasMore())return!1;null===this.state.currentPage||this.state.currentPage<=1?a.data.page=2:a.data.page=this.state.currentPage+1}return this.fetch(a)},hasMore:function(){return null===this.state.totalPages||null===this.state.totalObjects||null===this.state.currentPage?null:this.state.currentPage<this.state.totalPages}})}(wp,wpApiSettings,Backbone,_,window),function(a,b){"use strict";var c,d={};a.wp=a.wp||{},wp.api=wp.api||{},c=Backbone.Model.extend({defaults:{apiRoot:wpApiSettings.root,versionString:wp.api.versionString,schema:null,models:{},collections:{}},initialize:function(){var a,b=this;Backbone.Model.prototype.initialize.apply(b,arguments),a=jQuery.Deferred(),b.schemaConstructed=a.promise(),b.schemaModel=new wp.api.models.Schema(null,{apiRoot:b.get("apiRoot"),versionString:b.get("versionString")}),b.schemaModel.once("change",function(){b.constructFromSchema(),a.resolve(b)}),b.get("schema")?b.schemaModel.set(b.schemaModel.parse(b.get("schema"))):!_.isUndefined(sessionStorage)&&sessionStorage.getItem("wp-api-schema-model"+b.get("apiRoot")+b.get("versionString"))?b.schemaModel.set(b.schemaModel.parse(JSON.parse(sessionStorage.getItem("wp-api-schema-model"+b.get("apiRoot")+b.get("versionString"))))):b.schemaModel.fetch({success:function(a){_.isUndefined(sessionStorage)||sessionStorage.setItem("wp-api-schema-model"+b.get("apiRoot")+b.get("versionString"),JSON.stringify(a))},error:function(){}})},constructFromSchema:function(){var a,b,c,d,e=this,f=wpApiSettings.mapping||{models:{Categories:"Category",Comments:"Comment",Pages:"Page",PagesMeta:"PageMeta",PagesRevisions:"PageRevision",Posts:"Post",PostsCategories:"PostCategory",PostsRevisions:"PostRevision",PostsTags:"PostTag",Schema:"Schema",Statuses:"Status",Tags:"Tag",Taxonomies:"Taxonomy",Types:"Type",Users:"User"},collections:{PagesMeta:"PageMeta",PagesRevisions:"PageRevisions",PostsCategories:"PostCategories",PostsMeta:"PostMeta",PostsRevisions:"PostRevisions",PostsTags:"PostTags"}};a=[],b=[],c=e.get("apiRoot").replace(wp.api.utils.getRootUrl(),""),d={},d.models=e.get("models"),d.collections=e.get("collections"),_.each(e.schemaModel.get("routes"),function(d,f){f!==e.get(" versionString")&&f!==c&&f!=="/"+e.get("versionString").slice(0,-1)&&(f.endsWith("+)")?a.push({index:f,route:d}):f.endsWith("me")||b.push({index:f,route:d}))}),_.each(a,function(a){var b,c=wp.api.utils.extractRoutePart(a.index,2),g=wp.api.utils.extractRoutePart(a.index,4);""!==g&&g!==c?(b=wp.api.utils.capitalize(g)+wp.api.utils.capitalize(c),b=f.models[b]||b,d.models[b]=wp.api.WPApiBaseModel.extend({url:function(){var a=e.get("apiRoot")+e.get("versionString")+g+"/"+(_.isUndefined(this.get("parent"))||0===this.get("parent")?this.get("parent_post"):this.get("parent"))+"/"+c;return _.isUndefined(this.get("id"))||(a+="/"+this.get("id")),a},route:a,name:b,methods:a.route.methods,initialize:function(){"Posts"!==this.name&&"Pages"!==this.name&&_.contains(this.methods,"DELETE")&&(this.requireForceForDelete=!0)}})):(b=wp.api.utils.capitalize(c),b=f.models[b]||b,d.models[b]=wp.api.WPApiBaseModel.extend({url:function(){var a=e.get("apiRoot")+e.get("versionString")+c;return _.isUndefined(this.get("id"))||(a+="/"+this.get("id")),a},route:a,name:b,methods:a.route.methods})),wp.api.utils.decorateFromRoute(a.route.endpoints,d.models[b])}),_.each(b,function(a){var b,c,g=a.index.slice(a.index.lastIndexOf("/")+1),h=wp.api.utils.extractRoutePart(a.index,3);""!==h&&h!==g?(b=wp.api.utils.capitalize(h)+wp.api.utils.capitalize(g),c=f.models[b]||b,b=f.collections[b]||b,d.collections[b]=wp.api.WPApiBaseCollection.extend({url:function(){return e.get("apiRoot")+e.get("versionString")+h+"/"+this.parent+"/"+g},model:d.models[c],name:b,route:a,methods:a.route.methods})):(b=wp.api.utils.capitalize(g),c=f.models[b]||b,b=f.collections[b]||b,d.collections[b]=wp.api.WPApiBaseCollection.extend({url:e.get("apiRoot")+e.get("versionString")+g,model:d.models[c],name:b,route:a,methods:a.route.methods})),wp.api.utils.decorateFromRoute(a.route.endpoints,d.collections[b])}),_.each(d.models,function(a,b){d.models[b]=wp.api.utils.addMixinsAndHelpers(a,b,d)})}}),wp.api.endpoints=new Backbone.Collection({model:c}),wp.api.init=function(a){var b,e,f,g={};return a=a||{},g.apiRoot=a.apiRoot||wpApiSettings.root,g.versionString=a.versionString||wpApiSettings.versionString,g.schema=a.schema||null,g.schema||g.apiRoot!==wpApiSettings.root||g.versionString!==wpApiSettings.versionString||(g.schema=wpApiSettings.schema),d[g.apiRoot+g.versionString]||(b=wp.api.endpoints.findWhere({apiRoot:g.apiRoot,versionString:g.versionString}),b||(b=new c(g),wp.api.endpoints.add(b)),e=jQuery.Deferred(),f=e.promise(),b.schemaConstructed.done(function(a){wp.api.models=_.extend(a.get("models"),wp.api.models),wp.api.collections=_.extend(a.get("collections"),wp.api.collections),e.resolveWith(wp.api,[a])}),d[g.apiRoot+g.versionString]=f),d[g.apiRoot+g.versionString]},wp.api.init()}(window);
2
+ //# sourceMappingURL=wp-api.min.map
wp-api.min.map ADDED
@@ -0,0 +1 @@
 
1
+ {"version":3,"sources":["../../js/app.js","../../js/utils.js","../../js/models.js","../../js/collections.js","../../js/load.js"],"names":["window","undefined","WP_API","this","models","collections","views","wp","api","versionString","pad","r","utils","Date","prototype","toISOString","number","String","length","getUTCFullYear","getUTCMonth","getUTCDate","getUTCHours","getUTCMinutes","getUTCSeconds","getUTCMilliseconds","toFixed","slice","parseISO8601","date","timestamp","struct","i","k","minutesOffset","numericKeys","exec","UTC","parse","NaN","getRootUrl","location","origin","protocol","host","capitalize","str","_","isUndefined","charAt","toUpperCase","extractRoutePart","route","part","routeParts","replace","split","reverse","extractParentName","name","lastSlash","lastIndexOf","substr","pop","decorateFromRoute","routeEndpoints","modelInstance","each","routeEndpoint","contains","methods","isEmpty","args","defaults","union","options","addMixinsAndHelpers","model","modelClassName","loadingObjects","hasDate","parseableDates","TimeStampedMixin","toJSON","attributes","clone","key","isNull","response","buildModelGetter","parentModel","modelId","modelName","embedSourcePoint","embedCheckField","getModel","embeddeds","deferred","jQuery","Deferred","get","isNumber","findWhere","id","resolve","fetch","success","promise","reject","buildCollectionGetter","collectionName","embedIndex","postId","getObjects","classProperties","properties","parent","setHelperParentPost","collection","set","MetaMixin","getMeta","RevisionsMixin","getRevisions","TagsMixin","getTags","CategoriesMixin","getCategories","setCategories","categories","allCategories","newCategory","self","newCategories","isArray","Categories","allcats","category","PostCategories","slug","push","setCategoriesWithCollection","removedCategories","addedCategories","categoriesIds","existingCategoriesIds","done","existingCategories","pluck","difference","addedCategory","create","type","removedCategory","destroy","AuthorMixin","getAuthorUser","FeaturedImageMixin","getFeaturedImage","theDateKey","extend","author","featured_image","wpApiSettings","Backbone","WPApiBaseModel","Model","sync","method","beforeSend","nonce","xhr","setRequestHeader","apply","arguments","requireForceForDelete","url","save","attrs","call","Schema","_links","namespace","routes","initialize","apiRoot","root","WPApiBaseCollection","Collection","state","data","currentPage","totalPages","totalObjects","page","textStatus","request","parseInt","getResponseHeader","more","hasMore","Endpoint","initializedDeferreds","schema","schemaConstructed","schemaModel","once","constructFromSchema","sessionStorage","getItem","JSON","newSchemaModel","setItem","stringify","error","modelRoutes","collectionRoutes","schemaRoot","routeModel","mapping","Comments","Pages","PagesMeta","PagesRevisions","Posts","PostsCategories","PostsRevisions","PostsTags","Statuses","Tags","Taxonomies","Types","Users","PostsMeta","index","endsWith","modelRoute","routeName","parentName","endpoints","collectionRoute","collectionClassName","init","endpoint","add","resolveWith"],"mappings":"CAAA,SAAWA,EAAQC,GAElB,YAEA,SAASC,KACRC,KAAKC,UACLD,KAAKE,eACLF,KAAKG,SAGNN,EAAOO,GAAgBP,EAAOO,OAC9BA,GAAGC,IAAoBD,GAAGC,KAAO,GAAIN,GACrCK,GAAGC,IAAIC,cAAgBF,GAAGC,IAAIC,eAAiB,UAE5CT,QCdJ,SAAWA,EAAQC,GAElB,YAEA,IAAIS,GAAKC,CAETX,GAAOO,GAAKP,EAAOO,OACnBA,GAAGC,IAAMD,GAAGC,QACZD,GAAGC,IAAII,MAAQL,GAAGC,IAAII,UAMfC,KAAKC,UAAUC,cACrBL,EAAM,SAAUM,GAMf,MALAL,GAAIM,OAAQD,GACP,IAAML,EAAEO,SACZP,EAAI,IAAMA,GAGJA,GAGRE,KAAKC,UAAUC,YAAc,WAC5B,MAAOZ,MAAKgB,iBACX,IAAMT,EAAKP,KAAKiB,cAAgB,GAChC,IAAMV,EAAKP,KAAKkB,cAChB,IAAMX,EAAKP,KAAKmB,eAChB,IAAMZ,EAAKP,KAAKoB,iBAChB,IAAMb,EAAKP,KAAKqB,iBAChB,IAAMP,QAAUd,KAAKsB,qBAAuB,KAAOC,QAAS,IAAMC,MAAO,EAAG,GAC5E,MASHpB,GAAGC,IAAII,MAAMgB,aAAe,SAAUC,GACrC,GAAIC,GAAWC,EAAQC,EAAGC,EACzBC,EAAgB,EAChBC,GAAgB,EAAG,EAAG,EAAG,EAAG,EAAG,GAAI,GAMpC,IAAOJ,EAAS,qIAAqIK,KAAMP,GAAW,CAGrK,IAAMG,EAAI,EAAKC,EAAIE,EAAYH,KAAQA,EACtCD,EAAOE,IAAMF,EAAOE,IAAM,CAI3BF,GAAO,KAAQA,EAAO,IAAM,GAAM,EAClCA,EAAO,IAAMA,EAAO,IAAM,EAErB,MAAQA,EAAO,IAAO9B,IAAc8B,EAAO,KAC/CG,EAA6B,GAAbH,EAAO,IAAWA,EAAO,IAEpC,MAAQA,EAAO,KACnBG,EAAgB,EAAIA,IAItBJ,EAAYjB,KAAKwB,IAAKN,EAAO,GAAIA,EAAO,GAAIA,EAAO,GAAIA,EAAO,GAAIA,EAAO,GAAKG,EAAeH,EAAO,GAAIA,EAAO,QAE/GD,GAAYjB,KAAKyB,MAAQzB,KAAKyB,MAAOT,GAASU,GAG/C,OAAOT,IAORvB,GAAGC,IAAII,MAAM4B,WAAa,WACzB,MAAOxC,GAAOyC,SAASC,OACtB1C,EAAOyC,SAASC,OAAS,IACzB1C,EAAOyC,SAASE,SAAW,IAAM3C,EAAOyC,SAASG,KAAO,KAM1DrC,GAAGC,IAAII,MAAMiC,WAAa,SAAUC,GACnC,MAAKC,GAAEC,YAAaF,GACZA,EAEDA,EAAIG,OAAQ,GAAIC,cAAgBJ,EAAInB,MAAO,IAUnDpB,GAAGC,IAAII,MAAMuC,iBAAmB,SAAUC,EAAOC,GAChD,GAAIC,EAOJ,OALAD,GAAQA,GAAQ,EAGhBD,EAAQA,EAAMG,QAAShD,GAAGC,IAAIC,cAAe,IAC7C6C,EAAaF,EAAMI,MAAO,KAAMC,UAC3BV,EAAEC,YAAaM,IAAcD,IAC1B,GAEDC,EAAYD,IAQpB9C,GAAGC,IAAII,MAAM8C,kBAAoB,SAAUN,GAC1C,GAAIO,GACHC,EAAYR,EAAMS,YAAa,eAEhC,OAAiB,GAAZD,EACG,IAERD,EAAOP,EAAMU,OAAQ,EAAGF,EAAY,GACpCD,EAAOA,EAAKH,MAAO,KACnBG,EAAKI,MACLJ,EAAOA,EAAKI,QAWbxD,GAAGC,IAAII,MAAMoD,kBAAoB,SAAUC,EAAgBC,GAK1DnB,EAAEoB,KAAMF,EAAgB,SAAUG,GAG5BrB,EAAEsB,SAAUD,EAAcE,QAAS,SAAYvB,EAAEsB,SAAUD,EAAcE,QAAS,OAG/EvB,EAAEwB,QAASH,EAAcI,QAG1BzB,EAAEwB,QAASL,EAAcO,UAC7BP,EAAcO,SAAWL,EAAcI,KAIvCN,EAAcO,SAAW1B,EAAE2B,MAAON,EAAcI,KAAMN,EAAcO,WAMjE1B,EAAEsB,SAAUD,EAAcE,QAAS,SAGhCvB,EAAEwB,QAASH,EAAcI,QAG1BzB,EAAEwB,QAASL,EAAcS,SAC7BT,EAAcS,QAAUP,EAAcI,KAItCN,EAAcS,QAAU5B,EAAE2B,MAAON,EAAcI,KAAMN,EAAcS,cAkBzEpE,GAAGC,IAAII,MAAMgE,oBAAsB,SAAUC,EAAOC,EAAgBC,GAEnE,GAAIC,IAAU,EAObC,GAAmB,OAAQ,WAAY,WAAY,gBAWnDC,GAMCC,OAAQ,WACP,GAAIC,GAAarC,EAAEsC,MAAOlF,KAAKiF,WAa/B,OAVArC,GAAEoB,KAAMc,EAAgB,SAAUK,GAC5BA,IAAOF,KAGJrC,EAAEwC,OAAQH,EAAYE,MAC5BF,EAAYE,GAAQF,EAAYE,GAAMvE,kBAKlCqE,GASR9C,MAAO,SAAUkD,GAChB,GAAI1D,EAeJ,OAZAiB,GAAEoB,KAAMc,EAAgB,SAAUK,GACxBA,IAAOE,KAKTzC,EAAEwC,OAAQC,EAAUF,MAC1BxD,EAAYvB,GAAGC,IAAII,MAAMgB,aAAc4D,EAAUF,IACjDE,EAAUF,GAAQ,GAAIzE,MAAMiB,OAIvB0D,IAeTC,EAAmB,SAAUC,EAAaC,EAASC,EAAWC,EAAkBC,GAC/E,GAAIC,GAAUC,EAAWZ,EAAYa,CAMrC,OAJAA,GAAYC,OAAOC,WACnBH,EAAYN,EAAYU,IAAK,iBAGtBrD,EAAEsD,SAAUV,IAMdK,EAAWH,KACfT,EAAarC,EAAEuD,UAAWN,EAAWH,IAAsBU,GAAIZ,KAIzDP,IACNA,GAAemB,GAAIZ,IAIpBI,EAAW,GAAIxF,IAAGC,IAAIJ,OAAQwF,GAAaR,GAGpCW,EAASK,IAAKN,GAKpBG,EAASO,QAAST,GAJlBA,EAASU,OAASC,QAAS,SAAUX,GACpCE,EAASO,QAAST,MAObE,EAASU,YA3BfV,EAASW,SACFX,IAuCTY,EAAwB,SAAUnB,EAAaoB,EAAgBjB,EAAkBkB,GAUhF,GAAIC,GAAQhB,EAAWiB,EACtBC,EAAkB,GAClBC,EAAkB,GAClBlB,EAAkBC,OAAOC,UAM1B,OAJAa,GAAYtB,EAAYU,IAAK,MAC7BJ,EAAYN,EAAYU,IAAK,iBAGtBrD,EAAEsD,SAAUW,IAAY,IAAMA,GAM9BjE,EAAEC,YAAa6C,IAAwB9C,EAAEC,YAAagD,EAAWH,IAevEqB,GAAoBE,OAAQJ,GAT3BG,EAHIpE,EAAEC,YAAa+D,GAGNf,EAAWH,GAIXG,EAAWH,GAAoBkB,GAS9CE,EAAa,GAAI1G,IAAGC,IAAIH,YAAayG,GAAkBK,EAAYD,GAG9DnE,EAAEC,YAAaiE,EAAW7G,OAAO,IACrC6G,EAAWR,OAASC,QAAS,SAAUO,GAGtCI,EAAqBJ,EAAYD,GACjCf,EAASO,QAASS,OAKnBI,EAAqBJ,EAAYD,GACjCf,EAASO,QAASS,IAIZhB,EAASU,YA1CfV,EAASW,SACFX,IAgDToB,EAAsB,SAAUC,EAAYN,GAG3CjE,EAAEoB,KAAMmD,EAAWlH,OAAQ,SAAUyE,GACpCA,EAAM0C,IAAK,cAAeP,MAO5BQ,GACCC,QAAS,WACR,MAAOZ,GAAuB1G,KAAM,WAAY,4BAOlDuH,GACCC,aAAc,WACb,MAAOd,GAAuB1G,KAAM,mBAOtCyH,GACCC,QAAS,WACR,MAAOhB,GAAuB1G,KAAM,WAAY,yBAA0B,KAM5E2H,GAWCC,cAAe,WACd,MAAOlB,GAAuB1G,KAAM,iBAAkB,yBAA0B,IAWjF6H,cAAe,SAAUC,GACxB,GAAIC,GAAeC,EAClBC,EAAOjI,KACPkI,IAGItF,GAAEuF,QAASL,IAGfC,EAAgB,GAAI3H,IAAGC,IAAIH,YAAYkI,WACvCL,EAAczB,OACbC,QAAS,SAAU8B,GAGlBzF,EAAEoB,KAAM8D,EAAY,SAAUQ,GAC7BN,EAAc,GAAI5H,IAAGC,IAAIJ,OAAOsI,eAAgBF,EAAQlC,WAAaqC,KAAMF,KAG3EN,EAAYZ,IAAK,cAAea,EAAKhC,IAAK,OAG1CiC,EAAcO,KAAMT,KAErBF,EAAa,GAAI1H,IAAGC,IAAIH,YAAYqI,eAAgBL,GACpDD,EAAKS,4BAA6BZ,OAKpC9H,KAAK0I,4BAA6BZ,IAapCY,4BAA6B,SAAUZ,GACtC,GAAIa,GAAmBC,EAAiBC,EAAeC,CAGvD9I,MAAK4H,gBAAgBmB,KAAM,SAAUC,GAGpCH,EAAwBf,EAAWmB,MAAO,MAC1CH,EAAwBE,EAAmBC,MAAO,MAGlDL,EAAoBhG,EAAEsG,WAAYL,EAAeC,GACjDH,EAAoB/F,EAAEsG,WAAYJ,EAAuBD,GAGzDjG,EAAEoB,KAAM4E,EAAiB,SAAUO,GAGlCH,EAAmBI,OAAQtB,EAAW7B,IAAKkD,IAAmBE,KAAM,WAIrEzG,EAAEoB,KAAM2E,EAAmB,SAAUW,GACpCN,EAAmB/C,IAAKqD,GAAkBC,gBAS9CC,GACCC,cAAe,WACd,MAAOnE,GAAkBtF,KAAMA,KAAKiG,IAAK,UAAY,OAAQ,SAAU,UAOzEyD,GACCC,iBAAkB,WACjB,MAAOrE,GAAkBtF,KAAMA,KAAKiG,IAAK,kBAAoB,QAAS,kCAAmC,eAK5G,OAAKrD,GAAEC,YAAa6B,EAAMJ,UAClBI,GAIR9B,EAAEoB,KAAMc,EAAgB,SAAU8E,GAC1BhH,EAAEC,YAAa6B,EAAMJ,SAAUsF,MACrC/E,GAAU,KAKPA,IACJH,EAAQA,EAAMmF,OAAQ9E,IAIhBnC,EAAEC,YAAa6B,EAAMJ,SAASwF,UACpCpF,EAAQA,EAAMmF,OAAQL,IAIhB5G,EAAEC,YAAa6B,EAAMJ,SAASyF,kBACpCrF,EAAQA,EAAMmF,OAAQH,IAIhB9G,EAAEC,YAAa+B,EAAe1E,YAAayE,EAAiB,iBAClED,EAAQA,EAAMmF,OAAQlC,IAIhB/E,EAAEC,YAAa+B,EAAe1E,YAAayE,EAAiB,WAClED,EAAQA,EAAMmF,OAAQxC,IAIhBzE,EAAEC,YAAa+B,EAAe1E,YAAayE,EAAiB,WAClED,EAAQA,EAAMmF,OAAQpC,IAIhB7E,EAAEC,YAAa+B,EAAe1E,YAAayE,EAAiB,gBAClED,EAAQA,EAAMmF,OAAQtC,IAGhB7C,KAGL7E,QC9kBJ,SAAWO,EAAI4J,EAAeC,EAAUpK,EAAQC,GAE/C,YAKAM,GAAGC,IAAI6J,eAAiBD,EAASE,MAAMN,QAWrCO,KAAM,SAAUC,EAAQ3F,EAAOF,GAC9B,GAAI8F,EAuBJ,OArBA9F,GAAUA,MAEH5B,EAAEC,YAAamH,EAAcO,QAAa3H,EAAEwC,OAAQ4E,EAAcO,SACxED,EAAa9F,EAAQ8F,WAKrB9F,EAAQ8F,WAAa,SAAUE,GAG9B,MAFAA,GAAIC,iBAAkB,aAAcT,EAAcO,OAE7CD,EACGA,EAAWI,MAAO1K,KAAM2K,WADhC,SAOG3K,KAAK4K,uBAAyB,WAAaP,IAC/C3F,EAAMmG,IAAMnG,EAAMmG,MAAQ,eAEpBZ,EAASG,KAAMC,EAAQ3F,EAAOF,IAMtCsG,KAAM,SAAUC,EAAOvG,GAGtB,MAAK5B,GAAEsB,SAAUlE,KAAKmE,QAAS,QAAWvB,EAAEsB,SAAUlE,KAAKmE,QAAS,QAG5D8F,EAASE,MAAMxJ,UAAUmK,KAAKE,KAAMhL,KAAM+K,EAAOvG,IAIjD,GAOT+E,QAAS,SAAU/E,GAGlB,MAAK5B,GAAEsB,SAAUlE,KAAKmE,QAAS,UAGvB8F,EAASE,MAAMxJ,UAAU4I,QAAQyB,KAAMhL,KAAMwE,IAI7C,KAUXpE,EAAGC,IAAIJ,OAAOgL,OAAS7K,EAAGC,IAAI6J,eAAeL,QAG3CvF,UACC4G,UACAC,UAAW,KACXC,WAGDC,WAAY,SAAUpG,EAAYT,GACjC,GAAIE,GAAQ1E,IACZwE,GAAUA,MAEVpE,EAAGC,IAAI6J,eAAevJ,UAAU0K,WAAWL,KAAMtG,EAAOO,EAAYT,GAEpEE,EAAM4G,QAAU9G,EAAQ8G,SAAWtB,EAAcuB,KACjD7G,EAAMpE,cAAgBkE,EAAQlE,eAAiB0J,EAAc1J,eAG9DuK,IAAK,WACJ,MAAO7K,MAAKsL,QAAUtL,KAAKM,kBAI3BF,GAAI4J,cAAeC,SAAUpK,QChHjC,SAAWO,EAAI4J,EAAeC,EAAUrH,EAAG/C,EAAQC,GAElD,YAKAM,GAAGC,IAAImL,oBAAsBvB,EAASwB,WAAW5B,QAO/CwB,WAAY,SAAUpL,EAAQuE,GAC7BxE,KAAK0L,OACJC,QACAC,YAAa,KACbC,WAAY,KACZC,aAAc,MAEVlJ,EAAEC,YAAa2B,GACnBxE,KAAKiH,OAAS,GAEdjH,KAAKiH,OAASzC,EAAQyC,QAcxBmD,KAAM,SAAUC,EAAQ3F,EAAOF,GAC9B,GAAI8F,GAAY/D,EACf0B,EAAOjI,IAiDR,OA/CAwE,GAAaA,MACb8F,EAAa9F,EAAQ8F,WAEhB,mBAAuBN,GAAcO,QACzC/F,EAAQ8F,WAAa,SAAUE,GAG9B,MAFAA,GAAIC,iBAAkB,aAAcT,EAAcO,OAE7CD,EACGA,EAAWI,MAAOzC,EAAM0C,WADhC,SAMG,SAAWN,IACV7F,EAAQmH,MACZ1D,EAAKyD,MAAMC,KAAO/I,EAAEsC,MAAOV,EAAQmH,YAE5B1D,GAAKyD,MAAMC,KAAKI,MAEvB9D,EAAKyD,MAAMC,KAAOnH,EAAQmH,QAGtB,mBAAuBnH,GAAQmH,KAAKI,MACxC9D,EAAKyD,MAAME,YAAc,KACzB3D,EAAKyD,MAAMG,WAAa,KACxB5D,EAAKyD,MAAMI,aAAe,MAE1B7D,EAAKyD,MAAME,YAAcpH,EAAQmH,KAAKI,KAAO,EAG9CxF,EAAU/B,EAAQ+B,QAClB/B,EAAQ+B,QAAU,SAAUoF,EAAMK,EAAYC,GAU7C,MATAhE,GAAKyD,MAAMG,WAAaK,SAAUD,EAAQE,kBAAmB,mBAAqB,IAClFlE,EAAKyD,MAAMI,aAAeI,SAAUD,EAAQE,kBAAmB,cAAgB,IAE1E,OAASlE,EAAKyD,MAAME,YACxB3D,EAAKyD,MAAME,YAAc,EAEzB3D,EAAKyD,MAAME,cAGPrF,EACGA,EAAQmE,MAAO1K,KAAM2K,WAD7B,SAMKV,EAASG,KAAMC,EAAQ3F,EAAOF,IAStC4H,KAAM,SAAU5H,GAMf,GALAA,EAAUA,MACVA,EAAQmH,KAAOnH,EAAQmH,SAEvB/I,EAAEiH,OAAQrF,EAAQmH,KAAM3L,KAAK0L,MAAMC,MAE9B,mBAAuBnH,GAAQmH,KAAKI,KAAO,CAC/C,IAAO/L,KAAKqM,UACX,OAAO,CAGH,QAASrM,KAAK0L,MAAME,aAAe5L,KAAK0L,MAAME,aAAe,EACjEpH,EAAQmH,KAAKI,KAAO,EAEpBvH,EAAQmH,KAAKI,KAAO/L,KAAK0L,MAAME,YAAc,EAI/C,MAAO5L,MAAKsG,MAAO9B,IAQpB6H,QAAS,WACR,MAAK,QAASrM,KAAK0L,MAAMG,YACvB,OAAS7L,KAAK0L,MAAMI,cACpB,OAAS9L,KAAK0L,MAAME,YACd,KAEE5L,KAAK0L,MAAME,YAAc5L,KAAK0L,MAAMG,eAM9CzL,GAAI4J,cAAeC,SAAUrH,EAAG/C,QCxIpC,SAAWA,EAAQC,GAElB,YAEA,IAAIwM,GAAUC,IAEd1M,GAAOO,GAAKP,EAAOO,OACnBA,GAAGC,IAAMD,GAAGC,QAEZiM,EAAWrC,SAASE,MAAMN,QACzBvF,UACCgH,QAAStB,cAAcuB,KACvBjL,cAAeF,GAAGC,IAAIC,cACtBkM,OAAQ,KACRvM,UACAC,gBAGDmL,WAAY,WACX,GAAkBvF,GAAdpB,EAAQ1E,IAEZiK,UAASE,MAAMxJ,UAAU0K,WAAWX,MAAOhG,EAAOiG,WAElD7E,EAAWC,OAAOC,WAClBtB,EAAM+H,kBAAoB3G,EAASU,UAEnC9B,EAAMgI,YAAc,GAAItM,IAAGC,IAAIJ,OAAOgL,OAAQ,MAC7CK,QAAS5G,EAAMuB,IAAK,WACpB3F,cAAeoE,EAAMuB,IAAK,mBAG3BvB,EAAMgI,YAAYC,KAAM,SAAU,WACjCjI,EAAMkI,sBACN9G,EAASO,QAAS3B,KAGdA,EAAMuB,IAAK,UAGfvB,EAAMgI,YAAYtF,IAAK1C,EAAMgI,YAAYvK,MAAOuC,EAAMuB,IAAK,aAC9CrD,EAAEC,YAAagK,iBAAoBA,eAAeC,QAAS,sBAAwBpI,EAAMuB,IAAK,WAAcvB,EAAMuB,IAAK,kBAGpIvB,EAAMgI,YAAYtF,IAAK1C,EAAMgI,YAAYvK,MAAO4K,KAAK5K,MAAO0K,eAAeC,QAAS,sBAAwBpI,EAAMuB,IAAK,WAAcvB,EAAMuB,IAAK,qBAEhJvB,EAAMgI,YAAYpG,OAMjBC,QAAS,SAAUyG,GAGXpK,EAAEC,YAAagK,iBACrBA,eAAeI,QAAS,sBAAwBvI,EAAMuB,IAAK,WAAcvB,EAAMuB,IAAK,iBAAmB8G,KAAKG,UAAWF,KAKzHG,MAAO,gBAMVP,oBAAqB,WACpB,GAAuBQ,GAAaC,EAAkBC,EAAY1I,EAA9D2I,EAAavN,KASjBwN,EAAUxD,cAAcwD,UACvBvN,QACCmI,WAAmB,WACnBqF,SAAmB,UACnBC,MAAmB,OACnBC,UAAmB,WACnBC,eAAmB,eACnBC,MAAmB,OACnBC,gBAAmB,eACnBC,eAAmB,eACnBC,UAAmB,UACnB/C,OAAmB,SACnBgD,SAAmB,SACnBC,KAAmB,MACnBC,WAAmB,WACnBC,MAAmB,OACnBC,MAAmB,QAEpBnO,aACCyN,UAAmB,WACnBC,eAAmB,gBACnBE,gBAAmB,iBACnBQ,UAAmB,WACnBP,eAAmB,gBACnBC,UAAmB,YAQrBZ,MACAC,KACAC,EAA6BC,EAAWtH,IAAK,WAAY7C,QAAShD,GAAGC,IAAII,MAAM4B,aAAc,IAC7FuC,KAKAA,EAAe3E,OAAcsN,EAAWtH,IAAK,UAC7CrB,EAAe1E,YAAcqN,EAAWtH,IAAK,eAE7CrD,EAAEoB,KAAMuJ,EAAWb,YAAYzG,IAAK,UAAY,SAAUhD,EAAOsL,GAG3DA,IAAUhB,EAAWtH,IAAK,mBAC7BsI,IAAUjB,GACViB,IAAY,IAAMhB,EAAWtH,IAAK,iBAAkBzE,MAAO,EAAG,MAO1D+M,EAAMC,SAAU,MACpBpB,EAAY3E,MAAQ8F,MAAOA,EAAOtL,MAAOA,IAIlCsL,EAAMC,SAAU,OACtBnB,EAAiB5E,MAAQ8F,MAAOA,EAAOtL,MAAOA,OAWlDL,EAAEoB,KAAMoJ,EAAa,SAAUqB,GAG9B,GAAI9J,GACF+J,EAAatO,GAAGC,IAAII,MAAMuC,iBAAkByL,EAAWF,MAAO,GAC9DI,EAAavO,GAAGC,IAAII,MAAMuC,iBAAkByL,EAAWF,MAAO,EAG3D,MAAOI,GAAcA,IAAeD,GACxC/J,EAAiBvE,GAAGC,IAAII,MAAMiC,WAAYiM,GAAevO,GAAGC,IAAII,MAAMiC,WAAYgM,GAClF/J,EAAiB6I,EAAQvN,OAAQ0E,IAAoBA,EACrDC,EAAe3E,OAAQ0E,GAAmBvE,GAAGC,IAAI6J,eAAeL,QAG/DgB,IAAK,WACJ,GAAIA,GAAM0C,EAAWtH,IAAK,WAAcsH,EAAWtH,IAAK,iBACtD0I,EAAc,KACV/L,EAAEC,YAAa7C,KAAKiG,IAAK,YAAgB,IAAMjG,KAAKiG,IAAK,UAC5DjG,KAAKiG,IAAK,eACVjG,KAAKiG,IAAK,WAAe,IAC1ByI,CAIF,OAHO9L,GAAEC,YAAa7C,KAAKiG,IAAK,SAC/B4E,GAAQ,IAAM7K,KAAKiG,IAAK,OAElB4E,GAIR5H,MAAOwL,EAGPjL,KAAMmB,EAGNR,QAASsK,EAAWxL,MAAMkB,QAE1BkH,WAAY,WAQV,UAAYrL,KAAKwD,MACjB,UAAYxD,KAAKwD,MACjBZ,EAAEsB,SAAUlE,KAAKmE,QAAS,YAE1BnE,KAAK4K,uBAAwB,QAOhCjG,EAAiBvE,GAAGC,IAAII,MAAMiC,WAAYgM,GAC1C/J,EAAiB6I,EAAQvN,OAAQ0E,IAAoBA,EACrDC,EAAe3E,OAAQ0E,GAAmBvE,GAAGC,IAAI6J,eAAeL,QAG/DgB,IAAK,WACJ,GAAIA,GAAM0C,EAAWtH,IAAK,WAAcsH,EAAWtH,IAAK,iBAAoByI,CAI5E,OAHO9L,GAAEC,YAAa7C,KAAKiG,IAAK,SAC/B4E,GAAQ,IAAM7K,KAAKiG,IAAK,OAElB4E,GAIR5H,MAAOwL,EAGPjL,KAAMmB,EAGNR,QAASsK,EAAWxL,MAAMkB,WAK5B/D,GAAGC,IAAII,MAAMoD,kBAAmB4K,EAAWxL,MAAM2L,UAAWhK,EAAe3E,OAAQ0E,MASpF/B,EAAEoB,KAAMqJ,EAAkB,SAAUwB,GAGnC,GAAIC,GAAqBnK,EACvB+J,EAAaG,EAAgBN,MAAM/M,MAAOqN,EAAgBN,MAAM7K,YAAa,KAAQ,GACrFiL,EAAavO,GAAGC,IAAII,MAAMuC,iBAAkB6L,EAAgBN,MAAO,EAGhE,MAAOI,GAAcA,IAAeD,GAExCI,EAAsB1O,GAAGC,IAAII,MAAMiC,WAAYiM,GAAevO,GAAGC,IAAII,MAAMiC,WAAYgM,GACvF/J,EAAsB6I,EAAQvN,OAAQ6O,IAAyBA,EAC/DA,EAAsBtB,EAAQtN,YAAa4O,IAAyBA,EACpElK,EAAe1E,YAAa4O,GAAwB1O,GAAGC,IAAImL,oBAAoB3B,QAG9EgB,IAAK,WACJ,MAAO0C,GAAWtH,IAAK,WAAcsH,EAAWtH,IAAK,iBACnD0I,EAAa,IAAM3O,KAAKiH,OAAS,IACjCyH,GAIHhK,MAAOE,EAAe3E,OAAQ0E,GAG9BnB,KAAMsL,EAGN7L,MAAO4L,EAGP1K,QAAS0K,EAAgB5L,MAAMkB,YAKhC2K,EAAsB1O,GAAGC,IAAII,MAAMiC,WAAYgM,GAC/C/J,EAAsB6I,EAAQvN,OAAQ6O,IAAyBA,EAC/DA,EAAsBtB,EAAQtN,YAAa4O,IAAyBA,EACpElK,EAAe1E,YAAa4O,GAAwB1O,GAAGC,IAAImL,oBAAoB3B,QAG9EgB,IAAK0C,EAAWtH,IAAK,WAAcsH,EAAWtH,IAAK,iBAAoByI,EAGvEhK,MAAOE,EAAe3E,OAAQ0E,GAG9BnB,KAAMsL,EAGN7L,MAAO4L,EAGP1K,QAAS0K,EAAgB5L,MAAMkB,WAKjC/D,GAAGC,IAAII,MAAMoD,kBAAmBgL,EAAgB5L,MAAM2L,UAAWhK,EAAe1E,YAAa4O,MAI9FlM,EAAEoB,KAAMY,EAAe3E,OAAQ,SAAUyE,EAAO6J,GAC/C3J,EAAe3E,OAAQsO,GAAUnO,GAAGC,IAAII,MAAMgE,oBAAqBC,EAAO6J,EAAO3J,QAOpFxE,GAAGC,IAAIuO,UAAY,GAAI3E,UAASwB,YAC/B/G,MAAO4H,IAWRlM,GAAGC,IAAI0O,KAAO,SAAU1K,GACvB,GAAI2K,GAA2BlJ,EAAUU,EAA3BvB,IA4Bd,OA1BAZ,GAAOA,MACPY,EAAWqG,QAAUjH,EAAKiH,SAAWtB,cAAcuB,KACnDtG,EAAW3E,cAAgB+D,EAAK/D,eAAiB0J,cAAc1J,cAC/D2E,EAAWuH,OAASnI,EAAKmI,QAAU,KAC5BvH,EAAWuH,QAAUvH,EAAWqG,UAAYtB,cAAcuB,MAAQtG,EAAW3E,gBAAkB0J,cAAc1J,gBACnH2E,EAAWuH,OAASxC,cAAcwC,QAG5BD,EAAsBtH,EAAWqG,QAAUrG,EAAW3E,iBAC5D0O,EAAW5O,GAAGC,IAAIuO,UAAUzI,WAAamF,QAASrG,EAAWqG,QAAShL,cAAe2E,EAAW3E,gBACzF0O,IACNA,EAAW,GAAI1C,GAAUrH,GACzB7E,GAAGC,IAAIuO,UAAUK,IAAKD,IAEvBlJ,EAAWC,OAAOC,WAClBQ,EAAUV,EAASU,UAEnBwI,EAASvC,kBAAkB1D,KAAM,SAAUiG,GAG1C5O,GAAGC,IAAIJ,OAAc2C,EAAEiH,OAAQmF,EAAS/I,IAAK,UAAY7F,GAAGC,IAAIJ,QAChEG,GAAGC,IAAIH,YAAc0C,EAAEiH,OAAQmF,EAAS/I,IAAK,eAAiB7F,GAAGC,IAAIH,aACrE4F,EAASoJ,YAAa9O,GAAGC,KAAO2O,MAEjCzC,EAAsBtH,EAAWqG,QAAUrG,EAAW3E,eAAkBkG,GAElE+F,EAAsBtH,EAAWqG,QAAUrG,EAAW3E,gBAQ9DF,GAAGC,IAAI0O,QAEJlP","file":"wp-api.min.js"}