PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 4.6.3
Jetpack – WP Security, Backup, Speed, & Growth v4.6.3
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 9 years ago _inc 1 year ago bin 9 years ago css 9 years ago images 1 year ago json-endpoints 9 years ago languages 9 years ago modules 1 year ago sal 9 years ago scss 9 years ago sync 9 years ago views 9 years ago .svnignore 12 years ago changelog.txt 9 years ago class.frame-nonce-preview.php 9 years ago class.jetpack-admin.php 9 years ago class.jetpack-autoupdate.php 9 years ago class.jetpack-bbpress-json-api-compat.php 9 years ago class.jetpack-cli.php 9 years ago class.jetpack-client-server.php 9 years ago class.jetpack-client.php 9 years ago class.jetpack-connection-banner.php 9 years ago class.jetpack-constants.php 9 years ago class.jetpack-data.php 9 years ago class.jetpack-debugger.php 9 years ago class.jetpack-error.php 10 years ago class.jetpack-heartbeat.php 9 years ago class.jetpack-idc.php 9 years ago class.jetpack-ixr-client.php 10 years ago class.jetpack-jitm.php 9 years ago class.jetpack-modules-list-table.php 9 years ago class.jetpack-network-sites-list-table.php 9 years ago class.jetpack-network.php 9 years ago class.jetpack-options.php 9 years ago class.jetpack-post-images.php 9 years ago class.jetpack-signature.php 9 years ago class.jetpack-tracks.php 9 years ago class.jetpack-twitter-cards.php 9 years ago class.jetpack-user-agent.php 9 years ago class.jetpack-xmlrpc-server.php 9 years ago class.jetpack.php 9 years ago class.json-api-endpoints.php 3 years ago class.json-api.php 10 years ago class.photon.php 9 years ago composer.json 10 years ago functions.compat.php 9 years ago functions.gallery.php 10 years ago functions.global.php 9 years ago functions.opengraph.php 9 years ago functions.photon.php 9 years ago jetpack.php 1 year ago json-api-config.php 10 years ago json-endpoints.php 9 years ago locales.php 9 years ago readme.txt 1 year ago require-lib.php 10 years ago rest-api.md 9 years ago uninstall.php 9 years ago webpack.config.js 9 years ago wpml-config.xml 10 years ago
class.json-api.php
694 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 /**
34 * @return WPCOM_JSON_API instance
35 */
36 static function init( $method = null, $url = null, $post_body = null ) {
37 if ( !self::$self ) {
38 $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
39 self::$self = new $class( $method, $url, $post_body );
40 }
41 return self::$self;
42 }
43
44 function add( WPCOM_JSON_API_Endpoint $endpoint ) {
45 $path_versions = serialize( array (
46 $endpoint->path,
47 $endpoint->min_version,
48 $endpoint->max_version,
49 ) );
50 if ( !isset( $this->endpoints[$path_versions] ) ) {
51 $this->endpoints[$path_versions] = array();
52 }
53 $this->endpoints[$path_versions][$endpoint->method] = $endpoint;
54 }
55
56 static function is_truthy( $value ) {
57 switch ( strtolower( (string) $value ) ) {
58 case '1' :
59 case 't' :
60 case 'true' :
61 return true;
62 }
63
64 return false;
65 }
66
67 static function is_falsy( $value ) {
68 switch ( strtolower( (string) $value ) ) {
69 case '0' :
70 case 'f' :
71 case 'false' :
72 return true;
73 }
74
75 return false;
76 }
77
78 function __construct() {
79 $args = func_get_args();
80 call_user_func_array( array( $this, 'setup_inputs' ), $args );
81 }
82
83 function setup_inputs( $method = null, $url = null, $post_body = null ) {
84 if ( is_null( $method ) ) {
85 $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
86 } else {
87 $this->method = strtoupper( $method );
88 }
89 if ( is_null( $url ) ) {
90 $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
91 } else {
92 $this->url = $url;
93 }
94
95 $parsed = parse_url( $this->url );
96 $this->path = $parsed['path'];
97
98 if ( !empty( $parsed['query'] ) ) {
99 wp_parse_str( $parsed['query'], $this->query );
100 }
101
102 if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
103 $this->accept = $_SERVER['HTTP_ACCEPT'];
104 }
105
106 if ( 'POST' === $this->method ) {
107 if ( is_null( $post_body ) ) {
108 $this->post_body = file_get_contents( 'php://input' );
109
110 if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
111 $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
112 } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
113 $this->content_type = $_SERVER['CONTENT_TYPE'] ;
114 } elseif ( '{' === $this->post_body[0] ) {
115 $this->content_type = 'application/json';
116 } else {
117 $this->content_type = 'application/x-www-form-urlencoded';
118 }
119
120 if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
121 $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
122 $this->files = $_FILES;
123 $this->content_type = 'multipart/form-data';
124 }
125 } else {
126 $this->post_body = $post_body;
127 $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
128 }
129 } else {
130 $this->post_body = null;
131 $this->content_type = null;
132 }
133
134 $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
135 }
136
137 function initialize() {
138 $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
139 }
140
141 function serve( $exit = true ) {
142 ini_set( 'display_errors', false );
143
144 $this->exit = (bool) $exit;
145
146 // This was causing problems with Jetpack, but is necessary for wpcom
147 // @see https://github.com/Automattic/jetpack/pull/2603
148 // @see r124548-wpcom
149 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
150 add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
151 }
152
153 add_filter( 'user_can_richedit', '__return_true' );
154
155 add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
156
157 $initialization = $this->initialize();
158 if ( 'OPTIONS' == $this->method ) {
159 /**
160 * Fires before the page output.
161 * Can be used to specify custom header options.
162 *
163 * @module json-api
164 *
165 * @since 3.1.0
166 */
167 do_action( 'wpcom_json_api_options' );
168 return $this->output( 200, '', 'plain/text' );
169 }
170
171 if ( is_wp_error( $initialization ) ) {
172 $this->output_error( $initialization );
173 return;
174 }
175
176 // Normalize path and extract API version
177 $this->path = untrailingslashit( $this->path );
178 preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
179 $this->path = substr( $this->path, strlen( $matches[0] ) );
180 $this->version = $matches[1];
181
182 $allowed_methods = array( 'GET', 'POST' );
183 $four_oh_five = false;
184
185 $is_help = preg_match( '#/help/?$#i', $this->path );
186 $matching_endpoints = array();
187
188 if ( $is_help ) {
189 $origin = get_http_origin();
190
191 if ( !empty( $origin ) && 'GET' == $this->method ) {
192 header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
193 }
194
195 $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
196 // Show help for all matching endpoints regardless of method
197 $methods = $allowed_methods;
198 $find_all_matching_endpoints = true;
199 // How deep to truncate each endpoint's path to see if it matches this help request
200 $depth = substr_count( $this->path, '/' ) + 1;
201 if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
202 $help_content_type = 'json';
203 } else {
204 $help_content_type = 'html';
205 }
206 } else {
207 if ( in_array( $this->method, $allowed_methods ) ) {
208 // Only serve requested method
209 $methods = array( $this->method );
210 $find_all_matching_endpoints = false;
211 } else {
212 // We don't allow this requested method - find matching endpoints and send 405
213 $methods = $allowed_methods;
214 $find_all_matching_endpoints = true;
215 $four_oh_five = true;
216 }
217 }
218
219 // Find which endpoint to serve
220 $found = false;
221 foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
222 $endpoint_path_versions = unserialize( $endpoint_path_versions );
223 $endpoint_path = $endpoint_path_versions[0];
224 $endpoint_min_version = $endpoint_path_versions[1];
225 $endpoint_max_version = $endpoint_path_versions[2];
226
227 // Make sure max_version is not less than min_version
228 if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
229 $endpoint_max_version = $endpoint_min_version;
230 }
231
232 foreach ( $methods as $method ) {
233 if ( !isset( $endpoints_by_method[$method] ) ) {
234 continue;
235 }
236
237 // Normalize
238 $endpoint_path = untrailingslashit( $endpoint_path );
239 if ( $is_help ) {
240 // Truncate path at help depth
241 $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
242 }
243
244 // Generate regular expression from sprintf()
245 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
246
247 if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
248 // This endpoint does not match the requested path.
249 continue;
250 }
251
252 if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) {
253 // This endpoint does not match the requested version.
254 continue;
255 }
256
257 $found = true;
258
259 if ( $find_all_matching_endpoints ) {
260 $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
261 } else {
262 // The method parameters are now in $path_pieces
263 $endpoint = $endpoints_by_method[$method];
264 break 2;
265 }
266 }
267 }
268
269 if ( !$found ) {
270 return $this->output( 404, '', 'text/plain' );
271 }
272
273 if ( $four_oh_five ) {
274 $allowed_methods = array();
275 foreach ( $matching_endpoints as $matching_endpoint ) {
276 $allowed_methods[] = $matching_endpoint[0]->method;
277 }
278
279 header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
280 return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
281 }
282
283 if ( $is_help ) {
284 /**
285 * Fires before the API output.
286 *
287 * @since 1.9.0
288 *
289 * @param string help.
290 */
291 do_action( 'wpcom_json_api_output', 'help' );
292 if ( 'json' === $help_content_type ) {
293 $docs = array();
294 foreach ( $matching_endpoints as $matching_endpoint ) {
295 if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
296 $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
297 }
298 return $this->output( 200, $docs );
299 } else {
300 status_header( 200 );
301 foreach ( $matching_endpoints as $matching_endpoint ) {
302 if ( $matching_endpoint[0]->is_publicly_documentable() || WPCOM_JSON_API__DEBUG )
303 call_user_func( array( $matching_endpoint[0], 'document' ) );
304 }
305 }
306 exit;
307 }
308
309 if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
310 return $this->output( 404, '', 'text/plain' );
311 }
312
313 /** This action is documented in class.json-api.php */
314 do_action( 'wpcom_json_api_output', $endpoint->stat );
315
316 $response = $this->process_request( $endpoint, $path_pieces );
317
318 if ( !$response && !is_array( $response ) ) {
319 return $this->output( 500, '', 'text/plain' );
320 } elseif ( is_wp_error( $response ) ) {
321 return $this->output_error( $response );
322 }
323
324 $output_status_code = $this->output_status_code;
325 $this->set_output_status_code();
326
327 return $this->output( $output_status_code, $response );
328 }
329
330 function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
331 $this->endpoint = $endpoint;
332 return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
333 }
334
335 function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
336 $exit = $this->exit;
337 $this->exit = false;
338 if ( is_wp_error( $response ) )
339 $this->output_error( $response );
340 else
341 $this->output( $status_code, $response, $content_type );
342 $this->exit = $exit;
343 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
344 $this->finish_request();
345 }
346 }
347
348 function set_output_status_code( $code = 200 ) {
349 $this->output_status_code = $code;
350 }
351
352 function output( $status_code, $response = null, $content_type = 'application/json' ) {
353 // In case output() was called before the callback returned
354 if ( $this->did_output ) {
355 if ( $this->exit )
356 exit;
357 return $content_type;
358 }
359 $this->did_output = true;
360
361 // 400s and 404s are allowed for all origins
362 if ( 404 == $status_code || 400 == $status_code )
363 header( 'Access-Control-Allow-Origin: *' );
364
365 if ( is_null( $response ) ) {
366 $response = new stdClass;
367 }
368
369 if ( 'text/plain' === $content_type ) {
370 status_header( (int) $status_code );
371 header( 'Content-Type: text/plain' );
372 echo $response;
373 if ( $this->exit ) {
374 exit;
375 }
376
377 return $content_type;
378 }
379
380 $response = $this->filter_fields( $response );
381
382 if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
383 $response = array(
384 'code' => (int) $status_code,
385 'headers' => array(
386 array(
387 'name' => 'Content-Type',
388 'value' => $content_type,
389 ),
390 ),
391 'body' => $response,
392 );
393 $status_code = 200;
394 $content_type = 'application/json';
395 }
396
397 status_header( (int) $status_code );
398 header( "Content-Type: $content_type" );
399 if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
400 $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
401 } else {
402 $callback = false;
403 }
404
405 if ( $callback ) {
406 // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
407 // and by prepending the JSONP response with a JS comment.
408 // [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
409 echo "/**/$callback(";
410
411 }
412 echo $this->json_encode( $response );
413 if ( $callback ) {
414 echo ");";
415 }
416
417 if ( $this->exit ) {
418 exit;
419 }
420
421 return $content_type;
422 }
423
424 public static function serializable_error ( $error ) {
425
426 $status_code = $error->get_error_data();
427
428 if ( is_array( $status_code ) )
429 $status_code = $status_code['status_code'];
430
431 if ( !$status_code ) {
432 $status_code = 400;
433 }
434 $response = array(
435 'error' => $error->get_error_code(),
436 'message' => $error->get_error_message(),
437 );
438 return array(
439 'status_code' => $status_code,
440 'errors' => $response
441 );
442 }
443
444 function output_error( $error ) {
445 if ( function_exists( 'bump_stats_extra' ) ) {
446 $client_id = ! empty( $this->token_details['client_id'] ) ? $this->token_details['client_id'] : 0;
447 bump_stats_extra( 'rest-api-errors', $client_id );
448 }
449
450 $error_response = $this->serializable_error( $error );
451
452 return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
453 }
454
455 function filter_fields( $response ) {
456 if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) )
457 return $response;
458
459 $fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
460
461 if ( is_object( $response ) ) {
462 $response = (array) $response;
463 }
464
465 $has_filtered = false;
466 if ( is_array( $response ) && empty( $response['ID'] ) ) {
467 $keys_to_filter = array(
468 'categories',
469 'comments',
470 'connections',
471 'domains',
472 'groups',
473 'likes',
474 'media',
475 'notes',
476 'posts',
477 'services',
478 'sites',
479 'suggestions',
480 'tags',
481 'themes',
482 'topics',
483 'users',
484 );
485
486 foreach ( $keys_to_filter as $key_to_filter ) {
487 if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
488 continue;
489
490 foreach ( $response[ $key_to_filter ] as $key => $values ) {
491 if ( is_object( $values ) ) {
492 $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( (array) $values, array_flip( $fields ) );
493 } elseif ( is_array( $values ) ) {
494 $response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) );
495 }
496 }
497
498 $has_filtered = true;
499 }
500 }
501
502 if ( ! $has_filtered ) {
503 if ( is_object( $response ) ) {
504 $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
505 } else if ( is_array( $response ) ) {
506 $response = array_intersect_key( $response, array_flip( $fields ) );
507 }
508 }
509
510 return $response;
511 }
512
513 function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
514 if ( $original_scheme ) {
515 return $url;
516 }
517
518 return preg_replace( '#^https:#', 'http:', $url );
519 }
520
521 function comment_edit_pre( $comment_content ) {
522 return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
523 }
524
525 function json_encode( $data ) {
526 return json_encode( $data );
527 }
528
529 function ends_with( $haystack, $needle ) {
530 return $needle === substr( $haystack, -strlen( $needle ) );
531 }
532
533 // Returns the site's blog_id in the WP.com ecosystem
534 function get_blog_id_for_output() {
535 return $this->token_details['blog_id'];
536 }
537
538 // Returns the site's local blog_id
539 function get_blog_id( $blog_id ) {
540 return $GLOBALS['blog_id'];
541 }
542
543 function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
544 if ( $this->is_restricted_blog( $blog_id ) ) {
545 return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
546 }
547
548 if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
549 return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
550 }
551
552 return $blog_id;
553 }
554
555 // Returns true if the specified blog ID is a restricted blog
556 function is_restricted_blog( $blog_id ) {
557 /**
558 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
559 *
560 * @module json-api
561 *
562 * @since 3.4.0
563 *
564 * @param array $array Array of Blog IDs.
565 */
566 $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
567 return true === in_array( $blog_id, $restricted_blog_ids );
568 }
569
570 function post_like_count( $blog_id, $post_id ) {
571 return 0;
572 }
573
574 function is_liked( $blog_id, $post_id ) {
575 return false;
576 }
577
578 function is_reblogged( $blog_id, $post_id ) {
579 return false;
580 }
581
582 function is_following( $blog_id ) {
583 return false;
584 }
585
586 function add_global_ID( $blog_id, $post_id ) {
587 return '';
588 }
589
590 function get_avatar_url( $email, $avatar_size = 96 ) {
591 add_filter( 'pre_option_show_avatars', '__return_true', 999 );
592 $_SERVER['HTTPS'] = 'off';
593
594 $avatar_img_element = get_avatar( $email, $avatar_size, '' );
595
596 if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) {
597 $return = '';
598 } elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) {
599 $return = '';
600 } else {
601 $return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) );
602 }
603
604 remove_filter( 'pre_option_show_avatars', '__return_true', 999 );
605 if ( '--UNset--' === $this->_server_https ) {
606 unset( $_SERVER['HTTPS'] );
607 } else {
608 $_SERVER['HTTPS'] = $this->_server_https;
609 }
610
611 return $return;
612 }
613
614 /**
615 * Traps `wp_die()` calls and outputs a JSON response instead.
616 * The result is always output, never returned.
617 *
618 * @param string|null $error_code. Call with string to start the trapping. Call with null to stop.
619 */
620 function trap_wp_die( $error_code = null ) {
621 // Stop trapping
622 if ( is_null( $error_code ) ) {
623 $this->trapped_error = null;
624 remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
625 return;
626 }
627
628 // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
629 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
630 if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
631 return;
632 }
633 } else {
634 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
635 return;
636 }
637 }
638
639 // Start trapping
640 $this->trapped_error = array(
641 'status' => 500,
642 'code' => $error_code,
643 'message' => '',
644 );
645
646 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
647 }
648
649 function wp_die_handler_callback() {
650 return array( $this, 'wp_die_handler' );
651 }
652
653 function wp_die_handler( $message, $title = '', $args = array() ) {
654 $args = wp_parse_args( $args, array(
655 'response' => 500,
656 ) );
657
658 if ( $title ) {
659 $message = "$title: $message";
660 }
661
662 switch ( $this->trapped_error['code'] ) {
663 case 'comment_failure' :
664 if ( did_action( 'comment_duplicate_trigger' ) ) {
665 $this->trapped_error['code'] = 'comment_duplicate';
666 } else if ( did_action( 'comment_flood_trigger' ) ) {
667 $this->trapped_error['code'] = 'comment_flood';
668 }
669 break;
670 }
671
672 $this->trapped_error['status'] = $args['response'];
673 $this->trapped_error['message'] = wp_kses( $message, array() );
674
675 // We still want to exit so that code execution stops where it should.
676 // Attach the JSON output to WordPress' shutdown handler
677 add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
678 exit;
679 }
680
681 function output_trapped_error() {
682 $this->exit = false; // We're already exiting once. Don't do it twice.
683 $this->output( $this->trapped_error['status'], (object) array(
684 'error' => $this->trapped_error['code'],
685 'message' => $this->trapped_error['message'],
686 ) );
687 }
688
689 function finish_request() {
690 if ( function_exists( 'fastcgi_finish_request' ) )
691 return fastcgi_finish_request();
692 }
693 }
694