PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 6.5.4
Jetpack – WP Security, Backup, Speed, & Growth v6.5.4
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.json-api.php
jetpack Last commit date
3rd-party 7 years ago _inc 1 year ago bin 7 years ago css 7 years ago images 1 year ago json-endpoints 3 years ago languages 7 years ago modules 1 year ago sal 7 years ago scss 7 years ago sync 7 years ago views 8 years ago .svnignore 12 years ago CODE-OF-CONDUCT.md 9 years ago changelog.txt 7 years ago class.frame-nonce-preview.php 9 years ago class.jetpack-admin.php 8 years ago class.jetpack-autoupdate.php 8 years ago class.jetpack-bbpress-json-api-compat.php 9 years ago class.jetpack-cli.php 7 years ago class.jetpack-client-server.php 8 years ago class.jetpack-client.php 7 years ago class.jetpack-connection-banner.php 7 years ago class.jetpack-constants.php 8 years ago class.jetpack-data.php 7 years ago class.jetpack-debugger.php 8 years ago class.jetpack-error.php 10 years ago class.jetpack-heartbeat.php 7 years ago class.jetpack-idc.php 8 years ago class.jetpack-ixr-client.php 10 years ago class.jetpack-jitm.php 8 years ago class.jetpack-modules-list-table.php 8 years ago class.jetpack-network-sites-list-table.php 9 years ago class.jetpack-network.php 8 years ago class.jetpack-options.php 7 years ago class.jetpack-post-images.php 7 years ago class.jetpack-signature.php 7 years ago class.jetpack-tracks.php 8 years ago class.jetpack-twitter-cards.php 7 years ago class.jetpack-user-agent.php 8 years ago class.jetpack-xmlrpc-server.php 7 years ago class.jetpack.php 7 years ago class.json-api-endpoints.php 3 years ago class.json-api.php 7 years ago class.photon.php 7 years ago composer.json 7 years ago functions.compat.php 7 years ago functions.gallery.php 8 years ago functions.global.php 7 years ago functions.opengraph.php 8 years ago functions.photon.php 8 years ago jetpack.php 1 year ago json-api-config.php 10 years ago json-endpoints.php 8 years ago locales.php 9 years ago phpcs.xml 7 years ago readme.txt 1 year ago require-lib.php 8 years ago uninstall.php 8 years ago wpml-config.xml 10 years ago
class.json-api.php
704 lines
1 <?php
2
3 defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
4
5 require_once dirname( __FILE__ ) . '/sal/class.json-api-platform.php';
6
7 class WPCOM_JSON_API {
8 static $self = null;
9
10 public $endpoints = array();
11
12 public $token_details = array();
13
14 public $method = '';
15 public $url = '';
16 public $path = '';
17 public $version = null;
18 public $query = array();
19 public $post_body = null;
20 public $files = null;
21 public $content_type = null;
22 public $accept = '';
23
24 public $_server_https;
25 public $exit = true;
26 public $public_api_scheme = 'https';
27
28 public $output_status_code = 200;
29
30 public $trapped_error = null;
31 public $did_output = false;
32
33 public $extra_headers = array();
34
35 /**
36 * @return WPCOM_JSON_API instance
37 */
38 static function init( $method = null, $url = null, $post_body = null ) {
39 if ( !self::$self ) {
40 $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__; // phpcs:ignore PHPCompatibility
41 self::$self = new $class( $method, $url, $post_body );
42 }
43 return self::$self;
44 }
45
46 function add( WPCOM_JSON_API_Endpoint $endpoint ) {
47 $path_versions = serialize( array (
48 $endpoint->path,
49 $endpoint->min_version,
50 $endpoint->max_version,
51 ) );
52 if ( !isset( $this->endpoints[$path_versions] ) ) {
53 $this->endpoints[$path_versions] = array();
54 }
55 $this->endpoints[$path_versions][$endpoint->method] = $endpoint;
56 }
57
58 static function is_truthy( $value ) {
59 switch ( strtolower( (string) $value ) ) {
60 case '1' :
61 case 't' :
62 case 'true' :
63 return true;
64 }
65
66 return false;
67 }
68
69 static function is_falsy( $value ) {
70 switch ( strtolower( (string) $value ) ) {
71 case '0' :
72 case 'f' :
73 case 'false' :
74 return true;
75 }
76
77 return false;
78 }
79
80 function __construct() {
81 $args = func_get_args();
82 call_user_func_array( array( $this, 'setup_inputs' ), $args );
83 }
84
85 function setup_inputs( $method = null, $url = null, $post_body = null ) {
86 if ( is_null( $method ) ) {
87 $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
88 } else {
89 $this->method = strtoupper( $method );
90 }
91 if ( is_null( $url ) ) {
92 $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
93 } else {
94 $this->url = $url;
95 }
96
97 $parsed = parse_url( $this->url );
98 $this->path = $parsed['path'];
99
100 if ( !empty( $parsed['query'] ) ) {
101 wp_parse_str( $parsed['query'], $this->query );
102 }
103
104 if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
105 $this->accept = $_SERVER['HTTP_ACCEPT'];
106 }
107
108 if ( 'POST' === $this->method ) {
109 if ( is_null( $post_body ) ) {
110 $this->post_body = file_get_contents( 'php://input' );
111
112 if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
113 $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
114 } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
115 $this->content_type = $_SERVER['CONTENT_TYPE'] ;
116 } elseif ( '{' === $this->post_body[0] ) {
117 $this->content_type = 'application/json';
118 } else {
119 $this->content_type = 'application/x-www-form-urlencoded';
120 }
121
122 if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
123 $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
124 $this->files = $_FILES;
125 $this->content_type = 'multipart/form-data';
126 }
127 } else {
128 $this->post_body = $post_body;
129 $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
130 }
131 } else {
132 $this->post_body = null;
133 $this->content_type = null;
134 }
135
136 $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
137 }
138
139 function initialize() {
140 $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
141 }
142
143 function serve( $exit = true ) {
144 ini_set( 'display_errors', false );
145
146 $this->exit = (bool) $exit;
147
148 // This was causing problems with Jetpack, but is necessary for wpcom
149 // @see https://github.com/Automattic/jetpack/pull/2603
150 // @see r124548-wpcom
151 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
152 add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
153 }
154
155 add_filter( 'user_can_richedit', '__return_true' );
156
157 add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
158
159 $initialization = $this->initialize();
160 if ( 'OPTIONS' == $this->method ) {
161 /**
162 * Fires before the page output.
163 * Can be used to specify custom header options.
164 *
165 * @module json-api
166 *
167 * @since 3.1.0
168 */
169 do_action( 'wpcom_json_api_options' );
170 return $this->output( 200, '', 'text/plain' );
171 }
172
173 if ( is_wp_error( $initialization ) ) {
174 $this->output_error( $initialization );
175 return;
176 }
177
178 // Normalize path and extract API version
179 $this->path = untrailingslashit( $this->path );
180 preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
181 $this->path = substr( $this->path, strlen( $matches[0] ) );
182 $this->version = $matches[1];
183
184 $allowed_methods = array( 'GET', 'POST' );
185 $four_oh_five = false;
186
187 $is_help = preg_match( '#/help/?$#i', $this->path );
188 $matching_endpoints = array();
189
190 if ( $is_help ) {
191 $origin = get_http_origin();
192
193 if ( !empty( $origin ) && 'GET' == $this->method ) {
194 header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
195 }
196
197 $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
198 // Show help for all matching endpoints regardless of method
199 $methods = $allowed_methods;
200 $find_all_matching_endpoints = true;
201 // How deep to truncate each endpoint's path to see if it matches this help request
202 $depth = substr_count( $this->path, '/' ) + 1;
203 if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
204 $help_content_type = 'json';
205 } else {
206 $help_content_type = 'html';
207 }
208 } else {
209 if ( in_array( $this->method, $allowed_methods ) ) {
210 // Only serve requested method
211 $methods = array( $this->method );
212 $find_all_matching_endpoints = false;
213 } else {
214 // We don't allow this requested method - find matching endpoints and send 405
215 $methods = $allowed_methods;
216 $find_all_matching_endpoints = true;
217 $four_oh_five = true;
218 }
219 }
220
221 // Find which endpoint to serve
222 $found = false;
223 foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
224 $endpoint_path_versions = unserialize( $endpoint_path_versions );
225 $endpoint_path = $endpoint_path_versions[0];
226 $endpoint_min_version = $endpoint_path_versions[1];
227 $endpoint_max_version = $endpoint_path_versions[2];
228
229 // Make sure max_version is not less than min_version
230 if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
231 $endpoint_max_version = $endpoint_min_version;
232 }
233
234 foreach ( $methods as $method ) {
235 if ( !isset( $endpoints_by_method[$method] ) ) {
236 continue;
237 }
238
239 // Normalize
240 $endpoint_path = untrailingslashit( $endpoint_path );
241 if ( $is_help ) {
242 // Truncate path at help depth
243 $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
244 }
245
246 // Generate regular expression from sprintf()
247 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
248
249 if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
250 // This endpoint does not match the requested path.
251 continue;
252 }
253
254 if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
255 // This endpoint does not match the requested version.
256 continue;
257 }
258
259 $found = true;
260
261 if ( $find_all_matching_endpoints ) {
262 $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
263 } else {
264 // The method parameters are now in $path_pieces
265 $endpoint = $endpoints_by_method[$method];
266 break 2;
267 }
268 }
269 }
270
271 if ( !$found ) {
272 return $this->output( 404, '', 'text/plain' );
273 }
274
275 if ( $four_oh_five ) {
276 $allowed_methods = array();
277 foreach ( $matching_endpoints as $matching_endpoint ) {
278 $allowed_methods[] = $matching_endpoint[0]->method;
279 }
280
281 header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
282 return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
283 }
284
285 if ( $is_help ) {
286 /**
287 * Fires before the API output.
288 *
289 * @since 1.9.0
290 *
291 * @param string help.
292 */
293 do_action( 'wpcom_json_api_output', 'help' );
294 $proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
295 if ( 'json' === $help_content_type ) {
296 $docs = array();
297 foreach ( $matching_endpoints as $matching_endpoint ) {
298 if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
299 $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
300 }
301 return $this->output( 200, $docs );
302 } else {
303 status_header( 200 );
304 foreach ( $matching_endpoints as $matching_endpoint ) {
305 if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
306 call_user_func( array( $matching_endpoint[0], 'document' ) );
307 }
308 }
309 exit;
310 }
311
312 if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
313 return $this->output( 404, '', 'text/plain' );
314 }
315
316 /** This action is documented in class.json-api.php */
317 do_action( 'wpcom_json_api_output', $endpoint->stat );
318
319 $response = $this->process_request( $endpoint, $path_pieces );
320
321 if ( !$response && !is_array( $response ) ) {
322 return $this->output( 500, '', 'text/plain' );
323 } elseif ( is_wp_error( $response ) ) {
324 return $this->output_error( $response );
325 }
326
327 $output_status_code = $this->output_status_code;
328 $this->set_output_status_code();
329
330 return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers );
331 }
332
333 function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
334 $this->endpoint = $endpoint;
335 return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
336 }
337
338 function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
339 $exit = $this->exit;
340 $this->exit = false;
341 if ( is_wp_error( $response ) )
342 $this->output_error( $response );
343 else
344 $this->output( $status_code, $response, $content_type );
345 $this->exit = $exit;
346 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
347 $this->finish_request();
348 }
349 }
350
351 function set_output_status_code( $code = 200 ) {
352 $this->output_status_code = $code;
353 }
354
355 function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
356 // In case output() was called before the callback returned
357 if ( $this->did_output ) {
358 if ( $this->exit )
359 exit;
360 return $content_type;
361 }
362 $this->did_output = true;
363
364 // 400s and 404s are allowed for all origins
365 if ( 404 == $status_code || 400 == $status_code )
366 header( 'Access-Control-Allow-Origin: *' );
367
368 if ( is_null( $response ) ) {
369 $response = new stdClass;
370 }
371
372 if ( 'text/plain' === $content_type ) {
373 status_header( (int) $status_code );
374 header( 'Content-Type: text/plain' );
375 foreach( $extra as $key => $value ) {
376 header( "$key: $value" );
377 }
378 echo $response;
379 if ( $this->exit ) {
380 exit;
381 }
382
383 return $content_type;
384 }
385
386 $response = $this->filter_fields( $response );
387
388 if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
389 $headers = array(
390 array(
391 'name' => 'Content-Type',
392 'value' => $content_type,
393 )
394 );
395
396 foreach( $extra as $key => $value ) {
397 $headers[] = array( 'name' => $key, 'value' => $value );
398 }
399
400 $response = array(
401 'code' => (int) $status_code,
402 'headers' => $headers,
403 'body' => $response,
404 );
405 $status_code = 200;
406 $content_type = 'application/json';
407 }
408
409 status_header( (int) $status_code );
410 header( "Content-Type: $content_type" );
411 if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
412 $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
413 } else {
414 $callback = false;
415 }
416
417 if ( $callback ) {
418 // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
419 // and by prepending the JSONP response with a JS comment.
420 // [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
421 echo "/**/$callback(";
422
423 }
424 echo $this->json_encode( $response );
425 if ( $callback ) {
426 echo ");";
427 }
428
429 if ( $this->exit ) {
430 exit;
431 }
432
433 return $content_type;
434 }
435
436 public static function serializable_error ( $error ) {
437
438 $status_code = $error->get_error_data();
439
440 if ( is_array( $status_code ) ) {
441 $status_code = $status_code['status_code'];
442 }
443
444 if ( !$status_code ) {
445 $status_code = 400;
446 }
447 $response = array(
448 'error' => $error->get_error_code(),
449 'message' => $error->get_error_message(),
450 );
451
452 if ( $additional_data = $error->get_error_data( 'additional_data' ) ) {
453 $response['data'] = $additional_data;
454 }
455
456 return array(
457 'status_code' => $status_code,
458 'errors' => $response
459 );
460 }
461
462 function output_error( $error ) {
463 $error_response = $this->serializable_error( $error );
464
465 return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
466 }
467
468 function filter_fields( $response ) {
469 if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) )
470 return $response;
471
472 $fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
473
474 if ( is_object( $response ) ) {
475 $response = (array) $response;
476 }
477
478 $has_filtered = false;
479 if ( is_array( $response ) && empty( $response['ID'] ) ) {
480 $keys_to_filter = array(
481 'categories',
482 'comments',
483 'connections',
484 'domains',
485 'groups',
486 'likes',
487 'media',
488 'notes',
489 'posts',
490 'services',
491 'sites',
492 'suggestions',
493 'tags',
494 'themes',
495 'topics',
496 'users',
497 );
498
499 foreach ( $keys_to_filter as $key_to_filter ) {
500 if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
501 continue;
502
503 foreach ( $response[ $key_to_filter ] as $key => $values ) {
504 if ( is_object( $values ) ) {
505 if ( is_object( $response[ $key_to_filter ] ) ) {
506 $response[ $key_to_filter ]->$key = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
507 } elseif ( is_array( $response[ $key_to_filter ] ) ) {
508 $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
509 }
510 } elseif ( is_array( $values ) ) {
511 $response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
512 }
513 }
514
515 $has_filtered = true;
516 }
517 }
518
519 if ( ! $has_filtered ) {
520 if ( is_object( $response ) ) {
521 $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
522 } else if ( is_array( $response ) ) {
523 $response = array_intersect_key( $response, array_flip( $fields ) );
524 }
525 }
526
527 return $response;
528 }
529
530 function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
531 if ( $original_scheme ) {
532 return $url;
533 }
534
535 return preg_replace( '#^https:#', 'http:', $url );
536 }
537
538 function comment_edit_pre( $comment_content ) {
539 return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
540 }
541
542 function json_encode( $data ) {
543 return json_encode( $data );
544 }
545
546 function ends_with( $haystack, $needle ) {
547 return $needle === substr( $haystack, -strlen( $needle ) );
548 }
549
550 // Returns the site's blog_id in the WP.com ecosystem
551 function get_blog_id_for_output() {
552 return $this->token_details['blog_id'];
553 }
554
555 // Returns the site's local blog_id
556 function get_blog_id( $blog_id ) {
557 return $GLOBALS['blog_id'];
558 }
559
560 function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
561 if ( $this->is_restricted_blog( $blog_id ) ) {
562 return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
563 }
564
565 if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
566 return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
567 }
568
569 return $blog_id;
570 }
571
572 // Returns true if the specified blog ID is a restricted blog
573 function is_restricted_blog( $blog_id ) {
574 /**
575 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
576 *
577 * @module json-api
578 *
579 * @since 3.4.0
580 *
581 * @param array $array Array of Blog IDs.
582 */
583 $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
584 return true === in_array( $blog_id, $restricted_blog_ids );
585 }
586
587 function post_like_count( $blog_id, $post_id ) {
588 return 0;
589 }
590
591 function is_liked( $blog_id, $post_id ) {
592 return false;
593 }
594
595 function is_reblogged( $blog_id, $post_id ) {
596 return false;
597 }
598
599 function is_following( $blog_id ) {
600 return false;
601 }
602
603 function add_global_ID( $blog_id, $post_id ) {
604 return '';
605 }
606
607 function get_avatar_url( $email, $avatar_size = null ) {
608 if ( function_exists( 'wpcom_get_avatar_url' ) ) {
609 return null === $avatar_size
610 ? wpcom_get_avatar_url( $email )
611 : wpcom_get_avatar_url( $email, $avatar_size );
612 } else {
613 return null === $avatar_size
614 ? get_avatar_url( $email )
615 : get_avatar_url( $email, $avatar_size );
616 }
617 }
618
619 /**
620 * traps `wp_die()` calls and outputs a JSON response instead.
621 * The result is always output, never returned.
622 *
623 * @param string|null $error_code Call with string to start the trapping. Call with null to stop.
624 * @param int $http_status HTTP status code, 400 by default.
625 */
626 function trap_wp_die( $error_code = null, $http_status = 400 ) {
627 if ( is_null( $error_code ) ) {
628 $this->trapped_error = null;
629 // Stop trapping
630 remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
631 return;
632 }
633
634 // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
635 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
636 if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
637 return;
638 }
639 } else {
640 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
641 return;
642 }
643 }
644
645 $this->trapped_error = array(
646 'status' => $http_status,
647 'code' => $error_code,
648 'message' => '',
649 );
650 // Start trapping
651 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
652 }
653
654 function wp_die_handler_callback() {
655 return array( $this, 'wp_die_handler' );
656 }
657
658 function wp_die_handler( $message, $title = '', $args = array() ) {
659 // Allow wp_die calls to override HTTP status code...
660 $args = wp_parse_args( $args, array(
661 'response' => $this->trapped_error['status'],
662 ) );
663
664 // ... unless it's 500 ( see http://wp.me/pMz3w-5VV )
665 if ( (int) $args['response'] !== 500 ) {
666 $this->trapped_error['status'] = $args['response'];
667 }
668
669 if ( $title ) {
670 $message = "$title: $message";
671 }
672
673 $this->trapped_error['message'] = wp_kses( $message, array() );
674
675 switch ( $this->trapped_error['code'] ) {
676 case 'comment_failure' :
677 if ( did_action( 'comment_duplicate_trigger' ) ) {
678 $this->trapped_error['code'] = 'comment_duplicate';
679 } else if ( did_action( 'comment_flood_trigger' ) ) {
680 $this->trapped_error['code'] = 'comment_flood';
681 }
682 break;
683 }
684
685 // We still want to exit so that code execution stops where it should.
686 // Attach the JSON output to the WordPress shutdown handler
687 add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
688 exit;
689 }
690
691 function output_trapped_error() {
692 $this->exit = false; // We're already exiting once. Don't do it twice.
693 $this->output( $this->trapped_error['status'], (object) array(
694 'error' => $this->trapped_error['code'],
695 'message' => $this->trapped_error['message'],
696 ) );
697 }
698
699 function finish_request() {
700 if ( function_exists( 'fastcgi_finish_request' ) )
701 return fastcgi_finish_request();
702 }
703 }
704