PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.5.1
Jetpack – WP Security, Backup, Speed, & Growth v7.5.1
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
← All changes | class.json-api.php +231 -778 14.4.27.5.1 View file →
@@ -1,260 +1,77 @@
1 -<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 -/**
3 - * Jetpack JSON API.
4 - *
5 - * @package automattic/jetpack
6 - */
1 +<?php
7 2
8 -use Automattic\Jetpack\Status;
3 +defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
9 4
10 -if ( ! defined( 'WPCOM_JSON_API__DEBUG' ) ) {
11 - define( 'WPCOM_JSON_API__DEBUG', false );
12 -}
5 +require_once dirname( __FILE__ ) . '/sal/class.json-api-platform.php';
13 6
14 -require_once __DIR__ . '/sal/class.json-api-platform.php';
15 -
16 -/**
17 - * Jetpack JSON API.
18 - */
19 7 class WPCOM_JSON_API {
20 - /**
21 - * Static instance.
22 - *
23 - * @todo This should be private.
24 - * @var self|null
25 - */
26 - public static $self = null;
8 + static $self = null;
27 9
28 - /**
29 - * Registered endpoints.
30 - *
31 - * @var WPCOM_JSON_API_Endpoint[]
32 - */
33 10 public $endpoints = array();
34 11
35 - /**
36 - * Endpoint being processed.
37 - *
38 - * @var WPCOM_JSON_API_Endpoint
39 - */
40 - public $endpoint = null;
41 -
42 - /**
43 - * Token details.
44 - *
45 - * @var array
46 - */
47 12 public $token_details = array();
48 13
49 - /**
50 - * Request HTTP method.
51 - *
52 - * @var string
53 - */
54 14 public $method = '';
55 -
56 - /**
57 - * Request URL.
58 - *
59 - * @var string
60 - */
61 15 public $url = '';
62 -
63 - /**
64 - * Path part of the request URL.
65 - *
66 - * @var string
67 - */
68 16 public $path = '';
69 -
70 - /**
71 - * Version extracted from the request URL.
72 - *
73 - * @var string|null
74 - */
75 17 public $version = null;
76 -
77 - /**
78 - * Parsed query data.
79 - *
80 - * @var array
81 - */
82 18 public $query = array();
83 -
84 - /**
85 - * Post body, if the request is a POST.
86 - *
87 - * @var string|null
88 - */
89 19 public $post_body = null;
90 -
91 - /**
92 - * Copy of `$_FILES` if the request is a POST.
93 - *
94 - * @var null|array
95 - */
96 20 public $files = null;
97 -
98 - /**
99 - * Content type of the request.
100 - *
101 - * @var string|null
102 - */
103 21 public $content_type = null;
104 -
105 - /**
106 - * Value of `$_SERVER['HTTP_ACCEPT']`, if any
107 - *
108 - * @var string
109 - */
110 22 public $accept = '';
111 23
112 - /**
113 - * Value of `$_SERVER['HTTPS']`, or "--UNset--" if unset.
114 - *
115 - * @var string
116 - */
117 - public $_server_https; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
118 -
119 - /**
120 - * Whether to exit after serving a response.
121 - *
122 - * @var bool
123 - */
24 + public $_server_https;
124 25 public $exit = true;
125 -
126 - /**
127 - * Public API scheme.
128 - *
129 - * @var string
130 - */
131 26 public $public_api_scheme = 'https';
132 27
133 - /**
134 - * Output status code.
135 - *
136 - * @var int
137 - */
138 28 public $output_status_code = 200;
139 29
140 - /**
141 - * Trapped error.
142 - *
143 - * @var null|array
144 - */
145 30 public $trapped_error = null;
146 -
147 - /**
148 - * Whether output has been done.
149 - *
150 - * @var bool
151 - */
152 31 public $did_output = false;
153 32
154 - /**
155 - * Extra HTTP headers.
156 - *
157 - * @var string
158 - */
159 33 public $extra_headers = array();
160 34
161 35 /**
162 - * AMP source origin.
163 - *
164 - * @var string
165 - */
166 - public $amp_source_origin = null;
167 -
168 - /**
169 - * Initialize.
170 - *
171 - * @param string|null $method As for `$this->setup_inputs()`.
172 - * @param string|null $url As for `$this->setup_inputs()`.
173 - * @param string|null $post_body As for `$this->setup_inputs()`.
174 36 * @return WPCOM_JSON_API instance
175 37 */
176 - public static function init( $method = null, $url = null, $post_body = null ) {
177 - if ( ! self::$self ) {
178 - self::$self = new static( $method, $url, $post_body );
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 );
179 42 }
180 43 return self::$self;
181 44 }
182 45
183 - /**
184 - * Add an endpoint.
185 - *
186 - * @param WPCOM_JSON_API_Endpoint $endpoint Endpoint to add.
187 - */
188 - public function add( WPCOM_JSON_API_Endpoint $endpoint ) {
189 - // @todo Determine if anything depends on this being serialized rather than e.g. JSON.
190 - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Legacy, possibly depended on elsewhere.
191 - $path_versions = serialize(
192 - array(
193 - $endpoint->path,
194 - $endpoint->min_version,
195 - $endpoint->max_version,
196 - )
197 - );
198 - if ( ! isset( $this->endpoints[ $path_versions ] ) ) {
199 - $this->endpoints[ $path_versions ] = array();
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();
200 54 }
201 - $this->endpoints[ $path_versions ][ $endpoint->method ] = $endpoint;
55 + $this->endpoints[$path_versions][$endpoint->method] = $endpoint;
202 56 }
203 57
204 - /**
205 - * Determine if a string is truthy. If it's not a string, which can happen with
206 - * not well-formed data coming from Jetpack sites, we still consider it a truthy value.
207 - *
208 - * @param mixed $value true, 1, "1", "t", and "true" (case insensitive) are truthy, everything else isn't.
209 - * @return bool
210 - */
211 - public static function is_truthy( $value ) {
212 - if ( true === $value ) {
58 + static function is_truthy( $value ) {
59 + switch ( strtolower( (string) $value ) ) {
60 + case '1' :
61 + case 't' :
62 + case 'true' :
213 63 return true;
214 64 }
215 65
216 - if ( 1 === $value ) {
217 - return true;
218 - }
219 -
220 - if ( ! is_string( $value ) ) {
221 - return false;
222 - }
223 -
224 - switch ( strtolower( (string) $value ) ) {
225 - case '1':
226 - case 't':
227 - case 'true':
228 - return true;
229 - }
230 -
231 66 return false;
232 67 }
233 68
234 - /**
235 - * Determine if a string is falsey.
236 - *
237 - * @param mixed $value false, 0, "0", "f", and "false" (case insensitive) are falsey, everything else isn't.
238 - * @return bool
239 - */
240 - public static function is_falsy( $value ) {
241 - if ( false === $value ) {
242 - return true;
243 - }
244 -
245 - if ( 0 === $value ) {
246 - return true;
247 - }
248 -
249 - if ( ! is_string( $value ) ) {
250 - return false;
251 - }
252 -
69 + static function is_falsy( $value ) {
253 70 switch ( strtolower( (string) $value ) ) {
254 - case '0':
255 - case 'f':
256 - case 'false':
71 + case '0' :
72 + case 'f' :
73 + case 'false' :
257 74 return true;
258 75 }
259 76
260 77 return false;
@@ -259,154 +76,81 @@
259 76
260 77 return false;
261 78 }
262 79
263 - /**
264 - * Constructor.
265 - *
266 - * @todo This should be private.
267 - * @param string|null $method As for `$this->setup_inputs()`.
268 - * @param string|null $url As for `$this->setup_inputs()`.
269 - * @param string|null $post_body As for `$this->setup_inputs()`.
270 - */
271 - public function __construct( $method = null, $url = null, $post_body = null ) {
272 - $this->setup_inputs( $method, $url, $post_body );
80 + function __construct() {
81 + $args = func_get_args();
82 + call_user_func_array( array( $this, 'setup_inputs' ), $args );
273 83 }
274 84
275 - /**
276 - * Setup inputs.
277 - *
278 - * @param string|null $method Request HTTP method. Fetched from `$_SERVER` if null.
279 - * @param string|null $url URL requested. Determined from `$_SERVER` if null.
280 - * @param string|null $post_body POST body. Read from `php://input` if null and method is POST.
281 - */
282 - public function setup_inputs( $method = null, $url = null, $post_body = null ) {
283 - if ( $method === null ) {
284 - $this->method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( filter_var( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : '';
85 + function setup_inputs( $method = null, $url = null, $post_body = null ) {
86 + if ( is_null( $method ) ) {
87 + $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
285 88 } else {
286 89 $this->method = strtoupper( $method );
287 90 }
288 - if ( $url === null ) {
289 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sniff misses the esc_url_raw.
290 - $this->url = esc_url_raw( set_url_scheme( 'http://' . ( isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' ) ) );
91 + if ( is_null( $url ) ) {
92 + $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
291 93 } else {
292 94 $this->url = $url;
293 95 }
294 96
295 - $parsed = wp_parse_url( $this->url );
97 + $parsed = parse_url( $this->url );
296 98 if ( ! empty( $parsed['path'] ) ) {
297 99 $this->path = $parsed['path'];
298 100 }
299 101
300 - if ( ! empty( $parsed['query'] ) ) {
102 + if ( !empty( $parsed['query'] ) ) {
301 103 wp_parse_str( $parsed['query'], $this->query );
302 104 }
303 105
304 - if ( ! empty( $_SERVER['HTTP_ACCEPT'] ) ) {
305 - $this->accept = filter_var( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
106 + if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
107 + $this->accept = $_SERVER['HTTP_ACCEPT'];
306 108 }
307 109
308 110 if ( 'POST' === $this->method ) {
309 - if ( $post_body === null ) {
111 + if ( is_null( $post_body ) ) {
310 112 $this->post_body = file_get_contents( 'php://input' );
311 113
312 - if ( ! empty( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
313 - $this->content_type = filter_var( wp_unslash( $_SERVER['HTTP_CONTENT_TYPE'] ) );
314 - } elseif ( ! empty( $_SERVER['CONTENT_TYPE'] ) ) {
315 - $this->content_type = filter_var( wp_unslash( $_SERVER['CONTENT_TYPE'] ) );
316 - } elseif ( isset( $this->post_body[0] ) && '{' === $this->post_body[0] ) {
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] ) {
317 119 $this->content_type = 'application/json';
318 120 } else {
319 121 $this->content_type = 'application/x-www-form-urlencoded';
320 122 }
321 123
322 - if ( str_starts_with( strtolower( $this->content_type ), 'multipart/' ) ) {
323 - // phpcs:ignore WordPress.Security.NonceVerification.Missing
324 - $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
325 - $this->files = $_FILES;
124 + if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
125 + $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
126 + $this->files = $_FILES;
326 127 $this->content_type = 'multipart/form-data';
327 128 }
328 129 } else {
329 - $this->post_body = $post_body;
330 - $this->content_type = isset( $this->post_body[0] ) && '{' === $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
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';
331 132 }
332 133 } else {
333 - $this->post_body = null;
134 + $this->post_body = null;
334 135 $this->content_type = null;
335 136 }
336 137
337 - $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? filter_var( wp_unslash( $_SERVER['HTTPS'] ) ) : '--UNset--';
138 + $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
338 139 }
339 140
340 - /**
341 - * Initialize.
342 - *
343 - * @return null|WP_Error (although this implementation always returns null)
344 - */
345 - public function initialize() {
141 + function initialize() {
346 142 $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
347 - return null;
348 143 }
349 144
350 - /**
351 - * Checks if the current request is authorized with a blog token.
352 - * This method is overridden by a child class in WPCOM.
353 - *
354 - * @since 9.1.0
355 - *
356 - * @param boolean|int $site_id The site id.
357 - * @return boolean
358 - */
359 - public function is_jetpack_authorized_for_site( $site_id = false ) {
360 - if ( ! $this->token_details ) {
361 - return false;
362 - }
145 + function serve( $exit = true ) {
146 + ini_set( 'display_errors', false );
363 147
364 - $token_details = (object) $this->token_details;
365 -
366 - $site_in_token = (int) $token_details->blog_id;
367 -
368 - if ( $site_in_token < 1 ) {
369 - return false;
370 - }
371 -
372 - if ( $site_id && $site_in_token !== (int) $site_id ) {
373 - return false;
374 - }
375 -
376 - if ( (int) get_current_user_id() !== 0 ) {
377 - // If Jetpack blog token is used, no logged-in user should exist.
378 - return false;
379 - }
380 -
381 - return true;
382 - }
383 -
384 - /**
385 - * Checks if the current request is authorized with an upload token.
386 - * This method is overridden by a child class in WPCOM.
387 - *
388 - * @since 13.5
389 - * @return boolean
390 - */
391 - public function is_authorized_with_upload_token() {
392 - return false;
393 - }
394 -
395 - /**
396 - * Serve.
397 - *
398 - * @param bool $exit Whether to exit.
399 - * @return string|null Content type (assuming it didn't exit), or null in certain error cases.
400 - */
401 - public function serve( $exit = true ) {
402 - ini_set( 'display_errors', false ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Blacklisted
403 -
404 148 $this->exit = (bool) $exit;
405 149
406 150 // This was causing problems with Jetpack, but is necessary for wpcom
407 151 // @see https://github.com/Automattic/jetpack/pull/2603
408 - // @see r124548-wpcom .
152 + // @see r124548-wpcom
409 153 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
410 154 add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
411 155 }
412 156
@@ -414,9 +158,9 @@
414 158
415 159 add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
416 160
417 161 $initialization = $this->initialize();
418 - if ( 'OPTIONS' === $this->method ) {
162 + if ( 'OPTIONS' == $this->method ) {
419 163 /**
420 164 * Fires before the page output.
421 165 * Can be used to specify custom header options.
422 166 *
@@ -432,33 +176,32 @@
432 176 $this->output_error( $initialization );
433 177 return;
434 178 }
435 179
436 - // Normalize path and extract API version.
180 + // Normalize path and extract API version
437 181 $this->path = untrailingslashit( $this->path );
438 - if ( preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches ) ) {
439 - $this->path = substr( $this->path, strlen( $matches[0] ) );
440 - $this->version = $matches[1];
441 - }
182 + preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
183 + $this->path = substr( $this->path, strlen( $matches[0] ) );
184 + $this->version = $matches[1];
442 185
443 186 $allowed_methods = array( 'GET', 'POST' );
444 - $four_oh_five = false;
187 + $four_oh_five = false;
445 188
446 - $is_help = preg_match( '#/help/?$#i', $this->path );
189 + $is_help = preg_match( '#/help/?$#i', $this->path );
447 190 $matching_endpoints = array();
448 191
449 192 if ( $is_help ) {
450 193 $origin = get_http_origin();
451 194
452 - if ( ! empty( $origin ) && 'GET' === $this->method ) {
195 + if ( !empty( $origin ) && 'GET' == $this->method ) {
453 196 header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
454 197 }
455 198
456 199 $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
457 - // Show help for all matching endpoints regardless of method.
458 - $methods = $allowed_methods;
200 + // Show help for all matching endpoints regardless of method
201 + $methods = $allowed_methods;
459 202 $find_all_matching_endpoints = true;
460 - // How deep to truncate each endpoint's path to see if it matches this help request.
203 + // How deep to truncate each endpoint's path to see if it matches this help request
461 204 $depth = substr_count( $this->path, '/' ) + 1;
462 205 if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
463 206 $help_content_type = 'json';
464 207 } else {
@@ -463,50 +206,50 @@
463 206 $help_content_type = 'json';
464 207 } else {
465 208 $help_content_type = 'html';
466 209 }
467 - } elseif ( in_array( $this->method, $allowed_methods, true ) ) {
468 - // Only serve requested method.
469 - $methods = array( $this->method );
470 - $find_all_matching_endpoints = false;
471 210 } else {
472 - // We don't allow this requested method - find matching endpoints and send 405.
473 - $methods = $allowed_methods;
474 - $find_all_matching_endpoints = true;
475 - $four_oh_five = true;
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 + }
476 221 }
477 222
478 - // Find which endpoint to serve.
223 + // Find which endpoint to serve
479 224 $found = false;
480 225 foreach ( $this->endpoints as $endpoint_path_versions => $endpoints_by_method ) {
481 - // @todo Determine if anything depends on this being serialized rather than e.g. JSON.
482 - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Legacy, possibly depended on elsewhere.
483 226 $endpoint_path_versions = unserialize( $endpoint_path_versions );
484 - $endpoint_path = $endpoint_path_versions[0];
485 - $endpoint_min_version = $endpoint_path_versions[1];
486 - $endpoint_max_version = $endpoint_path_versions[2];
227 + $endpoint_path = $endpoint_path_versions[0];
228 + $endpoint_min_version = $endpoint_path_versions[1];
229 + $endpoint_max_version = $endpoint_path_versions[2];
487 230
488 - // Make sure max_version is not less than min_version.
231 + // Make sure max_version is not less than min_version
489 232 if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
490 233 $endpoint_max_version = $endpoint_min_version;
491 234 }
492 235
493 236 foreach ( $methods as $method ) {
494 - if ( ! isset( $endpoints_by_method[ $method ] ) ) {
237 + if ( !isset( $endpoints_by_method[$method] ) ) {
495 238 continue;
496 239 }
497 240
498 - // Normalize.
241 + // Normalize
499 242 $endpoint_path = untrailingslashit( $endpoint_path );
500 243 if ( $is_help ) {
501 - // Truncate path at help depth.
502 - $endpoint_path = implode( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
244 + // Truncate path at help depth
245 + $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
503 246 }
504 247
505 - // Generate regular expression from sprintf().
248 + // Generate regular expression from sprintf()
506 249 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
507 250
508 - if ( ! preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
251 + if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
509 252 // This endpoint does not match the requested path.
510 253 continue;
511 254 }
512 255
@@ -517,18 +260,18 @@
517 260
518 261 $found = true;
519 262
520 263 if ( $find_all_matching_endpoints ) {
521 - $matching_endpoints[] = array( $endpoints_by_method[ $method ], $path_pieces );
264 + $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
522 265 } else {
523 - // The method parameters are now in $path_pieces.
524 - $endpoint = $endpoints_by_method[ $method ];
266 + // The method parameters are now in $path_pieces
267 + $endpoint = $endpoints_by_method[$method];
525 268 break 2;
526 269 }
527 270 }
528 271 }
529 272
530 - if ( ! $found ) {
273 + if ( !$found ) {
531 274 return $this->output( 404, '', 'text/plain' );
532 275 }
533 276
534 277 if ( $four_oh_five ) {
@@ -536,16 +279,10 @@
536 279 foreach ( $matching_endpoints as $matching_endpoint ) {
537 280 $allowed_methods[] = $matching_endpoint[0]->method;
538 281 }
539 282
540 - header( 'Allow: ' . strtoupper( implode( ',', array_unique( $allowed_methods ) ) ) );
541 - return $this->output(
542 - 405,
543 - array(
544 - 'error' => 'not_allowed',
545 - 'error_message' => 'Method not allowed',
546 - )
547 - );
283 + header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
284 + return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
548 285 }
549 286
550 287 if ( $is_help ) {
551 288 /**
@@ -559,25 +296,23 @@
559 296 $proxied = function_exists( 'wpcom_is_proxied_request' ) ? wpcom_is_proxied_request() : false;
560 297 if ( 'json' === $help_content_type ) {
561 298 $docs = array();
562 299 foreach ( $matching_endpoints as $matching_endpoint ) {
563 - if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) {
300 + if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
564 301 $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
565 - }
566 302 }
567 303 return $this->output( 200, $docs );
568 304 } else {
569 305 status_header( 200 );
570 306 foreach ( $matching_endpoints as $matching_endpoint ) {
571 - if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG ) {
307 + if ( $matching_endpoint[0]->is_publicly_documentable() || $proxied || WPCOM_JSON_API__DEBUG )
572 308 call_user_func( array( $matching_endpoint[0], 'document' ) );
573 - }
574 309 }
575 310 }
576 - exit( 0 );
311 + exit;
577 312 }
578 313
579 - if ( $endpoint->in_testing && ! WPCOM_JSON_API__DEBUG ) {
314 + if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
580 315 return $this->output( 404, '', 'text/plain' );
581 316 }
582 317
583 318 /** This action is documented in class.json-api.php */
@@ -584,9 +319,9 @@
584 319 do_action( 'wpcom_json_api_output', $endpoint->stat );
585 320
586 321 $response = $this->process_request( $endpoint, $path_pieces );
587 322
588 - if ( ! $response && ! is_array( $response ) ) {
323 + if ( !$response && !is_array( $response ) ) {
589 324 return $this->output( 500, '', 'text/plain' );
590 325 } elseif ( is_wp_error( $response ) ) {
591 326 return $this->output_error( $response );
592 327 }
@@ -596,35 +331,20 @@
596 331
597 332 return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers );
598 333 }
599 334
600 - /**
601 - * Process a request.
602 - *
603 - * @param WPCOM_JSON_API_Endpoint $endpoint Endpoint.
604 - * @param array $path_pieces Path pieces.
605 - * @return array|WP_Error Return value from the endpoint's callback.
606 - */
607 - public function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
335 + function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
608 336 $this->endpoint = $endpoint;
609 337 return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
610 338 }
611 339
612 - /**
613 - * Output a response or error without exiting.
614 - *
615 - * @param int $status_code HTTP status code.
616 - * @param mixed $response Response data.
617 - * @param string $content_type Content type of the response.
618 - */
619 - public function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
620 - $exit = $this->exit;
340 + function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
341 + $exit = $this->exit;
621 342 $this->exit = false;
622 - if ( is_wp_error( $response ) ) {
343 + if ( is_wp_error( $response ) )
623 344 $this->output_error( $response );
624 - } else {
345 + else
625 346 $this->output( $status_code, $response, $content_type );
626 - }
627 347 $this->exit = $exit;
628 348 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
629 349 $this->finish_request();
630 350 }
@@ -629,63 +349,38 @@
629 349 $this->finish_request();
630 350 }
631 351 }
632 352
633 - /**
634 - * Set output status code.
635 - *
636 - * @param int $code HTTP status code.
637 - */
638 - public function set_output_status_code( $code = 200 ) {
353 + function set_output_status_code( $code = 200 ) {
639 354 $this->output_status_code = $code;
640 355 }
641 356
642 - /**
643 - * Output a response.
644 - *
645 - * @param int $status_code HTTP status code.
646 - * @param mixed $response Response data.
647 - * @param string $content_type Content type of the response.
648 - * @param array $extra Additional HTTP headers.
649 - * @return string Content type (assuming it didn't exit).
650 - */
651 - public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
652 - $status_code = (int) $status_code;
653 -
654 - // In case output() was called before the callback returned.
357 + function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
358 + // In case output() was called before the callback returned
655 359 if ( $this->did_output ) {
656 - if ( $this->exit ) {
657 - exit( 0 );
658 - }
360 + if ( $this->exit )
361 + exit;
659 362 return $content_type;
660 363 }
661 364 $this->did_output = true;
662 365
663 366 // 400s and 404s are allowed for all origins
664 - if ( 404 === $status_code || 400 === $status_code ) {
367 + if ( 404 == $status_code || 400 == $status_code )
665 368 header( 'Access-Control-Allow-Origin: *' );
666 - }
667 369
668 - /* Add headers for form submission from <amp-form/> */
669 - if ( $this->amp_source_origin ) {
670 - header( 'Access-Control-Allow-Origin: ' . wp_unslash( $this->amp_source_origin ) );
671 - header( 'Access-Control-Allow-Credentials: true' );
370 + if ( is_null( $response ) ) {
371 + $response = new stdClass;
672 372 }
673 373
674 - if ( $response === null ) {
675 - $response = new stdClass();
676 - }
677 -
678 - if ( 'text/plain' === $content_type ||
679 - 'text/html' === $content_type ) {
374 + if ( 'text/plain' === $content_type ) {
680 375 status_header( (int) $status_code );
681 - header( 'Content-Type: ' . $content_type );
682 - foreach ( $extra as $key => $value ) {
376 + header( 'Content-Type: text/plain' );
377 + foreach( $extra as $key => $value ) {
683 378 header( "$key: $value" );
684 379 }
685 - echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
380 + echo $response;
686 381 if ( $this->exit ) {
687 - exit( 0 );
382 + exit;
688 383 }
689 384
690 385 return $content_type;
691 386 }
@@ -692,11 +387,25 @@
692 387
693 388 $response = $this->filter_fields( $response );
694 389
695 390 if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
696 - $response = static::wrap_http_envelope( $status_code, $response, $content_type, $extra );
391 + $headers = array(
392 + array(
393 + 'name' => 'Content-Type',
394 + 'value' => $content_type,
395 + )
396 + );
697 397
698 - $status_code = 200;
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;
699 408 $content_type = 'application/json';
700 409 }
701 410
702 411 status_header( (int) $status_code );
@@ -709,73 +418,33 @@
709 418
710 419 if ( $callback ) {
711 420 // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
712 421 // and by prepending the JSONP response with a JS comment.
713 - // [1] <https://blog.miki.it/2014/7/8/abusing-jsonp-with-rosetta-flash/index.html>.
714 - echo "/**/$callback("; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSONP output, not HTML.
422 + // [1] http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
423 + echo "/**/$callback(";
715 424
716 425 }
717 - echo $this->json_encode( $response ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML.
426 + echo $this->json_encode( $response );
718 427 if ( $callback ) {
719 - echo ');';
428 + echo ");";
720 429 }
721 430
722 431 if ( $this->exit ) {
723 - exit( 0 );
432 + exit;
724 433 }
725 434
726 435 return $content_type;
727 436 }
728 437
729 - /**
730 - * Wrap JSON API response into an HTTP 200 one.
731 - *
732 - * @param int $status_code HTTP status code.
733 - * @param mixed $response Response body.
734 - * @param string $content_type Content type.
735 - * @param array|null $extra Extra data.
736 - *
737 - * @return array
738 - */
739 - public static function wrap_http_envelope( $status_code, $response, $content_type, $extra = null ) {
740 - $headers = array(
741 - array(
742 - 'name' => 'Content-Type',
743 - 'value' => $content_type,
744 - ),
745 - );
438 + public static function serializable_error ( $error ) {
746 439
747 - if ( is_array( $extra ) ) {
748 - foreach ( $extra as $key => $value ) {
749 - $headers[] = array(
750 - 'name' => $key,
751 - 'value' => $value,
752 - );
753 - }
754 - }
755 -
756 - return array(
757 - 'code' => (int) $status_code,
758 - 'headers' => $headers,
759 - 'body' => $response,
760 - );
761 - }
762 -
763 - /**
764 - * Serialize an error.
765 - *
766 - * @param WP_Error $error Error.
767 - * @return array with 'status_code' and 'errors' data.
768 - */
769 - public static function serializable_error( $error ) {
770 -
771 440 $status_code = $error->get_error_data();
772 441
773 - if ( is_array( $status_code ) && isset( $status_code['status_code'] ) ) {
442 + if ( is_array( $status_code ) ) {
774 443 $status_code = $status_code['status_code'];
775 444 }
776 445
777 - if ( ! $status_code ) {
446 + if ( !$status_code ) {
778 447 $status_code = 400;
779 448 }
780 449 $response = array(
781 450 'error' => $error->get_error_code(),
@@ -781,41 +450,27 @@
781 450 'error' => $error->get_error_code(),
782 451 'message' => $error->get_error_message(),
783 452 );
784 453
785 - $additional_data = $error->get_error_data( 'additional_data' );
786 - if ( $additional_data ) {
454 + if ( $additional_data = $error->get_error_data( 'additional_data' ) ) {
787 455 $response['data'] = $additional_data;
788 456 }
789 457
790 458 return array(
791 459 'status_code' => $status_code,
792 - 'errors' => $response,
460 + 'errors' => $response
793 461 );
794 462 }
795 463
796 - /**
797 - * Output an error.
798 - *
799 - * @param WP_Error $error Error.
800 - * @return string Content type (assuming it didn't exit).
801 - */
802 - public function output_error( $error ) {
803 - $error_response = static::serializable_error( $error );
464 + function output_error( $error ) {
465 + $error_response = $this->serializable_error( $error );
804 466
805 - return $this->output( $error_response['status_code'], $error_response['errors'] );
467 + return $this->output( $error_response[ 'status_code'], $error_response['errors'] );
806 468 }
807 469
808 - /**
809 - * Filter fields in a response.
810 - *
811 - * @param array|object $response Response.
812 - * @return array|object Filtered response.
813 - */
814 - public function filter_fields( $response ) {
815 - if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) ) {
470 + function filter_fields( $response ) {
471 + if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) )
816 472 return $response;
817 - }
818 473
819 474 $fields = array_map( 'trim', explode( ',', $this->query['fields'] ) );
820 475
821 476 if ( is_object( $response ) ) {
@@ -843,16 +498,14 @@
843 498 'users',
844 499 );
845 500
846 501 foreach ( $keys_to_filter as $key_to_filter ) {
847 - if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered ) {
502 + if ( ! isset( $response[ $key_to_filter ] ) || $has_filtered )
848 503 continue;
849 - }
850 504
851 505 foreach ( $response[ $key_to_filter ] as $key => $values ) {
852 506 if ( is_object( $values ) ) {
853 507 if ( is_object( $response[ $key_to_filter ] ) ) {
854 - // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found -- False positive.
855 508 $response[ $key_to_filter ]->$key = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
856 509 } elseif ( is_array( $response[ $key_to_filter ] ) ) {
857 510 $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
858 511 }
@@ -867,9 +520,9 @@
867 520
868 521 if ( ! $has_filtered ) {
869 522 if ( is_object( $response ) ) {
870 523 $response = (object) array_intersect_key( (array) $response, array_flip( $fields ) );
871 - } elseif ( is_array( $response ) ) {
524 + } else if ( is_array( $response ) ) {
872 525 $response = array_intersect_key( $response, array_flip( $fields ) );
873 526 }
874 527 }
875 528
@@ -875,19 +528,9 @@
875 528
876 529 return $response;
877 530 }
878 531
879 - /**
880 - * Filter for `home_url`.
881 - *
882 - * If `$original_scheme` is null, turns an https URL to http.
883 - *
884 - * @param string $url The complete home URL including scheme and path.
885 - * @param string $path Path relative to the home URL. Blank string if no path is specified.
886 - * @param string|null $original_scheme Scheme to give the home URL context. Accepts 'http', 'https', 'relative', 'rest', or null.
887 - * @return string URL.
888 - */
889 - public function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
532 + function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
890 533 if ( $original_scheme ) {
891 534 return $url;
892 535 }
893 536
@@ -893,78 +536,36 @@
893 536
894 537 return preg_replace( '#^https:#', 'http:', $url );
895 538 }
896 539
897 - /**
898 - * Decode HTML special characters in comment content.
899 - *
900 - * @param string $comment_content Comment content.
901 - * @return string
902 - */
903 - public function comment_edit_pre( $comment_content ) {
540 + function comment_edit_pre( $comment_content ) {
904 541 return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
905 542 }
906 543
907 - /**
908 - * JSON encode.
909 - *
910 - * @param mixed $data Data.
911 - * @return string|false
912 - */
913 - public function json_encode( $data ) {
914 - return wp_json_encode( $data );
544 + function json_encode( $data ) {
545 + return json_encode( $data );
915 546 }
916 547
917 - /**
918 - * Test if a string ends with a string.
919 - *
920 - * @param string $haystack String to check.
921 - * @param string $needle Suffix to check.
922 - * @return bool
923 - */
924 - public function ends_with( $haystack, $needle ) {
925 - return substr( $haystack, -strlen( $needle ) ) === $needle;
548 + function ends_with( $haystack, $needle ) {
549 + return $needle === substr( $haystack, -strlen( $needle ) );
926 550 }
927 551
928 - /**
929 - * Returns the site's blog_id in the WP.com ecosystem
930 - *
931 - * @return int
932 - */
933 - public function get_blog_id_for_output() {
552 + // Returns the site's blog_id in the WP.com ecosystem
553 + function get_blog_id_for_output() {
934 554 return $this->token_details['blog_id'];
935 555 }
936 556
937 - /**
938 - * Returns the site's local blog_id.
939 - *
940 - * @param int $blog_id Blog ID.
941 - * @return int
942 - */
943 - public function get_blog_id( $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
557 + // Returns the site's local blog_id
558 + function get_blog_id( $blog_id ) {
944 559 return $GLOBALS['blog_id'];
945 560 }
946 561
947 - /**
948 - * Switch to blog and validate user.
949 - *
950 - * @param int $blog_id Blog ID.
951 - * @param bool $verify_token_for_blog Whether to verify the token.
952 - * @return int Blog ID.
953 - */
954 - public function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
562 + function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
955 563 if ( $this->is_restricted_blog( $blog_id ) ) {
956 564 return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
957 565 }
958 - /**
959 - * If this is a private site we check for 2 things:
960 - * 1. In case of user based authentication, we need to check if the logged-in user has the 'read' capability.
961 - * 2. In case of site based authentication, make sure the endpoint accepts it.
962 - */
963 - if ( ( new Status() )->is_private_site() &&
964 - ! current_user_can( 'read' ) &&
965 - ! $this->endpoint->accepts_site_based_authentication()
966 - ) {
566 +
567 + if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
967 568 return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
968 569 }
969 570
970 571 return $blog_id;
@@ -969,15 +570,10 @@
969 570
970 571 return $blog_id;
971 572 }
972 573
973 - /**
974 - * Returns true if the specified blog ID is a restricted blog
975 - *
976 - * @param int $blog_id Blog ID.
977 - * @return bool
978 - */
979 - public function is_restricted_blog( $blog_id ) {
574 + // Returns true if the specified blog ID is a restricted blog
575 + function is_restricted_blog( $blog_id ) {
980 576 /**
981 577 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
982 578 *
983 579 * @module json-api
@@ -986,99 +582,47 @@
986 582 *
987 583 * @param array $array Array of Blog IDs.
988 584 */
989 585 $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
990 - return true === in_array( $blog_id, $restricted_blog_ids ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- I don't trust filters to return the right types.
586 + return true === in_array( $blog_id, $restricted_blog_ids );
991 587 }
992 588
993 - /**
994 - * Post like count.
995 - *
996 - * @param int $blog_id Blog ID.
997 - * @param int $post_id Post ID.
998 - * @return int
999 - */
1000 - public function post_like_count( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
589 + function post_like_count( $blog_id, $post_id ) {
1001 590 return 0;
1002 591 }
1003 592
1004 - /**
1005 - * Is liked?
1006 - *
1007 - * @param int $blog_id Blog ID.
1008 - * @param int $post_id Post ID.
1009 - * @return bool
1010 - */
1011 - public function is_liked( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
593 + function is_liked( $blog_id, $post_id ) {
1012 594 return false;
1013 595 }
1014 596
1015 - /**
1016 - * Is reblogged?
1017 - *
1018 - * @param int $blog_id Blog ID.
1019 - * @param int $post_id Post ID.
1020 - * @return bool
1021 - */
1022 - public function is_reblogged( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
597 + function is_reblogged( $blog_id, $post_id ) {
1023 598 return false;
1024 599 }
1025 600
1026 - /**
1027 - * Is following?
1028 - *
1029 - * @param int $blog_id Blog ID.
1030 - * @return bool
1031 - */
1032 - public function is_following( $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
601 + function is_following( $blog_id ) {
1033 602 return false;
1034 603 }
1035 604
1036 - /**
1037 - * Add global ID.
1038 - *
1039 - * @param int $blog_id Blog ID.
1040 - * @param int $post_id Post ID.
1041 - * @return string
1042 - */
1043 - public function add_global_ID( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
605 + function add_global_ID( $blog_id, $post_id ) {
1044 606 return '';
1045 607 }
1046 608
1047 - /**
1048 - * Return a count of comment likes.
1049 - * This method is overridden by a child class in WPCOM.
1050 - *
1051 - * @since 13.5
1052 - * @return int
1053 - */
1054 - public function comment_like_count() {
1055 - func_get_args(); // @phan-suppress-current-line PhanPluginUseReturnValueInternalKnown -- This is just here so Phan realizes the wpcom version does this.
1056 - return 0;
1057 - }
1058 -
1059 - /**
1060 - * Get avatar URL.
1061 - *
1062 - * @param string $email Email.
1063 - * @param array $args Args for `get_avatar_url()`.
1064 - * @return string|false
1065 - */
1066 - public function get_avatar_url( $email, $args = null ) {
609 + function get_avatar_url( $email, $avatar_size = null ) {
1067 610 if ( function_exists( 'wpcom_get_avatar_url' ) ) {
1068 - $ret = wpcom_get_avatar_url( $email, $args['size'] ?? 96, $args['default'] ?? '', false, $args['force_default'] ?? false );
1069 - return $ret ? $ret[0] : false;
611 + return null === $avatar_size
612 + ? wpcom_get_avatar_url( $email )
613 + : wpcom_get_avatar_url( $email, $avatar_size );
1070 614 } else {
1071 - return null === $args
615 + return null === $avatar_size
1072 616 ? get_avatar_url( $email )
1073 - : get_avatar_url( $email, $args );
617 + : get_avatar_url( $email, $avatar_size );
1074 618 }
1075 619 }
1076 620
1077 621 /**
1078 - * Counts the number of comments on a site, including certain comment types.
622 + * Counts the number of comments on a site, excluding certain comment types.
1079 623 *
1080 - * @param int $post_id Post ID.
624 + * @param $post_id int Post ID.
1081 625 * @return array Array of counts, matching the output of https://developer.wordpress.org/reference/functions/get_comment_count/.
1082 626 */
1083 627 public function wp_count_comments( $post_id ) {
1084 628 global $wpdb;
@@ -1091,65 +635,37 @@
1091 635 'all' => 0,
1092 636 );
1093 637
1094 638 /**
1095 - * Exclude certain comment types from comment counts in the REST API.
1096 - *
1097 - * @since 6.9.0
1098 - * @deprecated 11.1
1099 - * @module json-api
1100 - *
1101 - * @param array Array of comment types to exclude (default: 'order_note', 'webhook_delivery', 'review', 'action_log')
1102 - */
1103 - $exclude = apply_filters_deprecated( 'jetpack_api_exclude_comment_types_count', array( 'order_note', 'webhook_delivery', 'review', 'action_log' ), 'jetpack-11.1', 'jetpack_api_include_comment_types_count' ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1104 -
1105 - /**
1106 - * Include certain comment types in comment counts in the REST API.
1107 - * Note: the default array of comment types includes an empty string,
1108 - * to support comments posted before WP 5.5, that used an empty string as comment type.
1109 - *
1110 - * @since 11.1
1111 - * @module json-api
1112 - *
1113 - * @param array Array of comment types to include (default: 'comment', 'pingback', 'trackback')
1114 - */
1115 - $include = apply_filters(
1116 - 'jetpack_api_include_comment_types_count',
1117 - array( 'comment', 'pingback', 'trackback', '' )
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' )
1118 648 );
1119 649
1120 - if ( empty( $include ) ) {
650 + if ( empty( $exclude ) ) {
1121 651 return wp_count_comments( $post_id );
1122 652 }
1123 653
1124 - // The following caching mechanism is based on what the get_comments() function uses.
654 + array_walk( $exclude, 'esc_sql' );
655 + $where = sprintf(
656 + "WHERE comment_type NOT IN ( '%s' )",
657 + implode( "','", $exclude )
658 + );
1125 659
1126 - $key = md5( serialize( $include ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1127 - $last_changed = wp_cache_get_last_changed( 'comment' );
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 + );
1128 667
1129 - $cache_key = "wp_count_comments:$key:$last_changed";
1130 - $count = wp_cache_get( $cache_key, 'jetpack-json-api' );
1131 -
1132 - if ( false === $count ) {
1133 - array_walk( $include, 'esc_sql' );
1134 - $where = sprintf(
1135 - "WHERE comment_type IN ( '%s' )",
1136 - implode( "','", $include )
1137 - );
1138 -
1139 - // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- `$where` is built with escaping just above.
1140 - $count = $wpdb->get_results(
1141 - "SELECT comment_approved, COUNT(*) AS num_comments
1142 - FROM $wpdb->comments
1143 - {$where}
1144 - GROUP BY comment_approved
1145 - "
1146 - );
1147 - // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1148 -
1149 - wp_cache_add( $cache_key, $count, 'jetpack-json-api' );
1150 - }
1151 -
1152 668 $approved = array(
1153 669 '0' => 'moderated',
1154 670 '1' => 'approved',
1155 671 'spam' => 'spam',
@@ -1156,9 +672,9 @@
1156 672 'trash' => 'trash',
1157 673 'post-trashed' => 'post-trashed',
1158 674 );
1159 675
1160 - // <https://developer.wordpress.org/reference/functions/get_comment_count/#source>
676 + // https://developer.wordpress.org/reference/functions/get_comment_count/#source
1161 677 foreach ( $count as $row ) {
1162 678 if ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash', 'spam' ), true ) ) {
1163 679 $counts['all'] += $row->num_comments;
1164 680 $counts['total_comments'] += $row->num_comments;
@@ -1181,30 +697,19 @@
1181 697 return $counts;
1182 698 }
1183 699
1184 700 /**
1185 - * Traps `wp_die()` calls and outputs a JSON response instead.
701 + * traps `wp_die()` calls and outputs a JSON response instead.
1186 702 * The result is always output, never returned.
1187 703 *
1188 704 * @param string|null $error_code Call with string to start the trapping. Call with null to stop.
1189 705 * @param int $http_status HTTP status code, 400 by default.
1190 706 */
1191 - public function trap_wp_die( $error_code = null, $http_status = 400 ) {
1192 - // Determine the filter name; based on the conditionals inside the wp_die function.
1193 - if ( wp_is_json_request() ) {
1194 - $die_handler = 'wp_die_json_handler';
1195 - } elseif ( wp_is_jsonp_request() ) {
1196 - $die_handler = 'wp_die_jsonp_handler';
1197 - } elseif ( wp_is_xml_request() ) {
1198 - $die_handler = 'wp_die_xml_handler';
1199 - } else {
1200 - $die_handler = 'wp_die_handler';
1201 - }
1202 -
1203 - if ( $error_code === null ) {
707 + function trap_wp_die( $error_code = null, $http_status = 400 ) {
708 + if ( is_null( $error_code ) ) {
1204 709 $this->trapped_error = null;
1205 - // Stop trapping.
1206 - remove_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
710 + // Stop trapping
711 + remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
1207 712 return;
1208 713 }
1209 714
1210 715 // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
@@ -1211,10 +716,12 @@
1211 716 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1212 717 if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
1213 718 return;
1214 719 }
1215 - } elseif ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
1216 - return;
720 + } else {
721 + if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
722 + return;
723 + }
1217 724 }
1218 725
1219 726 $this->trapped_error = array(
1220 727 'status' => $http_status,
@@ -1220,40 +727,24 @@
1220 727 'status' => $http_status,
1221 728 'code' => $error_code,
1222 729 'message' => '',
1223 730 );
1224 - // Start trapping.
1225 - add_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
731 + // Start trapping
732 + add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
1226 733 }
1227 734
1228 - /**
1229 - * Filter function for `wp_die_handler` and similar filters.
1230 - *
1231 - * @return callable
1232 - */
1233 - public function wp_die_handler_callback() {
735 + function wp_die_handler_callback() {
1234 736 return array( $this, 'wp_die_handler' );
1235 737 }
1236 738
1237 - /**
1238 - * Handler for `wp_die` calls.
1239 - *
1240 - * @param string|WP_Error $message As for `wp_die()`.
1241 - * @param string|int $title As for `wp_die()`.
1242 - * @param string|array|int $args As for `wp_die()`.
1243 - * @return never
1244 - */
1245 - public function wp_die_handler( $message, $title = '', $args = array() ) {
739 + function wp_die_handler( $message, $title = '', $args = array() ) {
1246 740 // Allow wp_die calls to override HTTP status code...
1247 - $args = wp_parse_args(
1248 - $args,
1249 - array(
1250 - 'response' => $this->trapped_error['status'],
1251 - )
1252 - );
741 + $args = wp_parse_args( $args, array(
742 + 'response' => $this->trapped_error['status'],
743 + ) );
1253 744
1254 - // ... unless it's 500
1255 - if ( 500 !== (int) $args['response'] ) {
745 + // ... unless it's 500 ( see http://wp.me/pMz3w-5VV )
746 + if ( (int) $args['response'] !== 500 ) {
1256 747 $this->trapped_error['status'] = $args['response'];
1257 748 }
1258 749
1259 750 if ( $title ) {
@@ -1262,12 +753,12 @@
1262 753
1263 754 $this->trapped_error['message'] = wp_kses( $message, array() );
1264 755
1265 756 switch ( $this->trapped_error['code'] ) {
1266 - case 'comment_failure':
757 + case 'comment_failure' :
1267 758 if ( did_action( 'comment_duplicate_trigger' ) ) {
1268 759 $this->trapped_error['code'] = 'comment_duplicate';
1269 - } elseif ( did_action( 'comment_flood_trigger' ) ) {
760 + } else if ( did_action( 'comment_flood_trigger' ) ) {
1270 761 $this->trapped_error['code'] = 'comment_flood';
1271 762 }
1272 763 break;
1273 764 }
@@ -1272,60 +763,22 @@
1272 763 break;
1273 764 }
1274 765
1275 766 // We still want to exit so that code execution stops where it should.
1276 - // Attach the JSON output to the WordPress shutdown handler.
767 + // Attach the JSON output to the WordPress shutdown handler
1277 768 add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
1278 - exit( 0 );
769 + exit;
1279 770 }
1280 771
1281 - /**
1282 - * Output the trapped error.
1283 - */
1284 - public function output_trapped_error() {
772 + function output_trapped_error() {
1285 773 $this->exit = false; // We're already exiting once. Don't do it twice.
1286 - $this->output(
1287 - $this->trapped_error['status'],
1288 - (object) array(
1289 - 'error' => $this->trapped_error['code'],
1290 - 'message' => $this->trapped_error['message'],
1291 - )
1292 - );
774 + $this->output( $this->trapped_error['status'], (object) array(
775 + 'error' => $this->trapped_error['code'],
776 + 'message' => $this->trapped_error['message'],
777 + ) );
1293 778 }
1294 779
1295 - /**
1296 - * Finish the request.
1297 - */
1298 - public function finish_request() {
1299 - if ( function_exists( 'fastcgi_finish_request' ) ) {
780 + function finish_request() {
781 + if ( function_exists( 'fastcgi_finish_request' ) )
1300 782 return fastcgi_finish_request();
1301 - }
1302 - }
1303 -
1304 - /**
1305 - * Initialize the locale if different from 'en'.
1306 - *
1307 - * @param string $locale The locale to initialize.
1308 - */
1309 - public function init_locale( $locale ) {
1310 - if ( 'en' !== $locale ) {
1311 - // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
1312 - $new_locale = $locale;
1313 - if ( str_contains( $locale, '-' ) ) {
1314 - $locale_pieces = explode( '-', $locale );
1315 - $new_locale = $locale_pieces[0];
1316 - $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
1317 - } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
1318 - // .com might pass 'fr' because thats what our language files are named as, where core seems
1319 - // to do fr_FR - so try that if we don't think we can load the file.
1320 - if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
1321 - $new_locale = $locale . '_' . strtoupper( $locale );
1322 - }
1323 - }
1324 -
1325 - if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
1326 - unload_textdomain( 'default' );
1327 - load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
1328 - }
1329 - }
1330 783 }
1331 784 }