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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta14 to 2.0-beta15

CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 14.0 (September 30, 2016)
4
 
5
  - Add support for password protected posts
1
  # Changelog
2
 
3
+ ## 2.0 Beta 15.0 (October 07, 2016)
4
+
5
+ - Introduce support for Post Meta, Term Meta, User Meta, and Comment Meta in
6
+ their parent endpoints.
7
+
8
+ For your meta fields to be exposed in the REST API, you need to register
9
+ them. WordPress includes a `register_meta()` function which is not usually
10
+ required to get/set fields, but is required for API support.
11
+
12
+ To register your field, simply call register_meta and set the show_in_rest
13
+ flag to true. Note: register_meta must be called separately for each meta
14
+ key.
15
+
16
+ (props @rmccue, @danielbachhuber, @kjbenk, @duncanjbrown, [#2765][gh-2765])
17
+
18
+ - Introduce Settings endpoint.
19
+
20
+ Expose options to the REST API with the `register_setting()` function, by
21
+ passing `$args = array( 'show_in_rest' => true )`. Note: WordPress 4.7 is
22
+ required. See changeset [38635][https://core.trac.wordpress.org/changeset/38635].
23
+
24
+ (props @joehoyle, @fjarrett, @danielbachhuber, @jonathanbardo,
25
+ @greatislander, [#2739][gh-2739])
26
+
27
+ - Attachments controller, change permissions check to match core.
28
+
29
+ Check for the `upload_files` capability when creating an attachment.
30
+
31
+ (props @nullvariable, @adamsilverstein, [#2743][gh-2743])
32
+
33
+ - Add `?{taxonomy}_exclude=` query parameter
34
+
35
+ This mirrors our existing support for ?{taxonomy}= filtering in the posts
36
+ controller (which allows querying for only records with are associated with
37
+ any of the provided term IDs for the specified taxonomy) by adding an
38
+ equivalent `_exclude` variant to list IDs of terms for which associated posts
39
+ should NOT be returned.
40
+
41
+ (props @kadamwhite, [#2756][gh-2756])
42
+
43
+ - Use `get_comment_type()` when comparing updating comment status.
44
+
45
+ Comments having a empty `comment_type` within WordPress bites us again.
46
+ Fixes a bug where comments could not be updated because of bad comparison
47
+ logic.
48
+
49
+ (props @joehoyle, [#2753][gh-2753])
50
+
51
+ [gh-2765]: https://github.com/WP-API/WP-API/issues/2765
52
+ [gh-2739]: https://github.com/WP-API/WP-API/issues/2739
53
+ [gh-2743]: https://github.com/WP-API/WP-API/issues/2743
54
+ [gh-2756]: https://github.com/WP-API/WP-API/issues/2756
55
+ [gh-2753]: https://github.com/WP-API/WP-API/issues/2753
56
+
57
  ## 2.0 Beta 14.0 (September 30, 2016)
58
 
59
  - Add support for password protected posts
README.md CHANGED
@@ -3,7 +3,6 @@
3
  Access your WordPress site's data through an easy-to-use HTTP REST API.
4
 
5
  [![Build Status](https://travis-ci.org/WP-API/WP-API.svg?branch=develop)](https://travis-ci.org/WP-API/WP-API)
6
- [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WP-API/WP-API/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/WP-API/WP-API/?branch=develop)
7
  [![codecov.io](http://codecov.io/github/WP-API/WP-API/coverage.svg?branch=develop)](http://codecov.io/github/WP-API/WP-API?branch=develop)
8
 
9
  The **"develop"** branch is version 2 which is "beta" but stable and recommended for production. [Read the documentation](http://v2.wp-api.org/)
3
  Access your WordPress site's data through an easy-to-use HTTP REST API.
4
 
5
  [![Build Status](https://travis-ci.org/WP-API/WP-API.svg?branch=develop)](https://travis-ci.org/WP-API/WP-API)
 
6
  [![codecov.io](http://codecov.io/github/WP-API/WP-API/coverage.svg?branch=develop)](http://codecov.io/github/WP-API/WP-API?branch=develop)
7
 
8
  The **"develop"** branch is version 2 which is "beta" but stable and recommended for production. [Read the documentation](http://v2.wp-api.org/)
lib/endpoints/class-wp-rest-attachments-controller.php CHANGED
@@ -40,9 +40,8 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
40
  return $ret;
41
  }
42
 
43
- // "upload_files" cap is returned for an attachment by $post_type_obj->cap->create_posts
44
  $post_type_obj = get_post_type_object( $this->post_type );
45
- if ( ! current_user_can( $post_type_obj->cap->create_posts ) || ! current_user_can( $post_type_obj->cap->edit_posts ) ) {
46
  return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
47
  }
48
 
40
  return $ret;
41
  }
42
 
 
43
  $post_type_obj = get_post_type_object( $this->post_type );
44
+ if ( ! current_user_can( 'upload_files' ) ) {
45
  return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
46
  }
47
 
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -8,6 +8,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
8
  public function __construct() {
9
  $this->namespace = 'wp/v2';
10
  $this->rest_base = 'comments';
 
 
11
  }
12
 
13
  /**
@@ -374,6 +376,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
374
  $this->handle_status_param( $request['status'], $comment );
375
  }
376
 
 
 
 
 
 
 
 
 
377
  $comment = get_comment( $comment_id );
378
  $fields_update = $this->update_additional_fields_for_object( $comment, $request );
379
  if ( is_wp_error( $fields_update ) ) {
@@ -432,7 +442,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
432
  return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
433
  }
434
 
435
- if ( isset( $request['type'] ) && $request['type'] !== $comment->comment_type ) {
436
  return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you cannot change the comment type.' ), array( 'status' => 404 ) );
437
  }
438
 
@@ -457,6 +467,14 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
457
  }
458
  }
459
 
 
 
 
 
 
 
 
 
460
  $comment = get_comment( $id );
461
  $fields_update = $this->update_additional_fields_for_object( $comment, $request );
462
  if ( is_wp_error( $fields_update ) ) {
@@ -585,6 +603,10 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
585
  $data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email );
586
  }
587
 
 
 
 
 
588
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
589
  $data = $this->add_additional_fields_to_object( $data, $request );
590
  $data = $this->filter_response_by_context( $data, $context );
@@ -765,7 +787,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
765
  }
766
 
767
  if ( isset( $request['type'] ) ) {
768
- $prepared_comment['comment_type'] = $request['type'];
 
769
  }
770
 
771
  if ( isset( $request['karma'] ) ) {
@@ -920,9 +943,9 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
920
  'description' => __( 'Type of Comment for the object.' ),
921
  'type' => 'string',
922
  'context' => array( 'view', 'edit', 'embed' ),
 
923
  'arg_options' => array(
924
  'sanitize_callback' => 'sanitize_key',
925
- 'default' => '',
926
  ),
927
  ),
928
  ),
@@ -950,6 +973,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
950
  );
951
  }
952
 
 
 
953
  return $this->add_additional_fields_schema( $schema );
954
  }
955
 
8
  public function __construct() {
9
  $this->namespace = 'wp/v2';
10
  $this->rest_base = 'comments';
11
+
12
+ $this->meta = new WP_REST_Comment_Meta_Fields();
13
  }
14
 
15
  /**
376
  $this->handle_status_param( $request['status'], $comment );
377
  }
378
 
379
+ $schema = $this->get_item_schema();
380
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
381
+ $meta_update = $this->meta->update_value( $request['meta'], $comment_id );
382
+ if ( is_wp_error( $meta_update ) ) {
383
+ return $meta_update;
384
+ }
385
+ }
386
+
387
  $comment = get_comment( $comment_id );
388
  $fields_update = $this->update_additional_fields_for_object( $comment, $request );
389
  if ( is_wp_error( $fields_update ) ) {
442
  return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
443
  }
444
 
445
+ if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) {
446
  return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you cannot change the comment type.' ), array( 'status' => 404 ) );
447
  }
448
 
467
  }
468
  }
469
 
470
+ $schema = $this->get_item_schema();
471
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
472
+ $meta_update = $this->meta->update_value( $request['meta'], $id );
473
+ if ( is_wp_error( $meta_update ) ) {
474
+ return $meta_update;
475
+ }
476
+ }
477
+
478
  $comment = get_comment( $id );
479
  $fields_update = $this->update_additional_fields_for_object( $comment, $request );
480
  if ( is_wp_error( $fields_update ) ) {
603
  $data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email );
604
  }
605
 
606
+ if ( ! empty( $schema['properties']['meta'] ) ) {
607
+ $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request );
608
+ }
609
+
610
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
611
  $data = $this->add_additional_fields_to_object( $data, $request );
612
  $data = $this->filter_response_by_context( $data, $context );
787
  }
788
 
789
  if ( isset( $request['type'] ) ) {
790
+ // Comment type "comment" needs to be created as an empty string.
791
+ $prepared_comment['comment_type'] = 'comment' === $request['type'] ? '' : $request['type'];
792
  }
793
 
794
  if ( isset( $request['karma'] ) ) {
943
  'description' => __( 'Type of Comment for the object.' ),
944
  'type' => 'string',
945
  'context' => array( 'view', 'edit', 'embed' ),
946
+ 'default' => 'comment',
947
  'arg_options' => array(
948
  'sanitize_callback' => 'sanitize_key',
 
949
  ),
950
  ),
951
  ),
973
  );
974
  }
975
 
976
+ $schema['properties']['meta'] = $this->meta->get_field_schema();
977
+
978
  return $this->add_additional_fields_schema( $schema );
979
  }
980
 
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -9,6 +9,8 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
9
  $this->namespace = 'wp/v2';
10
  $obj = get_post_type_object( $post_type );
11
  $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
 
 
12
  }
13
 
14
  /**
@@ -171,6 +173,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
171
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
172
  foreach ( $taxonomies as $taxonomy ) {
173
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 
174
 
175
  if ( ! empty( $request[ $base ] ) ) {
176
  $query_args['tax_query'][] = array(
@@ -180,6 +183,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
180
  'include_children' => false,
181
  );
182
  }
 
 
 
 
 
 
 
 
 
 
183
  }
184
 
185
  $posts_query = new WP_Query();
@@ -415,6 +428,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
415
  }
416
 
417
  $post = $this->get_post( $post_id );
 
 
 
 
 
 
 
418
  $fields_update = $this->update_additional_fields_for_object( $post, $request );
419
  if ( is_wp_error( $fields_update ) ) {
420
  return $fields_update;
@@ -521,6 +541,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
521
  }
522
 
523
  $post = $this->get_post( $post_id );
 
 
 
 
 
 
 
 
524
  $fields_update = $this->update_additional_fields_for_object( $post, $request );
525
  if ( is_wp_error( $fields_update ) ) {
526
  return $fields_update;
@@ -1253,6 +1281,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1253
  }
1254
  }
1255
 
 
 
 
 
1256
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1257
  foreach ( $taxonomies as $taxonomy ) {
1258
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
@@ -1511,6 +1543,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1511
  'revisions',
1512
  'page-attributes',
1513
  'post-formats',
 
1514
  );
1515
  $fixed_schemas = array(
1516
  'post' => array(
@@ -1522,6 +1555,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1522
  'comments',
1523
  'revisions',
1524
  'post-formats',
 
1525
  ),
1526
  'page' => array(
1527
  'title',
@@ -1532,12 +1566,14 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1532
  'comments',
1533
  'revisions',
1534
  'page-attributes',
 
1535
  ),
1536
  'attachment' => array(
1537
  'title',
1538
  'author',
1539
  'comments',
1540
  'revisions',
 
1541
  ),
1542
  );
1543
  foreach ( $post_type_attributes as $attribute ) {
@@ -1672,6 +1708,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
1672
  );
1673
  break;
1674
 
 
 
 
 
1675
  }
1676
  }
1677
 
9
  $this->namespace = 'wp/v2';
10
  $obj = get_post_type_object( $post_type );
11
  $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
12
+
13
+ $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type );
14
  }
15
 
16
  /**
173
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
174
  foreach ( $taxonomies as $taxonomy ) {
175
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
176
+ $tax_exclude = $base . '_exclude';
177
 
178
  if ( ! empty( $request[ $base ] ) ) {
179
  $query_args['tax_query'][] = array(
183
  'include_children' => false,
184
  );
185
  }
186
+
187
+ if ( ! empty( $request[ $tax_exclude ] ) ) {
188
+ $query_args['tax_query'][] = array(
189
+ 'taxonomy' => $taxonomy->name,
190
+ 'field' => 'term_id',
191
+ 'terms' => $request[ $tax_exclude ],
192
+ 'include_children' => false,
193
+ 'operator' => 'NOT IN',
194
+ );
195
+ }
196
  }
197
 
198
  $posts_query = new WP_Query();
428
  }
429
 
430
  $post = $this->get_post( $post_id );
431
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
432
+ $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
433
+ if ( is_wp_error( $meta_update ) ) {
434
+ return $meta_update;
435
+ }
436
+ }
437
+
438
  $fields_update = $this->update_additional_fields_for_object( $post, $request );
439
  if ( is_wp_error( $fields_update ) ) {
440
  return $fields_update;
541
  }
542
 
543
  $post = $this->get_post( $post_id );
544
+
545
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
546
+ $meta_update = $this->meta->update_value( $request['meta'], $post->ID );
547
+ if ( is_wp_error( $meta_update ) ) {
548
+ return $meta_update;
549
+ }
550
+ }
551
+
552
  $fields_update = $this->update_additional_fields_for_object( $post, $request );
553
  if ( is_wp_error( $fields_update ) ) {
554
  return $fields_update;
1281
  }
1282
  }
1283
 
1284
+ if ( ! empty( $schema['properties']['meta'] ) ) {
1285
+ $data['meta'] = $this->meta->get_value( $post->ID, $request );
1286
+ }
1287
+
1288
  $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1289
  foreach ( $taxonomies as $taxonomy ) {
1290
  $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1543
  'revisions',
1544
  'page-attributes',
1545
  'post-formats',
1546
+ 'custom-fields',
1547
  );
1548
  $fixed_schemas = array(
1549
  'post' => array(
1555
  'comments',
1556
  'revisions',
1557
  'post-formats',
1558
+ 'custom-fields',
1559
  ),
1560
  'page' => array(
1561
  'title',
1566
  'comments',
1567
  'revisions',
1568
  'page-attributes',
1569
+ 'custom-fields',
1570
  ),
1571
  'attachment' => array(
1572
  'title',
1573
  'author',
1574
  'comments',
1575
  'revisions',
1576
+ 'custom-fields',
1577
  ),
1578
  );
1579
  foreach ( $post_type_attributes as $attribute ) {
1708
  );
1709
  break;
1710
 
1711
+ case 'custom-fields':
1712
+ $schema['properties']['meta'] = $this->meta->get_field_schema();
1713
+ break;
1714
+
1715
  }
1716
  }
1717
 
lib/endpoints/class-wp-rest-settings-controller.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Manage a WordPress site's settings.
5
+ */
6
+
7
+ class WP_REST_Settings_Controller extends WP_REST_Controller {
8
+
9
+ protected $rest_base = 'settings';
10
+ protected $namespace = 'wp/v2';
11
+
12
+ /**
13
+ * Register the routes for the objects of the controller.
14
+ */
15
+ public function register_routes() {
16
+ register_rest_route( $this->namespace, '/' . $this->rest_base, array(
17
+ array(
18
+ 'methods' => WP_REST_Server::READABLE,
19
+ 'callback' => array( $this, 'get_item' ),
20
+ 'args' => array(),
21
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
22
+ ),
23
+ array(
24
+ 'methods' => WP_REST_Server::EDITABLE,
25
+ 'callback' => array( $this, 'update_item' ),
26
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
27
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
28
+ ),
29
+ 'schema' => array( $this, 'get_public_item_schema' ),
30
+ ) );
31
+ }
32
+
33
+ /**
34
+ * Check if a given request has access to read and manage settings.
35
+ *
36
+ * @param WP_REST_Request $request Full details about the request.
37
+ * @return boolean
38
+ */
39
+ public function get_item_permissions_check( $request ) {
40
+ return current_user_can( 'manage_options' );
41
+ }
42
+
43
+ /**
44
+ * Get the settings.
45
+ *
46
+ * @param WP_REST_Request $request Full details about the request.
47
+ * @return WP_Error|array
48
+ */
49
+ public function get_item( $request ) {
50
+ $options = $this->get_registered_options();
51
+ $response = array();
52
+
53
+ foreach ( $options as $name => $args ) {
54
+ // Default to a null value as "null" in the response means "not set".
55
+ $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
56
+
57
+ // Because get_option() is lossy, we have to
58
+ // cast values to the type they are registered with.
59
+ $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
60
+ }
61
+
62
+ return $response;
63
+ }
64
+
65
+ /**
66
+ * Prepare a value for output based off a schema array.
67
+ *
68
+ * @param mixed $value
69
+ * @param array $schema
70
+ * @return mixed
71
+ */
72
+ protected function prepare_value( $value, $schema ) {
73
+ switch ( $schema['type'] ) {
74
+ case 'string':
75
+ return strval( $value );
76
+ case 'number':
77
+ return floatval( $value );
78
+ case 'boolean':
79
+ return (bool) $value;
80
+ default:
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Update settings for the settings object.
87
+ *
88
+ * @param WP_REST_Request $request Full detail about the request.
89
+ * @return WP_Error|array
90
+ */
91
+ public function update_item( $request ) {
92
+ $options = $this->get_registered_options();
93
+ $params = $request->get_params();
94
+
95
+ foreach ( $options as $name => $args ) {
96
+ if ( ! array_key_exists( $name, $params ) ) {
97
+ continue;
98
+ }
99
+ // A null value means reset the option, which is essentially deleting it
100
+ // from the database and then relying on the default value.
101
+ if ( is_null( $request[ $name ] ) ) {
102
+ delete_option( $args['option_name'] );
103
+ } else {
104
+ update_option( $args['option_name'], $request[ $name ] );
105
+ }
106
+ }
107
+
108
+ return $this->get_item( $request );
109
+ }
110
+
111
+ /**
112
+ * Get all the registered options for the Settings API
113
+ *
114
+ * @return array
115
+ */
116
+ protected function get_registered_options() {
117
+ $rest_options = array();
118
+
119
+ foreach ( get_registered_settings() as $name => $args ) {
120
+ if ( empty( $args['show_in_rest'] ) ) {
121
+ continue;
122
+ }
123
+
124
+ $rest_args = array();
125
+ if ( is_array( $args['show_in_rest'] ) ) {
126
+ $rest_args = $args['show_in_rest'];
127
+ }
128
+
129
+ $defaults = array(
130
+ 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
131
+ 'schema' => array(),
132
+ );
133
+ $rest_args = array_merge( $defaults, $rest_args );
134
+
135
+ $default_schema = array(
136
+ 'type' => empty( $args['type'] ) ? null : $args['type'],
137
+ 'description' => empty( $args['description'] ) ? '' : $args['description'],
138
+ 'default' => isset( $args['default'] ) ? $args['default'] : null,
139
+ );
140
+
141
+ $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
142
+ $rest_args['option_name'] = $name;
143
+
144
+ // Skip over settings that don't have a defined type in the schema.
145
+ if ( empty( $rest_args['schema']['type'] ) ) {
146
+ continue;
147
+ }
148
+
149
+ // Whitelist the supported types for settings, as we don't want invalid types
150
+ // to be updated with arbitrary values that we can't do decent sanitizing for.
151
+ if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'string', 'boolean' ), true ) ) {
152
+ continue;
153
+ }
154
+
155
+ $rest_options[ $rest_args['name'] ] = $rest_args;
156
+ }
157
+
158
+ return $rest_options;
159
+ }
160
+
161
+ /**
162
+ * Get the site setting schema, conforming to JSON Schema.
163
+ *
164
+ * @return array
165
+ */
166
+ public function get_item_schema() {
167
+ $options = $this->get_registered_options();
168
+
169
+ $schema = array(
170
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
171
+ 'title' => 'settings',
172
+ 'type' => 'object',
173
+ 'properties' => array(),
174
+ );
175
+
176
+ foreach ( $options as $option_name => $option ) {
177
+ $schema['properties'][ $option_name ] = $option['schema'];
178
+ }
179
+
180
+ return $this->add_additional_fields_schema( $schema );
181
+ }
182
+ }
lib/endpoints/class-wp-rest-terms-controller.php CHANGED
@@ -15,6 +15,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
15
  $this->namespace = 'wp/v2';
16
  $tax_obj = get_taxonomy( $taxonomy );
17
  $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
 
 
18
  }
19
 
20
  /**
@@ -371,6 +373,13 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
371
  */
372
  do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
373
 
 
 
 
 
 
 
 
374
  $fields_update = $this->update_additional_fields_for_object( $term, $request );
375
  if ( is_wp_error( $fields_update ) ) {
376
  return $fields_update;
@@ -445,6 +454,14 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
445
  /* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
446
  do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
447
 
 
 
 
 
 
 
 
 
448
  $fields_update = $this->update_additional_fields_for_object( $term, $request );
449
  if ( is_wp_error( $fields_update ) ) {
450
  return $fields_update;
@@ -593,6 +610,9 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
593
  if ( ! empty( $schema['properties']['parent'] ) ) {
594
  $data['parent'] = (int) $item->parent;
595
  }
 
 
 
596
 
597
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
598
  $data = $this->add_additional_fields_to_object( $data, $request );
@@ -739,6 +759,8 @@ class WP_REST_Terms_Controller extends WP_REST_Controller {
739
  'context' => array( 'view', 'edit' ),
740
  );
741
  }
 
 
742
  return $this->add_additional_fields_schema( $schema );
743
  }
744
 
15
  $this->namespace = 'wp/v2';
16
  $tax_obj = get_taxonomy( $taxonomy );
17
  $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
18
+
19
+ $this->meta = new WP_REST_Term_Meta_Fields( $taxonomy );
20
  }
21
 
22
  /**
373
  */
374
  do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
375
 
376
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
377
+ $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
378
+ if ( is_wp_error( $meta_update ) ) {
379
+ return $meta_update;
380
+ }
381
+ }
382
+
383
  $fields_update = $this->update_additional_fields_for_object( $term, $request );
384
  if ( is_wp_error( $fields_update ) ) {
385
  return $fields_update;
454
  /* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
455
  do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
456
 
457
+ $schema = $this->get_item_schema();
458
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
459
+ $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
460
+ if ( is_wp_error( $meta_update ) ) {
461
+ return $meta_update;
462
+ }
463
+ }
464
+
465
  $fields_update = $this->update_additional_fields_for_object( $term, $request );
466
  if ( is_wp_error( $fields_update ) ) {
467
  return $fields_update;
610
  if ( ! empty( $schema['properties']['parent'] ) ) {
611
  $data['parent'] = (int) $item->parent;
612
  }
613
+ if ( ! empty( $schema['properties']['meta'] ) ) {
614
+ $data['meta'] = $this->meta->get_value( $item->term_id, $request );
615
+ }
616
 
617
  $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
618
  $data = $this->add_additional_fields_to_object( $data, $request );
759
  'context' => array( 'view', 'edit' ),
760
  );
761
  }
762
+
763
+ $schema['properties']['meta'] = $this->meta->get_field_schema();
764
  return $this->add_additional_fields_schema( $schema );
765
  }
766
 
lib/endpoints/class-wp-rest-users-controller.php CHANGED
@@ -8,6 +8,8 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
8
  public function __construct() {
9
  $this->namespace = 'wp/v2';
10
  $this->rest_base = 'users';
 
 
11
  }
12
 
13
  /**
@@ -329,6 +331,13 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
329
  array_map( array( $user, 'add_role' ), $request['roles'] );
330
  }
331
 
 
 
 
 
 
 
 
332
  $fields_update = $this->update_additional_fields_for_object( $user, $request );
333
  if ( is_wp_error( $fields_update ) ) {
334
  return $fields_update;
@@ -421,6 +430,14 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
421
  array_map( array( $user, 'add_role' ), $request['roles'] );
422
  }
423
 
 
 
 
 
 
 
 
 
424
  $fields_update = $this->update_additional_fields_for_object( $user, $request );
425
  if ( is_wp_error( $fields_update ) ) {
426
  return $fields_update;
@@ -579,6 +596,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
579
  $data['avatar_urls'] = rest_get_avatar_urls( $user->user_email );
580
  }
581
 
 
 
 
 
582
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
583
 
584
  $data = $this->add_additional_fields_to_object( $data, $request );
@@ -866,9 +887,10 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
866
  'readonly' => true,
867
  'properties' => $avatar_properties,
868
  );
869
-
870
  }
871
 
 
 
872
  return $this->add_additional_fields_schema( $schema );
873
  }
874
 
8
  public function __construct() {
9
  $this->namespace = 'wp/v2';
10
  $this->rest_base = 'users';
11
+
12
+ $this->meta = new WP_REST_User_Meta_Fields();
13
  }
14
 
15
  /**
331
  array_map( array( $user, 'add_role' ), $request['roles'] );
332
  }
333
 
334
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
335
+ $meta_update = $this->meta->update_value( $request['meta'], $user_id );
336
+ if ( is_wp_error( $meta_update ) ) {
337
+ return $meta_update;
338
+ }
339
+ }
340
+
341
  $fields_update = $this->update_additional_fields_for_object( $user, $request );
342
  if ( is_wp_error( $fields_update ) ) {
343
  return $fields_update;
430
  array_map( array( $user, 'add_role' ), $request['roles'] );
431
  }
432
 
433
+ $schema = $this->get_item_schema();
434
+ if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
435
+ $meta_update = $this->meta->update_value( $request['meta'], $id );
436
+ if ( is_wp_error( $meta_update ) ) {
437
+ return $meta_update;
438
+ }
439
+ }
440
+
441
  $fields_update = $this->update_additional_fields_for_object( $user, $request );
442
  if ( is_wp_error( $fields_update ) ) {
443
  return $fields_update;
596
  $data['avatar_urls'] = rest_get_avatar_urls( $user->user_email );
597
  }
598
 
599
+ if ( ! empty( $schema['properties']['meta'] ) ) {
600
+ $data['meta'] = $this->meta->get_value( $user->ID, $request );
601
+ }
602
+
603
  $context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
604
 
605
  $data = $this->add_additional_fields_to_object( $data, $request );
887
  'readonly' => true,
888
  'properties' => $avatar_properties,
889
  );
 
890
  }
891
 
892
+ $schema['properties']['meta'] = $this->meta->get_field_schema();
893
+
894
  return $this->add_additional_fields_schema( $schema );
895
  }
896
 
lib/fields/class-wp-rest-comment-meta-fields.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields {
4
+ /**
5
+ * Get the object type for meta.
6
+ *
7
+ * @return string
8
+ */
9
+ protected function get_meta_type() {
10
+ return 'comment';
11
+ }
12
+
13
+ /**
14
+ * Get the type for `register_rest_field`.
15
+ *
16
+ * @return string
17
+ */
18
+ public function get_rest_field_type() {
19
+ return 'comment';
20
+ }
21
+ }
lib/fields/class-wp-rest-meta-fields.php ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Manage meta values for an object.
5
+ */
6
+
7
+ abstract class WP_REST_Meta_Fields {
8
+ /**
9
+ * Get the object type for meta.
10
+ *
11
+ * @return string One of 'post', 'comment', 'term', 'user', or anything else supported by `_get_meta_table()`
12
+ */
13
+ abstract protected function get_meta_type();
14
+
15
+ /**
16
+ * Get the object type for `register_rest_field`.
17
+ *
18
+ * @return string Custom post type, 'taxonomy', 'comment', or `user`
19
+ */
20
+ abstract protected function get_rest_field_type();
21
+
22
+ /**
23
+ * Register the meta field.
24
+ */
25
+ public function register_field() {
26
+ register_rest_field( $this->get_rest_field_type(), 'meta', array(
27
+ 'get_callback' => array( $this, 'get_value' ),
28
+ 'update_callback' => array( $this, 'update_value' ),
29
+ 'schema' => $this->get_field_schema(),
30
+ ));
31
+ }
32
+
33
+ /**
34
+ * Get the `meta` field value.
35
+ *
36
+ * @param int $id Object ID to fetch meta for.
37
+ * @param WP_REST_Request $request Full details about the request.
38
+ * @return WP_Error|array
39
+ */
40
+ public function get_value( $id, $request ) {
41
+ $fields = $this->get_registered_fields();
42
+ $response = array();
43
+
44
+ foreach ( $fields as $name => $args ) {
45
+ $all_values = get_metadata( $this->get_meta_type(), $id, $name, false );
46
+ if ( $args['single'] ) {
47
+ if ( empty( $all_values ) ) {
48
+ $value = $args['schema']['default'];
49
+ } else {
50
+ $value = $all_values[0];
51
+ }
52
+ $value = $this->prepare_value_for_response( $value, $request, $args );
53
+ } else {
54
+ $value = array();
55
+ foreach ( $all_values as $row ) {
56
+ $value[] = $this->prepare_value_for_response( $row, $request, $args );
57
+ }
58
+ }
59
+
60
+ $response[ $name ] = $value;
61
+ }
62
+
63
+ return (object) $response;
64
+ }
65
+
66
+ /**
67
+ * Prepare value for response.
68
+ *
69
+ * This is required because some native types cannot be stored correctly in
70
+ * the database, such as booleans. We need to cast back to the relevant type
71
+ * before passing back to JSON.
72
+ *
73
+ * @param mixed $value Value to prepare.
74
+ * @param WP_REST_Request $request Current request object.
75
+ * @param array $args Options for the field.
76
+ * @return mixed Prepared value.
77
+ */
78
+ protected function prepare_value_for_response( $value, $request, $args ) {
79
+ if ( ! empty( $args['prepare_callback'] ) ) {
80
+ $value = call_user_func( $args['prepare_callback'], $value, $request, $args );
81
+ }
82
+
83
+ return $value;
84
+ }
85
+
86
+ /**
87
+ * Update meta values.
88
+ *
89
+ * @param WP_REST_Request $request Full detail about the request.
90
+ * @return WP_Error|null Error if one occurs, null on success.
91
+ */
92
+ public function update_value( $params, $id ) {
93
+ $fields = $this->get_registered_fields();
94
+
95
+ foreach ( $fields as $name => $args ) {
96
+ if ( ! array_key_exists( $name, $params ) ) {
97
+ continue;
98
+ }
99
+
100
+ // A null value means reset the field, which is essentially deleting it
101
+ // from the database and then relying on the default value.
102
+ if ( is_null( $params[ $name ] ) ) {
103
+ $result = $this->delete_meta_value( $id, $name );
104
+ } elseif ( $args['single'] ) {
105
+ $result = $this->update_meta_value( $id, $name, $params[ $name ] );
106
+ } else {
107
+ $result = $this->update_multi_meta_value( $id, $name, $params[ $name ] );
108
+ }
109
+
110
+ if ( is_wp_error( $result ) ) {
111
+ return $result;
112
+ }
113
+ }
114
+
115
+ return null;
116
+ }
117
+
118
+ /**
119
+ * Delete meta value for an object.
120
+ *
121
+ * @param int $object Object ID the field belongs to.
122
+ * @param string $name Key for the field.
123
+ * @return bool|WP_Error True if meta field is deleted, error otherwise.
124
+ */
125
+ protected function delete_meta_value( $object, $name ) {
126
+ if ( ! current_user_can( 'delete_post_meta', $object, $name ) ) {
127
+ return new WP_Error(
128
+ 'rest_cannot_delete',
129
+ sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
130
+ array( 'key' => $name, 'status' => rest_authorization_required_code() )
131
+ );
132
+ }
133
+
134
+ if ( ! delete_metadata( $this->get_meta_type(), $object, wp_slash( $name ) ) ) {
135
+ return new WP_Error(
136
+ 'rest_meta_database_error',
137
+ __( 'Could not delete meta value from database.' ),
138
+ array( 'key' => $name, 'status' => WP_HTTP::INTERNAL_SERVER_ERROR )
139
+ );
140
+ }
141
+
142
+ return true;
143
+ }
144
+
145
+ /**
146
+ * Update multiple meta values for an object.
147
+ *
148
+ * Alters the list of values in the database to match the list of provided values.
149
+ *
150
+ * @param int $object Object ID.
151
+ * @param string $name Key for the custom field.
152
+ * @param array $values List of values to update to.
153
+ * @return bool|WP_Error True if meta fields are updated, error otherwise.
154
+ */
155
+ protected function update_multi_meta_value( $object, $name, $values ) {
156
+ if ( ! current_user_can( 'edit_post_meta', $object, $name ) ) {
157
+ return new WP_Error(
158
+ 'rest_cannot_update',
159
+ sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
160
+ array( 'key' => $name, 'status' => rest_authorization_required_code() )
161
+ );
162
+ }
163
+
164
+ $current = get_metadata( $this->get_meta_type(), $object, $name, false );
165
+
166
+ $to_remove = $current;
167
+ $to_add = $values;
168
+ foreach ( $to_add as $add_key => $value ) {
169
+ $remove_keys = array_keys( $to_remove, $value, true );
170
+ if ( empty( $remove_keys ) ) {
171
+ continue;
172
+ }
173
+
174
+ if ( count( $remove_keys ) > 1 ) {
175
+ // To remove, we need to remove first, then add, so don't touch.
176
+ continue;
177
+ }
178
+
179
+ $remove_key = $remove_keys[0];
180
+ unset( $to_remove[ $remove_key ] );
181
+ unset( $to_add[ $add_key ] );
182
+ }
183
+
184
+ // `delete_metadata` removes _all_ instances of the value, so only call
185
+ // once.
186
+ $to_remove = array_unique( $to_remove );
187
+ foreach ( $to_remove as $value ) {
188
+ if ( ! delete_metadata( $this->get_meta_type(), $object, wp_slash( $name ), wp_slash( $value ) ) ) {
189
+ return new WP_Error(
190
+ 'rest_meta_database_error',
191
+ __( 'Could not update meta value in database.' ),
192
+ array( 'key' => $name, 'status' => WP_HTTP::INTERNAL_SERVER_ERROR )
193
+ );
194
+ }
195
+ }
196
+ foreach ( $to_add as $value ) {
197
+ if ( ! add_metadata( $this->get_meta_type(), $object, wp_slash( $name ), wp_slash( $value ) ) ) {
198
+ return new WP_Error(
199
+ 'rest_meta_database_error',
200
+ __( 'Could not update meta value in database.' ),
201
+ array( 'key' => $name, 'status' => WP_HTTP::INTERNAL_SERVER_ERROR )
202
+ );
203
+ }
204
+ }
205
+
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Update meta value for an object.
211
+ *
212
+ * @param int $object Object ID.
213
+ * @param string $name Key for the custom field.
214
+ * @return bool|WP_Error True if meta field is deleted, error otherwise.
215
+ */
216
+ protected function update_meta_value( $object, $name, $value ) {
217
+ if ( ! current_user_can( 'edit_post_meta', $object, $name ) ) {
218
+ return new WP_Error(
219
+ 'rest_cannot_update',
220
+ sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
221
+ array( 'key' => $name, 'status' => rest_authorization_required_code() )
222
+ );
223
+ }
224
+
225
+ if ( ! update_metadata( $this->get_meta_type(), $object, wp_slash( $name ), wp_slash( $value ) ) ) {
226
+ return new WP_Error(
227
+ 'rest_meta_database_error',
228
+ __( 'Could not update meta value in database.' ),
229
+ array( 'key' => $name, 'status' => WP_HTTP::INTERNAL_SERVER_ERROR )
230
+ );
231
+ }
232
+
233
+ return true;
234
+ }
235
+
236
+ /**
237
+ * Get all the registered meta fields.
238
+ *
239
+ * @return array
240
+ */
241
+ protected function get_registered_fields() {
242
+ $registered = array();
243
+
244
+ foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) {
245
+ if ( empty( $args['show_in_rest'] ) ) {
246
+ continue;
247
+ }
248
+
249
+ $rest_args = array();
250
+ if ( is_array( $args['show_in_rest'] ) ) {
251
+ $rest_args = $args['show_in_rest'];
252
+ }
253
+
254
+ $default_args = array(
255
+ 'name' => $name,
256
+ 'single' => $args['single'],
257
+ 'schema' => array(),
258
+ 'prepare_callback' => array( $this, 'prepare_value' ),
259
+ );
260
+ $default_schema = array(
261
+ 'type' => null,
262
+ 'description' => empty( $args['description'] ) ? '' : $args['description'],
263
+ 'default' => isset( $args['default'] ) ? $args['default'] : null,
264
+ );
265
+ $rest_args = array_merge( $default_args, $rest_args );
266
+ $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
267
+
268
+ if ( empty( $rest_args['schema']['type'] ) ) {
269
+ // Skip over meta fields that don't have a defined type
270
+ if ( empty( $args['type'] ) ) {
271
+ continue;
272
+ }
273
+
274
+ if ( $rest_args['single'] ) {
275
+ $rest_args['schema']['type'] = $args['type'];
276
+ } else {
277
+ $rest_args['schema']['type'] = 'array';
278
+ $rest_args['schema']['items'] = array(
279
+ 'type' => $args['type'],
280
+ );
281
+ }
282
+ }
283
+
284
+ $registered[ $rest_args['name'] ] = $rest_args;
285
+ }
286
+
287
+ return $registered;
288
+ }
289
+
290
+ /**
291
+ * Get the object's `meta` schema, conforming to JSON Schema.
292
+ *
293
+ * @return array
294
+ */
295
+ public function get_field_schema() {
296
+ $fields = $this->get_registered_fields();
297
+
298
+ $schema = array(
299
+ 'description' => __( 'Meta fields.' ),
300
+ 'type' => 'object',
301
+ 'context' => array( 'view', 'edit' ),
302
+ 'properties' => array(),
303
+ );
304
+
305
+ foreach ( $fields as $key => $args ) {
306
+ $schema['properties'][ $key ] = $args['schema'];
307
+ }
308
+
309
+ return $schema;
310
+ }
311
+
312
+ /**
313
+ * Prepare a meta value for output.
314
+ *
315
+ * Default preparation for meta fields. Override by passing the
316
+ * `prepare_callback` in your `show_in_rest` options.
317
+ *
318
+ * @param mixed $value Meta value from the database.
319
+ * @param WP_REST_Request $request Request object.
320
+ * @param array $args REST-specific options for the meta key.
321
+ * @return mixed Value prepared for output.
322
+ */
323
+ public static function prepare_value( $value, $request, $args ) {
324
+ $type = $args['schema']['type'];
325
+
326
+ // For multi-value fields, check the item type instead.
327
+ if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) {
328
+ $type = $args['schema']['items']['type'];
329
+ }
330
+
331
+ switch ( $type ) {
332
+ case 'string':
333
+ $value = strval( $value );
334
+ break;
335
+ case 'number':
336
+ $value = floatval( $value );
337
+ break;
338
+ case 'boolean':
339
+ $value = (bool) $value;
340
+ break;
341
+ }
342
+
343
+ // Don't allow objects to be output.
344
+ if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) {
345
+ return null;
346
+ }
347
+
348
+ return $value;
349
+ }
350
+ }
lib/fields/class-wp-rest-post-meta-fields.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields {
4
+ /**
5
+ * Post type to register fields for.
6
+ *
7
+ * @var string
8
+ */
9
+ protected $post_type;
10
+
11
+ /**
12
+ * Constructor.
13
+ *
14
+ * @param string $post_type Post type to register fields for.
15
+ */
16
+ public function __construct( $post_type ) {
17
+ $this->post_type = $post_type;
18
+ }
19
+
20
+ /**
21
+ * Get the object type for meta.
22
+ *
23
+ * @return string
24
+ */
25
+ protected function get_meta_type() {
26
+ return 'post';
27
+ }
28
+
29
+ /**
30
+ * Get the type for `register_rest_field`.
31
+ *
32
+ * @return string Custom post type slug.
33
+ */
34
+ public function get_rest_field_type() {
35
+ return $this->post_type;
36
+ }
37
+ }
lib/fields/class-wp-rest-term-meta-fields.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields {
4
+ /**
5
+ * Taxonomy to register fields for.
6
+ *
7
+ * @var string
8
+ */
9
+ protected $taxonomy;
10
+ /**
11
+ * Constructor.
12
+ *
13
+ * @param string $taxonomy
14
+ */
15
+ public function __construct( $taxonomy ) {
16
+ $this->taxonomy = $taxonomy;
17
+ }
18
+
19
+ /**
20
+ * Get the object type for meta.
21
+ *
22
+ * @return string
23
+ */
24
+ protected function get_meta_type() {
25
+ return 'term';
26
+ }
27
+
28
+ /**
29
+ * Get the type for `register_rest_field`.
30
+ *
31
+ * @return string
32
+ */
33
+ public function get_rest_field_type() {
34
+ return 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy;
35
+ }
36
+ }
lib/fields/class-wp-rest-user-meta-fields.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields {
4
+ /**
5
+ * Get the object type for meta.
6
+ *
7
+ * @return string
8
+ */
9
+ protected function get_meta_type() {
10
+ return 'user';
11
+ }
12
+
13
+ /**
14
+ * Get the type for `register_rest_field`.
15
+ *
16
+ * @return string
17
+ */
18
+ public function get_rest_field_type() {
19
+ return 'user';
20
+ }
21
+ }
plugin.php CHANGED
@@ -4,7 +4,7 @@
4
  * Description: JSON-based REST API for WordPress, originally developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://v2.wp-api.org
7
- * Version: 2.0-beta14
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
@@ -79,6 +79,48 @@ if ( ! class_exists( 'WP_REST_Comments_Controller' ) ) {
79
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  /**
83
  * REST extras.
84
  */
@@ -87,6 +129,7 @@ require_once( dirname( __FILE__ ) . '/core-integration.php' );
87
 
88
  add_filter( 'init', '_add_extra_api_post_type_arguments', 11 );
89
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
 
90
  add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
91
 
92
  /**
@@ -145,6 +188,121 @@ function _add_extra_api_taxonomy_arguments() {
145
  }
146
  }
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  if ( ! function_exists( 'create_initial_rest_routes' ) ) {
149
  /**
150
  * Registers default REST API routes.
@@ -206,6 +364,13 @@ if ( ! function_exists( 'create_initial_rest_routes' ) ) {
206
  // Comments.
207
  $controller = new WP_REST_Comments_Controller;
208
  $controller->register_routes();
 
 
 
 
 
 
 
209
  }
210
  }
211
 
4
  * Description: JSON-based REST API for WordPress, originally developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://v2.wp-api.org
7
+ * Version: 2.0-beta15
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
79
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
80
  }
81
 
82
+ /**
83
+ * WP_REST_Settings_Controller class.
84
+ */
85
+ if ( ! class_exists( 'WP_REST_Settings_Controller' ) ) {
86
+ require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-settings-controller.php';
87
+ }
88
+
89
+ /**
90
+ * WP_REST_Meta_Fields class.
91
+ */
92
+ if ( ! class_exists( 'WP_REST_Meta_Fields' ) ) {
93
+ require_once dirname( __FILE__ ) . '/lib/fields/class-wp-rest-meta-fields.php';
94
+ }
95
+
96
+ /**
97
+ * WP_REST_Comment_Meta_Fields class.
98
+ */
99
+ if ( ! class_exists( 'WP_REST_Comment_Meta_Fields' ) ) {
100
+ require_once dirname( __FILE__ ) . '/lib/fields/class-wp-rest-comment-meta-fields.php';
101
+ }
102
+
103
+ /**
104
+ * WP_REST_Post_Meta_Fields class.
105
+ */
106
+ if ( ! class_exists( 'WP_REST_Post_Meta_Fields' ) ) {
107
+ require_once dirname( __FILE__ ) . '/lib/fields/class-wp-rest-post-meta-fields.php';
108
+ }
109
+
110
+ /**
111
+ * WP_REST_Term_Meta_Fields class.
112
+ */
113
+ if ( ! class_exists( 'WP_REST_Term_Meta_Fields' ) ) {
114
+ require_once dirname( __FILE__ ) . '/lib/fields/class-wp-rest-term-meta-fields.php';
115
+ }
116
+
117
+ /**
118
+ * WP_REST_User_Meta_Fields class.
119
+ */
120
+ if ( ! class_exists( 'WP_REST_User_Meta_Fields' ) ) {
121
+ require_once dirname( __FILE__ ) . '/lib/fields/class-wp-rest-user-meta-fields.php';
122
+ }
123
+
124
  /**
125
  * REST extras.
126
  */
129
 
130
  add_filter( 'init', '_add_extra_api_post_type_arguments', 11 );
131
  add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
132
+ add_action( 'rest_api_init', 'rest_register_settings', 0 );
133
  add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
134
 
135
  /**
188
  }
189
  }
190
 
191
+
192
+ /**
193
+ * Register the settings to be used in the REST API.
194
+ *
195
+ * This is required are WordPress Core does not internally register
196
+ * it's settings via `register_rest_setting()`. This should be removed
197
+ * once / if core starts to register settings internally.
198
+ */
199
+ function rest_register_settings() {
200
+ global $wp_version;
201
+ if ( version_compare( $wp_version, '4.7-alpha', '<' ) ) {
202
+ return;
203
+ }
204
+
205
+ register_setting( 'general', 'blogname', array(
206
+ 'show_in_rest' => array(
207
+ 'name' => 'title',
208
+ ),
209
+ 'type' => 'string',
210
+ 'description' => __( 'Site title.' ),
211
+ ) );
212
+
213
+ register_setting( 'general', 'blogdescription', array(
214
+ 'show_in_rest' => array(
215
+ 'name' => 'description',
216
+ ),
217
+ 'type' => 'string',
218
+ 'description' => __( 'Site description.' ),
219
+ ) );
220
+
221
+ register_setting( 'general', 'siteurl', array(
222
+ 'show_in_rest' => array(
223
+ 'name' => 'url',
224
+ 'schema' => array(
225
+ 'format' => 'uri',
226
+ ),
227
+ ),
228
+ 'type' => 'string',
229
+ 'description' => __( 'Site URL' ),
230
+ ) );
231
+
232
+ register_setting( 'general', 'admin_email', array(
233
+ 'show_in_rest' => array(
234
+ 'name' => 'email',
235
+ 'schema' => array(
236
+ 'format' => 'email',
237
+ ),
238
+ ),
239
+ 'type' => 'string',
240
+ 'description' => __( 'This address is used for admin purposes. If you change this we will send you an email at your new address to confirm it. The new address will not become active until confirmed.' ),
241
+ ) );
242
+
243
+ register_setting( 'general', 'timezone_string', array(
244
+ 'show_in_rest' => array(
245
+ 'name' => 'timezone',
246
+ ),
247
+ 'type' => 'string',
248
+ 'description' => __( 'A city in the same timezone as you.' ),
249
+ ) );
250
+
251
+ register_setting( 'general', 'date_format', array(
252
+ 'show_in_rest' => true,
253
+ 'type' => 'string',
254
+ 'description' => __( 'A date format for all date strings.' ),
255
+ ) );
256
+
257
+ register_setting( 'general', 'time_format', array(
258
+ 'show_in_rest' => true,
259
+ 'type' => 'string',
260
+ 'description' => __( 'A time format for all time strings.' ),
261
+ ) );
262
+
263
+ register_setting( 'general', 'start_of_week', array(
264
+ 'show_in_rest' => true,
265
+ 'type' => 'number',
266
+ 'description' => __( 'A day number of the week that the week should start on.' ),
267
+ ) );
268
+
269
+ register_setting( 'general', 'WPLANG', array(
270
+ 'show_in_rest' => array(
271
+ 'name' => 'language',
272
+ ),
273
+ 'type' => 'string',
274
+ 'description' => __( 'WordPress locale code.' ),
275
+ 'default' => 'en_US',
276
+ ) );
277
+
278
+ register_setting( 'writing', 'use_smilies', array(
279
+ 'show_in_rest' => true,
280
+ 'type' => 'boolean',
281
+ 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ),
282
+ 'default' => true,
283
+ ) );
284
+
285
+ register_setting( 'writing', 'default_category', array(
286
+ 'show_in_rest' => true,
287
+ 'type' => 'number',
288
+ 'description' => __( 'Default category.' ),
289
+ ) );
290
+
291
+ register_setting( 'writing', 'default_post_format', array(
292
+ 'show_in_rest' => true,
293
+ 'type' => 'string',
294
+ 'description' => __( 'Default post format.' ),
295
+ ) );
296
+
297
+ register_setting( 'reading', 'posts_per_page', array(
298
+ 'show_in_rest' => true,
299
+ 'type' => 'number',
300
+ 'description' => __( 'Blog pages show at most.' ),
301
+ 'default' => 10,
302
+ ) );
303
+ }
304
+
305
+
306
  if ( ! function_exists( 'create_initial_rest_routes' ) ) {
307
  /**
308
  * Registers default REST API routes.
364
  // Comments.
365
  $controller = new WP_REST_Comments_Controller;
366
  $controller->register_routes();
367
+
368
+ // Settings. 4.7+ only.
369
+ global $wp_version;
370
+ if ( version_compare( $wp_version, '4.7-alpha', '>=' ) ) {
371
+ $controller = new WP_REST_Settings_Controller;
372
+ $controller->register_routes();
373
+ }
374
  }
375
  }
376
 
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === WordPress REST API (Version 2) ===
2
  Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
- Tags: json, rest, api, rest-api, wp-rest-api
4
- Requires at least: 4.4
5
- Tested up to: 4.6.1
6
- Stable tag: 2.0-beta14
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -38,6 +38,60 @@ Once you've installed and activated the plugin, [check out the documentation](ht
38
 
39
  == Changelog ==
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  = 2.0 Beta 13.0 (March 29, 2016) =
42
 
43
  * BREAKING CHANGE: Fix Content-Disposition header parsing.
1
  === WordPress REST API (Version 2) ===
2
  Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
+ Tags: json, rest, api, rest-api
4
+ Requires at least: 4.6
5
+ Tested up to: 4.7-alpha
6
+ Stable tag: 2.0-beta15
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
38
 
39
  == Changelog ==
40
 
41
+ = 2.0 Beta 15.0 (October 07, 2016) =
42
+
43
+ * Introduce support for Post Meta, Term Meta, User Meta, and Comment Meta in
44
+ their parent endpoints.
45
+
46
+ For your meta fields to be exposed in the REST API, you need to register
47
+ them. WordPress includes a `register_meta()` function which is not usually
48
+ required to get/set fields, but is required for API support.
49
+
50
+ To register your field, simply call register_meta and set the show_in_rest
51
+ flag to true. Note: register_meta must be called separately for each meta
52
+ key.
53
+
54
+ (props @rmccue, @danielbachhuber, @kjbenk, @duncanjbrown, [#2765][gh-2765])
55
+
56
+ * Introduce Settings endpoint.
57
+
58
+ Expose options to the REST API with the `register_setting()` function, by
59
+ passing `$args = array( 'show_in_rest' => true )`. Note: WordPress 4.7 is
60
+ required. See changeset [38635][https://core.trac.wordpress.org/changeset/38635].
61
+
62
+ (props @joehoyle, @fjarrett, @danielbachhuber, @jonathanbardo,
63
+ @greatislander, [#2739][gh-2739])
64
+
65
+ * Attachments controller, change permissions check to match core.
66
+
67
+ Check for the `upload_files` capability when creating an attachment.
68
+
69
+ (props @nullvariable, @adamsilverstein, [#2743][gh-2743])
70
+
71
+ * Add `?{taxonomy}_exclude=` query parameter
72
+
73
+ This mirrors our existing support for ?{taxonomy}= filtering in the posts
74
+ controller (which allows querying for only records with are associated with
75
+ any of the provided term IDs for the specified taxonomy) by adding an
76
+ equivalent `_exclude` variant to list IDs of terms for which associated posts
77
+ should NOT be returned.
78
+
79
+ (props @kadamwhite, [#2756][gh-2756])
80
+
81
+ * Use `get_comment_type()` when comparing updating comment status.
82
+
83
+ Comments having a empty `comment_type` within WordPress bites us again.
84
+ Fixes a bug where comments could not be updated because of bad comparison
85
+ logic.
86
+
87
+ (props @joehoyle, [#2753][gh-2753])
88
+
89
+ [gh-2765]: https://github.com/WP-API/WP-API/issues/2765
90
+ [gh-2739]: https://github.com/WP-API/WP-API/issues/2739
91
+ [gh-2743]: https://github.com/WP-API/WP-API/issues/2743
92
+ [gh-2756]: https://github.com/WP-API/WP-API/issues/2756
93
+ [gh-2753]: https://github.com/WP-API/WP-API/issues/2753
94
+
95
  = 2.0 Beta 13.0 (March 29, 2016) =
96
 
97
  * BREAKING CHANGE: Fix Content-Disposition header parsing.