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