These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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.FunctionUse.NewFunctions.get_called_classFound |
||
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 | if ( ! empty( $parsed['path'] ) ) { |
||
99 | $this->path = $parsed['path']; |
||
100 | } |
||
101 | |||
102 | if ( !empty( $parsed['query'] ) ) { |
||
103 | wp_parse_str( $parsed['query'], $this->query ); |
||
104 | } |
||
105 | |||
106 | if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) { |
||
107 | $this->accept = $_SERVER['HTTP_ACCEPT']; |
||
108 | } |
||
109 | |||
110 | if ( 'POST' === $this->method ) { |
||
111 | if ( is_null( $post_body ) ) { |
||
112 | $this->post_body = file_get_contents( 'php://input' ); |
||
113 | |||
114 | if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) { |
||
115 | $this->content_type = $_SERVER['HTTP_CONTENT_TYPE']; |
||
116 | } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) { |
||
117 | $this->content_type = $_SERVER['CONTENT_TYPE'] ; |
||
118 | } elseif ( '{' === $this->post_body[0] ) { |
||
119 | $this->content_type = 'application/json'; |
||
120 | } else { |
||
121 | $this->content_type = 'application/x-www-form-urlencoded'; |
||
122 | } |
||
123 | |||
124 | if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) { |
||
125 | $this->post_body = http_build_query( stripslashes_deep( $_POST ) ); |
||
126 | $this->files = $_FILES; |
||
127 | $this->content_type = 'multipart/form-data'; |
||
128 | } |
||
129 | } else { |
||
130 | $this->post_body = $post_body; |
||
131 | $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded'; |
||
132 | } |
||
133 | } else { |
||
134 | $this->post_body = null; |
||
135 | $this->content_type = null; |
||
136 | } |
||
137 | |||
138 | $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--'; |
||
139 | } |
||
140 | |||
141 | function initialize() { |
||
142 | $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' ); |
||
143 | } |
||
144 | |||
145 | function serve( $exit = true ) { |
||
146 | ini_set( 'display_errors', false ); |
||
147 | |||
148 | $this->exit = (bool) $exit; |
||
149 | |||
150 | // This was causing problems with Jetpack, but is necessary for wpcom |
||
151 | // @see https://github.com/Automattic/jetpack/pull/2603 |
||
152 | // @see r124548-wpcom |
||
153 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { |
||
154 | add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 ); |
||
155 | } |
||
156 | |||
157 | add_filter( 'user_can_richedit', '__return_true' ); |
||
158 | |||
159 | add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) ); |
||
160 | |||
161 | $initialization = $this->initialize(); |
||
162 | if ( 'OPTIONS' == $this->method ) { |
||
163 | /** |
||
164 | * Fires before the page output. |
||
165 | * Can be used to specify custom header options. |
||
166 | * |
||
167 | * @module json-api |
||
168 | * |
||
169 | * @since 3.1.0 |
||
170 | */ |
||
171 | do_action( 'wpcom_json_api_options' ); |
||
172 | return $this->output( 200, '', 'text/plain' ); |
||
173 | } |
||
174 | |||
175 | if ( is_wp_error( $initialization ) ) { |
||
176 | $this->output_error( $initialization ); |
||
177 | return; |
||
178 | } |
||
179 | |||
180 | // Normalize path and extract API version |
||
181 | $this->path = untrailingslashit( $this->path ); |
||
182 | preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches ); |
||
183 | $this->path = substr( $this->path, strlen( $matches[0] ) ); |
||
184 | $this->version = $matches[1]; |
||
185 | |||
186 | $allowed_methods = array( 'GET', 'POST' ); |
||
187 | $four_oh_five = false; |
||
188 | |||
189 | $is_help = preg_match( '#/help/?$#i', $this->path ); |
||
190 | $matching_endpoints = array(); |
||
191 | |||
192 | if ( $is_help ) { |
||
193 | $origin = get_http_origin(); |
||
194 | |||
195 | if ( !empty( $origin ) && 'GET' == $this->method ) { |
||
196 | header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); |
||
197 | } |
||
198 | |||
199 | $this->path = substr( rtrim( $this->path, '/' ), 0, -5 ); |
||
200 | // Show help for all matching endpoints regardless of method |
||
201 | $methods = $allowed_methods; |
||
202 | $find_all_matching_endpoints = true; |
||
203 | // How deep to truncate each endpoint's path to see if it matches this help request |
||
204 | $depth = substr_count( $this->path, '/' ) + 1; |
||
205 | if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) { |
||
206 | $help_content_type = 'json'; |
||
207 | } else { |
||
208 | $help_content_type = 'html'; |
||
209 | } |
||
210 | } else { |
||
211 | if ( in_array( $this->method, $allowed_methods ) ) { |
||
212 | // Only serve requested method |
||
213 | $methods = array( $this->method ); |
||
214 | $find_all_matching_endpoints = false; |
||
215 | } else { |
||
216 | // We don't allow this requested method - find matching endpoints and send 405 |
||
217 | $methods = $allowed_methods; |
||
218 | $find_all_matching_endpoints = true; |
||
219 | $four_oh_five = true; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | // Find which endpoint to serve |
||
224 | $found = false; |
||
225 | foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) { |
||
226 | $endpoint_path_versions = unserialize( $endpoint_path_versions ); |
||
227 | $endpoint_path = $endpoint_path_versions[0]; |
||
228 | $endpoint_min_version = $endpoint_path_versions[1]; |
||
229 | $endpoint_max_version = $endpoint_path_versions[2]; |
||
230 | |||
231 | // Make sure max_version is not less than min_version |
||
232 | if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) { |
||
233 | $endpoint_max_version = $endpoint_min_version; |
||
234 | } |
||
235 | |||
236 | foreach ( $methods as $method ) { |
||
237 | if ( !isset( $endpoints_by_method[$method] ) ) { |
||
238 | continue; |
||
239 | } |
||
240 | |||
241 | // Normalize |
||
242 | $endpoint_path = untrailingslashit( $endpoint_path ); |
||
243 | if ( $is_help ) { |
||
244 | // Truncate path at help depth |
||
245 | $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) ); |
||
246 | } |
||
247 | |||
248 | // Generate regular expression from sprintf() |
||
249 | $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path ); |
||
250 | |||
251 | if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) { |
||
252 | // This endpoint does not match the requested path. |
||
253 | continue; |
||
254 | } |
||
255 | |||
256 | if ( version_compare( $this->version, $endpoint_min_version, '<' ) || version_compare( $this->version, $endpoint_max_version, '>' ) ) { |
||
257 | // This endpoint does not match the requested version. |
||
258 | continue; |
||
259 | } |
||
260 | |||
261 | $found = true; |
||
262 | |||
263 | if ( $find_all_matching_endpoints ) { |
||
264 | $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces ); |
||
265 | } else { |
||
266 | // The method parameters are now in $path_pieces |
||
267 | $endpoint = $endpoints_by_method[$method]; |
||
268 | break 2; |
||
269 | } |
||
270 | } |
||
271 | } |
||
272 | |||
273 | if ( !$found ) { |
||
274 | return $this->output( 404, '', 'text/plain' ); |
||
275 | } |
||
276 | |||
277 | if ( $four_oh_five ) { |
||
278 | $allowed_methods = array(); |
||
279 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
280 | $allowed_methods[] = $matching_endpoint[0]->method; |
||
281 | } |
||
282 | |||
283 | header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) ); |
||
284 | return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) ); |
||
285 | } |
||
286 | |||
287 | if ( $is_help ) { |
||
288 | /** |
||
289 | * Fires before the API output. |
||
290 | * |
||
291 | * @since 1.9.0 |
||
292 | * |
||
293 | * @param string help. |
||
294 | */ |
||
295 | do_action( 'wpcom_json_api_output', 'help' ); |
||
296 | $proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false; |
||
297 | if ( 'json' === $help_content_type ) { |
||
298 | $docs = array(); |
||
299 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
300 | if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) |
||
301 | $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) ); |
||
302 | } |
||
303 | return $this->output( 200, $docs ); |
||
304 | } else { |
||
305 | status_header( 200 ); |
||
306 | foreach ( $matching_endpoints as $matching_endpoint ) { |
||
307 | if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) |
||
308 | call_user_func( array( $matching_endpoint[0], 'document' ) ); |
||
309 | } |
||
310 | } |
||
311 | exit; |
||
312 | } |
||
313 | |||
314 | if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) { |
||
315 | return $this->output( 404, '', 'text/plain' ); |
||
316 | } |
||
317 | |||
318 | /** This action is documented in class.json-api.php */ |
||
319 | do_action( 'wpcom_json_api_output', $endpoint->stat ); |
||
320 | |||
321 | $response = $this->process_request( $endpoint, $path_pieces ); |
||
322 | |||
323 | if ( !$response && !is_array( $response ) ) { |
||
324 | return $this->output( 500, '', 'text/plain' ); |
||
325 | } elseif ( is_wp_error( $response ) ) { |
||
326 | return $this->output_error( $response ); |
||
327 | } |
||
328 | |||
329 | $output_status_code = $this->output_status_code; |
||
330 | $this->set_output_status_code(); |
||
331 | |||
332 | return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers ); |
||
333 | } |
||
334 | |||
335 | function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) { |
||
336 | $this->endpoint = $endpoint; |
||
337 | return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces ); |
||
338 | } |
||
339 | |||
340 | function output_early( $status_code, $response = null, $content_type = 'application/json' ) { |
||
341 | $exit = $this->exit; |
||
342 | $this->exit = false; |
||
343 | if ( is_wp_error( $response ) ) |
||
344 | $this->output_error( $response ); |
||
345 | else |
||
346 | $this->output( $status_code, $response, $content_type ); |
||
347 | $this->exit = $exit; |
||
348 | if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) { |
||
349 | $this->finish_request(); |
||
350 | } |
||
351 | } |
||
352 | |||
353 | function set_output_status_code( $code = 200 ) { |
||
354 | $this->output_status_code = $code; |
||
355 | } |
||
356 | |||
357 | function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) { |
||
358 | // In case output() was called before the callback returned |
||
359 | if ( $this->did_output ) { |
||
360 | if ( $this->exit ) |
||
361 | exit; |
||
362 | return $content_type; |
||
363 | } |
||
364 | $this->did_output = true; |
||
365 | |||
366 | // 400s and 404s are allowed for all origins |
||
367 | if ( 404 == $status_code || 400 == $status_code ) |
||
368 | header( 'Access-Control-Allow-Origin: *' ); |
||
369 | |||
370 | if ( is_null( $response ) ) { |
||
371 | $response = new stdClass; |
||
372 | } |
||
373 | |||
374 | if ( 'text/plain' === $content_type ) { |
||
375 | status_header( (int) $status_code ); |
||
376 | header( 'Content-Type: text/plain' ); |
||
377 | foreach( $extra as $key => $value ) { |
||
378 | header( "$key: $value" ); |
||
379 | } |
||
380 | echo $response; |
||
381 | if ( $this->exit ) { |
||
382 | exit; |
||
383 | } |
||
384 | |||
385 | return $content_type; |
||
386 | } |
||
387 | |||
388 | $response = $this->filter_fields( $response ); |
||
389 | |||
390 | if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) { |
||
391 | $headers = array( |
||
392 | array( |
||
393 | 'name' => 'Content-Type', |
||
394 | 'value' => $content_type, |
||
395 | ) |
||
396 | ); |
||
397 | |||
398 | foreach( $extra as $key => $value ) { |
||
399 | $headers[] = array( 'name' => $key, 'value' => $value ); |
||
400 | } |
||
401 | |||
402 | $response = array( |
||
403 | 'code' => (int) $status_code, |
||
404 | 'headers' => $headers, |
||
405 | 'body' => $response, |
||
406 | ); |
||
407 | $status_code = 200; |
||
408 | $content_type = 'application/json'; |
||
409 | } |
||
410 | |||
411 | status_header( (int) $status_code ); |
||
412 | header( "Content-Type: $content_type" ); |
||
413 | if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) { |
||
414 | $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] ); |
||
415 | } else { |
||
416 | $callback = false; |
||
417 | } |
||
418 | |||
419 | if ( $callback ) { |
||
0 ignored issues
–
show
|
|||
420 | // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header |
||
421 | // and by prepending the JSONP response with a JS comment. |
||
422 | // [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
||
423 | echo "/**/$callback("; |
||
424 | |||
425 | } |
||
426 | echo $this->json_encode( $response ); |
||
427 | if ( $callback ) { |
||
0 ignored issues
–
show
The expression
$callback of type string|false is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
428 | echo ");"; |
||
429 | } |
||
430 | |||
431 | if ( $this->exit ) { |
||
432 | exit; |
||
433 | } |
||
434 | |||
435 | return $content_type; |
||
436 | } |
||
437 | |||
438 | public static function serializable_error ( $error ) { |
||
439 | |||
440 | $status_code = $error->get_error_data(); |
||
441 | |||
442 | if ( is_array( $status_code ) ) { |
||
443 | $status_code = $status_code['status_code']; |
||
444 | } |
||
445 | |||
446 | if ( !$status_code ) { |
||
447 | $status_code = 400; |
||
448 | } |
||
449 | $response = array( |
||
450 | 'error' => $error->get_error_code(), |
||
451 | 'message' => $error->get_error_message(), |
||
452 | ); |
||
453 | |||
454 | if ( $additional_data = $error->get_error_data( 'additional_data' ) ) { |
||
455 | $response['data'] = $additional_data; |
||
456 | } |
||
457 | |||
458 | return array( |
||
459 | 'status_code' => $status_code, |
||
460 | 'errors' => $response |
||
461 | ); |
||
462 | } |
||
463 | |||
464 | function output_error( $error ) { |
||
465 | $error_response = $this->serializable_error( $error ); |
||
466 | |||
467 | return $this->output( $error_response[ 'status_code'], $error_response['errors'] ); |
||
468 | } |
||
469 | |||
470 | function filter_fields( $response ) { |
||
471 | if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) ) |
||
472 | return $response; |
||
473 | |||
474 | $fields = array_map( 'trim', explode( ',', $this->query['fields'] ) ); |
||
475 | |||
476 | if ( is_object( $response ) ) { |
||
477 | $response = (array) $response; |
||
478 | } |
||
479 | |||
480 | $has_filtered = false; |
||
481 | if ( is_array( $response ) && empty( $response['ID'] ) ) { |
||
482 | $keys_to_filter = array( |
||
483 | 'categories', |
||
484 | 'comments', |
||
485 | 'connections', |
||
486 | 'domains', |
||
487 | 'groups', |
||
488 | 'likes', |
||
489 | 'media', |
||
490 | 'notes', |
||
491 | 'posts', |
||
492 | 'services', |
||
493 | 'sites', |
||
494 | 'suggestions', |
||
495 | 'tags', |
||
496 | 'themes', |
||
497 | 'topics', |
||
498 | 'users', |
||
499 | ); |
||
500 | |||
501 | foreach ( $keys_to_filter as $key_to_filter ) { |
||
502 | if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered ) |
||
503 | continue; |
||
504 | |||
505 | foreach ( $response[ $key_to_filter ] as $key => $values ) { |
||
506 | if ( is_object( $values ) ) { |
||
507 | if ( is_object( $response[ $key_to_filter ] ) ) { |
||
508 | $response[ $key_to_filter ]->$key = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) ); |
||
509 | View Code Duplication | } elseif ( is_array( $response[ $key_to_filter ] ) ) { |
|
510 | $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) ); |
||
511 | } |
||
512 | View Code Duplication | } elseif ( is_array( $values ) ) { |
|
513 | $response[ $key_to_filter ][ $key ] = array_intersect_key( $values, array_flip( $fields ) ); |
||
514 | } |
||
515 | } |
||
516 | |||
517 | $has_filtered = true; |
||
518 | } |
||
519 | } |
||
520 | |||
521 | if ( ! $has_filtered ) { |
||
522 | if ( is_object( $response ) ) { |
||
523 | $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) ); |
||
524 | } else if ( is_array( $response ) ) { |
||
525 | $response = array_intersect_key( $response, array_flip( $fields ) ); |
||
526 | } |
||
527 | } |
||
528 | |||
529 | return $response; |
||
530 | } |
||
531 | |||
532 | function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) { |
||
533 | if ( $original_scheme ) { |
||
534 | return $url; |
||
535 | } |
||
536 | |||
537 | return preg_replace( '#^https:#', 'http:', $url ); |
||
538 | } |
||
539 | |||
540 | function comment_edit_pre( $comment_content ) { |
||
541 | return htmlspecialchars_decode( $comment_content, ENT_QUOTES ); |
||
542 | } |
||
543 | |||
544 | function json_encode( $data ) { |
||
545 | return json_encode( $data ); |
||
546 | } |
||
547 | |||
548 | function ends_with( $haystack, $needle ) { |
||
549 | return $needle === substr( $haystack, -strlen( $needle ) ); |
||
550 | } |
||
551 | |||
552 | // Returns the site's blog_id in the WP.com ecosystem |
||
553 | function get_blog_id_for_output() { |
||
554 | return $this->token_details['blog_id']; |
||
555 | } |
||
556 | |||
557 | // Returns the site's local blog_id |
||
558 | function get_blog_id( $blog_id ) { |
||
559 | return $GLOBALS['blog_id']; |
||
560 | } |
||
561 | |||
562 | function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) { |
||
563 | if ( $this->is_restricted_blog( $blog_id ) ) { |
||
564 | return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 ); |
||
565 | } |
||
566 | |||
567 | if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) { |
||
568 | return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 ); |
||
569 | } |
||
570 | |||
571 | return $blog_id; |
||
572 | } |
||
573 | |||
574 | // Returns true if the specified blog ID is a restricted blog |
||
575 | function is_restricted_blog( $blog_id ) { |
||
576 | /** |
||
577 | * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs. |
||
578 | * |
||
579 | * @module json-api |
||
580 | * |
||
581 | * @since 3.4.0 |
||
582 | * |
||
583 | * @param array $array Array of Blog IDs. |
||
584 | */ |
||
585 | $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() ); |
||
586 | return true === in_array( $blog_id, $restricted_blog_ids ); |
||
587 | } |
||
588 | |||
589 | function post_like_count( $blog_id, $post_id ) { |
||
590 | return 0; |
||
591 | } |
||
592 | |||
593 | function is_liked( $blog_id, $post_id ) { |
||
594 | return false; |
||
595 | } |
||
596 | |||
597 | function is_reblogged( $blog_id, $post_id ) { |
||
598 | return false; |
||
599 | } |
||
600 | |||
601 | function is_following( $blog_id ) { |
||
602 | return false; |
||
603 | } |
||
604 | |||
605 | function add_global_ID( $blog_id, $post_id ) { |
||
606 | return ''; |
||
607 | } |
||
608 | |||
609 | function get_avatar_url( $email, $avatar_size = null ) { |
||
610 | if ( function_exists( 'wpcom_get_avatar_url' ) ) { |
||
611 | return null === $avatar_size |
||
612 | ? wpcom_get_avatar_url( $email ) |
||
613 | : wpcom_get_avatar_url( $email, $avatar_size ); |
||
614 | } else { |
||
615 | return null === $avatar_size |
||
616 | ? get_avatar_url( $email ) |
||
617 | : get_avatar_url( $email, $avatar_size ); |
||
618 | } |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * Counts the number of comments on a site, excluding certain comment types. |
||
623 | * |
||
624 | * @param $post_id int Post ID. |
||
625 | * @return array Array of counts, matching the output of https://developer.wordpress.org/reference/functions/get_comment_count/. |
||
626 | */ |
||
627 | public function wp_count_comments( $post_id ) { |
||
628 | global $wpdb; |
||
629 | if ( 0 !== $post_id ) { |
||
630 | return wp_count_comments( $post_id ); |
||
631 | } |
||
632 | |||
633 | $counts = array( |
||
634 | 'total_comments' => 0, |
||
635 | 'all' => 0, |
||
636 | ); |
||
637 | |||
638 | /** |
||
639 | * Exclude certain comment types from comment counts in the REST API. |
||
640 | * |
||
641 | * @since 6.9.0 |
||
642 | * @module json-api |
||
643 | * |
||
644 | * @param array Array of comment types to exclude (default: 'order_note', 'webhook_delivery', 'review', 'action_log') |
||
645 | */ |
||
646 | $exclude = apply_filters( 'jetpack_api_exclude_comment_types_count', |
||
647 | array( 'order_note', 'webhook_delivery', 'review', 'action_log' ) |
||
648 | ); |
||
649 | |||
650 | if ( empty( $exclude ) ) { |
||
651 | return wp_count_comments( $post_id ); |
||
652 | } |
||
653 | |||
654 | array_walk( $exclude, 'esc_sql' ); |
||
655 | $where = sprintf( |
||
656 | "WHERE comment_type NOT IN ( '%s' )", |
||
657 | implode( "','", $exclude ) |
||
658 | ); |
||
659 | |||
660 | $count = $wpdb->get_results( |
||
661 | "SELECT comment_approved, COUNT(*) AS num_comments |
||
662 | FROM $wpdb->comments |
||
663 | {$where} |
||
664 | GROUP BY comment_approved |
||
665 | " |
||
666 | ); |
||
667 | |||
668 | $approved = array( |
||
669 | '0' => 'moderated', |
||
670 | '1' => 'approved', |
||
671 | 'spam' => 'spam', |
||
672 | 'trash' => 'trash', |
||
673 | 'post-trashed' => 'post-trashed', |
||
674 | ); |
||
675 | |||
676 | // https://developer.wordpress.org/reference/functions/get_comment_count/#source |
||
677 | foreach ( $count as $row ) { |
||
678 | if ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash', 'spam' ), true ) ) { |
||
679 | $counts['all'] += $row->num_comments; |
||
680 | $counts['total_comments'] += $row->num_comments; |
||
681 | } elseif ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash' ), true ) ) { |
||
682 | $counts['total_comments'] += $row->num_comments; |
||
683 | } |
||
684 | if ( isset( $approved[ $row->comment_approved ] ) ) { |
||
685 | $counts[ $approved[ $row->comment_approved ] ] = $row->num_comments; |
||
686 | } |
||
687 | } |
||
688 | |||
689 | foreach ( $approved as $key ) { |
||
690 | if ( empty( $counts[ $key ] ) ) { |
||
691 | $counts[ $key ] = 0; |
||
692 | } |
||
693 | } |
||
694 | |||
695 | $counts = (object) $counts; |
||
696 | |||
697 | return $counts; |
||
698 | } |
||
699 | |||
700 | /** |
||
701 | * traps `wp_die()` calls and outputs a JSON response instead. |
||
702 | * The result is always output, never returned. |
||
703 | * |
||
704 | * @param string|null $error_code Call with string to start the trapping. Call with null to stop. |
||
705 | * @param int $http_status HTTP status code, 400 by default. |
||
706 | */ |
||
707 | function trap_wp_die( $error_code = null, $http_status = 400 ) { |
||
708 | if ( is_null( $error_code ) ) { |
||
709 | $this->trapped_error = null; |
||
710 | // Stop trapping |
||
711 | remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) ); |
||
712 | return; |
||
713 | } |
||
714 | |||
715 | // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die(). |
||
716 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { |
||
717 | if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) { |
||
718 | return; |
||
719 | } |
||
720 | } else { |
||
721 | if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) { |
||
722 | return; |
||
723 | } |
||
724 | } |
||
725 | |||
726 | $this->trapped_error = array( |
||
727 | 'status' => $http_status, |
||
728 | 'code' => $error_code, |
||
729 | 'message' => '', |
||
730 | ); |
||
731 | // Start trapping |
||
732 | add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) ); |
||
733 | } |
||
734 | |||
735 | function wp_die_handler_callback() { |
||
736 | return array( $this, 'wp_die_handler' ); |
||
737 | } |
||
738 | |||
739 | function wp_die_handler( $message, $title = '', $args = array() ) { |
||
740 | // Allow wp_die calls to override HTTP status code... |
||
741 | $args = wp_parse_args( $args, array( |
||
742 | 'response' => $this->trapped_error['status'], |
||
743 | ) ); |
||
744 | |||
745 | // ... unless it's 500 ( see http://wp.me/pMz3w-5VV ) |
||
746 | if ( (int) $args['response'] !== 500 ) { |
||
747 | $this->trapped_error['status'] = $args['response']; |
||
748 | } |
||
749 | |||
750 | if ( $title ) { |
||
751 | $message = "$title: $message"; |
||
752 | } |
||
753 | |||
754 | $this->trapped_error['message'] = wp_kses( $message, array() ); |
||
755 | |||
756 | switch ( $this->trapped_error['code'] ) { |
||
757 | case 'comment_failure' : |
||
758 | if ( did_action( 'comment_duplicate_trigger' ) ) { |
||
759 | $this->trapped_error['code'] = 'comment_duplicate'; |
||
760 | } else if ( did_action( 'comment_flood_trigger' ) ) { |
||
761 | $this->trapped_error['code'] = 'comment_flood'; |
||
762 | } |
||
763 | break; |
||
764 | } |
||
765 | |||
766 | // We still want to exit so that code execution stops where it should. |
||
767 | // Attach the JSON output to the WordPress shutdown handler |
||
768 | add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 ); |
||
769 | exit; |
||
770 | } |
||
771 | |||
772 | function output_trapped_error() { |
||
773 | $this->exit = false; // We're already exiting once. Don't do it twice. |
||
774 | $this->output( $this->trapped_error['status'], (object) array( |
||
775 | 'error' => $this->trapped_error['code'], |
||
776 | 'message' => $this->trapped_error['message'], |
||
777 | ) ); |
||
778 | } |
||
779 | |||
780 | function finish_request() { |
||
781 | if ( function_exists( 'fastcgi_finish_request' ) ) |
||
782 | return fastcgi_finish_request(); |
||
783 | } |
||
784 | } |
||
785 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: