WordPress REST API (Version 2) - Version 2.0-beta4.1

Version Description

Download this release

Release Info

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

Code changes from version 2.0-beta5.1 to 2.0-beta4.1

CHANGELOG.md CHANGED
@@ -1,46 +1,11 @@
1
  # Changelog
2
 
3
- ## 2.0 Beta 5.1
4
 
5
  - Ensure media of private posts are private too.
6
 
7
  Reported by @danielbachhuber on 2016-01-08.
8
 
9
- ## 2.0 Beta 5.0
10
-
11
- - Load api-core as a compatibility library
12
-
13
- Now api-core has been merged into WordPress trunk (for 4.4) we should no longer load the infrastructure code
14
- when it's already available. This also fixes a fatal error for users who were on trunk.
15
-
16
- (props @rmccue)
17
-
18
- - Switch to new mysql_to_rfc3339
19
-
20
- (props @rmccue)
21
-
22
- - Double-check term taxonomy
23
-
24
- (props @rmccue)
25
-
26
- - Load admin functions
27
-
28
- This was removed from the latest beta of WordPress in the REST API infrastructure, a more long term fix is planned.
29
-
30
- (props @joehoyle)
31
-
32
- - Add Add compat shim for renamed `rest_mysql_to_rfc3339()`
33
-
34
- (props @danielbachhuber)
35
-
36
- - Compat shim for `wp_is_numeric_array()`
37
-
38
- (props @danielbachhuber)
39
-
40
- - Revert Switch to register_post_type_args filter
41
-
42
- (props @joehoyle)
43
-
44
  ## 2.0 Beta 4.0
45
 
46
  - Show public user information through the user controller.
1
  # Changelog
2
 
3
+ ## 2.0 Beta 4.1
4
 
5
  - Ensure media of private posts are private too.
6
 
7
  Reported by @danielbachhuber on 2016-01-08.
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  ## 2.0 Beta 4.0
10
 
11
  - Show public user information through the user controller.
core/rest-api.php DELETED
@@ -1,103 +0,0 @@
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 DELETED
@@ -1,81 +0,0 @@
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 DELETED
@@ -1,25 +0,0 @@
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 DELETED
@@ -1,28 +0,0 @@
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 DELETED
@@ -1,26 +0,0 @@
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 DELETED
@@ -1,165 +0,0 @@
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/wp-includes/rest-api/class-wp-rest-response.php DELETED
@@ -1,255 +0,0 @@
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/wp-includes/rest-api/rest-functions.php DELETED
@@ -1,666 +0,0 @@
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,11 +11,20 @@
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,14 +36,134 @@ function rest_register_scripts() {
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,31 +177,75 @@ function rest_get_avatar_urls( $email ) {
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,7 +259,7 @@ function rest_get_timezone() {
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,25 +275,23 @@ function rest_get_timezone() {
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,39 +303,3 @@ function rest_get_avatar_url( $email ) {
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
- }
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
  }
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
  }
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
  $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
  }
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
 
304
  return '';
305
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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' => 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,
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,
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 mysql_to_rfc3339( $date );
582
  }
583
 
584
- return mysql_to_rfc3339( $date_gmt );
585
  }
586
 
587
  protected function prepare_password_response( $password ) {
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 ) {
lib/endpoints/class-wp-rest-posts-terms-controller.php CHANGED
@@ -200,8 +200,7 @@ 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
- $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
  }
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
  }
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 mysql_to_rfc3339( $date );
230
  }
231
 
232
- return mysql_to_rfc3339( $date_gmt );
233
  }
234
 
235
  /**
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
  /**
lib/infrastructure/class-jsonserializable.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
{core/wp-includes/rest-api → lib/infrastructure}/class-wp-rest-request.php RENAMED
@@ -1,14 +1,7 @@
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,101 +9,75 @@
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,7 +86,7 @@ class WP_REST_Request implements ArrayAccess {
119
  'POST' => array(),
120
  'FILES' => array(),
121
 
122
- // See parse_json_params.
123
  'JSON' => null,
124
 
125
  'defaults' => array(),
@@ -131,43 +98,34 @@ class WP_REST_Request implements ArrayAccess {
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,12 +137,8 @@ class WP_REST_Request implements ArrayAccess {
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,17 +148,14 @@ class WP_REST_Request implements ArrayAccess {
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,13 +168,10 @@ class WP_REST_Request implements ArrayAccess {
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,13 +184,10 @@ class WP_REST_Request implements ArrayAccess {
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,13 +197,10 @@ class WP_REST_Request implements ArrayAccess {
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,25 +214,19 @@ class WP_REST_Request implements ArrayAccess {
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,12 +239,9 @@ class WP_REST_Request implements ArrayAccess {
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,7 +259,7 @@ class WP_REST_Request implements ArrayAccess {
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,14 +269,11 @@ class WP_REST_Request implements ArrayAccess {
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,7 +281,7 @@ class WP_REST_Request implements ArrayAccess {
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,37 +297,29 @@ class WP_REST_Request implements ArrayAccess {
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,13 +329,10 @@ class WP_REST_Request implements ArrayAccess {
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,15 +347,12 @@ class WP_REST_Request implements ArrayAccess {
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,40 +367,31 @@ class WP_REST_Request implements ArrayAccess {
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,54 +400,42 @@ class WP_REST_Request implements ArrayAccess {
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,26 +444,20 @@ class WP_REST_Request implements ArrayAccess {
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,93 +466,74 @@ class WP_REST_Request implements ArrayAccess {
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,115 +542,90 @@ class WP_REST_Request implements ArrayAccess {
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,30 +637,25 @@ class WP_REST_Request implements ArrayAccess {
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,11 +672,8 @@ class WP_REST_Request implements ArrayAccess {
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,15 +702,14 @@ class WP_REST_Request implements ArrayAccess {
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,43 +722,41 @@ class WP_REST_Request implements ArrayAccess {
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
  }
1
  <?php
 
 
 
 
 
 
 
2
 
3
  /**
4
+ * Request object
5
  *
6
  * Contains data from the request, to be passed to the callback.
7
  *
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
  'POST' => array(),
87
  'FILES' => array(),
88
 
89
+ // See parse_json_params
90
  'JSON' => null,
91
 
92
  'defaults' => array(),
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
  * @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
  }
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
  }
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
  }
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
  }
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
  }
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
  }
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
  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
  }
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
 
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
  $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
  }
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
  }
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
  }
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
  }
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
  }
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
  }
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
  }
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
  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
  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
  }
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
  }
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
  }
lib/infrastructure/class-wp-rest-response.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
{core/wp-includes/rest-api → lib/infrastructure}/class-wp-rest-server.php RENAMED
@@ -1,98 +1,145 @@
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,20 +154,16 @@ class WP_REST_Server {
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,35 +173,28 @@ class WP_REST_Server {
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,92 +202,60 @@ class WP_REST_Server {
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,14 +266,8 @@ class WP_REST_Server {
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,14 +282,13 @@ class WP_REST_Server {
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,30 +308,26 @@ class WP_REST_Server {
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,27 +335,26 @@ class WP_REST_Server {
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,43 +366,34 @@ class WP_REST_Server {
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,15 +404,11 @@ class WP_REST_Server {
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,7 +419,7 @@ class WP_REST_Server {
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,18 +435,10 @@ class WP_REST_Server {
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,9 +447,8 @@ class WP_REST_Server {
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,18 +456,18 @@ class WP_REST_Server {
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,26 +477,27 @@ class WP_REST_Server {
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,18 +512,15 @@ class WP_REST_Server {
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,30 +530,23 @@ class WP_REST_Server {
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,7 +568,7 @@ class WP_REST_Server {
607
  ) );
608
  }
609
 
610
- // Associative to avoid double-registration.
611
  $this->namespaces[ $namespace ][ $route ] = true;
612
  $route_args['namespace'] = $namespace;
613
 
@@ -619,7 +580,7 @@ class WP_REST_Server {
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,27 +594,13 @@ class WP_REST_Server {
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,40 +608,32 @@ class WP_REST_Server {
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,10 +644,7 @@ class WP_REST_Server {
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,10 +653,7 @@ class WP_REST_Server {
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,30 +667,23 @@ class WP_REST_Server {
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,6 +694,7 @@ class WP_REST_Server {
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,7 +735,7 @@ class WP_REST_Server {
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,18 +749,14 @@ class WP_REST_Server {
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,24 +781,18 @@ class WP_REST_Server {
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,22 +801,16 @@ class WP_REST_Server {
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,7 +821,6 @@ class WP_REST_Server {
910
  );
911
 
912
  $response = new WP_REST_Response( $available );
913
-
914
  $response->add_link( 'help', 'http://v2.wp-api.org/' );
915
 
916
  /**
@@ -920,22 +830,16 @@ class WP_REST_Server {
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,7 +857,7 @@ class WP_REST_Server {
953
  );
954
  $response = rest_ensure_response( $data );
955
 
956
- // Link to the root index.
957
  $response->add_link( 'up', rest_url( '/' ) );
958
 
959
  /**
@@ -962,41 +866,28 @@ class WP_REST_Server {
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,23 +898,18 @@ class WP_REST_Server {
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,14 +918,11 @@ class WP_REST_Server {
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,7 +931,7 @@ class WP_REST_Server {
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,7 +956,7 @@ class WP_REST_Server {
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,7 +965,7 @@ class WP_REST_Server {
1082
  }
1083
 
1084
  if ( empty( $data['methods'] ) ) {
1085
- // No methods supported, hide the route.
1086
  return null;
1087
  }
1088
 
@@ -1090,45 +973,33 @@ class WP_REST_Server {
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,22 +1008,15 @@ class WP_REST_Server {
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,18 +1025,59 @@ class WP_REST_Server {
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 ) {
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
 
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
  * 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
  }
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
  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
  }
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
  // 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
  $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
  $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
  }
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
  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
  }
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
 
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
  $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
  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
  }
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
  );
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
  ) );
569
  }
570
 
571
+ // Associative to avoid double-registration
572
  $this->namespaces[ $namespace ][ $route ] = true;
573
  $route_args['namespace'] = $namespace;
574
 
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
  * 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
  '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
  }
645
 
646
  /**
647
+ * Get namespaces registered on the server.
 
 
 
648
  *
649
  * @return array List of registered namespaces.
650
  */
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
  }
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
  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
  }
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
 
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
  }
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
  }
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
  );
822
 
823
  $response = new WP_REST_Response( $available );
 
824
  $response->add_link( 'help', 'http://v2.wp-api.org/' );
825
 
826
  /**
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
  );
858
  $response = rest_ensure_response( $data );
859
 
860
+ // Link to the root index
861
  $response->add_link( 'up', rest_url( '/' ) );
862
 
863
  /**
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
  * 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
  '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
  $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
 
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
  }
966
 
967
  if ( empty( $data['methods'] ) ) {
968
+ // No methods supported, hide the route
969
  return null;
970
  }
971
 
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
  }
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
  }
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 ) {
plugin.php CHANGED
@@ -4,75 +4,146 @@
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.1
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,13 +164,8 @@ function _add_extra_api_post_type_arguments() {
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,11 +182,10 @@ function _add_extra_api_taxonomy_arguments() {
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,19 +222,27 @@ function create_initial_rest_routes() {
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,11 +257,503 @@ function create_initial_rest_routes() {
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
  }
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.1
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.1' );
 
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
  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
  $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
  }
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
  $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
  }
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === WordPress REST API (Version 2) ===
2
- Contributors: rmccue, rachelbaker, danielbachhuber, joehoyle
3
  Tags: json, rest, api, rest-api
4
  Requires at least: 4.3-alpha
5
- Tested up to: 4.4-beta
6
- Stable tag: 2.0-beta5.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,46 +36,12 @@ For full-flavoured API support, you'll need to be using pretty permalinks to use
36
 
37
  == Changelog ==
38
 
39
- = 2.0 Beta 5.1 =
40
 
41
  * Ensure media of private posts are private too.
42
 
43
  Reported by @danielbachhuber on 2016-01-08.
44
 
45
- = 2.0 Beta 5.0 =
46
-
47
- * Load api-core as a compatibility library
48
-
49
- 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.
50
-
51
- (props @rmccue)
52
-
53
- * Switch to new mysql_to_rfc3339
54
-
55
- (props @rmccue)
56
-
57
- * Double-check term taxonomy
58
-
59
- (props @rmccue)
60
-
61
- * Load admin functions
62
-
63
- This was removed from the latest beta of WordPress in the REST API infrastructure, a more long term fix is planned.
64
-
65
- (props @joehoyle)
66
-
67
- * Add Add compat shim for renamed `rest_mysql_to_rfc3339()`
68
-
69
- (props @danielbachhuber)
70
-
71
- * Compat shim for `wp_is_numeric_array()`
72
-
73
- (props @danielbachhuber)
74
-
75
- * Revert Switch to register_post_type_args filter
76
-
77
- (props @joehoyle)
78
-
79
  = 2.0 Beta 4.0 =
80
 
81
  * Show public user information through the user controller.
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.1
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 4.1 =
40
 
41
  * Ensure media of private posts are private too.
42
 
43
  Reported by @danielbachhuber on 2016-01-08.
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  = 2.0 Beta 4.0 =
46
 
47
  * Show public user information through the user controller.