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

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta4 to 2.0-beta5

CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2.0 Beta 4.0
4
 
5
  - Show public user information through the user controller.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 5.0
4
+
5
+ - Load api-core as a compatibility library
6
+
7
+ Now api-core has been merged into WordPress trunk (for 4.4) we should no longer load the infrastructure code
8
+ when it's already available. This also fixes a fatal error for users who were on trunk.
9
+
10
+ (props @rmccue)
11
+
12
+ - Switch to new mysql_to_rfc3339
13
+
14
+ (props @rmccue)
15
+
16
+ - Double-check term taxonomy
17
+
18
+ (props @rmccue)
19
+
20
+ - Load admin functions
21
+
22
+ This was removed from the latest beta of WordPress in the REST API infrastructure, a more long term fix is planned.
23
+
24
+ (props @joehoyle)
25
+
26
+ - Add Add compat shim for renamed `rest_mysql_to_rfc3339()`
27
+
28
+ (props @danielbachhuber)
29
+
30
+ - Compat shim for `wp_is_numeric_array()`
31
+
32
+ (props @danielbachhuber)
33
+
34
+ - Revert Switch to register_post_type_args filter
35
+
36
+ (props @joehoyle)
37
+
38
  ## 2.0 Beta 4.0
39
 
40
  - Show public user information through the user controller.
core/rest-api.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API functions.
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ */
8
+
9
+ /**
10
+ * Version number for our API.
11
+ *
12
+ * @var string
13
+ */
14
+ define( 'REST_API_VERSION', '2.0' );
15
+
16
+ /** Compatibility shims for PHP functions */
17
+ include_once( dirname( __FILE__ ) . '/wp-includes/compat.php' );
18
+
19
+ /** Main API functions */
20
+ include_once( dirname( __FILE__ ) . '/wp-includes/functions.php' );
21
+
22
+ /** WP_REST_Server class */
23
+ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-server.php' );
24
+
25
+ /** WP_HTTP_Response class */
26
+ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-http-response.php' );
27
+
28
+ /** WP_REST_Response class */
29
+ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-response.php' );
30
+
31
+ /** WP_REST_Request class */
32
+ require_once( dirname( __FILE__ ) . '/wp-includes/rest-api/class-wp-rest-request.php' );
33
+
34
+ /** REST functions */
35
+ include_once( dirname( __FILE__ ) . '/wp-includes/rest-api/rest-functions.php' );
36
+
37
+ /** REST filters */
38
+ include_once( dirname( __FILE__ ) . '/wp-includes/filters.php' );
39
+
40
+ /**
41
+ * Determines if the rewrite rules should be flushed.
42
+ *
43
+ * @since 4.4.0
44
+ */
45
+ function rest_api_maybe_flush_rewrites() {
46
+ $version = get_option( 'rest_api_plugin_version', null );
47
+
48
+ if ( empty( $version ) || REST_API_VERSION !== $version ) {
49
+ flush_rewrite_rules();
50
+ update_option( 'rest_api_plugin_version', REST_API_VERSION );
51
+ }
52
+ }
53
+ add_action( 'init', 'rest_api_maybe_flush_rewrites', 999 );
54
+
55
+ /**
56
+ * Registers routes and flush the rewrite rules on activation.
57
+ *
58
+ * @since 4.4.0
59
+ *
60
+ * @param bool $network_wide ?
61
+ */
62
+ function rest_api_activation( $network_wide ) {
63
+ if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
64
+ $mu_blogs = wp_get_sites();
65
+
66
+ foreach ( $mu_blogs as $mu_blog ) {
67
+ switch_to_blog( $mu_blog['blog_id'] );
68
+
69
+ rest_api_register_rewrites();
70
+ update_option( 'rest_api_plugin_version', null );
71
+ }
72
+
73
+ restore_current_blog();
74
+ } else {
75
+ rest_api_register_rewrites();
76
+ update_option( 'rest_api_plugin_version', null );
77
+ }
78
+ }
79
+ register_activation_hook( __FILE__, 'rest_api_activation' );
80
+
81
+ /**
82
+ * Flushes the rewrite rules on deactivation.
83
+ *
84
+ * @since 4.4.0
85
+ *
86
+ * @param bool $network_wide ?
87
+ */
88
+ function rest_api_deactivation( $network_wide ) {
89
+ if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
90
+
91
+ $mu_blogs = wp_get_sites();
92
+
93
+ foreach ( $mu_blogs as $mu_blog ) {
94
+ switch_to_blog( $mu_blog['blog_id'] );
95
+ delete_option( 'rest_api_plugin_version' );
96
+ }
97
+
98
+ restore_current_blog();
99
+ } else {
100
+ delete_option( 'rest_api_plugin_version' );
101
+ }
102
+ }
103
+ register_deactivation_hook( __FILE__, 'rest_api_deactivation' );
core/wp-includes/compat.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WordPress implementation for PHP functions either missing from older PHP
4
+ * versions or not included by default.
5
+ *
6
+ * REST API compatibility functions located in wp-includes/compat.php.
7
+ *
8
+ * @package PHP
9
+ */
10
+
11
+ if ( ! function_exists( 'json_last_error_msg' ) ) :
12
+ /**
13
+ * Retrieves the error string of the last json_encode() or json_decode() call.
14
+ *
15
+ * @since 4.4.0
16
+ *
17
+ * @internal This is a compatibility function for PHP <5.5
18
+ *
19
+ * @return bool|string Returns the error message on success, "No Error" if no error has occurred,
20
+ * or false on failure.
21
+ */
22
+ function json_last_error_msg() {
23
+ // See https://core.trac.wordpress.org/ticket/27799.
24
+ if ( ! function_exists( 'json_last_error' ) ) {
25
+ return false;
26
+ }
27
+
28
+ $last_error_code = json_last_error();
29
+
30
+ // Just in case JSON_ERROR_NONE is not defined.
31
+ $error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0;
32
+
33
+ switch ( true ) {
34
+ case $last_error_code === $error_code_none:
35
+ return 'No error';
36
+
37
+ case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code:
38
+ return 'Maximum stack depth exceeded';
39
+
40
+ case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code:
41
+ return 'State mismatch (invalid or malformed JSON)';
42
+
43
+ case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code:
44
+ return 'Control character error, possibly incorrectly encoded';
45
+
46
+ case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code:
47
+ return 'Syntax error';
48
+
49
+ case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code:
50
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
51
+
52
+ case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code:
53
+ return 'Recursion detected';
54
+
55
+ case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code:
56
+ return 'Inf and NaN cannot be JSON encoded';
57
+
58
+ case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
59
+ return 'Type is not supported';
60
+
61
+ default:
62
+ return 'An unknown error occurred';
63
+ }
64
+ }
65
+ endif;
66
+
67
+ if ( ! interface_exists( 'JsonSerializable' ) ) {
68
+ define( 'WP_JSON_SERIALIZE_COMPATIBLE', true );
69
+ /**
70
+ * JsonSerializable interface.
71
+ *
72
+ * Compatibility shim for PHP <5.4
73
+ *
74
+ * @link http://php.net/jsonserializable
75
+ *
76
+ * @since 4.4.0
77
+ */
78
+ interface JsonSerializable {
79
+ public function jsonSerialize();
80
+ }
81
+ }
core/wp-includes/filters.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API filters.
4
+ *
5
+ * REST API functions located in wp-includes/default-filters.php.
6
+ *
7
+ * @package WordPress
8
+ * @subpackage REST_API
9
+ */
10
+
11
+ // REST API filters.
12
+ add_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
13
+ add_action( 'wp_head', 'rest_output_link_wp_head', 10, 0 );
14
+ add_action( 'template_redirect', 'rest_output_link_header', 11, 0 );
15
+ add_action( 'auth_cookie_malformed', 'rest_cookie_collect_status' );
16
+ add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' );
17
+ add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' );
18
+ add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' );
19
+ add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' );
20
+ add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 );
21
+
22
+ // REST API actions.
23
+ add_action( 'init', 'rest_api_init' );
24
+ add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 );
25
+ add_action( 'parse_request', 'rest_api_loaded' );
core/wp-includes/functions.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Main WordPress API.
4
+ *
5
+ * REST API functions located in wp-includes/functions.php.
6
+ *
7
+ * @package WordPress
8
+ */
9
+
10
+ if ( ! function_exists( 'mysql_to_rfc3339' ) ) :
11
+ /**
12
+ * Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601/RFC3339.
13
+ *
14
+ * Explicitly strips timezones, as datetimes are not saved with any timezone
15
+ * information. Including any information on the offset could be misleading.
16
+ *
17
+ * @since 4.4.0
18
+ *
19
+ * @param string $date_string Date string to parse and format.
20
+ * @return string Date formatted for ISO8601/RFC3339.
21
+ */
22
+ function mysql_to_rfc3339( $date_string ) {
23
+ $formatted = mysql2date( 'c', $date_string, false );
24
+
25
+ // Strip timezone information
26
+ return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
27
+ }
28
+ endif;
core/wp-includes/rest-api.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API functions.
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ */
8
+
9
+ /**
10
+ * Version number for our API.
11
+ *
12
+ * @var string
13
+ */
14
+ define( 'REST_API_VERSION', '2.0' );
15
+
16
+ /** WP_REST_Server class */
17
+ require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' );
18
+
19
+ /** WP_REST_Response class */
20
+ require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' );
21
+
22
+ /** WP_REST_Request class */
23
+ require_once( ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' );
24
+
25
+ /** REST functions */
26
+ require_once( ABSPATH . WPINC . '/rest-api/rest-functions.php' );
core/wp-includes/rest-api/class-wp-http-response.php ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API: WP_HTTP_Response class
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ * @since 4.4.0
8
+ */
9
+
10
+ /**
11
+ * Core class used to prepare HTTP responses.
12
+ *
13
+ * @since 4.4.0
14
+ */
15
+ class WP_HTTP_Response {
16
+
17
+ /**
18
+ * Response data.
19
+ *
20
+ * @since 4.4.0
21
+ * @access public
22
+ * @var mixed
23
+ */
24
+ public $data;
25
+
26
+ /**
27
+ * Response headers.
28
+ *
29
+ * @since 4.4.0
30
+ * @access public
31
+ * @var int
32
+ */
33
+ public $headers;
34
+
35
+ /**
36
+ * Response status.
37
+ *
38
+ * @since 4.4.0
39
+ * @access public
40
+ * @var array
41
+ */
42
+ public $status;
43
+
44
+ /**
45
+ * Constructor.
46
+ *
47
+ * @since 4.4.0
48
+ * @access public
49
+ *
50
+ * @param mixed $data Response data. Default null.
51
+ * @param int $status Optional. HTTP status code. Default 200.
52
+ * @param array $headers Optional. HTTP header map. Default empty array.
53
+ */
54
+ public function __construct( $data = null, $status = 200, $headers = array() ) {
55
+ $this->data = $data;
56
+ $this->set_status( $status );
57
+ $this->set_headers( $headers );
58
+ }
59
+
60
+ /**
61
+ * Retrieves headers associated with the response.
62
+ *
63
+ * @since 4.4.0
64
+ * @access public
65
+ *
66
+ * @return array Map of header name to header value.
67
+ */
68
+ public function get_headers() {
69
+ return $this->headers;
70
+ }
71
+
72
+ /**
73
+ * Sets all header values.
74
+ *
75
+ * @since 4.4.0
76
+ * @access public
77
+ *
78
+ * @param array $headers Map of header name to header value.
79
+ */
80
+ public function set_headers( $headers ) {
81
+ $this->headers = $headers;
82
+ }
83
+
84
+ /**
85
+ * Sets a single HTTP header.
86
+ *
87
+ * @since 4.4.0
88
+ * @access public
89
+ *
90
+ * @param string $key Header name.
91
+ * @param string $value Header value.
92
+ * @param bool $replace Optional. Whether to replace an existing header of the same name.
93
+ * Default true.
94
+ */
95
+ public function header( $key, $value, $replace = true ) {
96
+ if ( $replace || ! isset( $this->headers[ $key ] ) ) {
97
+ $this->headers[ $key ] = $value;
98
+ } else {
99
+ $this->headers[ $key ] .= ', ' . $value;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Retrieves the HTTP return code for the response.
105
+ *
106
+ * @since 4.4.0
107
+ * @access public
108
+ *
109
+ * @return int The 3-digit HTTP status code.
110
+ */
111
+ public function get_status() {
112
+ return $this->status;
113
+ }
114
+
115
+ /**
116
+ * Sets the 3-digit HTTP status code.
117
+ *
118
+ * @since 4.4.0
119
+ * @access public
120
+ *
121
+ * @param int $code HTTP status.
122
+ */
123
+ public function set_status( $code ) {
124
+ $this->status = absint( $code );
125
+ }
126
+
127
+ /**
128
+ * Retrieves the response data.
129
+ *
130
+ * @since 4.4.0
131
+ * @access public
132
+ *
133
+ * @return mixed Response data.
134
+ */
135
+ public function get_data() {
136
+ return $this->data;
137
+ }
138
+
139
+ /**
140
+ * Sets the response data.
141
+ *
142
+ * @since 4.4.0
143
+ * @access public
144
+ *
145
+ * @param mixed $data Response data.
146
+ */
147
+ public function set_data( $data ) {
148
+ $this->data = $data;
149
+ }
150
+
151
+ /**
152
+ * Retrieves the response data for JSON serialization.
153
+ *
154
+ * It is expected that in most implementations, this will return the same as get_data(),
155
+ * however this may be different if you want to do custom JSON data handling.
156
+ *
157
+ * @since 4.4.0
158
+ * @access public
159
+ *
160
+ * @return mixed Any JSON-serializable value.
161
+ */
162
+ public function jsonSerialize() {
163
+ return $this->get_data();
164
+ }
165
+ }
{lib/infrastructure → core/wp-includes/rest-api}/class-wp-rest-request.php RENAMED
@@ -1,7 +1,14 @@
1
  <?php
 
 
 
 
 
 
 
2
 
3
  /**
4
- * Request object
5
  *
6
  * Contains data from the request, to be passed to the callback.
7
  *
@@ -9,75 +16,101 @@
9
  * used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
10
  * so be aware it may have non-array behaviour in some cases.
11
  *
12
- * @package WordPress
 
 
13
  */
14
  class WP_REST_Request implements ArrayAccess {
 
15
  /**
16
- * HTTP method
17
  *
 
 
18
  * @var string
19
  */
20
  protected $method = '';
21
 
22
  /**
23
- * Parameters passed to the request
24
  *
25
  * These typically come from the `$_GET`, `$_POST` and `$_FILES`
26
  * superglobals when being created from the global scope.
27
  *
28
- * @var array Contains GET, POST and FILES keys mapping to arrays of data
 
 
29
  */
30
  protected $params;
31
 
32
  /**
33
- * HTTP headers for the request
34
  *
35
- * @var array Map of key to value. Key is always lowercase, as per HTTP specification
 
 
36
  */
37
  protected $headers = array();
38
 
39
  /**
40
- * Body data
41
  *
42
- * @var string Binary data from the request
 
 
43
  */
44
  protected $body = null;
45
 
46
  /**
47
- * Route matched for the request
48
  *
 
 
49
  * @var string
50
  */
51
  protected $route;
52
 
53
  /**
54
- * Attributes (options) for the route that was matched
55
  *
56
  * This is the options array used when the route was registered, typically
57
  * containing the callback as well as the valid methods for the route.
58
  *
59
- * @return array Attributes for the request
 
 
60
  */
61
  protected $attributes = array();
62
 
63
  /**
64
- * Have we parsed the JSON data yet?
65
  *
66
  * Allows lazy-parsing of JSON data where possible.
67
  *
68
- * @var boolean
 
 
69
  */
70
  protected $parsed_json = false;
71
 
72
  /**
73
- * Have we parsed body data yet?
74
  *
75
- * @var boolean
 
 
76
  */
77
  protected $parsed_body = false;
78
 
79
  /**
80
- * Constructor
 
 
 
 
 
 
 
81
  */
82
  public function __construct( $method = '', $route = '', $attributes = array() ) {
83
  $this->params = array(
@@ -86,7 +119,7 @@ class WP_REST_Request implements ArrayAccess {
86
  'POST' => array(),
87
  'FILES' => array(),
88
 
89
- // See parse_json_params
90
  'JSON' => null,
91
 
92
  'defaults' => array(),
@@ -98,34 +131,43 @@ class WP_REST_Request implements ArrayAccess {
98
  }
99
 
100
  /**
101
- * Get HTTP method for the request
102
  *
103
- * @return string HTTP method
 
 
 
104
  */
105
  public function get_method() {
106
  return $this->method;
107
  }
108
 
109
  /**
110
- * Set HTTP method for the request
 
 
 
111
  *
112
- * @param string $method HTTP method
113
  */
114
  public function set_method( $method ) {
115
  $this->method = strtoupper( $method );
116
  }
117
 
118
  /**
119
- * Get all headers from the request
120
  *
121
- * @return array Map of key to value. Key is always lowercase, as per HTTP specification
 
 
 
122
  */
123
  public function get_headers() {
124
  return $this->headers;
125
  }
126
 
127
  /**
128
- * Canonicalize header name
129
  *
130
  * Ensures that header names are always treated the same regardless of
131
  * source. Header names are always case insensitive.
@@ -137,8 +179,12 @@ class WP_REST_Request implements ArrayAccess {
137
  * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
138
  * @link http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
139
  *
140
- * @param string $key Header name
141
- * @return string Canonicalized name
 
 
 
 
142
  */
143
  public static function canonicalize_header_name( $key ) {
144
  $key = strtolower( $key );
@@ -148,14 +194,17 @@ class WP_REST_Request implements ArrayAccess {
148
  }
149
 
150
  /**
151
- * Get header from request
152
  *
153
  * If the header has multiple values, they will be concatenated with a comma
154
  * as per the HTTP specification. Be aware that some non-compliant headers
155
  * (notably cookie headers) cannot be joined this way.
156
  *
157
- * @param string $key Header name, will be canonicalized to lowercase
158
- * @return string|null String value if set, null otherwise
 
 
 
159
  */
160
  public function get_header( $key ) {
161
  $key = $this->canonicalize_header_name( $key );
@@ -168,10 +217,13 @@ class WP_REST_Request implements ArrayAccess {
168
  }
169
 
170
  /**
171
- * Get header values from request
 
 
 
172
  *
173
- * @param string $key Header name, will be canonicalized to lowercase
174
- * @return array|null List of string values if set, null otherwise
175
  */
176
  public function get_header_as_array( $key ) {
177
  $key = $this->canonicalize_header_name( $key );
@@ -184,10 +236,13 @@ class WP_REST_Request implements ArrayAccess {
184
  }
185
 
186
  /**
187
- * Set header on request
188
  *
189
- * @param string $key Header name
190
- * @param string|string[] $value Header value, or list of values
 
 
 
191
  */
192
  public function set_header( $key, $value ) {
193
  $key = $this->canonicalize_header_name( $key );
@@ -197,10 +252,13 @@ class WP_REST_Request implements ArrayAccess {
197
  }
198
 
199
  /**
200
- * Append a header value for the given header
 
 
 
201
  *
202
- * @param string $key Header name
203
- * @param string|string[] $value Header value, or list of values
204
  */
205
  public function add_header( $key, $value ) {
206
  $key = $this->canonicalize_header_name( $key );
@@ -214,19 +272,25 @@ class WP_REST_Request implements ArrayAccess {
214
  }
215
 
216
  /**
217
- * Remove all values for a header
218
  *
219
- * @param string $key Header name
 
 
 
220
  */
221
  public function remove_header( $key ) {
222
  unset( $this->headers[ $key ] );
223
  }
224
 
225
  /**
226
- * Set headers on the request
 
 
 
227
  *
228
- * @param array $headers Map of header name to value
229
- * @param boolean $override If true, replace the request's headers. Otherwise, merge with existing.
230
  */
231
  public function set_headers( $headers, $override = true ) {
232
  if ( true === $override ) {
@@ -239,9 +303,12 @@ class WP_REST_Request implements ArrayAccess {
239
  }
240
 
241
  /**
242
- * Get the content-type of the request
 
 
 
243
  *
244
- * @return array Map containing 'value' and 'parameters' keys
245
  */
246
  public function get_content_type() {
247
  $value = $this->get_header( 'content-type' );
@@ -259,7 +326,7 @@ class WP_REST_Request implements ArrayAccess {
259
  return null;
260
  }
261
 
262
- // Parse type and subtype out
263
  list( $type, $subtype ) = explode( '/', $value, 2 );
264
 
265
  $data = compact( 'value', 'type', 'subtype', 'parameters' );
@@ -269,11 +336,14 @@ class WP_REST_Request implements ArrayAccess {
269
  }
270
 
271
  /**
272
- * Get the parameter priority order
273
  *
274
- * Used when checking parameters in {@see get_param}.
275
  *
276
- * @return string[] List of types to check, in order of priority
 
 
 
277
  */
278
  protected function get_parameter_order() {
279
  $order = array();
@@ -281,7 +351,7 @@ class WP_REST_Request implements ArrayAccess {
281
 
282
  $this->parse_json_params();
283
 
284
- // Ensure we parse the body data
285
  $body = $this->get_body();
286
  if ( $this->method !== 'POST' && ! empty( $body ) ) {
287
  $this->parse_body_params();
@@ -297,29 +367,37 @@ class WP_REST_Request implements ArrayAccess {
297
  $order[] = 'defaults';
298
 
299
  /**
300
- * Alter the parameter checking order
 
 
 
301
  *
302
- * The order affects which parameters are checked when using
303
- * {@see get_param} and family. This acts similarly to PHP's
304
- * `request_order` setting.
305
  *
306
- * @param string[] $order List of types to check, in order of priority
307
- * @param WP_REST_Request $this Request object
 
 
 
 
308
  */
309
  return apply_filters( 'rest_request_parameter_order', $order, $this );
310
  }
311
 
312
  /**
313
- * Get a parameter from the request
 
 
 
314
  *
315
- * @param string $key Parameter name
316
- * @return mixed|null Value if set, null otherwise
317
  */
318
  public function get_param( $key ) {
319
  $order = $this->get_parameter_order();
320
 
321
  foreach ( $order as $type ) {
322
- // Do we have the parameter for this type?
323
  if ( isset( $this->params[ $type ][ $key ] ) ) {
324
  return $this->params[ $type ][ $key ];
325
  }
@@ -329,10 +407,13 @@ class WP_REST_Request implements ArrayAccess {
329
  }
330
 
331
  /**
332
- * Set a parameter on the request
333
  *
334
- * @param string $key Parameter name
335
- * @param mixed $value Parameter value
 
 
 
336
  */
337
  public function set_param( $key, $value ) {
338
  switch ( $this->method ) {
@@ -347,12 +428,15 @@ class WP_REST_Request implements ArrayAccess {
347
  }
348
 
349
  /**
350
- * Get merged parameters from the request
 
 
 
351
  *
352
- * The equivalent of {@see get_param}, but returns all parameters for the
353
- * request. Handles merging all the available values into a single array.
354
  *
355
- * @return array Map of key to value
356
  */
357
  public function get_params() {
358
  $order = $this->get_parameter_order();
@@ -367,31 +451,40 @@ class WP_REST_Request implements ArrayAccess {
367
  }
368
 
369
  /**
370
- * Get parameters from the route itself
371
  *
372
  * These are parsed from the URL using the regex.
373
  *
374
- * @return array Parameter map of key to value
 
 
 
375
  */
376
  public function get_url_params() {
377
  return $this->params['URL'];
378
  }
379
 
380
  /**
381
- * Set parameters from the route
382
  *
383
  * Typically, this is set after parsing the URL.
384
  *
385
- * @param array $params Parameter map of key to value
 
 
 
386
  */
387
  public function set_url_params( $params ) {
388
  $this->params['URL'] = $params;
389
  }
390
 
391
  /**
392
- * Get parameters from the query string
 
 
393
  *
394
- * These are the parameters you'd typically find in `$_GET`
 
395
  *
396
  * @return array Parameter map of key to value
397
  */
@@ -400,42 +493,54 @@ class WP_REST_Request implements ArrayAccess {
400
  }
401
 
402
  /**
403
- * Set parameters from the query string
404
  *
405
- * Typically, this is set from `$_GET`
406
  *
407
- * @param array $params Parameter map of key to value
 
 
 
408
  */
409
  public function set_query_params( $params ) {
410
  $this->params['GET'] = $params;
411
  }
412
 
413
  /**
414
- * Get parameters from the body
415
  *
416
- * These are the parameters you'd typically find in `$_POST`
417
  *
418
- * @return array Parameter map of key to value
 
 
 
419
  */
420
  public function get_body_params() {
421
  return $this->params['POST'];
422
  }
423
 
424
  /**
425
- * Set parameters from the body
 
 
426
  *
427
- * Typically, this is set from `$_POST`
 
428
  *
429
- * @param array $params Parameter map of key to value
430
  */
431
  public function set_body_params( $params ) {
432
  $this->params['POST'] = $params;
433
  }
434
 
435
  /**
436
- * Get multipart file parameters from the body
437
  *
438
- * These are the parameters you'd typically find in `$_FILES`
 
 
 
439
  *
440
  * @return array Parameter map of key to value
441
  */
@@ -444,20 +549,26 @@ class WP_REST_Request implements ArrayAccess {
444
  }
445
 
446
  /**
447
- * Set multipart file parameters from the body
 
 
448
  *
449
- * Typically, this is set from `$_FILES`
 
450
  *
451
- * @param array $params Parameter map of key to value
452
  */
453
  public function set_file_params( $params ) {
454
  $this->params['FILES'] = $params;
455
  }
456
 
457
  /**
458
- * Get default parameters
 
 
459
  *
460
- * These are the parameters set in the route registration
 
461
  *
462
  * @return array Parameter map of key to value
463
  */
@@ -466,74 +577,93 @@ class WP_REST_Request implements ArrayAccess {
466
  }
467
 
468
  /**
469
- * Set default parameters
470
  *
471
- * These are the parameters set in the route registration
472
  *
473
- * @param array $params Parameter map of key to value
 
 
 
474
  */
475
  public function set_default_params( $params ) {
476
  $this->params['defaults'] = $params;
477
  }
478
 
479
  /**
480
- * Get body content
 
 
 
481
  *
482
- * @return string Binary data from the request body
483
  */
484
  public function get_body() {
485
  return $this->body;
486
  }
487
 
488
  /**
489
- * Set body content
490
  *
491
- * @param string $data Binary data from the request body
 
 
 
492
  */
493
  public function set_body( $data ) {
494
  $this->body = $data;
495
 
496
- // Enable lazy parsing
497
  $this->parsed_json = false;
498
  $this->parsed_body = false;
499
  $this->params['JSON'] = null;
500
  }
501
 
502
  /**
503
- * Get parameters from a JSON-formatted body
504
  *
505
- * @return array Parameter map of key to value
 
 
 
506
  */
507
  public function get_json_params() {
508
- // Ensure the parameters have been parsed out
509
  $this->parse_json_params();
510
 
511
  return $this->params['JSON'];
512
  }
513
 
514
  /**
515
- * Parse the JSON parameters
516
  *
517
  * Avoids parsing the JSON data until we need to access it.
 
 
 
518
  */
519
  protected function parse_json_params() {
520
  if ( $this->parsed_json ) {
521
  return;
522
  }
 
523
  $this->parsed_json = true;
524
 
525
- // Check that we actually got JSON
526
  $content_type = $this->get_content_type();
 
527
  if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
528
  return;
529
  }
530
 
531
  $params = json_decode( $this->get_body(), true );
532
 
533
- // Check for a parsing error
534
- //
535
- // Note that due to WP's JSON compatibility functions, json_last_error
536
- // might not be defined: https://core.trac.wordpress.org/ticket/27799
 
 
537
  if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
538
  return;
539
  }
@@ -542,90 +672,115 @@ class WP_REST_Request implements ArrayAccess {
542
  }
543
 
544
  /**
545
- * Parse body parameters.
546
  *
547
  * Parses out URL-encoded bodies for request methods that aren't supported
548
  * natively by PHP. In PHP 5.x, only POST has these parsed automatically.
 
 
 
549
  */
550
  protected function parse_body_params() {
551
  if ( $this->parsed_body ) {
552
  return;
553
  }
 
554
  $this->parsed_body = true;
555
 
556
- // Check that we got URL-encoded. Treat a missing content-type as
557
- // URL-encoded for maximum compatibility
 
 
558
  $content_type = $this->get_content_type();
 
559
  if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
560
  return;
561
  }
562
 
563
  parse_str( $this->get_body(), $params );
564
 
565
- // Amazingly, parse_str follows magic quote rules. Sigh.
566
- // NOTE: Do not refactor to use `wp_unslash`.
567
- // @codeCoverageIgnoreStart
 
 
568
  if ( get_magic_quotes_gpc() ) {
569
  $params = stripslashes_deep( $params );
570
  }
571
- // @codeCoverageIgnoreEnd
572
 
573
- // Add to the POST parameters stored internally. If a user has already
574
- // set these manually (via `set_body_params`), don't override them.
 
 
575
  $this->params['POST'] = array_merge( $params, $this->params['POST'] );
576
  }
577
 
578
  /**
579
- * Get route that matched the request
 
 
 
580
  *
581
- * @return string Route matching regex
582
  */
583
  public function get_route() {
584
  return $this->route;
585
  }
586
 
587
  /**
588
- * Set route that matched the request
589
  *
590
- * @param string $route Route matching regex
 
 
 
591
  */
592
  public function set_route( $route ) {
593
  $this->route = $route;
594
  }
595
 
596
  /**
597
- * Get attributes for the request
598
  *
599
  * These are the options for the route that was matched.
600
  *
601
- * @return array Attributes for the request
 
 
 
602
  */
603
  public function get_attributes() {
604
  return $this->attributes;
605
  }
606
 
607
  /**
608
- * Set attributes for the request
609
  *
610
- * @param array $attributes Attributes for the request
 
 
 
611
  */
612
  public function set_attributes( $attributes ) {
613
  $this->attributes = $attributes;
614
  }
615
 
616
  /**
617
- * Sanitize (where possible) the params on the request.
618
  *
619
  * This is primarily based off the sanitize_callback param on each registered
620
  * argument.
621
  *
622
- * @return null
 
 
 
623
  */
624
  public function sanitize_params() {
625
 
626
  $attributes = $this->get_attributes();
627
 
628
- // No arguments set, skip sanitizing
629
  if ( empty( $attributes['args'] ) ) {
630
  return true;
631
  }
@@ -637,25 +792,30 @@ class WP_REST_Request implements ArrayAccess {
637
  continue;
638
  }
639
  foreach ( $this->params[ $type ] as $key => $value ) {
640
- // check if this param has a sanitize_callback added
641
  if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
642
  $this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
643
  }
644
  }
645
  }
 
646
  }
647
 
648
  /**
649
- * Check whether this request is valid according to its attributes
 
 
 
650
  *
651
- * @return bool|WP_Error
 
652
  */
653
  public function has_valid_params() {
654
 
655
  $attributes = $this->get_attributes();
656
  $required = array();
657
 
658
- // No arguments set, skip validation
659
  if ( empty( $attributes['args'] ) ) {
660
  return true;
661
  }
@@ -672,8 +832,11 @@ class WP_REST_Request implements ArrayAccess {
672
  return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
673
  }
674
 
675
- // check the validation callbacks for each registered arg.
676
- // This is done after required checking as required checking is cheaper.
 
 
 
677
  $invalid_params = array();
678
 
679
  foreach ( $attributes['args'] as $key => $arg ) {
@@ -702,14 +865,15 @@ class WP_REST_Request implements ArrayAccess {
702
  }
703
 
704
  /**
705
- * Check if a parameter is set
706
  *
707
- * @param string $key Parameter name
708
- * @return boolean
 
 
 
709
  */
710
- // @codingStandardsIgnoreStart
711
  public function offsetExists( $offset ) {
712
- // @codingStandardsIgnoreEnd
713
  $order = $this->get_parameter_order();
714
 
715
  foreach ( $order as $type ) {
@@ -722,41 +886,43 @@ class WP_REST_Request implements ArrayAccess {
722
  }
723
 
724
  /**
725
- * Get a parameter from the request
 
 
 
726
  *
727
- * @param string $key Parameter name
728
- * @return mixed|null Value if set, null otherwise
729
  */
730
- // @codingStandardsIgnoreStart
731
  public function offsetGet( $offset ) {
732
- // @codingStandardsIgnoreEnd
733
  return $this->get_param( $offset );
734
  }
735
 
736
  /**
737
- * Set a parameter on the request
738
  *
739
- * @param string $key Parameter name
740
- * @param mixed $value Parameter value
 
 
 
741
  */
742
- // @codingStandardsIgnoreStart
743
  public function offsetSet( $offset, $value ) {
744
- // @codingStandardsIgnoreEnd
745
- return $this->set_param( $offset, $value );
746
  }
747
 
748
  /**
749
- * Remove a parameter from the request
 
 
 
750
  *
751
- * @param string $key Parameter name
752
- * @param mixed $value Parameter value
753
  */
754
- // @codingStandardsIgnoreStart
755
  public function offsetUnset( $offset ) {
756
- // @codingStandardsIgnoreEnd
757
  $order = $this->get_parameter_order();
758
 
759
- // Remove the offset from every group
760
  foreach ( $order as $type ) {
761
  unset( $this->params[ $type ][ $offset ] );
762
  }
1
  <?php
2
+ /**
3
+ * REST API: WP_REST_Request class
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ * @since 4.4.0
8
+ */
9
 
10
  /**
11
+ * Core class used to implement a REST request object.
12
  *
13
  * Contains data from the request, to be passed to the callback.
14
  *
16
  * used in that manner. It does not use ArrayObject (as we cannot rely on SPL),
17
  * so be aware it may have non-array behaviour in some cases.
18
  *
19
+ * @since 4.4.0
20
+ *
21
+ * @see ArrayAccess
22
  */
23
  class WP_REST_Request implements ArrayAccess {
24
+
25
  /**
26
+ * HTTP method.
27
  *
28
+ * @since 4.4.0
29
+ * @access protected
30
  * @var string
31
  */
32
  protected $method = '';
33
 
34
  /**
35
+ * Parameters passed to the request.
36
  *
37
  * These typically come from the `$_GET`, `$_POST` and `$_FILES`
38
  * superglobals when being created from the global scope.
39
  *
40
+ * @since 4.4.0
41
+ * @access protected
42
+ * @var array Contains GET, POST and FILES keys mapping to arrays of data.
43
  */
44
  protected $params;
45
 
46
  /**
47
+ * HTTP headers for the request.
48
  *
49
+ * @since 4.4.0
50
+ * @access protected
51
+ * @var array Map of key to value. Key is always lowercase, as per HTTP specification.
52
  */
53
  protected $headers = array();
54
 
55
  /**
56
+ * Body data.
57
  *
58
+ * @since 4.4.0
59
+ * @access protected
60
+ * @var string Binary data from the request.
61
  */
62
  protected $body = null;
63
 
64
  /**
65
+ * Route matched for the request.
66
  *
67
+ * @since 4.4.0
68
+ * @access protected
69
  * @var string
70
  */
71
  protected $route;
72
 
73
  /**
74
+ * Attributes (options) for the route that was matched.
75
  *
76
  * This is the options array used when the route was registered, typically
77
  * containing the callback as well as the valid methods for the route.
78
  *
79
+ * @since 4.4.0
80
+ * @access protected
81
+ * @var array Attributes for the request.
82
  */
83
  protected $attributes = array();
84
 
85
  /**
86
+ * Used to determine if the JSON data has been parsed yet.
87
  *
88
  * Allows lazy-parsing of JSON data where possible.
89
  *
90
+ * @since 4.4.0
91
+ * @access protected
92
+ * @var bool
93
  */
94
  protected $parsed_json = false;
95
 
96
  /**
97
+ * Used to determine if the body data has been parsed yet.
98
  *
99
+ * @since 4.4.0
100
+ * @access protected
101
+ * @var bool
102
  */
103
  protected $parsed_body = false;
104
 
105
  /**
106
+ * Constructor.
107
+ *
108
+ * @since 4.4.0
109
+ * @access public
110
+ *
111
+ * @param string $method Optional. Request method. Default empty.
112
+ * @param string $route Optional. Request route. Default empty.
113
+ * @param array $attributes Optional. Request attributes. Default empty array.
114
  */
115
  public function __construct( $method = '', $route = '', $attributes = array() ) {
116
  $this->params = array(
119
  'POST' => array(),
120
  'FILES' => array(),
121
 
122
+ // See parse_json_params.
123
  'JSON' => null,
124
 
125
  'defaults' => array(),
131
  }
132
 
133
  /**
134
+ * Retrieves the HTTP method for the request.
135
  *
136
+ * @since 4.4.0
137
+ * @access public
138
+ *
139
+ * @return string HTTP method.
140
  */
141
  public function get_method() {
142
  return $this->method;
143
  }
144
 
145
  /**
146
+ * Sets HTTP method for the request.
147
+ *
148
+ * @since 4.4.0
149
+ * @access public
150
  *
151
+ * @param string $method HTTP method.
152
  */
153
  public function set_method( $method ) {
154
  $this->method = strtoupper( $method );
155
  }
156
 
157
  /**
158
+ * Retrieves all headers from the request.
159
  *
160
+ * @since 4.4.0
161
+ * @access public
162
+ *
163
+ * @return array Map of key to value. Key is always lowercase, as per HTTP specification.
164
  */
165
  public function get_headers() {
166
  return $this->headers;
167
  }
168
 
169
  /**
170
+ * Canonicalizes the header name.
171
  *
172
  * Ensures that header names are always treated the same regardless of
173
  * source. Header names are always case insensitive.
179
  * @link http://wiki.nginx.org/Pitfalls#Missing_.28disappearing.29_HTTP_headers
180
  * @link http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
181
  *
182
+ * @since 4.4.0
183
+ * @access public
184
+ * @static
185
+ *
186
+ * @param string $key Header name.
187
+ * @return string Canonicalized name.
188
  */
189
  public static function canonicalize_header_name( $key ) {
190
  $key = strtolower( $key );
194
  }
195
 
196
  /**
197
+ * Retrieves the given header from the request.
198
  *
199
  * If the header has multiple values, they will be concatenated with a comma
200
  * as per the HTTP specification. Be aware that some non-compliant headers
201
  * (notably cookie headers) cannot be joined this way.
202
  *
203
+ * @since 4.4.0
204
+ * @access public
205
+ *
206
+ * @param string $key Header name, will be canonicalized to lowercase.
207
+ * @return string|null String value if set, null otherwise.
208
  */
209
  public function get_header( $key ) {
210
  $key = $this->canonicalize_header_name( $key );
217
  }
218
 
219
  /**
220
+ * Retrieves header values from the request.
221
+ *
222
+ * @since 4.4.0
223
+ * @access public
224
  *
225
+ * @param string $key Header name, will be canonicalized to lowercase.
226
+ * @return array|null List of string values if set, null otherwise.
227
  */
228
  public function get_header_as_array( $key ) {
229
  $key = $this->canonicalize_header_name( $key );
236
  }
237
 
238
  /**
239
+ * Sets the header on request.
240
  *
241
+ * @since 4.4.0
242
+ * @access public
243
+ *
244
+ * @param string $key Header name.
245
+ * @param string $value Header value, or list of values.
246
  */
247
  public function set_header( $key, $value ) {
248
  $key = $this->canonicalize_header_name( $key );
252
  }
253
 
254
  /**
255
+ * Appends a header value for the given header.
256
+ *
257
+ * @since 4.4.0
258
+ * @access public
259
  *
260
+ * @param string $key Header name.
261
+ * @param string $value Header value, or list of values.
262
  */
263
  public function add_header( $key, $value ) {
264
  $key = $this->canonicalize_header_name( $key );
272
  }
273
 
274
  /**
275
+ * Removes all values for a header.
276
  *
277
+ * @since 4.4.0
278
+ * @access public
279
+ *
280
+ * @param string $key Header name.
281
  */
282
  public function remove_header( $key ) {
283
  unset( $this->headers[ $key ] );
284
  }
285
 
286
  /**
287
+ * Sets headers on the request.
288
+ *
289
+ * @since 4.4.0
290
+ * @access public
291
  *
292
+ * @param array $headers Map of header name to value.
293
+ * @param bool $override If true, replace the request's headers. Otherwise, merge with existing.
294
  */
295
  public function set_headers( $headers, $override = true ) {
296
  if ( true === $override ) {
303
  }
304
 
305
  /**
306
+ * Retrieves the content-type of the request.
307
+ *
308
+ * @since 4.4.0
309
+ * @access public
310
  *
311
+ * @return array Map containing 'value' and 'parameters' keys.
312
  */
313
  public function get_content_type() {
314
  $value = $this->get_header( 'content-type' );
326
  return null;
327
  }
328
 
329
+ // Parse type and subtype out.
330
  list( $type, $subtype ) = explode( '/', $value, 2 );
331
 
332
  $data = compact( 'value', 'type', 'subtype', 'parameters' );
336
  }
337
 
338
  /**
339
+ * Retrieves the parameter priority order.
340
  *
341
+ * Used when checking parameters in get_param().
342
  *
343
+ * @since 4.4.0
344
+ * @access public
345
+ *
346
+ * @return array List of types to check, in order of priority.
347
  */
348
  protected function get_parameter_order() {
349
  $order = array();
351
 
352
  $this->parse_json_params();
353
 
354
+ // Ensure we parse the body data.
355
  $body = $this->get_body();
356
  if ( $this->method !== 'POST' && ! empty( $body ) ) {
357
  $this->parse_body_params();
367
  $order[] = 'defaults';
368
 
369
  /**
370
+ * Filter the parameter order.
371
+ *
372
+ * The order affects which parameters are checked when using get_param() and family.
373
+ * This acts similarly to PHP's `request_order` setting.
374
  *
375
+ * @since 4.4.0
 
 
376
  *
377
+ * @param array $order {
378
+ * An array of types to check, in order of priority.
379
+ *
380
+ * @param string $type The type to check.
381
+ * }
382
+ * @param WP_REST_Request $this The request object.
383
  */
384
  return apply_filters( 'rest_request_parameter_order', $order, $this );
385
  }
386
 
387
  /**
388
+ * Retrieves a parameter from the request.
389
+ *
390
+ * @since 4.4.0
391
+ * @access public
392
  *
393
+ * @param string $key Parameter name.
394
+ * @return mixed|null Value if set, null otherwise.
395
  */
396
  public function get_param( $key ) {
397
  $order = $this->get_parameter_order();
398
 
399
  foreach ( $order as $type ) {
400
+ // Determine if we have the parameter for this type.
401
  if ( isset( $this->params[ $type ][ $key ] ) ) {
402
  return $this->params[ $type ][ $key ];
403
  }
407
  }
408
 
409
  /**
410
+ * Sets a parameter on the request.
411
  *
412
+ * @since 4.4.0
413
+ * @access public
414
+ *
415
+ * @param string $key Parameter name.
416
+ * @param mixed $value Parameter value.
417
  */
418
  public function set_param( $key, $value ) {
419
  switch ( $this->method ) {
428
  }
429
 
430
  /**
431
+ * Retrieves merged parameters from the request.
432
+ *
433
+ * The equivalent of get_param(), but returns all parameters for the request.
434
+ * Handles merging all the available values into a single array.
435
  *
436
+ * @since 4.4.0
437
+ * @access public
438
  *
439
+ * @return array Map of key to value.
440
  */
441
  public function get_params() {
442
  $order = $this->get_parameter_order();
451
  }
452
 
453
  /**
454
+ * Retrieves parameters from the route itself.
455
  *
456
  * These are parsed from the URL using the regex.
457
  *
458
+ * @since 4.4.0
459
+ * @access public
460
+ *
461
+ * @return array Parameter map of key to value.
462
  */
463
  public function get_url_params() {
464
  return $this->params['URL'];
465
  }
466
 
467
  /**
468
+ * Sets parameters from the route.
469
  *
470
  * Typically, this is set after parsing the URL.
471
  *
472
+ * @since 4.4.0
473
+ * @access public
474
+ *
475
+ * @param array $params Parameter map of key to value.
476
  */
477
  public function set_url_params( $params ) {
478
  $this->params['URL'] = $params;
479
  }
480
 
481
  /**
482
+ * Retrieves parameters from the query string.
483
+ *
484
+ * These are the parameters you'd typically find in `$_GET`.
485
  *
486
+ * @since 4.4.0
487
+ * @access public
488
  *
489
  * @return array Parameter map of key to value
490
  */
493
  }
494
 
495
  /**
496
+ * Sets parameters from the query string.
497
  *
498
+ * Typically, this is set from `$_GET`.
499
  *
500
+ * @since 4.4.0
501
+ * @access public
502
+ *
503
+ * @param array $params Parameter map of key to value.
504
  */
505
  public function set_query_params( $params ) {
506
  $this->params['GET'] = $params;
507
  }
508
 
509
  /**
510
+ * Retrieves parameters from the body.
511
  *
512
+ * These are the parameters you'd typically find in `$_POST`.
513
  *
514
+ * @since 4.4.0
515
+ * @access public
516
+ *
517
+ * @return array Parameter map of key to value.
518
  */
519
  public function get_body_params() {
520
  return $this->params['POST'];
521
  }
522
 
523
  /**
524
+ * Sets parameters from the body.
525
+ *
526
+ * Typically, this is set from `$_POST`.
527
  *
528
+ * @since 4.4.0
529
+ * @access public
530
  *
531
+ * @param array $params Parameter map of key to value.
532
  */
533
  public function set_body_params( $params ) {
534
  $this->params['POST'] = $params;
535
  }
536
 
537
  /**
538
+ * Retrieves multipart file parameters from the body.
539
  *
540
+ * These are the parameters you'd typically find in `$_FILES`.
541
+ *
542
+ * @since 4.4.0
543
+ * @access public
544
  *
545
  * @return array Parameter map of key to value
546
  */
549
  }
550
 
551
  /**
552
+ * Sets multipart file parameters from the body.
553
+ *
554
+ * Typically, this is set from `$_FILES`.
555
  *
556
+ * @since 4.4.0
557
+ * @access public
558
  *
559
+ * @param array $params Parameter map of key to value.
560
  */
561
  public function set_file_params( $params ) {
562
  $this->params['FILES'] = $params;
563
  }
564
 
565
  /**
566
+ * Retrieves the default parameters.
567
+ *
568
+ * These are the parameters set in the route registration.
569
  *
570
+ * @since 4.4.0
571
+ * @access public
572
  *
573
  * @return array Parameter map of key to value
574
  */
577
  }
578
 
579
  /**
580
+ * Sets default parameters.
581
  *
582
+ * These are the parameters set in the route registration.
583
  *
584
+ * @since 4.4.0
585
+ * @access public
586
+ *
587
+ * @param array $params Parameter map of key to value.
588
  */
589
  public function set_default_params( $params ) {
590
  $this->params['defaults'] = $params;
591
  }
592
 
593
  /**
594
+ * Retrieves the request body content.
595
+ *
596
+ * @since 4.4.0
597
+ * @access public
598
  *
599
+ * @return string Binary data from the request body.
600
  */
601
  public function get_body() {
602
  return $this->body;
603
  }
604
 
605
  /**
606
+ * Sets body content.
607
  *
608
+ * @since 4.4.0
609
+ * @access public
610
+ *
611
+ * @param string $data Binary data from the request body.
612
  */
613
  public function set_body( $data ) {
614
  $this->body = $data;
615
 
616
+ // Enable lazy parsing.
617
  $this->parsed_json = false;
618
  $this->parsed_body = false;
619
  $this->params['JSON'] = null;
620
  }
621
 
622
  /**
623
+ * Retrieves the parameters from a JSON-formatted body.
624
  *
625
+ * @since 4.4.0
626
+ * @access public
627
+ *
628
+ * @return array Parameter map of key to value.
629
  */
630
  public function get_json_params() {
631
+ // Ensure the parameters have been parsed out.
632
  $this->parse_json_params();
633
 
634
  return $this->params['JSON'];
635
  }
636
 
637
  /**
638
+ * Parses the JSON parameters.
639
  *
640
  * Avoids parsing the JSON data until we need to access it.
641
+ *
642
+ * @since 4.4.0
643
+ * @access protected
644
  */
645
  protected function parse_json_params() {
646
  if ( $this->parsed_json ) {
647
  return;
648
  }
649
+
650
  $this->parsed_json = true;
651
 
652
+ // Check that we actually got JSON.
653
  $content_type = $this->get_content_type();
654
+
655
  if ( empty( $content_type ) || 'application/json' !== $content_type['value'] ) {
656
  return;
657
  }
658
 
659
  $params = json_decode( $this->get_body(), true );
660
 
661
+ /*
662
+ * Check for a parsing error.
663
+ *
664
+ * Note that due to WP's JSON compatibility functions, json_last_error
665
+ * might not be defined: https://core.trac.wordpress.org/ticket/27799
666
+ */
667
  if ( null === $params && ( ! function_exists( 'json_last_error' ) || JSON_ERROR_NONE !== json_last_error() ) ) {
668
  return;
669
  }
672
  }
673
 
674
  /**
675
+ * Parses the request body parameters.
676
  *
677
  * Parses out URL-encoded bodies for request methods that aren't supported
678
  * natively by PHP. In PHP 5.x, only POST has these parsed automatically.
679
+ *
680
+ * @since 4.4.0
681
+ * @access protected
682
  */
683
  protected function parse_body_params() {
684
  if ( $this->parsed_body ) {
685
  return;
686
  }
687
+
688
  $this->parsed_body = true;
689
 
690
+ /*
691
+ * Check that we got URL-encoded. Treat a missing content-type as
692
+ * URL-encoded for maximum compatibility.
693
+ */
694
  $content_type = $this->get_content_type();
695
+
696
  if ( ! empty( $content_type ) && 'application/x-www-form-urlencoded' !== $content_type['value'] ) {
697
  return;
698
  }
699
 
700
  parse_str( $this->get_body(), $params );
701
 
702
+ /*
703
+ * Amazingly, parse_str follows magic quote rules. Sigh.
704
+ *
705
+ * NOTE: Do not refactor to use `wp_unslash`.
706
+ */
707
  if ( get_magic_quotes_gpc() ) {
708
  $params = stripslashes_deep( $params );
709
  }
 
710
 
711
+ /*
712
+ * Add to the POST parameters stored internally. If a user has already
713
+ * set these manually (via `set_body_params`), don't override them.
714
+ */
715
  $this->params['POST'] = array_merge( $params, $this->params['POST'] );
716
  }
717
 
718
  /**
719
+ * Retrieves the route that matched the request.
720
+ *
721
+ * @since 4.4.0
722
+ * @access public
723
  *
724
+ * @return string Route matching regex.
725
  */
726
  public function get_route() {
727
  return $this->route;
728
  }
729
 
730
  /**
731
+ * Sets the route that matched the request.
732
  *
733
+ * @since 4.4.0
734
+ * @access public
735
+ *
736
+ * @param string $route Route matching regex.
737
  */
738
  public function set_route( $route ) {
739
  $this->route = $route;
740
  }
741
 
742
  /**
743
+ * Retrieves the attributes for the request.
744
  *
745
  * These are the options for the route that was matched.
746
  *
747
+ * @since 4.4.0
748
+ * @access public
749
+ *
750
+ * @return array Attributes for the request.
751
  */
752
  public function get_attributes() {
753
  return $this->attributes;
754
  }
755
 
756
  /**
757
+ * Sets the attributes for the request.
758
  *
759
+ * @since 4.4.0
760
+ * @access public
761
+ *
762
+ * @param array $attributes Attributes for the request.
763
  */
764
  public function set_attributes( $attributes ) {
765
  $this->attributes = $attributes;
766
  }
767
 
768
  /**
769
+ * Sanitizes (where possible) the params on the request.
770
  *
771
  * This is primarily based off the sanitize_callback param on each registered
772
  * argument.
773
  *
774
+ * @since 4.4.0
775
+ * @access public
776
+ *
777
+ * @return true|null True if there are no parameters to sanitize, null otherwise.
778
  */
779
  public function sanitize_params() {
780
 
781
  $attributes = $this->get_attributes();
782
 
783
+ // No arguments set, skip sanitizing.
784
  if ( empty( $attributes['args'] ) ) {
785
  return true;
786
  }
792
  continue;
793
  }
794
  foreach ( $this->params[ $type ] as $key => $value ) {
795
+ // Check if this param has a sanitize_callback added.
796
  if ( isset( $attributes['args'][ $key ] ) && ! empty( $attributes['args'][ $key ]['sanitize_callback'] ) ) {
797
  $this->params[ $type ][ $key ] = call_user_func( $attributes['args'][ $key ]['sanitize_callback'], $value, $this, $key );
798
  }
799
  }
800
  }
801
+ return null;
802
  }
803
 
804
  /**
805
+ * Checks whether this request is valid according to its attributes.
806
+ *
807
+ * @since 4.4.0
808
+ * @access public
809
  *
810
+ * @return bool|WP_Error True if there are no parameters to validate or if all pass validation,
811
+ * WP_Error if required parameters are missing.
812
  */
813
  public function has_valid_params() {
814
 
815
  $attributes = $this->get_attributes();
816
  $required = array();
817
 
818
+ // No arguments set, skip validation.
819
  if ( empty( $attributes['args'] ) ) {
820
  return true;
821
  }
832
  return new WP_Error( 'rest_missing_callback_param', sprintf( __( 'Missing parameter(s): %s' ), implode( ', ', $required ) ), array( 'status' => 400, 'params' => $required ) );
833
  }
834
 
835
+ /*
836
+ * Check the validation callbacks for each registered arg.
837
+ *
838
+ * This is done after required checking as required checking is cheaper.
839
+ */
840
  $invalid_params = array();
841
 
842
  foreach ( $attributes['args'] as $key => $arg ) {
865
  }
866
 
867
  /**
868
+ * Checks if a parameter is set.
869
  *
870
+ * @since 4.4.0
871
+ * @access public
872
+ *
873
+ * @param string $offset Parameter name.
874
+ * @return bool Whether the parameter is set.
875
  */
 
876
  public function offsetExists( $offset ) {
 
877
  $order = $this->get_parameter_order();
878
 
879
  foreach ( $order as $type ) {
886
  }
887
 
888
  /**
889
+ * Retrieves a parameter from the request.
890
+ *
891
+ * @since 4.4.0
892
+ * @access public
893
  *
894
+ * @param string $offset Parameter name.
895
+ * @return mixed|null Value if set, null otherwise.
896
  */
 
897
  public function offsetGet( $offset ) {
 
898
  return $this->get_param( $offset );
899
  }
900
 
901
  /**
902
+ * Sets a parameter on the request.
903
  *
904
+ * @since 4.4.0
905
+ * @access public
906
+ *
907
+ * @param string $offset Parameter name.
908
+ * @param mixed $value Parameter value.
909
  */
 
910
  public function offsetSet( $offset, $value ) {
911
+ $this->set_param( $offset, $value );
 
912
  }
913
 
914
  /**
915
+ * Removes a parameter from the request.
916
+ *
917
+ * @since 4.4.0
918
+ * @access public
919
  *
920
+ * @param string $offset Parameter name.
 
921
  */
 
922
  public function offsetUnset( $offset ) {
 
923
  $order = $this->get_parameter_order();
924
 
925
+ // Remove the offset from every group.
926
  foreach ( $order as $type ) {
927
  unset( $this->params[ $type ][ $offset ] );
928
  }
core/wp-includes/rest-api/class-wp-rest-response.php ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API: WP_REST_Response class
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ * @since 4.4.0
8
+ */
9
+
10
+ /**
11
+ * Core class used to implement a REST response object.
12
+ *
13
+ * @since 4.4.0
14
+ *
15
+ * @see WP_HTTP_Response
16
+ */
17
+ class WP_REST_Response extends WP_HTTP_Response {
18
+
19
+ /**
20
+ * Links related to the response.
21
+ *
22
+ * @since 4.4.0
23
+ * @access protected
24
+ * @var array
25
+ */
26
+ protected $links = array();
27
+
28
+ /**
29
+ * The route that was to create the response.
30
+ *
31
+ * @since 4.4.0
32
+ * @access protected
33
+ * @var string
34
+ */
35
+ protected $matched_route = '';
36
+
37
+ /**
38
+ * The handler that was used to create the response.
39
+ *
40
+ * @since 4.4.0
41
+ * @access protected
42
+ * @var null|array
43
+ */
44
+ protected $matched_handler = null;
45
+
46
+ /**
47
+ * Adds a link to the response.
48
+ *
49
+ * @internal The $rel parameter is first, as this looks nicer when sending multiple.
50
+ *
51
+ * @since 4.4.0
52
+ * @access public
53
+ *
54
+ * @link http://tools.ietf.org/html/rfc5988
55
+ * @link http://www.iana.org/assignments/link-relations/link-relations.xml
56
+ *
57
+ * @param string $rel Link relation. Either an IANA registered type,
58
+ * or an absolute URL.
59
+ * @param string $href Target URI for the link.
60
+ * @param array $attributes Optional. Link parameters to send along with the URL. Default empty array.
61
+ */
62
+ public function add_link( $rel, $href, $attributes = array() ) {
63
+ if ( empty( $this->links[ $rel ] ) ) {
64
+ $this->links[ $rel ] = array();
65
+ }
66
+
67
+ if ( isset( $attributes['href'] ) ) {
68
+ // Remove the href attribute, as it's used for the main URL.
69
+ unset( $attributes['href'] );
70
+ }
71
+
72
+ $this->links[ $rel ][] = array(
73
+ 'href' => $href,
74
+ 'attributes' => $attributes,
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Removes a link from the response.
80
+ *
81
+ * @since 4.4.0
82
+ * @access public
83
+ *
84
+ * @param string $rel Link relation. Either an IANA registered type, or an absolute URL.
85
+ * @param string $href Optional. Only remove links for the relation matching the given href.
86
+ * Default null.
87
+ */
88
+ public function remove_link( $rel, $href = null ) {
89
+ if ( ! isset( $this->links[ $rel ] ) ) {
90
+ return;
91
+ }
92
+
93
+ if ( $href ) {
94
+ $this->links[ $rel ] = wp_list_filter( $this->links[ $rel ], array( 'href' => $href ), 'NOT' );
95
+ } else {
96
+ $this->links[ $rel ] = array();
97
+ }
98
+
99
+ if ( ! $this->links[ $rel ] ) {
100
+ unset( $this->links[ $rel ] );
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Adds multiple links to the response.
106
+ *
107
+ * Link data should be an associative array with link relation as the key.
108
+ * The value can either be an associative array of link attributes
109
+ * (including `href` with the URL for the response), or a list of these
110
+ * associative arrays.
111
+ *
112
+ * @since 4.4.0
113
+ * @access public
114
+ *
115
+ * @param array $links Map of link relation to list of links.
116
+ */
117
+ public function add_links( $links ) {
118
+ foreach ( $links as $rel => $set ) {
119
+ // If it's a single link, wrap with an array for consistent handling.
120
+ if ( isset( $set['href'] ) ) {
121
+ $set = array( $set );
122
+ }
123
+
124
+ foreach ( $set as $attributes ) {
125
+ $this->add_link( $rel, $attributes['href'], $attributes );
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Retrieves links for the response.
132
+ *
133
+ * @since 4.4.0
134
+ * @access public
135
+ *
136
+ * @return array List of links.
137
+ */
138
+ public function get_links() {
139
+ return $this->links;
140
+ }
141
+
142
+ /**
143
+ * Sets a single link header.
144
+ *
145
+ * @internal The $rel parameter is first, as this looks nicer when sending multiple.
146
+ *
147
+ * @since 4.4.0
148
+ * @access public
149
+ *
150
+ * @link http://tools.ietf.org/html/rfc5988
151
+ * @link http://www.iana.org/assignments/link-relations/link-relations.xml
152
+ *
153
+ * @param string $rel Link relation. Either an IANA registered type, or an absolute URL.
154
+ * @param string $link Target IRI for the link.
155
+ * @param array $other Optional. Other parameters to send, as an assocative array.
156
+ * Default empty array.
157
+ */
158
+ public function link_header( $rel, $link, $other = array() ) {
159
+ $header = '<' . $link . '>; rel="' . $rel . '"';
160
+
161
+ foreach ( $other as $key => $value ) {
162
+ if ( 'title' === $key ) {
163
+ $value = '"' . $value . '"';
164
+ }
165
+ $header .= '; ' . $key . '=' . $value;
166
+ }
167
+ $this->header( 'Link', $header, false );
168
+ }
169
+
170
+ /**
171
+ * Retrieves the route that was used.
172
+ *
173
+ * @since 4.4.0
174
+ * @access public
175
+ *
176
+ * @return string The matched route.
177
+ */
178
+ public function get_matched_route() {
179
+ return $this->matched_route;
180
+ }
181
+
182
+ /**
183
+ * Sets the route (regex for path) that caused the response.
184
+ *
185
+ * @since 4.4.0
186
+ * @access public
187
+ *
188
+ * @param string $route Route name.
189
+ */
190
+ public function set_matched_route( $route ) {
191
+ $this->matched_route = $route;
192
+ }
193
+
194
+ /**
195
+ * Retrieves the handler that was used to generate the response.
196
+ *
197
+ * @since 4.4.0
198
+ * @access public
199
+ *
200
+ * @return null|array The handler that was used to create the response.
201
+ */
202
+ public function get_matched_handler() {
203
+ return $this->matched_handler;
204
+ }
205
+
206
+ /**
207
+ * Retrieves the handler that was responsible for generating the response.
208
+ *
209
+ * @since 4.4.0
210
+ * @access public
211
+ *
212
+ * @param array $handler The matched handler.
213
+ */
214
+ public function set_matched_handler( $handler ) {
215
+ $this->matched_handler = $handler;
216
+ }
217
+
218
+ /**
219
+ * Checks if the response is an error, i.e. >= 400 response code.
220
+ *
221
+ * @since 4.4.0
222
+ * @access public
223
+ *
224
+ * @return bool Whether the response is an error.
225
+ */
226
+ public function is_error() {
227
+ return $this->get_status() >= 400;
228
+ }
229
+
230
+ /**
231
+ * Retrieves a WP_Error object from the response.
232
+ *
233
+ * @since 4.4.0
234
+ * @access public
235
+ *
236
+ * @return WP_Error|null WP_Error or null on not an errored response.
237
+ */
238
+ public function as_error() {
239
+ if ( ! $this->is_error() ) {
240
+ return null;
241
+ }
242
+
243
+ $error = new WP_Error;
244
+
245
+ if ( is_array( $this->get_data() ) ) {
246
+ foreach ( $this->get_data() as $err ) {
247
+ $error->add( $err['code'], $err['message'], $err['data'] );
248
+ }
249
+ } else {
250
+ $error->add( $this->get_status(), '', array( 'status' => $this->get_status() ) );
251
+ }
252
+
253
+ return $error;
254
+ }
255
+ }
{lib/infrastructure → core/wp-includes/rest-api}/class-wp-rest-server.php RENAMED
@@ -1,145 +1,98 @@
1
  <?php
2
  /**
3
- * WordPress REST API
4
- *
5
- * Contains the WP_REST_Server class.
6
  *
7
  * @package WordPress
 
 
8
  */
9
 
 
10
  require_once( ABSPATH . 'wp-admin/includes/admin.php' );
11
 
12
  /**
13
- * WordPress REST API server handler
14
  *
15
- * @package WordPress
16
  */
17
  class WP_REST_Server {
18
- /**
19
- * GET transport method.
20
- *
21
- * @var string
22
- */
23
- const METHOD_GET = 'GET';
24
-
25
- /**
26
- * POST transport method.
27
- *
28
- * @var string
29
- */
30
- const METHOD_POST = 'POST';
31
-
32
- /**
33
- * PUT transport method.
34
- *
35
- * @var string
36
- */
37
- const METHOD_PUT = 'PUT';
38
-
39
- /**
40
- * PATCH transport method.
41
- *
42
- * @var string
43
- */
44
- const METHOD_PATCH = 'PATCH';
45
-
46
- /**
47
- * DELETE transport method.
48
- *
49
- * @var string
50
- */
51
- const METHOD_DELETE = 'DELETE';
52
 
53
  /**
54
  * Alias for GET transport method.
55
  *
 
56
  * @var string
57
  */
58
- const READABLE = 'GET';
59
 
60
  /**
61
  * Alias for POST transport method.
62
  *
 
63
  * @var string
64
  */
65
- const CREATABLE = 'POST';
66
 
67
  /**
68
- * Alias for GET, PUT, PATCH transport methods together.
69
  *
 
70
  * @var string
71
  */
72
- const EDITABLE = 'POST, PUT, PATCH';
73
 
74
  /**
75
  * Alias for DELETE transport method.
76
  *
 
77
  * @var string
78
  */
79
- const DELETABLE = 'DELETE';
80
 
81
  /**
82
  * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
83
  *
 
84
  * @var string
85
  */
86
  const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
87
 
88
  /**
89
- * Does the endpoint accept raw JSON entities?
90
- */
91
- const ACCEPT_RAW = 64;
92
-
93
- /**
94
- * Does the endpoint accept encoded JSON?
95
- */
96
- const ACCEPT_JSON = 128;
97
-
98
- /**
99
- * Should we hide this endpoint from the index?
100
- */
101
- const HIDDEN_ENDPOINT = 256;
102
-
103
- /**
104
- * Map of HTTP verbs to constants
105
- * @var array
106
- */
107
- public static $method_map = array(
108
- 'HEAD' => self::METHOD_GET,
109
- 'GET' => self::METHOD_GET,
110
- 'POST' => self::METHOD_POST,
111
- 'PUT' => self::METHOD_PUT,
112
- 'PATCH' => self::METHOD_PATCH,
113
- 'DELETE' => self::METHOD_DELETE,
114
- );
115
-
116
- /**
117
- * Namespaces registered to the server
118
  *
 
 
119
  * @var array
120
  */
121
  protected $namespaces = array();
122
 
123
  /**
124
- * Endpoints registered to the server
125
  *
 
 
126
  * @var array
127
  */
128
  protected $endpoints = array();
129
 
130
  /**
131
- * Options defined for the routes
132
  *
 
 
133
  * @var array
134
  */
135
  protected $route_options = array();
136
 
137
  /**
138
- * Instantiate the server
 
 
 
139
  */
140
  public function __construct() {
141
  $this->endpoints = array(
142
- // Meta endpoints
143
  '/' => array(
144
  'callback' => array( $this, 'get_index' ),
145
  'methods' => 'GET',
@@ -154,16 +107,20 @@ class WP_REST_Server {
154
 
155
 
156
  /**
157
- * Check the authentication headers if supplied
 
 
 
158
  *
159
- * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful or no authentication provided
 
160
  */
161
  public function check_authentication() {
162
  /**
163
  * Pass an authentication error to the API
164
  *
165
- * This is used to pass a {@see WP_Error} from an authentication method
166
- * back to the API.
167
  *
168
  * Authentication methods should check first if they're being used, as
169
  * multiple authentication methods can be enabled on a site (cookies,
@@ -173,28 +130,35 @@ class WP_REST_Server {
173
  * callbacks should ensure the value is `null` before checking for
174
  * errors.
175
  *
176
- * A {@see WP_Error} instance can be returned if an error occurs, and
177
- * this should match the format used by API methods internally (that is,
178
- * the `status` data should be used). A callback can return `true` to
179
- * indicate that the authentication method was used, and it succeeded.
180
  *
181
- * @param WP_Error|null|boolean WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded
 
 
 
182
  */
183
  return apply_filters( 'rest_authentication_errors', null );
184
  }
185
 
186
  /**
187
- * Convert an error to a response object
188
  *
189
  * This iterates over all error codes and messages to change it into a flat
190
  * array. This enables simpler client behaviour, as it is represented as a
191
- * list in JSON rather than an object/map
 
 
 
192
  *
193
- * @param WP_Error $error
194
- * @return array List of associative arrays with code and message keys
195
  */
196
  protected function error_to_response( $error ) {
197
  $error_data = $error->get_error_data();
 
198
  if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
199
  $status = $error_data['status'];
200
  } else {
@@ -202,60 +166,92 @@ class WP_REST_Server {
202
  }
203
 
204
  $data = array();
 
205
  foreach ( (array) $error->errors as $code => $messages ) {
206
  foreach ( (array) $messages as $message ) {
207
  $data[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ) );
208
  }
209
  }
 
210
  $response = new WP_REST_Response( $data, $status );
211
 
212
  return $response;
213
  }
214
 
215
  /**
216
- * Get an appropriate error representation in JSON
 
 
 
 
 
217
  *
218
- * Note: This should only be used in {@see WP_REST_Server::serve_request()},
219
- * as it cannot handle WP_Error internally. All callbacks and other internal
220
- * methods should instead return a WP_Error with the data set to an array
221
- * that includes a 'status' key, with the value being the HTTP status to
222
- * send.
223
  *
224
- * @param string $code WP_Error-style code
225
- * @param string $message Human-readable message
226
- * @param int $status HTTP status code to send
227
  * @return string JSON representation of the error
228
  */
229
  protected function json_error( $code, $message, $status = null ) {
230
  if ( $status ) {
231
  $this->set_status( $status );
232
  }
 
233
  $error = compact( 'code', 'message' );
234
 
235
  return wp_json_encode( array( $error ) );
236
  }
237
 
238
  /**
239
- * Handle serving an API request
240
  *
241
  * Matches the current server URI to a route and runs the first matching
242
  * callback then outputs a JSON representation of the returned value.
243
  *
244
- * @uses WP_REST_Server::dispatch()
 
 
 
 
 
 
 
245
  */
246
  public function serve_request( $path = null ) {
247
  $content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
248
  $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
249
 
250
- // Mitigate possible JSONP Flash attacks
251
- // http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
 
 
 
252
  $this->send_header( 'X-Content-Type-Options', 'nosniff' );
 
 
253
 
254
- // Proper filter for turning off the JSON API. It is on by default.
 
 
 
 
 
 
255
  $enabled = apply_filters( 'rest_enabled', true );
256
 
 
 
 
 
 
 
 
257
  $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
258
 
 
 
259
  if ( ! $enabled ) {
260
  echo $this->json_error( 'rest_disabled', __( 'The REST API is disabled on this site.' ), 404 );
261
  return false;
@@ -266,8 +262,14 @@ class WP_REST_Server {
266
  return false;
267
  }
268
 
269
- // Check for invalid characters (only alphanumeric allowed)
270
- if ( ! is_string( $_GET['_jsonp'] ) || preg_match( '/[^\w\.]/', $_GET['_jsonp'] ) ) {
 
 
 
 
 
 
271
  echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 );
272
  return false;
273
  }
@@ -282,13 +284,14 @@ class WP_REST_Server {
282
  }
283
 
284
  $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
 
285
  $request->set_query_params( $_GET );
286
  $request->set_body_params( $_POST );
287
  $request->set_file_params( $_FILES );
288
  $request->set_headers( $this->get_headers( $_SERVER ) );
289
  $request->set_body( $this->get_raw_data() );
290
 
291
- /**
292
  * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
293
  * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
294
  * header.
@@ -308,26 +311,30 @@ class WP_REST_Server {
308
  // Normalize to either WP_Error or WP_REST_Response...
309
  $result = rest_ensure_response( $result );
310
 
311
- // ...then convert WP_Error across
312
  if ( is_wp_error( $result ) ) {
313
  $result = $this->error_to_response( $result );
314
  }
315
 
316
  /**
317
- * Allow modifying the response before returning
 
 
318
  *
319
- * @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response
320
- * @param WP_REST_Server $this Server instance
321
- * @param WP_REST_Request $request Request used to generate the response
 
 
322
  */
323
  $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
324
 
325
- // Wrap the response in an envelope if asked for
326
  if ( isset( $_GET['_envelope'] ) ) {
327
  $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) );
328
  }
329
 
330
- // Send extra data from response objects
331
  $headers = $result->get_headers();
332
  $this->send_headers( $headers );
333
 
@@ -335,26 +342,27 @@ class WP_REST_Server {
335
  $this->set_status( $code );
336
 
337
  /**
338
- * Allow sending the request manually
339
  *
340
- * If `$served` is true, the result will not be sent to the client.
 
341
  *
342
- * This is a filter rather than an action, since this is designed to be
343
- * re-entrant if needed.
344
  *
345
- * @param bool $served Whether the request has already been served
346
- * @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response
347
- * @param WP_REST_Request $request Request used to generate the response
348
- * @param WP_REST_Server $this Server instance
 
349
  */
350
  $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
351
 
352
  if ( ! $served ) {
353
  if ( 'HEAD' === $request->get_method() ) {
354
- return;
355
  }
356
 
357
- // Embed links inside the request
358
  $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
359
 
360
  $result = wp_json_encode( $result );
@@ -366,34 +374,43 @@ class WP_REST_Server {
366
  $result = wp_json_encode( $result->data[0] );
367
  }
368
 
369
- if ( isset( $_GET['_jsonp'] ) ) {
370
  // Prepend '/**/' to mitigate possible JSONP Flash attacks
371
  // http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
372
- echo '/**/' . $_GET['_jsonp'] . '(' . $result . ')';
373
  } else {
374
  echo $result;
375
  }
376
  }
 
377
  }
378
 
379
  /**
380
- * Convert a response to data to send
381
  *
382
- * @param WP_REST_Response $response Response object
383
- * @param boolean $embed Should we embed links?
384
- * @return array
 
 
 
 
 
 
 
 
385
  */
386
  public function response_to_data( $response, $embed ) {
387
- $data = $this->prepare_response( $response->get_data() );
388
  $links = $this->get_response_links( $response );
389
 
390
  if ( ! empty( $links ) ) {
391
- // Convert links to part of the data
392
  $data['_links'] = $links;
393
  }
394
  if ( $embed ) {
395
- // Is this a numeric array?
396
- if ( rest_is_list( $data ) ) {
397
  $data = array_map( array( $this, 'embed_links' ), $data );
398
  } else {
399
  $data = $this->embed_links( $data );
@@ -404,11 +421,15 @@ class WP_REST_Server {
404
  }
405
 
406
  /**
407
- * Get links from a response.
408
  *
409
- * Extracts the links from a reponse into a structured hash, suitable for
410
  * direct output.
411
  *
 
 
 
 
412
  * @param WP_REST_Response $response Response to extract links from.
413
  * @return array Map of link relation to list of link hashes.
414
  */
@@ -419,7 +440,7 @@ class WP_REST_Server {
419
  return array();
420
  }
421
 
422
- // Convert links to part of the data
423
  $data = array();
424
  foreach ( $links as $rel => $items ) {
425
  $data[ $rel ] = array();
@@ -435,10 +456,18 @@ class WP_REST_Server {
435
  }
436
 
437
  /**
438
- * Embed the links from the data into the request
 
 
 
 
 
 
 
439
  *
440
- * @param array $data Data from the request
441
- * @return array Data with sub-requests embedded
 
442
  */
443
  protected function embed_links( $data ) {
444
  if ( empty( $data['_links'] ) ) {
@@ -447,8 +476,9 @@ class WP_REST_Server {
447
 
448
  $embedded = array();
449
  $api_root = rest_url();
 
450
  foreach ( $data['_links'] as $rel => $links ) {
451
- // Ignore links to self, for obvious reasons
452
  if ( 'self' === $rel ) {
453
  continue;
454
  }
@@ -456,18 +486,18 @@ class WP_REST_Server {
456
  $embeds = array();
457
 
458
  foreach ( $links as $item ) {
459
- // Is the link embeddable?
460
  if ( empty( $item['embeddable'] ) || strpos( $item['href'], $api_root ) !== 0 ) {
461
- // Ensure we keep the same order
462
  $embeds[] = array();
463
  continue;
464
  }
465
 
466
- // Run through our internal routing and serve
467
  $route = substr( $item['href'], strlen( untrailingslashit( $api_root ) ) );
468
  $query_params = array();
469
 
470
- // Parse out URL query parameters
471
  $parsed = parse_url( $route );
472
  if ( empty( $parsed['path'] ) ) {
473
  $embeds[] = array();
@@ -477,27 +507,26 @@ class WP_REST_Server {
477
  if ( ! empty( $parsed['query'] ) ) {
478
  parse_str( $parsed['query'], $query_params );
479
 
480
- // Ensure magic quotes are stripped
481
- // @codeCoverageIgnoreStart
482
  if ( get_magic_quotes_gpc() ) {
483
  $query_params = stripslashes_deep( $query_params );
484
  }
485
- // @codeCoverageIgnoreEnd
486
  }
487
 
488
- // Embedded resources get passed context=embed
489
  if ( empty( $query_params['context'] ) ) {
490
  $query_params['context'] = 'embed';
491
  }
492
 
493
  $request = new WP_REST_Request( 'GET', $parsed['path'] );
 
494
  $request->set_query_params( $query_params );
495
  $response = $this->dispatch( $request );
496
 
497
  $embeds[] = $this->response_to_data( $response, false );
498
  }
499
 
500
- // Did we get any real links?
501
  $has_links = count( array_filter( $embeds ) );
502
  if ( $has_links ) {
503
  $embedded[ $rel ] = $embeds;
@@ -512,15 +541,18 @@ class WP_REST_Server {
512
  }
513
 
514
  /**
515
- * Wrap the response in an envelope
516
  *
517
  * The enveloping technique is used to work around browser/client
518
  * compatibility issues. Essentially, it converts the full HTTP response to
519
  * data instead.
520
  *
521
- * @param WP_REST_Response $response Response object
522
- * @param boolean $embed Should we embed links?
523
- * @return WP_REST_Response New reponse with wrapped data
 
 
 
524
  */
525
  public function envelope_response( $response, $embed ) {
526
  $envelope = array(
@@ -530,23 +562,30 @@ class WP_REST_Server {
530
  );
531
 
532
  /**
533
- * Alter the enveloped form of a response
 
 
534
  *
535
- * @param array $envelope Envelope data
536
- * @param WP_REST_Response $response Original response data
537
  */
538
  $envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
539
 
540
- // Ensure it's still a response
541
  return rest_ensure_response( $envelope );
542
  }
543
 
544
  /**
545
- * Register a route to the server
546
  *
547
- * @param string $route
548
- * @param array $route_args
549
- * @param boolean $override If the route already exists, should we override it? True overrides, false merges (with newer overriding if duplicate keys exist)
 
 
 
 
 
550
  */
551
  public function register_route( $namespace, $route, $route_args, $override = false ) {
552
  if ( ! isset( $this->namespaces[ $namespace ] ) ) {
@@ -568,7 +607,7 @@ class WP_REST_Server {
568
  ) );
569
  }
570
 
571
- // Associative to avoid double-registration
572
  $this->namespaces[ $namespace ][ $route ] = true;
573
  $route_args['namespace'] = $namespace;
574
 
@@ -580,7 +619,7 @@ class WP_REST_Server {
580
  }
581
 
582
  /**
583
- * Retrieve the route map
584
  *
585
  * The route map is an associative array with path regexes as the keys. The
586
  * value is an indexed array with the callback function/method as the first
@@ -594,13 +633,27 @@ class WP_REST_Server {
594
  * Note that the path regexes (array keys) must have @ escaped, as this is
595
  * used as the delimiter with preg_match()
596
  *
597
- * @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)`
 
 
 
 
598
  */
599
  public function get_routes() {
600
 
 
 
 
 
 
 
 
 
 
 
601
  $endpoints = apply_filters( 'rest_endpoints', $this->endpoints );
602
 
603
- // Normalise the endpoints
604
  $defaults = array(
605
  'methods' => '',
606
  'accept_json' => false,
@@ -608,32 +661,40 @@ class WP_REST_Server {
608
  'show_in_index' => true,
609
  'args' => array(),
610
  );
 
611
  foreach ( $endpoints as $route => &$handlers ) {
 
612
  if ( isset( $handlers['callback'] ) ) {
613
- // Single endpoint, add one deeper
614
  $handlers = array( $handlers );
615
  }
 
616
  if ( ! isset( $this->route_options[ $route ] ) ) {
617
  $this->route_options[ $route ] = array();
618
  }
619
 
620
  foreach ( $handlers as $key => &$handler ) {
 
621
  if ( ! is_numeric( $key ) ) {
622
- // Route option, move it to the options
623
  $this->route_options[ $route ][ $key ] = $handler;
624
  unset( $handlers[ $key ] );
625
  continue;
626
  }
 
627
  $handler = wp_parse_args( $handler, $defaults );
628
 
629
- // Allow comma-separated HTTP methods
630
  if ( is_string( $handler['methods'] ) ) {
631
  $methods = explode( ',', $handler['methods'] );
632
  } else if ( is_array( $handler['methods'] ) ) {
633
  $methods = $handler['methods'];
 
 
634
  }
635
 
636
  $handler['methods'] = array();
 
637
  foreach ( $methods as $method ) {
638
  $method = strtoupper( trim( $method ) );
639
  $handler['methods'][ $method ] = true;
@@ -644,7 +705,10 @@ class WP_REST_Server {
644
  }
645
 
646
  /**
647
- * Get namespaces registered on the server.
 
 
 
648
  *
649
  * @return array List of registered namespaces.
650
  */
@@ -653,7 +717,10 @@ class WP_REST_Server {
653
  }
654
 
655
  /**
656
- * Get specified options for a route.
 
 
 
657
  *
658
  * @param string $route Route pattern to fetch options for.
659
  * @return array|null Data as an associative array if found, or null if not found.
@@ -667,23 +734,30 @@ class WP_REST_Server {
667
  }
668
 
669
  /**
670
- * Match the request to a callback and call it
671
  *
672
- * @param WP_REST_Request $request Request to attempt dispatching
673
- * @return WP_REST_Response Response returned by the callback
 
 
 
674
  */
675
  public function dispatch( $request ) {
676
  /**
677
- * Allow hijacking the request before dispatching
 
 
 
678
  *
679
- * If `$result` is non-empty, this value will be used to serve the
680
- * request instead.
681
  *
682
- * @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
683
- * @param WP_REST_Server $this Server instance
684
- * @param WP_REST_Request $request Request used to generate the response
 
685
  */
686
  $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
 
687
  if ( ! empty( $result ) ) {
688
  return $result;
689
  }
@@ -694,7 +768,6 @@ class WP_REST_Server {
694
  foreach ( $this->get_routes() as $route => $handlers ) {
695
  foreach ( $handlers as $handler ) {
696
  $callback = $handler['callback'];
697
- $supported = $handler['methods'];
698
  $response = null;
699
 
700
  if ( empty( $handler['methods'][ $method ] ) ) {
@@ -735,7 +808,7 @@ class WP_REST_Server {
735
  }
736
 
737
  if ( ! is_wp_error( $response ) ) {
738
- // check permission specified on the route.
739
  if ( ! empty( $handler['permission_callback'] ) ) {
740
  $permission = call_user_func( $handler['permission_callback'], $request );
741
 
@@ -749,14 +822,18 @@ class WP_REST_Server {
749
 
750
  if ( ! is_wp_error( $response ) ) {
751
  /**
752
- * Allow plugins to override dispatching the request
 
 
753
  *
754
- * @param boolean $dispatch_result Dispatch result, will be used if not empty
755
- * @param WP_REST_Request $request
 
 
756
  */
757
  $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request );
758
 
759
- // Allow plugins to halt the request via this filter
760
  if ( null !== $dispatch_result ) {
761
  $response = $dispatch_result;
762
  } else {
@@ -781,18 +858,24 @@ class WP_REST_Server {
781
  }
782
 
783
  /**
784
- * Returns if an error occurred during most recent JSON encode/decode
785
- * Strings to be translated will be in format like "Encoding error: Maximum stack depth exceeded"
 
 
786
  *
787
- * @return boolean|string Boolean false or string error message
 
 
 
788
  */
789
- protected function get_json_last_error( ) {
790
- // see https://core.trac.wordpress.org/ticket/27799
791
  if ( ! function_exists( 'json_last_error' ) ) {
792
  return false;
793
  }
794
 
795
  $last_error_code = json_last_error();
 
796
  if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) {
797
  return false;
798
  }
@@ -801,16 +884,22 @@ class WP_REST_Server {
801
  }
802
 
803
  /**
804
- * Get the site index.
805
  *
806
  * This endpoint describes the capabilities of the site.
807
  *
808
- * @todo Should we generate text documentation too based on PHPDoc?
 
809
  *
 
 
 
 
 
810
  * @return array Index entity
811
  */
812
  public function get_index( $request ) {
813
- // General site data
814
  $available = array(
815
  'name' => get_option( 'blogname' ),
816
  'description' => get_option( 'blogdescription' ),
@@ -821,6 +910,7 @@ class WP_REST_Server {
821
  );
822
 
823
  $response = new WP_REST_Response( $available );
 
824
  $response->add_link( 'help', 'http://v2.wp-api.org/' );
825
 
826
  /**
@@ -830,16 +920,22 @@ class WP_REST_Server {
830
  * about supported authentication schemes, supported namespaces, routes
831
  * available on the API, and a small amount of data about the site.
832
  *
 
 
833
  * @param WP_REST_Response $response Response data.
834
  */
835
  return apply_filters( 'rest_index', $response );
836
  }
837
 
838
  /**
839
- * Get the index for a namespace.
 
 
 
840
  *
841
- * @param WP_REST_Request $request
842
- * @return array|WP_REST_Response
 
843
  */
844
  public function get_namespace_index( $request ) {
845
  $namespace = $request['namespace'];
@@ -857,7 +953,7 @@ class WP_REST_Server {
857
  );
858
  $response = rest_ensure_response( $data );
859
 
860
- // Link to the root index
861
  $response->add_link( 'up', rest_url( '/' ) );
862
 
863
  /**
@@ -866,28 +962,41 @@ class WP_REST_Server {
866
  * This typically is just the route data for the namespace, but you can
867
  * add any data you'd like here.
868
  *
 
 
869
  * @param WP_REST_Response $response Response data.
870
- * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
871
  */
872
  return apply_filters( 'rest_namespace_index', $response, $request );
873
  }
874
 
875
  /**
876
- * Get the publicly-visible data for routes.
877
  *
878
- * @param array $routes Routes to get data for
879
- * @param string $context Context for data. One of 'view', 'help'.
 
 
 
880
  * @return array Route data to expose in indexes.
881
  */
882
  public function get_data_for_routes( $routes, $context = 'view' ) {
883
  $available = array();
884
- // Find the available routes
 
885
  foreach ( $routes as $route => $callbacks ) {
886
  $data = $this->get_data_for_route( $route, $callbacks, $context );
887
  if ( empty( $data ) ) {
888
  continue;
889
  }
890
 
 
 
 
 
 
 
 
891
  $available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
892
  }
893
 
@@ -898,18 +1007,23 @@ class WP_REST_Server {
898
  * developers to investigate the site and find out how to use it. It
899
  * acts as a form of self-documentation.
900
  *
 
 
901
  * @param array $available Map of route to route data.
902
- * @param array $routes Internal route data as an associative array.
903
  */
904
  return apply_filters( 'rest_route_data', $available, $routes );
905
  }
906
 
907
  /**
908
- * Get publicly-visible data for the route.
 
 
 
909
  *
910
- * @param string $route Route to get data for.
911
- * @param array $callbacks Callbacks to convert to data.
912
- * @param string $context Context for the data.
913
  * @return array|null Data for the route, or null if no publicly-visible data.
914
  */
915
  public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
@@ -918,11 +1032,14 @@ class WP_REST_Server {
918
  'methods' => array(),
919
  'endpoints' => array(),
920
  );
 
921
  if ( isset( $this->route_options[ $route ] ) ) {
922
  $options = $this->route_options[ $route ];
 
923
  if ( isset( $options['namespace'] ) ) {
924
  $data['namespace'] = $options['namespace'];
925
  }
 
926
  if ( isset( $options['schema'] ) && 'help' === $context ) {
927
  $data['schema'] = call_user_func( $options['schema'] );
928
  }
@@ -931,7 +1048,7 @@ class WP_REST_Server {
931
  $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
932
 
933
  foreach ( $callbacks as $callback ) {
934
- // Skip to the next route if any callback is hidden
935
  if ( empty( $callback['show_in_index'] ) ) {
936
  continue;
937
  }
@@ -956,7 +1073,7 @@ class WP_REST_Server {
956
 
957
  $data['endpoints'][] = $endpoint_data;
958
 
959
- // For non-variable routes, generate links
960
  if ( strpos( $route, '{' ) === false ) {
961
  $data['_links'] = array(
962
  'self' => rest_url( $route ),
@@ -965,7 +1082,7 @@ class WP_REST_Server {
965
  }
966
 
967
  if ( empty( $data['methods'] ) ) {
968
- // No methods supported, hide the route
969
  return null;
970
  }
971
 
@@ -973,33 +1090,45 @@ class WP_REST_Server {
973
  }
974
 
975
  /**
976
- * Send a HTTP status code
 
 
 
977
  *
978
- * @param int $code HTTP status
979
  */
980
  protected function set_status( $code ) {
981
  status_header( $code );
982
  }
983
 
984
  /**
985
- * Send a HTTP header
986
  *
987
- * @param string $key Header key
988
- * @param string $value Header value
 
 
 
989
  */
990
  public function send_header( $key, $value ) {
991
- // Sanitize as per RFC2616 (Section 4.2):
992
- // Any LWS that occurs between field-content MAY be replaced with a
993
- // single SP before interpreting the field value or forwarding the
994
- // message downstream.
 
 
 
995
  $value = preg_replace( '/\s+/', ' ', $value );
996
  header( sprintf( '%s: %s', $key, $value ) );
997
  }
998
 
999
  /**
1000
- * Send multiple HTTP headers
 
 
 
1001
  *
1002
- * @param array Map of header name to header value
1003
  */
1004
  public function send_headers( $headers ) {
1005
  foreach ( $headers as $key => $value ) {
@@ -1008,15 +1137,22 @@ class WP_REST_Server {
1008
  }
1009
 
1010
  /**
1011
- * Retrieve the raw request entity (body)
 
 
 
 
 
1012
  *
1013
- * @return string
1014
  */
1015
  public function get_raw_data() {
1016
  global $HTTP_RAW_POST_DATA;
1017
 
1018
- // A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
1019
- // but we can do it ourself.
 
 
1020
  if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
1021
  $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
1022
  }
@@ -1025,59 +1161,18 @@ class WP_REST_Server {
1025
  }
1026
 
1027
  /**
1028
- * Prepares response data to be serialized to JSON
1029
- *
1030
- * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
1031
  *
1032
- * @codeCoverageIgnore This is a compatibility shim.
1033
- *
1034
- * @param mixed $data Native representation
1035
- * @return array|string Data ready for `json_encode()`
1036
- */
1037
- public function prepare_response( $data ) {
1038
- if ( ! defined( 'WP_REST_SERIALIZE_COMPATIBLE' ) || WP_REST_SERIALIZE_COMPATIBLE === false ) {
1039
- return $data;
1040
- }
1041
-
1042
- switch ( gettype( $data ) ) {
1043
- case 'boolean':
1044
- case 'integer':
1045
- case 'double':
1046
- case 'string':
1047
- case 'NULL':
1048
- // These values can be passed through
1049
- return $data;
1050
-
1051
- case 'array':
1052
- // Arrays must be mapped in case they also return objects
1053
- return array_map( array( $this, 'prepare_response' ), $data );
1054
-
1055
- case 'object':
1056
- if ( $data instanceof JsonSerializable ) {
1057
- $data = $data->jsonSerialize();
1058
- } else {
1059
- $data = get_object_vars( $data );
1060
- }
1061
-
1062
- // Now, pass the array (or whatever was returned from
1063
- // jsonSerialize through.)
1064
- return $this->prepare_response( $data );
1065
-
1066
- default:
1067
- return null;
1068
- }
1069
- }
1070
-
1071
- /**
1072
- * Extract headers from a PHP-style $_SERVER array
1073
  *
1074
- * @param array $server Associative array similar to $_SERVER
1075
- * @return array Headers extracted from the input
1076
  */
1077
  public function get_headers( $server ) {
1078
  $headers = array();
1079
 
1080
- // CONTENT_* headers are not prefixed with HTTP_
1081
  $additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true );
1082
 
1083
  foreach ( $server as $key => $value ) {
1
  <?php
2
  /**
3
+ * REST API: WP_REST_Server class
 
 
4
  *
5
  * @package WordPress
6
+ * @subpackage REST_API
7
+ * @since 4.4.0
8
  */
9
 
10
+ /** Admin bootstrap */
11
  require_once( ABSPATH . 'wp-admin/includes/admin.php' );
12
 
13
  /**
14
+ * Core class used to implement the WordPress REST API server.
15
  *
16
+ * @since 4.4.0
17
  */
18
  class WP_REST_Server {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  /**
21
  * Alias for GET transport method.
22
  *
23
+ * @since 4.4.0
24
  * @var string
25
  */
26
+ const READABLE = 'GET';
27
 
28
  /**
29
  * Alias for POST transport method.
30
  *
31
+ * @since 4.4.0
32
  * @var string
33
  */
34
+ const CREATABLE = 'POST';
35
 
36
  /**
37
+ * Alias for POST, PUT, PATCH transport methods together.
38
  *
39
+ * @since 4.4.0
40
  * @var string
41
  */
42
+ const EDITABLE = 'POST, PUT, PATCH';
43
 
44
  /**
45
  * Alias for DELETE transport method.
46
  *
47
+ * @since 4.4.0
48
  * @var string
49
  */
50
+ const DELETABLE = 'DELETE';
51
 
52
  /**
53
  * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
54
  *
55
+ * @since 4.4.0
56
  * @var string
57
  */
58
  const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
59
 
60
  /**
61
+ * Namespaces registered to the server.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  *
63
+ * @since 4.4.0
64
+ * @access protected
65
  * @var array
66
  */
67
  protected $namespaces = array();
68
 
69
  /**
70
+ * Endpoints registered to the server.
71
  *
72
+ * @since 4.4.0
73
+ * @access protected
74
  * @var array
75
  */
76
  protected $endpoints = array();
77
 
78
  /**
79
+ * Options defined for the routes.
80
  *
81
+ * @since 4.4.0
82
+ * @access protected
83
  * @var array
84
  */
85
  protected $route_options = array();
86
 
87
  /**
88
+ * Instantiates the REST server.
89
+ *
90
+ * @since 4.4.0
91
+ * @access public
92
  */
93
  public function __construct() {
94
  $this->endpoints = array(
95
+ // Meta endpoints.
96
  '/' => array(
97
  'callback' => array( $this, 'get_index' ),
98
  'methods' => 'GET',
107
 
108
 
109
  /**
110
+ * Checks the authentication headers if supplied.
111
+ *
112
+ * @since 4.4.0
113
+ * @access public
114
  *
115
+ * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful
116
+ * or no authentication provided
117
  */
118
  public function check_authentication() {
119
  /**
120
  * Pass an authentication error to the API
121
  *
122
+ * This is used to pass a WP_Error from an authentication method back to
123
+ * the API.
124
  *
125
  * Authentication methods should check first if they're being used, as
126
  * multiple authentication methods can be enabled on a site (cookies,
130
  * callbacks should ensure the value is `null` before checking for
131
  * errors.
132
  *
133
+ * A WP_Error instance can be returned if an error occurs, and this should
134
+ * match the format used by API methods internally (that is, the `status`
135
+ * data should be used). A callback can return `true` to indicate that
136
+ * the authentication method was used, and it succeeded.
137
  *
138
+ * @since 4.4.0
139
+ *
140
+ * @param WP_Error|null|bool WP_Error if authentication error, null if authentication
141
+ * method wasn't used, true if authentication succeeded.
142
  */
143
  return apply_filters( 'rest_authentication_errors', null );
144
  }
145
 
146
  /**
147
+ * Converts an error to a response object.
148
  *
149
  * This iterates over all error codes and messages to change it into a flat
150
  * array. This enables simpler client behaviour, as it is represented as a
151
+ * list in JSON rather than an object/map.
152
+ *
153
+ * @since 4.4.0
154
+ * @access protected
155
  *
156
+ * @param WP_Error $error WP_Error instance.
157
+ * @return WP_REST_Response List of associative arrays with code and message keys.
158
  */
159
  protected function error_to_response( $error ) {
160
  $error_data = $error->get_error_data();
161
+
162
  if ( is_array( $error_data ) && isset( $error_data['status'] ) ) {
163
  $status = $error_data['status'];
164
  } else {
166
  }
167
 
168
  $data = array();
169
+
170
  foreach ( (array) $error->errors as $code => $messages ) {
171
  foreach ( (array) $messages as $message ) {
172
  $data[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ) );
173
  }
174
  }
175
+
176
  $response = new WP_REST_Response( $data, $status );
177
 
178
  return $response;
179
  }
180
 
181
  /**
182
+ * Retrieves an appropriate error representation in JSON.
183
+ *
184
+ * Note: This should only be used in WP_REST_Server::serve_request(), as it
185
+ * cannot handle WP_Error internally. All callbacks and other internal methods
186
+ * should instead return a WP_Error with the data set to an array that includes
187
+ * a 'status' key, with the value being the HTTP status to send.
188
  *
189
+ * @since 4.4.0
190
+ * @access protected
 
 
 
191
  *
192
+ * @param string $code WP_Error-style code.
193
+ * @param string $message Human-readable message.
194
+ * @param int $status Optional. HTTP status code to send. Default null.
195
  * @return string JSON representation of the error
196
  */
197
  protected function json_error( $code, $message, $status = null ) {
198
  if ( $status ) {
199
  $this->set_status( $status );
200
  }
201
+
202
  $error = compact( 'code', 'message' );
203
 
204
  return wp_json_encode( array( $error ) );
205
  }
206
 
207
  /**
208
+ * Handles serving an API request.
209
  *
210
  * Matches the current server URI to a route and runs the first matching
211
  * callback then outputs a JSON representation of the returned value.
212
  *
213
+ * @since 4.4.0
214
+ * @access public
215
+ *
216
+ * @see WP_REST_Server::dispatch()
217
+ *
218
+ * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
219
+ * Default null.
220
+ * @return false|null Null if not served and a HEAD request, false otherwise.
221
  */
222
  public function serve_request( $path = null ) {
223
  $content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
224
  $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
225
 
226
+ /*
227
+ * Mitigate possible JSONP Flash attacks.
228
+ *
229
+ * http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
230
+ */
231
  $this->send_header( 'X-Content-Type-Options', 'nosniff' );
232
+ $this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' );
233
+ $this->send_header( 'Access-Control-Allow-Headers', 'Authorization' );
234
 
235
+ /**
236
+ * Filter whether the REST API is enabled.
237
+ *
238
+ * @since 4.4.0
239
+ *
240
+ * @param bool $rest_enabled Whether the REST API is enabled. Default true.
241
+ */
242
  $enabled = apply_filters( 'rest_enabled', true );
243
 
244
+ /**
245
+ * Filter whether jsonp is enabled.
246
+ *
247
+ * @since 4.4.0
248
+ *
249
+ * @param bool $jsonp_enabled Whether jsonp is enabled. Default true.
250
+ */
251
  $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
252
 
253
+ $jsonp_callback = null;
254
+
255
  if ( ! $enabled ) {
256
  echo $this->json_error( 'rest_disabled', __( 'The REST API is disabled on this site.' ), 404 );
257
  return false;
262
  return false;
263
  }
264
 
265
+ // Check for invalid characters (only alphanumeric allowed).
266
+ if ( is_string( $_GET['_jsonp'] ) ) {
267
+ $jsonp_callback = preg_replace( '/[^\w\.]/', '', wp_unslash( $_GET['_jsonp'] ), -1, $illegal_char_count );
268
+ if ( 0 !== $illegal_char_count ) {
269
+ $jsonp_callback = null;
270
+ }
271
+ }
272
+ if ( null === $jsonp_callback ) {
273
  echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 );
274
  return false;
275
  }
284
  }
285
 
286
  $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
287
+
288
  $request->set_query_params( $_GET );
289
  $request->set_body_params( $_POST );
290
  $request->set_file_params( $_FILES );
291
  $request->set_headers( $this->get_headers( $_SERVER ) );
292
  $request->set_body( $this->get_raw_data() );
293
 
294
+ /*
295
  * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
296
  * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
297
  * header.
311
  // Normalize to either WP_Error or WP_REST_Response...
312
  $result = rest_ensure_response( $result );
313
 
314
+ // ...then convert WP_Error across.
315
  if ( is_wp_error( $result ) ) {
316
  $result = $this->error_to_response( $result );
317
  }
318
 
319
  /**
320
+ * Filter the API response.
321
+ *
322
+ * Allows modification of the response before returning.
323
  *
324
+ * @since 4.4.0
325
+ *
326
+ * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response.
327
+ * @param WP_REST_Server $this Server instance.
328
+ * @param WP_REST_Request $request Request used to generate the response.
329
  */
330
  $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
331
 
332
+ // Wrap the response in an envelope if asked for.
333
  if ( isset( $_GET['_envelope'] ) ) {
334
  $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) );
335
  }
336
 
337
+ // Send extra data from response objects.
338
  $headers = $result->get_headers();
339
  $this->send_headers( $headers );
340
 
342
  $this->set_status( $code );
343
 
344
  /**
345
+ * Filter whether the request has already been served.
346
  *
347
+ * Allow sending the request manually - by returning true, the API result
348
+ * will not be sent to the client.
349
  *
350
+ * @since 4.4.0
 
351
  *
352
+ * @param bool $served Whether the request has already been served.
353
+ * Default false.
354
+ * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response.
355
+ * @param WP_REST_Request $request Request used to generate the response.
356
+ * @param WP_REST_Server $this Server instance.
357
  */
358
  $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
359
 
360
  if ( ! $served ) {
361
  if ( 'HEAD' === $request->get_method() ) {
362
+ return null;
363
  }
364
 
365
+ // Embed links inside the request.
366
  $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
367
 
368
  $result = wp_json_encode( $result );
374
  $result = wp_json_encode( $result->data[0] );
375
  }
376
 
377
+ if ( $jsonp_callback ) {
378
  // Prepend '/**/' to mitigate possible JSONP Flash attacks
379
  // http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
380
+ echo '/**/' . $jsonp_callback . '(' . $result . ')';
381
  } else {
382
  echo $result;
383
  }
384
  }
385
+ return null;
386
  }
387
 
388
  /**
389
+ * Converts a response to data to send.
390
  *
391
+ * @since 4.4.0
392
+ * @access public
393
+ *
394
+ * @param WP_REST_Response $response Response object.
395
+ * @param bool $embed Whether links should be embedded.
396
+ * @return array {
397
+ * Data with sub-requests embedded.
398
+ *
399
+ * @type array [$_links] Links.
400
+ * @type array [$_embedded] Embeddeds.
401
+ * }
402
  */
403
  public function response_to_data( $response, $embed ) {
404
+ $data = $response->get_data();
405
  $links = $this->get_response_links( $response );
406
 
407
  if ( ! empty( $links ) ) {
408
+ // Convert links to part of the data.
409
  $data['_links'] = $links;
410
  }
411
  if ( $embed ) {
412
+ // Determine if this is a numeric array.
413
+ if ( wp_is_numeric_array( $data ) ) {
414
  $data = array_map( array( $this, 'embed_links' ), $data );
415
  } else {
416
  $data = $this->embed_links( $data );
421
  }
422
 
423
  /**
424
+ * Retrieves links from a response.
425
  *
426
+ * Extracts the links from a response into a structured hash, suitable for
427
  * direct output.
428
  *
429
+ * @since 4.4.0
430
+ * @access public
431
+ * @static
432
+ *
433
  * @param WP_REST_Response $response Response to extract links from.
434
  * @return array Map of link relation to list of link hashes.
435
  */
440
  return array();
441
  }
442
 
443
+ // Convert links to part of the data.
444
  $data = array();
445
  foreach ( $links as $rel => $items ) {
446
  $data[ $rel ] = array();
456
  }
457
 
458
  /**
459
+ * Embeds the links from the data into the request.
460
+ *
461
+ * @since 4.4.0
462
+ * @access protected
463
+ *
464
+ * @param array $data Data from the request.
465
+ * @return array {
466
+ * Data with sub-requests embedded.
467
  *
468
+ * @type array [$_links] Links.
469
+ * @type array [$_embedded] Embeddeds.
470
+ * }
471
  */
472
  protected function embed_links( $data ) {
473
  if ( empty( $data['_links'] ) ) {
476
 
477
  $embedded = array();
478
  $api_root = rest_url();
479
+
480
  foreach ( $data['_links'] as $rel => $links ) {
481
+ // Ignore links to self, for obvious reasons.
482
  if ( 'self' === $rel ) {
483
  continue;
484
  }
486
  $embeds = array();
487
 
488
  foreach ( $links as $item ) {
489
+ // Determine if the link is embeddable.
490
  if ( empty( $item['embeddable'] ) || strpos( $item['href'], $api_root ) !== 0 ) {
491
+ // Ensure we keep the same order.
492
  $embeds[] = array();
493
  continue;
494
  }
495
 
496
+ // Run through our internal routing and serve.
497
  $route = substr( $item['href'], strlen( untrailingslashit( $api_root ) ) );
498
  $query_params = array();
499
 
500
+ // Parse out URL query parameters.
501
  $parsed = parse_url( $route );
502
  if ( empty( $parsed['path'] ) ) {
503
  $embeds[] = array();
507
  if ( ! empty( $parsed['query'] ) ) {
508
  parse_str( $parsed['query'], $query_params );
509
 
510
+ // Ensure magic quotes are stripped.
 
511
  if ( get_magic_quotes_gpc() ) {
512
  $query_params = stripslashes_deep( $query_params );
513
  }
 
514
  }
515
 
516
+ // Embedded resources get passed context=embed.
517
  if ( empty( $query_params['context'] ) ) {
518
  $query_params['context'] = 'embed';
519
  }
520
 
521
  $request = new WP_REST_Request( 'GET', $parsed['path'] );
522
+
523
  $request->set_query_params( $query_params );
524
  $response = $this->dispatch( $request );
525
 
526
  $embeds[] = $this->response_to_data( $response, false );
527
  }
528
 
529
+ // Determine if any real links were found.
530
  $has_links = count( array_filter( $embeds ) );
531
  if ( $has_links ) {
532
  $embedded[ $rel ] = $embeds;
541
  }
542
 
543
  /**
544
+ * Wraps the response in an envelope.
545
  *
546
  * The enveloping technique is used to work around browser/client
547
  * compatibility issues. Essentially, it converts the full HTTP response to
548
  * data instead.
549
  *
550
+ * @since 4.4.0
551
+ * @access public
552
+ *
553
+ * @param WP_REST_Response $response Response object.
554
+ * @param bool $embed Whether links should be embedded.
555
+ * @return WP_REST_Response New response with wrapped data
556
  */
557
  public function envelope_response( $response, $embed ) {
558
  $envelope = array(
562
  );
563
 
564
  /**
565
+ * Filter the enveloped form of a response.
566
+ *
567
+ * @since 4.4.0
568
  *
569
+ * @param array $envelope Envelope data.
570
+ * @param WP_REST_Response $response Original response data.
571
  */
572
  $envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
573
 
574
+ // Ensure it's still a response and return.
575
  return rest_ensure_response( $envelope );
576
  }
577
 
578
  /**
579
+ * Registers a route to the server.
580
  *
581
+ * @since 4.4.0
582
+ * @access public
583
+ *
584
+ * @param string $namespace Namespace.
585
+ * @param string $route The REST route.
586
+ * @param array $route_args Route arguments.
587
+ * @param bool $override Optional. Whether the route should be overriden if it already exists.
588
+ * Default false.
589
  */
590
  public function register_route( $namespace, $route, $route_args, $override = false ) {
591
  if ( ! isset( $this->namespaces[ $namespace ] ) ) {
607
  ) );
608
  }
609
 
610
+ // Associative to avoid double-registration.
611
  $this->namespaces[ $namespace ][ $route ] = true;
612
  $route_args['namespace'] = $namespace;
613
 
619
  }
620
 
621
  /**
622
+ * Retrieves the route map.
623
  *
624
  * The route map is an associative array with path regexes as the keys. The
625
  * value is an indexed array with the callback function/method as the first
633
  * Note that the path regexes (array keys) must have @ escaped, as this is
634
  * used as the delimiter with preg_match()
635
  *
636
+ * @since 4.4.0
637
+ * @access public
638
+ *
639
+ * @return array `'/path/regex' => array( $callback, $bitmask )` or
640
+ * `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
641
  */
642
  public function get_routes() {
643
 
644
+ /**
645
+ * Filter the array of available endpoints.
646
+ *
647
+ * @since 4.4.0
648
+ *
649
+ * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
650
+ * to an array of callbacks for the endpoint. These take the format
651
+ * `'/path/regex' => array( $callback, $bitmask )` or
652
+ * `'/path/regex' => array( array( $callback, $bitmask ).
653
+ */
654
  $endpoints = apply_filters( 'rest_endpoints', $this->endpoints );
655
 
656
+ // Normalise the endpoints.
657
  $defaults = array(
658
  'methods' => '',
659
  'accept_json' => false,
661
  'show_in_index' => true,
662
  'args' => array(),
663
  );
664
+
665
  foreach ( $endpoints as $route => &$handlers ) {
666
+
667
  if ( isset( $handlers['callback'] ) ) {
668
+ // Single endpoint, add one deeper.
669
  $handlers = array( $handlers );
670
  }
671
+
672
  if ( ! isset( $this->route_options[ $route ] ) ) {
673
  $this->route_options[ $route ] = array();
674
  }
675
 
676
  foreach ( $handlers as $key => &$handler ) {
677
+
678
  if ( ! is_numeric( $key ) ) {
679
+ // Route option, move it to the options.
680
  $this->route_options[ $route ][ $key ] = $handler;
681
  unset( $handlers[ $key ] );
682
  continue;
683
  }
684
+
685
  $handler = wp_parse_args( $handler, $defaults );
686
 
687
+ // Allow comma-separated HTTP methods.
688
  if ( is_string( $handler['methods'] ) ) {
689
  $methods = explode( ',', $handler['methods'] );
690
  } else if ( is_array( $handler['methods'] ) ) {
691
  $methods = $handler['methods'];
692
+ } else {
693
+ $methods = array();
694
  }
695
 
696
  $handler['methods'] = array();
697
+
698
  foreach ( $methods as $method ) {
699
  $method = strtoupper( trim( $method ) );
700
  $handler['methods'][ $method ] = true;
705
  }
706
 
707
  /**
708
+ * Retrieves namespaces registered on the server.
709
+ *
710
+ * @since 4.4.0
711
+ * @access public
712
  *
713
  * @return array List of registered namespaces.
714
  */
717
  }
718
 
719
  /**
720
+ * Retrieves specified options for a route.
721
+ *
722
+ * @since 4.4.0
723
+ * @access public
724
  *
725
  * @param string $route Route pattern to fetch options for.
726
  * @return array|null Data as an associative array if found, or null if not found.
734
  }
735
 
736
  /**
737
+ * Matches the request to a callback and call it.
738
  *
739
+ * @since 4.4.0
740
+ * @access public
741
+ *
742
+ * @param WP_REST_Request $request Request to attempt dispatching.
743
+ * @return WP_REST_Response Response returned by the callback.
744
  */
745
  public function dispatch( $request ) {
746
  /**
747
+ * Filter the pre-calculated result of a REST dispatch request.
748
+ *
749
+ * Allow hijacking the request before dispatching by returning a non-empty. The returned value
750
+ * will be used to serve the request instead.
751
  *
752
+ * @since 4.4.0
 
753
  *
754
+ * @param mixed $result Response to replace the requested version with. Can be anything
755
+ * a normal endpoint can return, or null to not hijack the request.
756
+ * @param WP_REST_Server $this Server instance.
757
+ * @param WP_REST_Request $request Request used to generate the response.
758
  */
759
  $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
760
+
761
  if ( ! empty( $result ) ) {
762
  return $result;
763
  }
768
  foreach ( $this->get_routes() as $route => $handlers ) {
769
  foreach ( $handlers as $handler ) {
770
  $callback = $handler['callback'];
 
771
  $response = null;
772
 
773
  if ( empty( $handler['methods'][ $method ] ) ) {
808
  }
809
 
810
  if ( ! is_wp_error( $response ) ) {
811
+ // Check permission specified on the route.
812
  if ( ! empty( $handler['permission_callback'] ) ) {
813
  $permission = call_user_func( $handler['permission_callback'], $request );
814
 
822
 
823
  if ( ! is_wp_error( $response ) ) {
824
  /**
825
+ * Filter the REST dispatch request result.
826
+ *
827
+ * Allow plugins to override dispatching the request.
828
  *
829
+ * @since 4.4.0
830
+ *
831
+ * @param bool $dispatch_result Dispatch result, will be used if not empty.
832
+ * @param WP_REST_Request $request Request used to generate the response.
833
  */
834
  $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request );
835
 
836
+ // Allow plugins to halt the request via this filter.
837
  if ( null !== $dispatch_result ) {
838
  $response = $dispatch_result;
839
  } else {
858
  }
859
 
860
  /**
861
+ * Returns if an error occurred during most recent JSON encode/decode.
862
+ *
863
+ * Strings to be translated will be in format like
864
+ * "Encoding error: Maximum stack depth exceeded".
865
  *
866
+ * @since 4.4.0
867
+ * @access protected
868
+ *
869
+ * @return bool|string Boolean false or string error message.
870
  */
871
+ protected function get_json_last_error() {
872
+ // See https://core.trac.wordpress.org/ticket/27799.
873
  if ( ! function_exists( 'json_last_error' ) ) {
874
  return false;
875
  }
876
 
877
  $last_error_code = json_last_error();
878
+
879
  if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) {
880
  return false;
881
  }
884
  }
885
 
886
  /**
887
+ * Retrieves the site index.
888
  *
889
  * This endpoint describes the capabilities of the site.
890
  *
891
+ * @since 4.4.0
892
+ * @access public
893
  *
894
+ * @param array $request {
895
+ * Request.
896
+ *
897
+ * @type string $context Context.
898
+ * }
899
  * @return array Index entity
900
  */
901
  public function get_index( $request ) {
902
+ // General site data.
903
  $available = array(
904
  'name' => get_option( 'blogname' ),
905
  'description' => get_option( 'blogdescription' ),
910
  );
911
 
912
  $response = new WP_REST_Response( $available );
913
+
914
  $response->add_link( 'help', 'http://v2.wp-api.org/' );
915
 
916
  /**
920
  * about supported authentication schemes, supported namespaces, routes
921
  * available on the API, and a small amount of data about the site.
922
  *
923
+ * @since 4.4.0
924
+ *
925
  * @param WP_REST_Response $response Response data.
926
  */
927
  return apply_filters( 'rest_index', $response );
928
  }
929
 
930
  /**
931
+ * Retrieves the index for a namespace.
932
+ *
933
+ * @since 4.4.0
934
+ * @access public
935
  *
936
+ * @param WP_REST_Request $request REST request instance.
937
+ * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,
938
+ * WP_Error if the namespace isn't set.
939
  */
940
  public function get_namespace_index( $request ) {
941
  $namespace = $request['namespace'];
953
  );
954
  $response = rest_ensure_response( $data );
955
 
956
+ // Link to the root index.
957
  $response->add_link( 'up', rest_url( '/' ) );
958
 
959
  /**
962
  * This typically is just the route data for the namespace, but you can
963
  * add any data you'd like here.
964
  *
965
+ * @since 4.4.0
966
+ *
967
  * @param WP_REST_Response $response Response data.
968
+ * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
969
  */
970
  return apply_filters( 'rest_namespace_index', $response, $request );
971
  }
972
 
973
  /**
974
+ * Retrieves the publicly-visible data for routes.
975
  *
976
+ * @since 4.4.0
977
+ * @access public
978
+ *
979
+ * @param array $routes Routes to get data for.
980
+ * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
981
  * @return array Route data to expose in indexes.
982
  */
983
  public function get_data_for_routes( $routes, $context = 'view' ) {
984
  $available = array();
985
+
986
+ // Find the available routes.
987
  foreach ( $routes as $route => $callbacks ) {
988
  $data = $this->get_data_for_route( $route, $callbacks, $context );
989
  if ( empty( $data ) ) {
990
  continue;
991
  }
992
 
993
+ /**
994
+ * Filter the REST endpoint data.
995
+ *
996
+ * @since 4.4.0
997
+ *
998
+ * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
999
+ */
1000
  $available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
1001
  }
1002
 
1007
  * developers to investigate the site and find out how to use it. It
1008
  * acts as a form of self-documentation.
1009
  *
1010
+ * @since 4.4.0
1011
+ *
1012
  * @param array $available Map of route to route data.
1013
+ * @param array $routes Internal route data as an associative array.
1014
  */
1015
  return apply_filters( 'rest_route_data', $available, $routes );
1016
  }
1017
 
1018
  /**
1019
+ * Retrieves publicly-visible data for the route.
1020
+ *
1021
+ * @since 4.4.0
1022
+ * @access public
1023
  *
1024
+ * @param string $route Route to get data for.
1025
+ * @param array $callbacks Callbacks to convert to data.
1026
+ * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'.
1027
  * @return array|null Data for the route, or null if no publicly-visible data.
1028
  */
1029
  public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
1032
  'methods' => array(),
1033
  'endpoints' => array(),
1034
  );
1035
+
1036
  if ( isset( $this->route_options[ $route ] ) ) {
1037
  $options = $this->route_options[ $route ];
1038
+
1039
  if ( isset( $options['namespace'] ) ) {
1040
  $data['namespace'] = $options['namespace'];
1041
  }
1042
+
1043
  if ( isset( $options['schema'] ) && 'help' === $context ) {
1044
  $data['schema'] = call_user_func( $options['schema'] );
1045
  }
1048
  $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
1049
 
1050
  foreach ( $callbacks as $callback ) {
1051
+ // Skip to the next route if any callback is hidden.
1052
  if ( empty( $callback['show_in_index'] ) ) {
1053
  continue;
1054
  }
1073
 
1074
  $data['endpoints'][] = $endpoint_data;
1075
 
1076
+ // For non-variable routes, generate links.
1077
  if ( strpos( $route, '{' ) === false ) {
1078
  $data['_links'] = array(
1079
  'self' => rest_url( $route ),
1082
  }
1083
 
1084
  if ( empty( $data['methods'] ) ) {
1085
+ // No methods supported, hide the route.
1086
  return null;
1087
  }
1088
 
1090
  }
1091
 
1092
  /**
1093
+ * Sends an HTTP status code.
1094
+ *
1095
+ * @since 4.4.0
1096
+ * @access protected
1097
  *
1098
+ * @param int $code HTTP status.
1099
  */
1100
  protected function set_status( $code ) {
1101
  status_header( $code );
1102
  }
1103
 
1104
  /**
1105
+ * Sends an HTTP header.
1106
  *
1107
+ * @since 4.4.0
1108
+ * @access public
1109
+ *
1110
+ * @param string $key Header key.
1111
+ * @param string $value Header value.
1112
  */
1113
  public function send_header( $key, $value ) {
1114
+ /*
1115
+ * Sanitize as per RFC2616 (Section 4.2):
1116
+ *
1117
+ * Any LWS that occurs between field-content MAY be replaced with a
1118
+ * single SP before interpreting the field value or forwarding the
1119
+ * message downstream.
1120
+ */
1121
  $value = preg_replace( '/\s+/', ' ', $value );
1122
  header( sprintf( '%s: %s', $key, $value ) );
1123
  }
1124
 
1125
  /**
1126
+ * Sends multiple HTTP headers.
1127
+ *
1128
+ * @since 4.4.0
1129
+ * @access public
1130
  *
1131
+ * @param array $headers Map of header name to header value.
1132
  */
1133
  public function send_headers( $headers ) {
1134
  foreach ( $headers as $key => $value ) {
1137
  }
1138
 
1139
  /**
1140
+ * Retrieves the raw request entity (body).
1141
+ *
1142
+ * @since 4.4.0
1143
+ * @access public
1144
+ *
1145
+ * @global string $HTTP_RAW_POST_DATA Raw post data.
1146
  *
1147
+ * @return string Raw request data.
1148
  */
1149
  public function get_raw_data() {
1150
  global $HTTP_RAW_POST_DATA;
1151
 
1152
+ /*
1153
+ * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
1154
+ * but we can do it ourself.
1155
+ */
1156
  if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
1157
  $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
1158
  }
1161
  }
1162
 
1163
  /**
1164
+ * Extracts headers from a PHP-style $_SERVER array.
 
 
1165
  *
1166
+ * @since 4.4.0
1167
+ * @access public
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1168
  *
1169
+ * @param array $server Associative array similar to `$_SERVER`.
1170
+ * @return array Headers extracted from the input.
1171
  */
1172
  public function get_headers( $server ) {
1173
  $headers = array();
1174
 
1175
+ // CONTENT_* headers are not prefixed with HTTP_.
1176
  $additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true );
1177
 
1178
  foreach ( $server as $key => $value ) {
core/wp-includes/rest-api/rest-functions.php ADDED
@@ -0,0 +1,666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * REST API functions.
4
+ *
5
+ * @package WordPress
6
+ * @subpackage REST_API
7
+ */
8
+
9
+ /**
10
+ * Registers a REST API route.
11
+ *
12
+ * @since 4.4.0
13
+ *
14
+ * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
15
+ * @param string $route The base URL for route you are adding.
16
+ * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
17
+ * multiple methods. Default empty array.
18
+ * @param bool $override Optional. If the route already exists, should we override it? True overrides,
19
+ * false merges (with newer overriding if duplicate keys exist). Default false.
20
+ */
21
+ function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
22
+
23
+ /** @var WP_REST_Server $wp_rest_server */
24
+ global $wp_rest_server;
25
+
26
+ if ( isset( $args['callback'] ) ) {
27
+ // Upgrade a single set to multiple.
28
+ $args = array( $args );
29
+ }
30
+
31
+ $defaults = array(
32
+ 'methods' => 'GET',
33
+ 'callback' => null,
34
+ 'args' => array(),
35
+ );
36
+ foreach ( $args as $key => &$arg_group ) {
37
+ if ( ! is_numeric( $arg_group ) ) {
38
+ // Route option, skip here.
39
+ continue;
40
+ }
41
+
42
+ $arg_group = array_merge( $defaults, $arg_group );
43
+ }
44
+
45
+ if ( $namespace ) {
46
+ $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
47
+ } else {
48
+ /*
49
+ * Non-namespaced routes are not allowed, with the exception of the main
50
+ * and namespace indexes. If you really need to register a
51
+ * non-namespaced route, call `WP_REST_Server::register_route` directly.
52
+ */
53
+ _doing_it_wrong( 'register_rest_route', 'Routes must be namespaced with plugin name and version', 'WPAPI-2.0' );
54
+
55
+ $full_route = '/' . trim( $route, '/' );
56
+ }
57
+
58
+ $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
59
+ }
60
+
61
+ /**
62
+ * Registers a new field on an existing WordPress object type.
63
+ *
64
+ * @since 4.4.0
65
+ *
66
+ * @global array $wp_rest_additional_fields Holds registered fields, organized
67
+ * by object type.
68
+ *
69
+ * @param string|array $object_type Object(s) the field is being registered
70
+ * to, "post"|"term"|"comment" etc.
71
+ * @param string $attribute The attribute name.
72
+ * @param array $args {
73
+ * Optional. An array of arguments used to handle the registered field.
74
+ *
75
+ * @type string|array|null $get_callback Optional. The callback function used to retrieve the field
76
+ * value. Default is 'null', the field will not be returned in
77
+ * the response.
78
+ * @type string|array|null $update_callback Optional. The callback function used to set and update the
79
+ * field value. Default is 'null', the value cannot be set or
80
+ * updated.
81
+ * @type string|array|null schema Optional. The callback function used to create the schema for
82
+ * this field. Default is 'null', no schema entry will be returned.
83
+ * }
84
+ */
85
+ function register_api_field( $object_type, $attribute, $args = array() ) {
86
+
87
+ $defaults = array(
88
+ 'get_callback' => null,
89
+ 'update_callback' => null,
90
+ 'schema' => null,
91
+ );
92
+
93
+ $args = wp_parse_args( $args, $defaults );
94
+
95
+ global $wp_rest_additional_fields;
96
+
97
+ $object_types = (array) $object_type;
98
+
99
+ foreach ( $object_types as $object_type ) {
100
+ $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Registers rewrite rules for the API.
106
+ *
107
+ * @since 4.4.0
108
+ *
109
+ * @see rest_api_register_rewrites()
110
+ * @global WP $wp Current WordPress environment instance.
111
+ */
112
+ function rest_api_init() {
113
+ rest_api_register_rewrites();
114
+
115
+ global $wp;
116
+ $wp->add_query_var( 'rest_route' );
117
+ }
118
+
119
+ /**
120
+ * Adds REST rewrite rules.
121
+ *
122
+ * @since 4.4.0
123
+ *
124
+ * @see add_rewrite_rule()
125
+ */
126
+ function rest_api_register_rewrites() {
127
+ add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
128
+ add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
129
+ }
130
+
131
+ /**
132
+ * Registers the default REST API filters.
133
+ *
134
+ * @since 4.4.0
135
+ *
136
+ * @internal This will live in default-filters.php
137
+ */
138
+ function rest_api_default_filters() {
139
+ // Deprecated reporting.
140
+ add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
141
+ add_filter( 'deprecated_function_trigger_error', '__return_false' );
142
+ add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
143
+ add_filter( 'deprecated_argument_trigger_error', '__return_false' );
144
+
145
+ // Default serving.
146
+ add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
147
+ add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
148
+
149
+ add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
150
+ }
151
+
152
+ /**
153
+ * Loads the REST API.
154
+ *
155
+ * @since 4.4.0
156
+ */
157
+ function rest_api_loaded() {
158
+ if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
159
+ return;
160
+ }
161
+
162
+ /**
163
+ * Whether this is a REST Request.
164
+ *
165
+ * @var bool
166
+ */
167
+ define( 'REST_REQUEST', true );
168
+
169
+ /** @var WP_REST_Server $wp_rest_server */
170
+ global $wp_rest_server;
171
+
172
+ /**
173
+ * Filter the REST Server Class.
174
+ *
175
+ * This filter allows you to adjust the server class used by the API, using a
176
+ * different class to handle requests.
177
+ *
178
+ * @since 4.4.0
179
+ *
180
+ * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
181
+ */
182
+ $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
183
+ $wp_rest_server = new $wp_rest_server_class;
184
+
185
+ /**
186
+ * Fires when preparing to serve an API request.
187
+ *
188
+ * Endpoint objects should be created and register their hooks on this action rather
189
+ * than another action to ensure they're only loaded when needed.
190
+ *
191
+ * @since 4.4.0
192
+ *
193
+ * @param WP_REST_Server $wp_rest_server Server object.
194
+ */
195
+ do_action( 'rest_api_init', $wp_rest_server );
196
+
197
+ // Fire off the request.
198
+ $wp_rest_server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
199
+
200
+ // We're done.
201
+ die();
202
+ }
203
+
204
+ /**
205
+ * Retrieves the URL prefix for any API resource.
206
+ *
207
+ * @since 4.4.0
208
+ *
209
+ * @return string Prefix.
210
+ */
211
+ function rest_get_url_prefix() {
212
+ /**
213
+ * Filter the REST URL prefix.
214
+ *
215
+ * @since 4.4.0
216
+ *
217
+ * @param string $prefix URL prefix. Default 'wp-json'.
218
+ */
219
+ return apply_filters( 'rest_url_prefix', 'wp-json' );
220
+ }
221
+
222
+ /**
223
+ * Retrieves the URL to a REST endpoint on a site.
224
+ *
225
+ * Note: The returned URL is NOT escaped.
226
+ *
227
+ * @since 4.4.0
228
+ *
229
+ * @todo Check if this is even necessary
230
+ *
231
+ * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
232
+ * @param string $path Optional. REST route. Default '/'.
233
+ * @param string $scheme Optional. Sanitization scheme. Default 'json'.
234
+ * @return string Full URL to the endpoint.
235
+ */
236
+ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
237
+ if ( empty( $path ) ) {
238
+ $path = '/';
239
+ }
240
+
241
+ if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
242
+ $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
243
+ $url .= '/' . ltrim( $path, '/' );
244
+ } else {
245
+ $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
246
+
247
+ $path = '/' . ltrim( $path, '/' );
248
+
249
+ $url = add_query_arg( 'rest_route', $path, $url );
250
+ }
251
+
252
+ /**
253
+ * Filter the REST URL.
254
+ *
255
+ * Use this filter to adjust the url returned by the `get_rest_url` function.
256
+ *
257
+ * @since 4.4.0
258
+ *
259
+ * @param string $url REST URL.
260
+ * @param string $path REST route.
261
+ * @param int $blod_ig Blog ID.
262
+ * @param string $scheme Sanitization scheme.
263
+ */
264
+ return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
265
+ }
266
+
267
+ /**
268
+ * Retrieves the URL to a REST endpoint.
269
+ *
270
+ * Note: The returned URL is NOT escaped.
271
+ *
272
+ * @since 4.4.0
273
+ *
274
+ * @param string $path Optional. REST route. Default empty.
275
+ * @param string $scheme Optional. Sanitization scheme. Default 'json'.
276
+ * @return string Full URL to the endpoint.
277
+ */
278
+ function rest_url( $path = '', $scheme = 'json' ) {
279
+ return get_rest_url( null, $path, $scheme );
280
+ }
281
+
282
+ /**
283
+ * Do a REST request.
284
+ *
285
+ * Used primarily to route internal requests through WP_REST_Server.
286
+ *
287
+ * @since 4.4.0
288
+ *
289
+ * @global WP_REST_Server $wp_rest_server
290
+ *
291
+ * @param WP_REST_Request|string $request Request.
292
+ * @return WP_REST_Response REST response.
293
+ */
294
+ function rest_do_request( $request ) {
295
+ global $wp_rest_server;
296
+ $request = rest_ensure_request( $request );
297
+ return $wp_rest_server->dispatch( $request );
298
+ }
299
+
300
+ /**
301
+ * Ensures request arguments are a request object (for consistency).
302
+ *
303
+ * @since 4.4.0
304
+ *
305
+ * @param array|WP_REST_Request $request Request to check.
306
+ * @return WP_REST_Request REST request instance.
307
+ */
308
+ function rest_ensure_request( $request ) {
309
+ if ( $request instanceof WP_REST_Request ) {
310
+ return $request;
311
+ }
312
+
313
+ return new WP_REST_Request( 'GET', '', $request );
314
+ }
315
+
316
+ /**
317
+ * Ensures a REST response is a response object (for consistency).
318
+ *
319
+ * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
320
+ * without needing to double-check the object. Will also allow WP_Error to indicate error
321
+ * responses, so users should immediately check for this value.
322
+ *
323
+ * @since 4.4.0
324
+ *
325
+ * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
326
+ * @return mixed WP_Error if response generated an error, WP_HTTP_Response if response
327
+ * is a already an instance, otherwise returns a new WP_REST_Response instance.
328
+ */
329
+ function rest_ensure_response( $response ) {
330
+ if ( is_wp_error( $response ) ) {
331
+ return $response;
332
+ }
333
+
334
+ if ( $response instanceof WP_HTTP_Response ) {
335
+ return $response;
336
+ }
337
+
338
+ return new WP_REST_Response( $response );
339
+ }
340
+
341
+ /**
342
+ * Handles _deprecated_function() errors.
343
+ *
344
+ * @since 4.4.0
345
+ *
346
+ * @param string $function Function name.
347
+ * @param string $replacement Replacement function name.
348
+ * @param string $version Version.
349
+ */
350
+ function rest_handle_deprecated_function( $function, $replacement, $version ) {
351
+ if ( ! empty( $replacement ) ) {
352
+ $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
353
+ } else {
354
+ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
355
+ }
356
+
357
+ header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
358
+ }
359
+
360
+ /**
361
+ * Handles _deprecated_argument() errors.
362
+ *
363
+ * @since 4.4.0
364
+ *
365
+ * @param string $function Function name.
366
+ * @param string $replacement Replacement function name.
367
+ * @param string $version Version.
368
+ */
369
+ function rest_handle_deprecated_argument( $function, $replacement, $version ) {
370
+ if ( ! empty( $replacement ) ) {
371
+ $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $replacement );
372
+ } else {
373
+ $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
374
+ }
375
+
376
+ header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
377
+ }
378
+
379
+ /**
380
+ * Sends Cross-Origin Resource Sharing headers with API requests.
381
+ *
382
+ * @since 4.4.0
383
+ *
384
+ * @param mixed $value Response data.
385
+ * @return mixed Response data.
386
+ */
387
+ function rest_send_cors_headers( $value ) {
388
+ $origin = get_http_origin();
389
+
390
+ if ( $origin ) {
391
+ header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
392
+ header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
393
+ header( 'Access-Control-Allow-Credentials: true' );
394
+ }
395
+
396
+ return $value;
397
+ }
398
+
399
+ /**
400
+ * Handles OPTIONS requests for the server.
401
+ *
402
+ * This is handled outside of the server code, as it doesn't obey normal route
403
+ * mapping.
404
+ *
405
+ * @since 4.4.0
406
+ *
407
+ * @param mixed $response Current response, either response or `null` to indicate pass-through.
408
+ * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
409
+ * @param WP_REST_Request $request The request that was used to make current response.
410
+ * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
411
+ */
412
+ function rest_handle_options_request( $response, $handler, $request ) {
413
+ if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
414
+ return $response;
415
+ }
416
+
417
+ $response = new WP_REST_Response();
418
+ $data = array();
419
+
420
+ $accept = array();
421
+
422
+ foreach ( $handler->get_routes() as $route => $endpoints ) {
423
+ $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
424
+
425
+ if ( ! $match ) {
426
+ continue;
427
+ }
428
+
429
+ $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
430
+ $accept = array_merge( $accept, $data['methods'] );
431
+ break;
432
+ }
433
+ $response->header( 'Accept', implode( ', ', $accept ) );
434
+
435
+ $response->set_data( $data );
436
+ return $response;
437
+ }
438
+
439
+ /**
440
+ * Sends the "Allow" header to state all methods that can be sent to the current route.
441
+ *
442
+ * @since 4.4.0
443
+ *
444
+ * @param WP_REST_Response $response Current response being served.
445
+ * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
446
+ * @param WP_REST_Request $request The request that was used to make current response.
447
+ * @return WP_REST_Response Current response being served.
448
+ */
449
+ function rest_send_allow_header( $response, $server, $request ) {
450
+
451
+ $matched_route = $response->get_matched_route();
452
+
453
+ if ( ! $matched_route ) {
454
+ return $response;
455
+ }
456
+
457
+ $routes = $server->get_routes();
458
+
459
+ $allowed_methods = array();
460
+
461
+ // Get the allowed methods across the routes.
462
+ foreach ( $routes[ $matched_route ] as $_handler ) {
463
+ foreach ( $_handler['methods'] as $handler_method => $value ) {
464
+
465
+ if ( ! empty( $_handler['permission_callback'] ) ) {
466
+
467
+ $permission = call_user_func( $_handler['permission_callback'], $request );
468
+
469
+ $allowed_methods[ $handler_method ] = true === $permission;
470
+ } else {
471
+ $allowed_methods[ $handler_method ] = true;
472
+ }
473
+ }
474
+ }
475
+
476
+ // Strip out all the methods that are not allowed (false values).
477
+ $allowed_methods = array_filter( $allowed_methods );
478
+
479
+ if ( $allowed_methods ) {
480
+ $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
481
+ }
482
+
483
+ return $response;
484
+ }
485
+
486
+ /**
487
+ * Adds the REST API URL to the WP RSD endpoint.
488
+ *
489
+ * @since 4.4.0
490
+ *
491
+ * @see get_rest_url()
492
+ */
493
+ function rest_output_rsd() {
494
+ $api_root = get_rest_url();
495
+
496
+ if ( empty( $api_root ) ) {
497
+ return;
498
+ }
499
+ ?>
500
+ <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
501
+ <?php
502
+ }
503
+
504
+ /**
505
+ * Outputs the REST API link tag into page header.
506
+ *
507
+ * @since 4.4.0
508
+ *
509
+ * @see get_rest_url()
510
+ */
511
+ function rest_output_link_wp_head() {
512
+ $api_root = get_rest_url();
513
+
514
+ if ( empty( $api_root ) ) {
515
+ return;
516
+ }
517
+
518
+ echo "<link rel='https://github.com/WP-API/WP-API' href='" . esc_url( $api_root ) . "' />\n";
519
+ }
520
+
521
+ /**
522
+ * Sends a Link header for the REST API.
523
+ *
524
+ * @since 4.4.0
525
+ */
526
+ function rest_output_link_header() {
527
+ if ( headers_sent() ) {
528
+ return;
529
+ }
530
+
531
+ $api_root = get_rest_url();
532
+
533
+ if ( empty( $api_root ) ) {
534
+ return;
535
+ }
536
+
537
+ header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://github.com/WP-API/WP-API"', false );
538
+ }
539
+
540
+ /**
541
+ * Checks for errors when using cookie-based authentication.
542
+ *
543
+ * WordPress' built-in cookie authentication is always active
544
+ * for logged in users. However, the API has to check nonces
545
+ * for each request to ensure users are not vulnerable to CSRF.
546
+ *
547
+ * @since 4.4.0
548
+ *
549
+ * @global mixed $wp_rest_auth_cookie
550
+ *
551
+ * @param WP_Error|mixed $result Error from another authentication handler, null if we should handle it,
552
+ * or another value if not.
553
+ * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
554
+ */
555
+ function rest_cookie_check_errors( $result ) {
556
+ if ( ! empty( $result ) ) {
557
+ return $result;
558
+ }
559
+
560
+ global $wp_rest_auth_cookie;
561
+
562
+ /*
563
+ * Is cookie authentication being used? (If we get an auth
564
+ * error, but we're still logged in, another authentication
565
+ * must have been used).
566
+ */
567
+ if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
568
+ return $result;
569
+ }
570
+
571
+ // Determine if there is a nonce.
572
+ $nonce = null;
573
+
574
+ if ( isset( $_REQUEST['_wp_rest_nonce'] ) ) {
575
+ $nonce = $_REQUEST['_wp_rest_nonce'];
576
+ } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
577
+ $nonce = $_SERVER['HTTP_X_WP_NONCE'];
578
+ }
579
+
580
+ if ( null === $nonce ) {
581
+ // No nonce at all, so act as if it's an unauthenticated request.
582
+ wp_set_current_user( 0 );
583
+ return true;
584
+ }
585
+
586
+ // Check the nonce.
587
+ $result = wp_verify_nonce( $nonce, 'wp_rest' );
588
+
589
+ if ( ! $result ) {
590
+ return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
591
+ }
592
+
593
+ return true;
594
+ }
595
+
596
+ /**
597
+ * Collects cookie authentication status.
598
+ *
599
+ * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
600
+ *
601
+ * @since 4.4.0
602
+ *
603
+ * @see current_action()
604
+ * @global mixed $wp_rest_auth_cookie
605
+ */
606
+ function rest_cookie_collect_status() {
607
+ global $wp_rest_auth_cookie;
608
+
609
+ $status_type = current_action();
610
+
611
+ if ( 'auth_cookie_valid' !== $status_type ) {
612
+ $wp_rest_auth_cookie = substr( $status_type, 12 );
613
+ return;
614
+ }
615
+
616
+ $wp_rest_auth_cookie = true;
617
+ }
618
+
619
+ /**
620
+ * Parses an RFC3339 timestamp into a DateTime.
621
+ *
622
+ * @since 4.4.0
623
+ *
624
+ * @param string $date RFC3339 timestamp.
625
+ * @param bool $force_utc Optional. Whether to force UTC timezone instead of using
626
+ * the timestamp's timezone. Default false.
627
+ * @return DateTime DateTime instance.
628
+ */
629
+ function rest_parse_date( $date, $force_utc = false ) {
630
+ if ( $force_utc ) {
631
+ $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
632
+ }
633
+
634
+ $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
635
+
636
+ if ( ! preg_match( $regex, $date, $matches ) ) {
637
+ return false;
638
+ }
639
+
640
+ return strtotime( $date );
641
+ }
642
+
643
+ /**
644
+ * Retrieves a local date with its GMT equivalent, in MySQL datetime format.
645
+ *
646
+ * @since 4.4.0
647
+ *
648
+ * @see rest_parse_date()
649
+ *
650
+ * @param string $date RFC3339 timestamp.
651
+ * @param bool $force_utc Whether a UTC timestamp should be forced. Default false.
652
+ * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
653
+ * null on failure.
654
+ */
655
+ function rest_get_date_with_gmt( $date, $force_utc = false ) {
656
+ $date = rest_parse_date( $date, $force_utc );
657
+
658
+ if ( empty( $date ) ) {
659
+ return null;
660
+ }
661
+
662
+ $utc = date( 'Y-m-d H:i:s', $date );
663
+ $local = get_date_from_gmt( $utc );
664
+
665
+ return array( $local, $utc );
666
+ }
extras.php CHANGED
@@ -11,20 +11,11 @@
11
 
12
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
13
  add_action( 'admin_enqueue_scripts', 'rest_register_scripts', -100 );
14
- add_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' );
15
- add_action( 'wp_head', 'rest_output_link_wp_head', 10, 0 );
16
- add_action( 'template_redirect', 'rest_output_link_header', 11, 0 );
17
- add_action( 'auth_cookie_malformed', 'rest_cookie_collect_status' );
18
- add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' );
19
- add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' );
20
- add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' );
21
- add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' );
22
- add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 );
23
-
24
-
25
 
26
  /**
27
- * Register API Javascript helpers.
 
 
28
  *
29
  * @see wp_register_scripts()
30
  */
@@ -36,134 +27,14 @@ function rest_register_scripts() {
36
  }
37
 
38
  /**
39
- * Add the API URL to the WP RSD endpoint.
40
- */
41
- function rest_output_rsd() {
42
- $api_root = get_rest_url();
43
-
44
- if ( empty( $api_root ) ) {
45
- return;
46
- }
47
- ?>
48
- <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
49
- <?php
50
- }
51
-
52
- /**
53
- * Output API link tag into page header.
54
- *
55
- * @see get_rest_url()
56
- */
57
- function rest_output_link_wp_head() {
58
- $api_root = get_rest_url();
59
-
60
- if ( empty( $api_root ) ) {
61
- return;
62
- }
63
-
64
- echo "<link rel='https://github.com/WP-API/WP-API' href='" . esc_url( $api_root ) . "' />\n";
65
- }
66
-
67
- /**
68
- * Send a Link header for the API.
69
- */
70
- function rest_output_link_header() {
71
- if ( headers_sent() ) {
72
- return;
73
- }
74
-
75
- $api_root = get_rest_url();
76
-
77
- if ( empty( $api_root ) ) {
78
- return;
79
- }
80
-
81
- header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://github.com/WP-API/WP-API"', false );
82
- }
83
-
84
- /**
85
- * Check for errors when using cookie-based authentication.
86
- *
87
- * WordPress' built-in cookie authentication is always active
88
- * for logged in users. However, the API has to check nonces
89
- * for each request to ensure users are not vulnerable to CSRF.
90
- *
91
- * @global mixed $wp_rest_auth_cookie
92
- *
93
- * @param WP_Error|mixed $result Error from another authentication handler,
94
- * null if we should handle it, or another
95
- * value if not
96
- * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result,
97
- * otherwise true.
98
- */
99
- function rest_cookie_check_errors( $result ) {
100
- if ( ! empty( $result ) ) {
101
- return $result;
102
- }
103
-
104
- global $wp_rest_auth_cookie;
105
-
106
- /*
107
- * Is cookie authentication being used? (If we get an auth
108
- * error, but we're still logged in, another authentication
109
- * must have been used.)
110
- */
111
- if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
112
- return $result;
113
- }
114
-
115
- // Is there a nonce?
116
- $nonce = null;
117
- if ( isset( $_REQUEST['_wp_rest_nonce'] ) ) {
118
- $nonce = $_REQUEST['_wp_rest_nonce'];
119
- } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
120
- $nonce = $_SERVER['HTTP_X_WP_NONCE'];
121
- }
122
-
123
- if ( null === $nonce ) {
124
- // No nonce at all, so act as if it's an unauthenticated request.
125
- wp_set_current_user( 0 );
126
- return true;
127
- }
128
-
129
- // Check the nonce.
130
- $result = wp_verify_nonce( $nonce, 'wp_rest' );
131
- if ( ! $result ) {
132
- return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
133
- }
134
-
135
- return true;
136
- }
137
-
138
- /**
139
- * Collect cookie authentication status.
140
  *
141
- * Collects errors from {@see wp_validate_auth_cookie} for
142
- * use by {@see rest_cookie_check_errors}.
143
- *
144
- * @see current_action()
145
- * @global mixed $wp_rest_auth_cookie
146
- */
147
- function rest_cookie_collect_status() {
148
- global $wp_rest_auth_cookie;
149
-
150
- $status_type = current_action();
151
-
152
- if ( 'auth_cookie_valid' !== $status_type ) {
153
- $wp_rest_auth_cookie = substr( $status_type, 12 );
154
- return;
155
- }
156
-
157
- $wp_rest_auth_cookie = true;
158
- }
159
-
160
- /**
161
- * Retrieve the avatar urls in various sizes based on a given email address.
162
  *
163
- * {@see get_avatar_url()}
164
  *
165
  * @param string $email Email address.
166
- * @return array $urls Gravatar url for each size.
167
  */
168
  function rest_get_avatar_urls( $email ) {
169
  $avatar_sizes = rest_get_avatar_sizes();
@@ -177,75 +48,31 @@ function rest_get_avatar_urls( $email ) {
177
  }
178
 
179
  /**
180
- * Return the pixel sizes for avatars.
 
 
181
  *
182
- * @return array
183
  */
184
  function rest_get_avatar_sizes() {
 
 
 
 
 
 
 
 
 
 
 
185
  return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
186
  }
187
 
188
  /**
189
- * Parse an RFC3339 timestamp into a DateTime.
190
- *
191
- * @param string $date RFC3339 timestamp.
192
- * @param bool $force_utc Force UTC timezone instead of using the timestamp's TZ.
193
- * @return DateTime DateTime instance.
194
- */
195
- function rest_parse_date( $date, $force_utc = false ) {
196
- if ( $force_utc ) {
197
- $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
198
- }
199
-
200
- $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
201
-
202
- if ( ! preg_match( $regex, $date, $matches ) ) {
203
- return false;
204
- }
205
-
206
- return strtotime( $date );
207
- }
208
-
209
- /**
210
- * Get a local date with its GMT equivalent, in MySQL datetime format.
211
- *
212
- * @param string $date RFC3339 timestamp
213
- * @param bool $force_utc Whether a UTC timestamp should be forced.
214
- * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
215
- * null on failure.
216
- */
217
- function rest_get_date_with_gmt( $date, $force_utc = false ) {
218
- $date = rest_parse_date( $date, $force_utc );
219
-
220
- if ( empty( $date ) ) {
221
- return null;
222
- }
223
-
224
- $utc = date( 'Y-m-d H:i:s', $date );
225
- $local = get_date_from_gmt( $utc );
226
-
227
- return array( $local, $utc );
228
- }
229
-
230
- /**
231
- * Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601/RFC3339
232
  *
233
- * Explicitly strips timezones, as datetimes are not saved with any timezone
234
- * information. Including any information on the offset could be misleading.
235
- *
236
- * @param string $date
237
- * @return string Date formatted for ISO8601/RFC3339.
238
- */
239
- function rest_mysql_to_rfc3339( $date_string ) {
240
- $formatted = mysql2date( 'c', $date_string, false );
241
-
242
- // Strip timezone information
243
- return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
244
- }
245
-
246
-
247
- /**
248
- * Get the timezone object for the site.
249
  *
250
  * @return DateTimeZone DateTimeZone instance.
251
  */
@@ -259,7 +86,7 @@ function rest_get_timezone() {
259
  $tzstring = get_option( 'timezone_string' );
260
 
261
  if ( ! $tzstring ) {
262
- // Create a UTC+- zone if no timezone string exists
263
  $current_offset = get_option( 'gmt_offset' );
264
  if ( 0 === $current_offset ) {
265
  $tzstring = 'UTC';
@@ -275,23 +102,25 @@ function rest_get_timezone() {
275
  }
276
 
277
  /**
278
- * Retrieve the avatar url for a user who provided a user ID or email address.
279
  *
280
- * @deprecated WPAPI-2.0
281
- * {@see get_avatar()} doesn't return just the URL, so we have to
282
- * extract it here.
 
 
283
  *
284
  * @param string $email Email address.
285
  * @return string URL for the user's avatar, empty string otherwise.
286
  */
287
  function rest_get_avatar_url( $email ) {
288
  _deprecated_function( 'rest_get_avatar_url', 'WPAPI-2.0', 'rest_get_avatar_urls' );
289
- /**
290
- * Use the WP Core `get_avatar_url()` function introduced in 4.2.
291
- */
292
  if ( function_exists( 'get_avatar_url' ) ) {
293
  return esc_url_raw( get_avatar_url( $email ) );
294
  }
 
295
  $avatar_html = get_avatar( $email );
296
 
297
  // Strip the avatar url from the get_avatar img tag.
@@ -303,3 +132,39 @@ function rest_get_avatar_url( $email ) {
303
 
304
  return '';
305
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  add_action( 'wp_enqueue_scripts', 'rest_register_scripts', -100 );
13
  add_action( 'admin_enqueue_scripts', 'rest_register_scripts', -100 );
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  /**
16
+ * Registers REST API JavaScript helpers.
17
+ *
18
+ * @since 4.4.0
19
  *
20
  * @see wp_register_scripts()
21
  */
27
  }
28
 
29
  /**
30
+ * Retrieves the avatar urls in various sizes based on a given email address.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  *
32
+ * @since 4.4.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *
34
+ * @see get_avatar_url()
35
  *
36
  * @param string $email Email address.
37
+ * @return array $urls Gravatar url for each size.
38
  */
39
  function rest_get_avatar_urls( $email ) {
40
  $avatar_sizes = rest_get_avatar_sizes();
48
  }
49
 
50
  /**
51
+ * Retrieves the pixel sizes for avatars.
52
+ *
53
+ * @since 4.4.0
54
  *
55
+ * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
56
  */
57
  function rest_get_avatar_sizes() {
58
+ /**
59
+ * Filter the REST avatar sizes.
60
+ *
61
+ * Use this filter to adjust the array of sizes returned by the
62
+ * `rest_get_avatar_sizes` function.
63
+ *
64
+ * @since 4.4.0
65
+ *
66
+ * @param array $sizes An array of int values that are the pixel sizes for avatars.
67
+ * Default `[ 24, 48, 96 ]`.
68
+ */
69
  return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
70
  }
71
 
72
  /**
73
+ * Retrieves the timezone object for the site.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  *
75
+ * @since 4.4.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  *
77
  * @return DateTimeZone DateTimeZone instance.
78
  */
86
  $tzstring = get_option( 'timezone_string' );
87
 
88
  if ( ! $tzstring ) {
89
+ // Create a UTC+- zone if no timezone string exists.
90
  $current_offset = get_option( 'gmt_offset' );
91
  if ( 0 === $current_offset ) {
92
  $tzstring = 'UTC';
102
  }
103
 
104
  /**
105
+ * Retrieves the avatar url for a user who provided a user ID or email address.
106
  *
107
+ * get_avatar() doesn't return just the URL, so we have to extract it here.
108
+ *
109
+ * @since 4.4.0
110
+ * @deprecated WPAPI-2.0 rest_get_avatar_urls()
111
+ * @see rest_get_avatar_urls()
112
  *
113
  * @param string $email Email address.
114
  * @return string URL for the user's avatar, empty string otherwise.
115
  */
116
  function rest_get_avatar_url( $email ) {
117
  _deprecated_function( 'rest_get_avatar_url', 'WPAPI-2.0', 'rest_get_avatar_urls' );
118
+
119
+ // Use the WP Core `get_avatar_url()` function introduced in 4.2.
 
120
  if ( function_exists( 'get_avatar_url' ) ) {
121
  return esc_url_raw( get_avatar_url( $email ) );
122
  }
123
+
124
  $avatar_html = get_avatar( $email );
125
 
126
  // Strip the avatar url from the get_avatar img tag.
132
 
133
  return '';
134
  }
135
+
136
+ if ( ! function_exists( 'wp_is_numeric_array' ) ) {
137
+ /**
138
+ * Determines if the variable is a numeric-indexed array.
139
+ *
140
+ * @since 4.4.0
141
+ *
142
+ * @param mixed $data Variable to check.
143
+ * @return bool Whether the variable is a list.
144
+ */
145
+ function wp_is_numeric_array( $data ) {
146
+ if ( ! is_array( $data ) ) {
147
+ return false;
148
+ }
149
+
150
+ $keys = array_keys( $data );
151
+ $string_keys = array_filter( $keys, 'is_string' );
152
+ return count( $string_keys ) === 0;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Parses and formats a MySQL datetime (Y-m-d H:i:s) for ISO8601/RFC3339.
158
+ *
159
+ * Explicitly strips timezones, as datetimes are not saved with any timezone
160
+ * information. Including any information on the offset could be misleading.
161
+ *
162
+ * @deprecated WPAPI-2.0 mysql_to_rfc3339()
163
+ *
164
+ * @param string $date_string Date string to parse and format.
165
+ * @return string Date formatted for ISO8601/RFC3339.
166
+ */
167
+ function rest_mysql_to_rfc3339( $date_string ) {
168
+ _deprecated_function( 'rest_mysql_to_rfc3339', 'WPAPI-2.0', 'mysql_to_rfc3339' );
169
+ return mysql_to_rfc3339( $date_string );
170
+ }
lib/endpoints/class-wp-rest-comments-controller.php CHANGED
@@ -436,8 +436,8 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
436
  'author_ip' => $comment->comment_author_IP,
437
  'author_avatar_urls' => rest_get_avatar_urls( $comment->comment_author_email ),
438
  'author_user_agent' => $comment->comment_agent,
439
- 'date' => rest_mysql_to_rfc3339( $comment->comment_date ),
440
- 'date_gmt' => rest_mysql_to_rfc3339( $comment->comment_date_gmt ),
441
  'content' => array(
442
  'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
443
  'raw' => $comment->comment_content,
436
  'author_ip' => $comment->comment_author_IP,
437
  'author_avatar_urls' => rest_get_avatar_urls( $comment->comment_author_email ),
438
  'author_user_agent' => $comment->comment_agent,
439
+ 'date' => mysql_to_rfc3339( $comment->comment_date ),
440
+ 'date_gmt' => mysql_to_rfc3339( $comment->comment_date_gmt ),
441
  'content' => array(
442
  'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
443
  'raw' => $comment->comment_content,
lib/endpoints/class-wp-rest-posts-controller.php CHANGED
@@ -578,10 +578,10 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
578
  }
579
 
580
  if ( isset( $date ) ) {
581
- return rest_mysql_to_rfc3339( $date );
582
  }
583
 
584
- return rest_mysql_to_rfc3339( $date_gmt );
585
  }
586
 
587
  protected function prepare_password_response( $password ) {
578
  }
579
 
580
  if ( isset( $date ) ) {
581
+ return mysql_to_rfc3339( $date );
582
  }
583
 
584
+ return mysql_to_rfc3339( $date_gmt );
585
  }
586
 
587
  protected function prepare_password_response( $password ) {
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -200,7 +200,8 @@ class WP_REST_Posts_Terms_Controller extends WP_REST_Controller {
200
  if ( ! empty( $request['term_id'] ) ) {
201
  $term_id = absint( $request['term_id'] );
202
 
203
- if ( ! get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy ) ) {
 
204
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
205
  }
206
  }
200
  if ( ! empty( $request['term_id'] ) ) {
201
  $term_id = absint( $request['term_id'] );
202
 
203
+ $term = get_term_by( 'term_taxonomy_id', $term_id, $this->taxonomy );
204
+ if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
205
  return new WP_Error( 'rest_term_invalid', __( "Term doesn't exist." ), array( 'status' => 404 ) );
206
  }
207
  }
lib/endpoints/class-wp-rest-revisions-controller.php CHANGED
@@ -226,10 +226,10 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
226
  }
227
 
228
  if ( isset( $date ) ) {
229
- return rest_mysql_to_rfc3339( $date );
230
  }
231
 
232
- return rest_mysql_to_rfc3339( $date_gmt );
233
  }
234
 
235
  /**
226
  }
227
 
228
  if ( isset( $date ) ) {
229
+ return mysql_to_rfc3339( $date );
230
  }
231
 
232
+ return mysql_to_rfc3339( $date_gmt );
233
  }
234
 
235
  /**
lib/infrastructure/class-jsonserializable.php DELETED
@@ -1,18 +0,0 @@
1
- <?php
2
- /**
3
- * Compatibility shim for PHP <5.4
4
- *
5
- * @link http://php.net/jsonserializable
6
- *
7
- * @package WordPress
8
- * @subpackage JSON API
9
- */
10
-
11
- if ( ! interface_exists( 'JsonSerializable' ) ) {
12
- define( 'WP_JSON_SERIALIZE_COMPATIBLE', true );
13
- // @codingStandardsIgnoreStart
14
- interface JsonSerializable {
15
- public function jsonSerialize();
16
- }
17
- // @codingStandardsIgnoreEnd
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/infrastructure/class-wp-http-response.php DELETED
@@ -1,112 +0,0 @@
1
- <?php
2
-
3
- class WP_HTTP_Response implements WP_HTTP_ResponseInterface {
4
- /**
5
- * @var mixed
6
- */
7
- public $data;
8
- /**
9
- * @var integer
10
- */
11
- public $headers;
12
- /**
13
- * @var array
14
- */
15
- public $status;
16
- /**
17
- * Constructor
18
- *
19
- * @param mixed $data Response data
20
- * @param integer $status HTTP status code
21
- * @param array $headers HTTP header map
22
- */
23
- public function __construct( $data = null, $status = 200, $headers = array() ) {
24
- $this->data = $data;
25
- $this->set_status( $status );
26
- $this->set_headers( $headers );
27
- }
28
-
29
- /**
30
- * Get headers associated with the response
31
- *
32
- * @return array Map of header name to header value
33
- */
34
- public function get_headers() {
35
- return $this->headers;
36
- }
37
-
38
- /**
39
- * Set all header values
40
- *
41
- * @param array $headers Map of header name to header value
42
- */
43
- public function set_headers( $headers ) {
44
- $this->headers = $headers;
45
- }
46
-
47
- /**
48
- * Set a single HTTP header
49
- *
50
- * @param string $key Header name
51
- * @param string $value Header value
52
- * @param boolean $replace Replace an existing header of the same name?
53
- */
54
- public function header( $key, $value, $replace = true ) {
55
- if ( $replace || ! isset( $this->headers[ $key ] ) ) {
56
- $this->headers[ $key ] = $value;
57
- } else {
58
- $this->headers[ $key ] .= ', ' . $value;
59
- }
60
- }
61
-
62
- /**
63
- * Get the HTTP return code for the response
64
- *
65
- * @return integer 3-digit HTTP status code
66
- */
67
- public function get_status() {
68
- return $this->status;
69
- }
70
-
71
- /**
72
- * Set the HTTP status code
73
- *
74
- * @param int $code HTTP status
75
- */
76
- public function set_status( $code ) {
77
- $this->status = absint( $code );
78
- }
79
-
80
- /**
81
- * Get the response data
82
- *
83
- * @return mixed
84
- */
85
- public function get_data() {
86
- return $this->data;
87
- }
88
-
89
- /**
90
- * Set the response data
91
- *
92
- * @param mixed $data
93
- */
94
- public function set_data( $data ) {
95
- $this->data = $data;
96
- }
97
-
98
- /**
99
- * Get the response data for JSON serialization
100
- *
101
- * It is expected that in most implementations, this will return the same as
102
- * {@see get_data()}, however this may be different if you want to do custom
103
- * JSON data handling.
104
- *
105
- * @return mixed Any JSON-serializable value
106
- */
107
- // @codingStandardsIgnoreStart
108
- public function jsonSerialize() {
109
- // @codingStandardsIgnoreEnd
110
- return $this->get_data();
111
- }
112
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/infrastructure/class-wp-http-responseinterface.php DELETED
@@ -1,35 +0,0 @@
1
- <?php
2
-
3
- interface WP_HTTP_ResponseInterface extends JsonSerializable {
4
- /**
5
- * Get headers associated with the response
6
- *
7
- * @return array Map of header name to header value
8
- */
9
- public function get_headers();
10
-
11
- /**
12
- * Get the HTTP return code for the response
13
- *
14
- * @return integer 3-digit HTTP status code
15
- */
16
- public function get_status();
17
-
18
- /**
19
- * Get the response data
20
- *
21
- * @return mixed
22
- */
23
- public function get_data();
24
-
25
- /**
26
- * Get the response data for JSON serialization
27
- *
28
- * It is expected that in most implementations, this will return the same as
29
- * {@see get_data()}, however this may be different if you want to do custom
30
- * JSON data handling.
31
- *
32
- * @return mixed Any JSON-serializable value
33
- */
34
- // public function jsonSerialize();
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/infrastructure/class-wp-rest-response.php DELETED
@@ -1,176 +0,0 @@
1
- <?php
2
-
3
- class WP_REST_Response extends WP_HTTP_Response {
4
- /**
5
- * Links related to the response
6
- *
7
- * @var array
8
- */
9
- protected $links = array();
10
-
11
- /**
12
- * The route that was to create the response
13
- *
14
- * @var string
15
- */
16
- protected $matched_route = '';
17
-
18
- /**
19
- * The handler that was used to create the response
20
- *
21
- * @var null|array
22
- */
23
- protected $matched_handler = null;
24
-
25
- /**
26
- * Add a link to the response
27
- *
28
- * @internal The $rel parameter is first, as this looks nicer when sending multiple
29
- *
30
- * @link http://tools.ietf.org/html/rfc5988
31
- * @link http://www.iana.org/assignments/link-relations/link-relations.xml
32
- *
33
- * @param string $rel Link relation. Either an IANA registered type, or an absolute URL
34
- * @param string $link Target IRI for the link
35
- * @param array $attributes Link parameters to send along with the URL
36
- */
37
- public function add_link( $rel, $href, $attributes = array() ) {
38
- if ( empty( $this->links[ $rel ] ) ) {
39
- $this->links[ $rel ] = array();
40
- }
41
-
42
- if ( isset( $attributes['href'] ) ) {
43
- // Remove the href attribute, as it's used for the main URL
44
- unset( $attributes['href'] );
45
- }
46
-
47
- $this->links[ $rel ][] = array(
48
- 'href' => $href,
49
- 'attributes' => $attributes,
50
- );
51
- }
52
-
53
- /**
54
- * Add multiple links to the response.
55
- *
56
- * Link data should be an associative array with link relation as the key.
57
- * The value can either be an associative array of link attributes
58
- * (including `href` with the URL for the response), or a list of these
59
- * associative arrays.
60
- *
61
- * @param array $links Map of link relation to list of links.
62
- */
63
- public function add_links( $links ) {
64
- foreach ( $links as $rel => $set ) {
65
- // If it's a single link, wrap with an array for consistent handling
66
- if ( isset( $set['href'] ) ) {
67
- $set = array( $set );
68
- }
69
-
70
- foreach ( $set as $attributes ) {
71
- $this->add_link( $rel, $attributes['href'], $attributes );
72
- }
73
- }
74
- }
75
-
76
- /**
77
- * Get links for the response
78
- *
79
- * @return array
80
- */
81
- public function get_links() {
82
- return $this->links;
83
- }
84
-
85
- /**
86
- * Set a single link header
87
- *
88
- * @internal The $rel parameter is first, as this looks nicer when sending multiple
89
- *
90
- * @link http://tools.ietf.org/html/rfc5988
91
- * @link http://www.iana.org/assignments/link-relations/link-relations.xml
92
- *
93
- * @param string $rel Link relation. Either an IANA registered type, or an absolute URL
94
- * @param string $link Target IRI for the link
95
- * @param array $other Other parameters to send, as an assocative array
96
- */
97
- public function link_header( $rel, $link, $other = array() ) {
98
- $header = '<' . $link . '>; rel="' . $rel . '"';
99
-
100
- foreach ( $other as $key => $value ) {
101
- if ( 'title' === $key ) {
102
- $value = '"' . $value . '"';
103
- }
104
- $header .= '; ' . $key . '=' . $value;
105
- }
106
- return $this->header( 'Link', $header, false );
107
- }
108
-
109
- /**
110
- * Get the route that was used to
111
- *
112
- * @return string
113
- */
114
- public function get_matched_route() {
115
- return $this->matched_route;
116
- }
117
-
118
- /**
119
- * Set the route (regex for path) that caused the response
120
- *
121
- * @param string $route
122
- */
123
- public function set_matched_route( $route ) {
124
- $this->matched_route = $route;
125
- }
126
-
127
- /**
128
- * Get the handler that was used to generate the response
129
- *
130
- * @return null|array
131
- */
132
- public function get_matched_handler() {
133
- return $this->matched_handler;
134
- }
135
-
136
- /**
137
- * Get the handler that was responsible for generting the response
138
- *
139
- * @param array $handler
140
- */
141
- public function set_matched_handler( $handler ) {
142
- $this->matched_handler = $handler;
143
- }
144
-
145
- /**
146
- * Check if the response is an error, i.e. >= 400 response code
147
- *
148
- * @return boolean
149
- */
150
- public function is_error() {
151
- return $this->get_status() >= 400;
152
- }
153
-
154
- /**
155
- * Get a WP_Error object from the response's
156
- *
157
- * @return WP_Error|null on not an errored response
158
- */
159
- public function as_error() {
160
- if ( ! $this->is_error() ) {
161
- return null;
162
- }
163
-
164
- $error = new WP_Error;
165
-
166
- if ( is_array( $this->get_data() ) ) {
167
- foreach ( $this->get_data() as $err ) {
168
- $error->add( $err['code'], $err['message'], $err['data'] );
169
- }
170
- } else {
171
- $error->add( $this->get_status(), '', array( 'status' => $this->get_status() ) );
172
- }
173
-
174
- return $error;
175
- }
176
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
plugin.php CHANGED
@@ -4,146 +4,75 @@
4
  * Description: JSON-based REST API for WordPress, developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://wp-api.org
7
- * Version: 2.0-beta4
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
11
 
12
- /**
13
- * Version number for our API.
14
- *
15
- * @var string
16
- */
17
- define( 'REST_API_VERSION', '2.0-beta4' );
18
-
19
- /**
20
- * Include our files for the API.
21
- */
22
- include_once( dirname( __FILE__ ) . '/compatibility-v1.php' );
23
- include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-jsonserializable.php' );
24
 
25
- include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-server.php' );
 
26
 
27
- include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-http-responseinterface.php' );
28
- include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-http-response.php' );
29
- include_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-response.php' );
30
- require_once( dirname( __FILE__ ) . '/lib/infrastructure/class-wp-rest-request.php' );
31
 
 
32
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
 
 
33
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
 
 
34
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
 
 
35
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
 
 
36
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
 
 
37
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
 
 
38
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
 
 
39
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
 
 
40
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
 
 
41
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
 
 
42
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
 
 
43
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
 
 
44
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
45
 
 
46
  include_once( dirname( __FILE__ ) . '/extras.php' );
47
 
 
 
48
 
49
  /**
50
- * Register a REST API route
51
- *
52
- * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
53
- * @param string $route The base URL for route you are adding.
54
- * @param array $args Either an array of options for the endpoint, or an array of arrays for multiple methods
55
- * @param boolean $override If the route already exists, should we override it? True overrides, false merges (with newer overriding if duplicate keys exist)
56
- */
57
- function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
58
-
59
- /** @var WP_REST_Server $wp_rest_server */
60
- global $wp_rest_server;
61
-
62
- if ( isset( $args['callback'] ) ) {
63
- // Upgrade a single set to multiple
64
- $args = array( $args );
65
- }
66
-
67
- $defaults = array(
68
- 'methods' => 'GET',
69
- 'callback' => null,
70
- 'args' => array(),
71
- );
72
- foreach ( $args as $key => &$arg_group ) {
73
- if ( ! is_numeric( $arg_group ) ) {
74
- // Route option, skip here
75
- continue;
76
- }
77
-
78
- $arg_group = array_merge( $defaults, $arg_group );
79
- }
80
-
81
- if ( $namespace ) {
82
- $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
83
- } else {
84
- // Non-namespaced routes are not allowed, with the exception of the main
85
- // and namespace indexes. If you really need to register a
86
- // non-namespaced route, call `WP_REST_Server::register_route` directly.
87
- _doing_it_wrong( 'register_rest_route', 'Routes must be namespaced with plugin name and version', 'WPAPI-2.0' );
88
-
89
- $full_route = '/' . trim( $route, '/' );
90
- }
91
-
92
- $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
93
- }
94
-
95
- /**
96
- * Register a new field on an existing WordPress object type
97
  *
98
- * @global array $wp_rest_additional_fields Holds registered fields, organized
99
- * by object type.
100
  *
101
- * @param string|array $object_type Object(s) the field is being registered
102
- * to, "post"|"term"|"comment" etc.
103
- * @param string $attribute The attribute name.
104
- * @param array $args {
105
- * Optional. An array of arguments used to handle the registered field.
106
  *
107
- * @type string|array|null $get_callback Optional. The callback function
108
- * used to retrieve the field
109
- * value. Default is 'null', the
110
- * field will not be returned in
111
- * the response.
112
- * @type string|array|null $update_callback Optional. The callback function
113
- * used to set and update the
114
- * field value. Default is 'null',
115
- * the value cannot be set or
116
- * updated.
117
- * @type string|array|null schema Optional. The callback function
118
- * used to create the schema for
119
- * this field. Default is 'null',
120
- * no schema entry will be
121
- * returned.
122
- * }
123
- * @return bool|wp_error
124
- */
125
- function register_api_field( $object_type, $attribute, $args = array() ) {
126
-
127
- $defaults = array(
128
- 'get_callback' => null,
129
- 'update_callback' => null,
130
- 'schema' => null,
131
- );
132
-
133
- $args = wp_parse_args( $args, $defaults );
134
-
135
- global $wp_rest_additional_fields;
136
-
137
- $object_types = (array) $object_type;
138
-
139
- foreach ( $object_types as $object_type ) {
140
- $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
141
- }
142
- }
143
-
144
- /**
145
- * Add the extra Post Type registration arguments we need
146
- * These attributes will eventually be committed to core.
147
  */
148
  function _add_extra_api_post_type_arguments() {
149
  global $wp_post_types;
@@ -164,8 +93,13 @@ function _add_extra_api_post_type_arguments() {
164
  add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
165
 
166
  /**
167
- * Add the extra Taxonomy registration arguments we need.
 
168
  * These attributes will eventually be committed to core.
 
 
 
 
169
  */
170
  function _add_extra_api_taxonomy_arguments() {
171
  global $wp_taxonomies;
@@ -182,10 +116,11 @@ function _add_extra_api_taxonomy_arguments() {
182
  $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
183
  }
184
  }
185
- add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
186
 
187
  /**
188
- * Register default REST API routes
 
 
189
  */
190
  function create_initial_rest_routes() {
191
 
@@ -222,27 +157,19 @@ function create_initial_rest_routes() {
222
  }
223
  }
224
 
225
- /*
226
- * Post types
227
- */
228
  $controller = new WP_REST_Post_Types_Controller;
229
  $controller->register_routes();
230
 
231
- /*
232
- * Post statuses
233
- */
234
  $controller = new WP_REST_Post_Statuses_Controller;
235
  $controller->register_routes();
236
 
237
- /*
238
- * Taxonomies
239
- */
240
  $controller = new WP_REST_Taxonomies_Controller;
241
  $controller->register_routes();
242
 
243
- /*
244
- * Terms
245
- */
246
  foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
247
  $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
248
 
@@ -257,503 +184,11 @@ function create_initial_rest_routes() {
257
  $controller->register_routes();
258
  }
259
 
260
- /*
261
- * Users
262
- */
263
  $controller = new WP_REST_Users_Controller;
264
  $controller->register_routes();
265
 
266
- /**
267
- * Comments
268
- */
269
  $controller = new WP_REST_Comments_Controller;
270
  $controller->register_routes();
271
-
272
- }
273
- add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
274
-
275
- /**
276
- * Register rewrite rules for the API.
277
- *
278
- * @global WP $wp Current WordPress environment instance.
279
- */
280
- function rest_api_init() {
281
- rest_api_register_rewrites();
282
-
283
- global $wp;
284
- $wp->add_query_var( 'rest_route' );
285
- }
286
- add_action( 'init', 'rest_api_init' );
287
-
288
- /**
289
- * Add rewrite rules.
290
- */
291
- function rest_api_register_rewrites() {
292
- add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
293
- add_rewrite_rule( '^' . rest_get_url_prefix() . '(.*)?','index.php?rest_route=$matches[1]','top' );
294
- }
295
-
296
- /**
297
- * Determine if the rewrite rules should be flushed.
298
- */
299
- function rest_api_maybe_flush_rewrites() {
300
- $version = get_option( 'rest_api_plugin_version', null );
301
-
302
- if ( empty( $version ) || REST_API_VERSION !== $version ) {
303
- flush_rewrite_rules();
304
- update_option( 'rest_api_plugin_version', REST_API_VERSION );
305
- }
306
-
307
- }
308
- add_action( 'init', 'rest_api_maybe_flush_rewrites', 999 );
309
-
310
- /**
311
- * Register the default REST API filters.
312
- *
313
- * @internal This will live in default-filters.php
314
- *
315
- * @global WP_REST_Posts $WP_REST_posts
316
- * @global WP_REST_Pages $WP_REST_pages
317
- * @global WP_REST_Media $WP_REST_media
318
- * @global WP_REST_Taxonomies $WP_REST_taxonomies
319
- *
320
- * @param WP_REST_Server $server Server object.
321
- */
322
- function rest_api_default_filters( $server ) {
323
- // Deprecated reporting.
324
- add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
325
- add_filter( 'deprecated_function_trigger_error', '__return_false' );
326
- add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
327
- add_filter( 'deprecated_argument_trigger_error', '__return_false' );
328
-
329
- // Default serving
330
- add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
331
- add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
332
-
333
- add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
334
-
335
- }
336
- add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 );
337
-
338
- /**
339
- * Load the REST API.
340
- *
341
- * @todo Extract code that should be unit tested into isolated methods such as
342
- * the wp_rest_server_class filter and serving requests. This would also
343
- * help for code re-use by `wp-json` endpoint. Note that we can't unit
344
- * test any method that calls die().
345
- */
346
- function rest_api_loaded() {
347
- if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
348
- return;
349
- }
350
-
351
- /**
352
- * Whether this is a XML-RPC Request.
353
- *
354
- * @var bool
355
- * @todo Remove me in favour of REST_REQUEST
356
- */
357
- define( 'XMLRPC_REQUEST', true );
358
-
359
- /**
360
- * Whether this is a REST Request.
361
- *
362
- * @var bool
363
- */
364
- define( 'REST_REQUEST', true );
365
-
366
- /** @var WP_REST_Server $wp_rest_server */
367
- global $wp_rest_server;
368
-
369
- // Allow for a plugin to insert a different class to handle requests.
370
- $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
371
- $wp_rest_server = new $wp_rest_server_class;
372
-
373
- /**
374
- * Fires when preparing to serve an API request.
375
- *
376
- * Endpoint objects should be created and register their hooks on this
377
- * action rather than another action to ensure they're only loaded when
378
- * needed.
379
- *
380
- * @param WP_REST_Server $wp_rest_server Server object.
381
- */
382
- do_action( 'rest_api_init', $wp_rest_server );
383
-
384
- // Fire off the request.
385
- $wp_rest_server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
386
-
387
- // We're done.
388
- die();
389
- }
390
- add_action( 'parse_request', 'rest_api_loaded' );
391
-
392
- /**
393
- * Register routes and flush the rewrite rules on activation.
394
- *
395
- * @param bool $network_wide ?
396
- */
397
- function rest_api_activation( $network_wide ) {
398
- if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
399
- $mu_blogs = wp_get_sites();
400
-
401
- foreach ( $mu_blogs as $mu_blog ) {
402
- switch_to_blog( $mu_blog['blog_id'] );
403
-
404
- rest_api_register_rewrites();
405
- update_option( 'rest_api_plugin_version', null );
406
- }
407
-
408
- restore_current_blog();
409
- } else {
410
- rest_api_register_rewrites();
411
- update_option( 'rest_api_plugin_version', null );
412
- }
413
- }
414
- register_activation_hook( __FILE__, 'rest_api_activation' );
415
-
416
- /**
417
- * Flush the rewrite rules on deactivation.
418
- *
419
- * @param bool $network_wide ?
420
- */
421
- function rest_api_deactivation( $network_wide ) {
422
- if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) {
423
-
424
- $mu_blogs = wp_get_sites();
425
-
426
- foreach ( $mu_blogs as $mu_blog ) {
427
- switch_to_blog( $mu_blog['blog_id'] );
428
- delete_option( 'rest_api_plugin_version' );
429
- }
430
-
431
- restore_current_blog();
432
- } else {
433
- delete_option( 'rest_api_plugin_version' );
434
- }
435
- }
436
- register_deactivation_hook( __FILE__, 'rest_api_deactivation' );
437
-
438
- /**
439
- * Get the URL prefix for any API resource.
440
- *
441
- * @return string Prefix.
442
- */
443
- function rest_get_url_prefix() {
444
- /**
445
- * Filter the rest URL prefix.
446
- *
447
- * @since 1.0
448
- *
449
- * @param string $prefix URL prefix. Default 'wp-json'.
450
- */
451
- return apply_filters( 'rest_url_prefix', 'wp-json' );
452
- }
453
-
454
- /**
455
- * Get URL to a REST endpoint on a site.
456
- *
457
- * @todo Check if this is even necessary
458
- *
459
- * @param int $blog_id Blog ID. Optional. The ID of the multisite blog to get URL for. Default null of null returns URL for current blog.
460
- * @param string $path Optional. REST route. Default empty.
461
- * @param string $scheme Optional. Sanitization scheme. Default 'json'.
462
- * @return string Full URL to the endpoint.
463
- */
464
- function get_rest_url( $blog_id = null, $path = '/', $scheme = 'json' ) {
465
- if ( empty( $path ) ) {
466
- $path = '/';
467
- }
468
-
469
- if ( get_option( 'permalink_structure' ) ) {
470
- $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
471
- $url .= '/' . ltrim( $path, '/' );
472
- } else {
473
- $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
474
-
475
- $path = '/' . ltrim( $path, '/' );
476
-
477
- $url = add_query_arg( 'rest_route', $path, $url );
478
- }
479
-
480
- /**
481
- * Filter the REST URL.
482
- *
483
- * @since 1.0
484
- *
485
- * @param string $url REST URL.
486
- * @param string $path REST route.
487
- * @param int $blod_ig Blog ID.
488
- * @param string $scheme Sanitization scheme.
489
- */
490
- return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
491
- }
492
-
493
- /**
494
- * Get URL to a REST endpoint.
495
- *
496
- * @param string $path Optional. REST route. Default empty.
497
- * @param string $scheme Optional. Sanitization scheme. Default 'json'.
498
- * @return string Full URL to the endpoint.
499
- */
500
- function rest_url( $path = '', $scheme = 'json' ) {
501
- return get_rest_url( null, $path, $scheme );
502
- }
503
-
504
- /**
505
- * Do a REST request.
506
- * Used primarily to route internal requests through WP_REST_Server
507
- *
508
- * @param WP_REST_Request|string $request
509
- * @return WP_REST_Response
510
- */
511
- function rest_do_request( $request ) {
512
- global $wp_rest_server;
513
- $request = rest_ensure_request( $request );
514
- return $wp_rest_server->dispatch( $request );
515
- }
516
-
517
- /**
518
- * Ensure request arguments are a request object.
519
- *
520
- * This ensures that the request is consistent.
521
- *
522
- * @param array|WP_REST_Request $request Request to check.
523
- * @return WP_REST_Request
524
- */
525
- function rest_ensure_request( $request ) {
526
- if ( $request instanceof WP_REST_Request ) {
527
- return $request;
528
- }
529
-
530
- return new WP_REST_Request( 'GET', '', $request );
531
- }
532
-
533
- /**
534
- * Ensure a REST response is a response object.
535
- *
536
- * This ensures that the response is consistent, and implements
537
- * {@see WP_HTTP_ResponseInterface}, allowing usage of
538
- * `set_status`/`header`/etc without needing to double-check the object. Will
539
- * also allow {@see WP_Error} to indicate error responses, so users should
540
- * immediately check for this value.
541
- *
542
- * @param WP_Error|WP_HTTP_ResponseInterface|mixed $response Response to check.
543
- * @return WP_Error|WP_HTTP_ResponseInterface|WP_REST_Response WP_Error if response generated an error, WP_HTTP_ResponseInterface if response is a already an instance, otherwise returns a new WP_REST_Response instance.
544
- */
545
- function rest_ensure_response( $response ) {
546
- if ( is_wp_error( $response ) ) {
547
- return $response;
548
- }
549
-
550
- if ( $response instanceof WP_HTTP_ResponseInterface ) {
551
- return $response;
552
- }
553
-
554
- return new WP_REST_Response( $response );
555
- }
556
-
557
- /**
558
- * Handle {@see _deprecated_function()} errors.
559
- *
560
- * @param string $function Function name.
561
- * @param string $replacement Replacement function name.
562
- * @param string $version Version.
563
- */
564
- function rest_handle_deprecated_function( $function, $replacement, $version ) {
565
- if ( ! empty( $replacement ) ) {
566
- $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
567
- } else {
568
- $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
569
- }
570
-
571
- header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
572
- }
573
-
574
- /**
575
- * Handle {@see _deprecated_function} errors.
576
- *
577
- * @param string $function Function name.
578
- * @param string $replacement Replacement function name.
579
- * @param string $version Version.
580
- */
581
- function rest_handle_deprecated_argument( $function, $replacement, $version ) {
582
- if ( ! empty( $replacement ) ) {
583
- $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $replacement );
584
- } else {
585
- $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
586
- }
587
-
588
- header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
589
- }
590
-
591
- /**
592
- * Send Cross-Origin Resource Sharing headers with API requests
593
- *
594
- * @param mixed $value Response data
595
- * @return mixed Response data
596
- */
597
- function rest_send_cors_headers( $value ) {
598
- $origin = get_http_origin();
599
-
600
- if ( $origin ) {
601
- header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
602
- header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
603
- header( 'Access-Control-Allow-Credentials: true' );
604
- }
605
-
606
- return $value;
607
- }
608
-
609
- /**
610
- * Handle OPTIONS requests for the server
611
- *
612
- * This is handled outside of the server code, as it doesn't obey normal route
613
- * mapping.
614
- *
615
- * @param mixed $response Current response, either response or `null` to indicate pass-through.
616
- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
617
- * @param WP_REST_Request $request The request that was used to make current response.
618
- * @return WP_REST_Response $response Modified response, either response or `null` to indicate pass-through.
619
- */
620
- function rest_handle_options_request( $response, $handler, $request ) {
621
- if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
622
- return $response;
623
- }
624
-
625
- $response = new WP_REST_Response();
626
- $data = array();
627
-
628
- $accept = array();
629
-
630
- foreach ( $handler->get_routes() as $route => $endpoints ) {
631
- $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
632
-
633
- if ( ! $match ) {
634
- continue;
635
- }
636
-
637
- $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
638
- $accept = array_merge( $accept, $data['methods'] );
639
- break;
640
- }
641
- $response->header( 'Accept', implode( ', ', $accept ) );
642
-
643
- $response->set_data( $data );
644
- return $response;
645
- }
646
-
647
- /**
648
- * Send the "Allow" header to state all methods that can be sen
649
- * to the current route
650
- *
651
- * @param WP_REST_Response $response Current response being served.
652
- * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server)
653
- * @param WP_REST_Request $request The request that was used to make current response.
654
- */
655
- function rest_send_allow_header( $response, $server, $request ) {
656
-
657
- $matched_route = $response->get_matched_route();
658
-
659
- if ( ! $matched_route ) {
660
- return $response;
661
- }
662
-
663
- $routes = $server->get_routes();
664
-
665
- $allowed_methods = array();
666
-
667
- // get the allowed methods across the routes
668
- foreach ( $routes[ $matched_route ] as $_handler ) {
669
- foreach ( $_handler['methods'] as $handler_method => $value ) {
670
-
671
- if ( ! empty( $_handler['permission_callback'] ) ) {
672
-
673
- $permission = call_user_func( $_handler['permission_callback'], $request );
674
-
675
- $allowed_methods[ $handler_method ] = true === $permission;
676
- } else {
677
- $allowed_methods[ $handler_method ] = true;
678
- }
679
- }
680
- }
681
-
682
- // strip out all the methods that are not allowed (false values)
683
- $allowed_methods = array_filter( $allowed_methods );
684
-
685
- if ( $allowed_methods ) {
686
- $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
687
- }
688
-
689
- return $response;
690
- }
691
-
692
- if ( ! function_exists( 'json_last_error_msg' ) ) :
693
- /**
694
- * Returns the error string of the last json_encode() or json_decode() call
695
- *
696
- * @internal This is a compatibility function for PHP <5.5
697
- *
698
- * @return boolean|string Returns the error message on success, "No Error" if no error has occurred, or FALSE on failure.
699
- */
700
- function json_last_error_msg() {
701
- // see https://core.trac.wordpress.org/ticket/27799
702
- if ( ! function_exists( 'json_last_error' ) ) {
703
- return false;
704
- }
705
-
706
- $last_error_code = json_last_error();
707
-
708
- // just in case JSON_ERROR_NONE is not defined
709
- $error_code_none = defined( 'JSON_ERROR_NONE' ) ? JSON_ERROR_NONE : 0;
710
-
711
- switch ( true ) {
712
- case $last_error_code === $error_code_none:
713
- return 'No error';
714
-
715
- case defined( 'JSON_ERROR_DEPTH' ) && JSON_ERROR_DEPTH === $last_error_code:
716
- return 'Maximum stack depth exceeded';
717
-
718
- case defined( 'JSON_ERROR_STATE_MISMATCH' ) && JSON_ERROR_STATE_MISMATCH === $last_error_code:
719
- return 'State mismatch (invalid or malformed JSON)';
720
-
721
- case defined( 'JSON_ERROR_CTRL_CHAR' ) && JSON_ERROR_CTRL_CHAR === $last_error_code:
722
- return 'Control character error, possibly incorrectly encoded';
723
-
724
- case defined( 'JSON_ERROR_SYNTAX' ) && JSON_ERROR_SYNTAX === $last_error_code:
725
- return 'Syntax error';
726
-
727
- case defined( 'JSON_ERROR_UTF8' ) && JSON_ERROR_UTF8 === $last_error_code:
728
- return 'Malformed UTF-8 characters, possibly incorrectly encoded';
729
-
730
- case defined( 'JSON_ERROR_RECURSION' ) && JSON_ERROR_RECURSION === $last_error_code:
731
- return 'Recursion detected';
732
-
733
- case defined( 'JSON_ERROR_INF_OR_NAN' ) && JSON_ERROR_INF_OR_NAN === $last_error_code:
734
- return 'Inf and NaN cannot be JSON encoded';
735
-
736
- case defined( 'JSON_ERROR_UNSUPPORTED_TYPE' ) && JSON_ERROR_UNSUPPORTED_TYPE === $last_error_code:
737
- return 'Type is not supported';
738
-
739
- default:
740
- return 'An unknown error occurred';
741
- }
742
- }
743
- endif;
744
-
745
- /**
746
- * Is the variable a list? (Numeric-indexed array)
747
- *
748
- * @param mixed $data Variable to check.
749
- * @return boolean
750
- */
751
- function rest_is_list( $data ) {
752
- if ( ! is_array( $data ) ) {
753
- return false;
754
- }
755
-
756
- $keys = array_keys( $data );
757
- $string_keys = array_filter( $keys, 'is_string' );
758
- return count( $string_keys ) === 0;
759
  }
4
  * Description: JSON-based REST API for WordPress, developed as part of GSoC 2013.
5
  * Author: WP REST API Team
6
  * Author URI: http://wp-api.org
7
+ * Version: 2.0-beta5
8
  * Plugin URI: https://github.com/WP-API/WP-API
9
  * License: GPL2+
10
  */
11
 
12
+ // Do we need the compatibility repo?
13
+ if ( ! defined( 'REST_API_VERSION' ) ) {
14
+ require_once dirname( __FILE__ ) . '/core/rest-api.php';
15
+ }
 
 
 
 
 
 
 
 
16
 
17
+ /** Include admin functions that are used in the endpoints, such as get_page_templates() */
18
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
19
 
20
+ /** v1 Compatibility */
21
+ include_once( dirname( __FILE__ ) . '/compatibility-v1.php' );
 
 
22
 
23
+ /** WP_REST_Controller class */
24
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-controller.php';
25
+
26
+ /** WP_REST_Posts_Controller class */
27
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-controller.php';
28
+
29
+ /** WP_REST_Attachments_Controller class */
30
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-attachments-controller.php';
31
+
32
+ /** WP_REST_Post_Types_Controller class */
33
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-types-controller.php';
34
+
35
+ /** WP_REST_Post_Statuses_Controller class */
36
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-post-statuses-controller.php';
37
+
38
+ /** WP_REST_Revisions_Controller class */
39
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-revisions-controller.php';
40
+
41
+ /** WP_REST_Taxonomies_Controller class */
42
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-taxonomies-controller.php';
43
+
44
+ /** WP_REST_Terms_Controller class */
45
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-terms-controller.php';
46
+
47
+ /** WP_REST_Users_Controller class */
48
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-users-controller.php';
49
+
50
+ /** WP_REST_Comments_Controller class */
51
  require_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-comments-controller.php';
52
+
53
+ /** WP_REST_Meta_Controller class */
54
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-controller.php';
55
+
56
+ /** WP_REST_Meta_Posts_Controller class */
57
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-meta-posts-controller.php';
58
+
59
+ /** WP_REST_Posts_Terms_Controller class */
60
  include_once dirname( __FILE__ ) . '/lib/endpoints/class-wp-rest-posts-terms-controller.php';
61
 
62
+ /** REST extras */
63
  include_once( dirname( __FILE__ ) . '/extras.php' );
64
 
65
+ add_action( 'init', '_add_extra_api_taxonomy_arguments', 11 );
66
+ add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
67
 
68
  /**
69
+ * Adds extra post type registration arguments.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  *
71
+ * These attributes will eventually be committed to core.
 
72
  *
73
+ * @since 4.4.0
 
 
 
 
74
  *
75
+ * @global array $wp_post_types Registered post types.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  */
77
  function _add_extra_api_post_type_arguments() {
78
  global $wp_post_types;
93
  add_action( 'init', '_add_extra_api_post_type_arguments', 11 );
94
 
95
  /**
96
+ * Adds extra taxonomy registration arguments.
97
+ *
98
  * These attributes will eventually be committed to core.
99
+ *
100
+ * @since 4.4.0
101
+ *
102
+ * @global array $wp_taxonomies Registered taxonomies.
103
  */
104
  function _add_extra_api_taxonomy_arguments() {
105
  global $wp_taxonomies;
116
  $wp_taxonomies['post_tag']->rest_controller_class = 'WP_REST_Terms_Controller';
117
  }
118
  }
 
119
 
120
  /**
121
+ * Registers default REST API routes.
122
+ *
123
+ * @since 4.4.0
124
  */
125
  function create_initial_rest_routes() {
126
 
157
  }
158
  }
159
 
160
+ // Post types.
 
 
161
  $controller = new WP_REST_Post_Types_Controller;
162
  $controller->register_routes();
163
 
164
+ // Post statuses.
 
 
165
  $controller = new WP_REST_Post_Statuses_Controller;
166
  $controller->register_routes();
167
 
168
+ // Taxonomies.
 
 
169
  $controller = new WP_REST_Taxonomies_Controller;
170
  $controller->register_routes();
171
 
172
+ // Terms.
 
 
173
  foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
174
  $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
175
 
184
  $controller->register_routes();
185
  }
186
 
187
+ // Users.
 
 
188
  $controller = new WP_REST_Users_Controller;
189
  $controller->register_routes();
190
 
191
+ // Comments.
 
 
192
  $controller = new WP_REST_Comments_Controller;
193
  $controller->register_routes();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  }
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === WordPress REST API (Version 2) ===
2
- Contributors: rmccue, rachelbaker
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3-alpha
5
- Tested up to: 4.3-beta
6
- Stable tag: 2.0-beta4
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,6 +36,40 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 2.0 Beta 4.0 =
40
 
41
  * Show public user information through the user controller.
1
  === WordPress REST API (Version 2) ===
2
+ Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3-alpha
5
+ Tested up to: 4.4-beta
6
+ Stable tag: 2.0-beta5
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
36
 
37
  == Changelog ==
38
 
39
+ = 2.0 Beta 5.0 =
40
+
41
+ * Load api-core as a compatibility library
42
+
43
+ Now api-core has been merged into WordPress trunk (for 4.4) we should no longer load the infrastructure code when it's already available. This also fixes a fatal error for users who were on trunk.
44
+
45
+ (props @rmccue)
46
+
47
+ * Switch to new mysql_to_rfc3339
48
+
49
+ (props @rmccue)
50
+
51
+ * Double-check term taxonomy
52
+
53
+ (props @rmccue)
54
+
55
+ * Load admin functions
56
+
57
+ This was removed from the latest beta of WordPress in the REST API infrastructure, a more long term fix is planned.
58
+
59
+ (props @joehoyle)
60
+
61
+ * Add Add compat shim for renamed `rest_mysql_to_rfc3339()`
62
+
63
+ (props @danielbachhuber)
64
+
65
+ * Compat shim for `wp_is_numeric_array()`
66
+
67
+ (props @danielbachhuber)
68
+
69
+ * Revert Switch to register_post_type_args filter
70
+
71
+ (props @joehoyle)
72
+
73
  = 2.0 Beta 4.0 =
74
 
75
  * Show public user information through the user controller.