PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 8.2
Jetpack – WP Security, Backup, Speed, & Growth v8.2
16.2-beta 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 All 501 releases
← All changes | class.json-api.php +147 -612 13.6.28.2 View file →
@@ -1,194 +1,50 @@
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 - public $method = '';
55 -
56 - /**
57 - * Request URL.
58 - *
59 - * @var string
60 - */
61 - public $url = '';
62 -
63 - /**
64 - * Path part of the request URL.
65 - *
66 - * @var string
67 - */
68 - public $path = '';
69 -
70 - /**
71 - * Version extracted from the request URL.
72 - *
73 - * @var string|null
74 - */
75 - public $version = null;
76 -
77 - /**
78 - * Parsed query data.
79 - *
80 - * @var array
81 - */
82 - public $query = array();
83 -
84 - /**
85 - * Post body, if the request is a POST.
86 - *
87 - * @var string|null
88 - */
89 - public $post_body = null;
90 -
91 - /**
92 - * Copy of `$_FILES` if the request is a POST.
93 - *
94 - * @var null|array
95 - */
96 - public $files = null;
97 -
98 - /**
99 - * Content type of the request.
100 - *
101 - * @var string|null
102 - */
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;
103 21 public $content_type = null;
22 + public $accept = '';
104 23
105 - /**
106 - * Value of `$_SERVER['HTTP_ACCEPT']`, if any
107 - *
108 - * @var string
109 - */
110 - public $accept = '';
111 -
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 - */
124 - public $exit = true;
125 -
126 - /**
127 - * Public API scheme.
128 - *
129 - * @var string
130 - */
24 + public $_server_https;
25 + public $exit = true;
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;
31 + public $did_output = false;
146 32
147 - /**
148 - * Whether output has been done.
149 - *
150 - * @var bool
151 - */
152 - public $did_output = false;
153 -
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 ) {
38 + static function init( $method = null, $url = null, $post_body = null ) {
177 39 if ( ! self::$self ) {
178 - self::$self = new static( $method, $url, $post_body );
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.
46 + function add( WPCOM_JSON_API_Endpoint $endpoint ) {
191 47 $path_versions = serialize(
192 48 array(
193 49 $endpoint->path,
194 50 $endpoint->min_version,
@@ -200,28 +56,9 @@
200 56 }
201 57 $this->endpoints[ $path_versions ][ $endpoint->method ] = $endpoint;
202 58 }
203 59
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 ) {
213 - return true;
214 - }
215 -
216 - if ( 1 === $value ) {
217 - return true;
218 - }
219 -
220 - if ( ! is_string( $value ) ) {
221 - return false;
222 - }
223 -
60 + static function is_truthy( $value ) {
224 61 switch ( strtolower( (string) $value ) ) {
225 62 case '1':
226 63 case 't':
227 64 case 'true':
@@ -230,27 +67,9 @@
230 67
231 68 return false;
232 69 }
233 70
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 -
71 + static function is_falsy( $value ) {
253 72 switch ( strtolower( (string) $value ) ) {
254 73 case '0':
255 74 case 'f':
256 75 case 'false':
@@ -259,36 +78,20 @@
259 78
260 79 return false;
261 80 }
262 81
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 );
82 + function __construct( ...$args ) {
83 + call_user_func_array( array( $this, 'setup_inputs' ), $args );
273 84 }
274 85
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'] ) ) ) : '';
86 + function setup_inputs( $method = null, $url = null, $post_body = null ) {
87 + if ( is_null( $method ) ) {
88 + $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
285 89 } else {
286 90 $this->method = strtoupper( $method );
287 91 }
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'] ) : '' ) ) );
92 + if ( is_null( $url ) ) {
93 + $this->url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
291 94 } else {
292 95 $this->url = $url;
293 96 }
294 97
@@ -300,28 +103,27 @@
300 103 if ( ! empty( $parsed['query'] ) ) {
301 104 wp_parse_str( $parsed['query'], $this->query );
302 105 }
303 106
304 - if ( ! empty( $_SERVER['HTTP_ACCEPT'] ) ) {
305 - $this->accept = filter_var( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
107 + if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
108 + $this->accept = $_SERVER['HTTP_ACCEPT'];
306 109 }
307 110
308 111 if ( 'POST' === $this->method ) {
309 - if ( $post_body === null ) {
112 + if ( is_null( $post_body ) ) {
310 113 $this->post_body = file_get_contents( 'php://input' );
311 114
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] ) {
115 + if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
116 + $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
117 + } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
118 + $this->content_type = $_SERVER['CONTENT_TYPE'];
119 + } elseif ( '{' === $this->post_body[0] ) {
317 120 $this->content_type = 'application/json';
318 121 } else {
319 122 $this->content_type = 'application/x-www-form-urlencoded';
320 123 }
321 124
322 - if ( str_starts_with( strtolower( $this->content_type ), 'multipart/' ) ) {
323 - // phpcs:ignore WordPress.Security.NonceVerification.Missing
125 + if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
324 126 $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
325 127 $this->files = $_FILES;
326 128 $this->content_type = 'multipart/form-data';
327 129 }
@@ -326,9 +128,9 @@
326 128 $this->content_type = 'multipart/form-data';
327 129 }
328 130 } else {
329 131 $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';
132 + $this->content_type = '{' === isset( $this->post_body[0] ) && $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
331 133 }
332 134 } else {
333 135 $this->post_body = null;
334 136 $this->content_type = null;
@@ -333,80 +135,23 @@
333 135 $this->post_body = null;
334 136 $this->content_type = null;
335 137 }
336 138
337 - $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? filter_var( wp_unslash( $_SERVER['HTTPS'] ) ) : '--UNset--';
139 + $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
338 140 }
339 141
340 - /**
341 - * Initialize.
342 - *
343 - * @return null|WP_Error (although this implementation always returns null)
344 - */
345 - public function initialize() {
142 + function initialize() {
346 143 $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
347 - return null;
348 144 }
349 145
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 - }
146 + function serve( $exit = true ) {
147 + ini_set( 'display_errors', false );
363 148
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 149 $this->exit = (bool) $exit;
405 150
406 151 // This was causing problems with Jetpack, but is necessary for wpcom
407 152 // @see https://github.com/Automattic/jetpack/pull/2603
408 - // @see r124548-wpcom .
153 + // @see r124548-wpcom
409 154 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
410 155 add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
411 156 }
412 157
@@ -414,9 +159,9 @@
414 159
415 160 add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
416 161
417 162 $initialization = $this->initialize();
418 - if ( 'OPTIONS' === $this->method ) {
163 + if ( 'OPTIONS' == $this->method ) {
419 164 /**
420 165 * Fires before the page output.
421 166 * Can be used to specify custom header options.
422 167 *
@@ -432,14 +177,13 @@
432 177 $this->output_error( $initialization );
433 178 return;
434 179 }
435 180
436 - // Normalize path and extract API version.
181 + // Normalize path and extract API version
437 182 $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 - }
183 + preg_match( '#^/rest/v(\d+(\.\d+)*)#', $this->path, $matches );
184 + $this->path = substr( $this->path, strlen( $matches[0] ) );
185 + $this->version = $matches[1];
442 186
443 187 $allowed_methods = array( 'GET', 'POST' );
444 188 $four_oh_five = false;
445 189
@@ -448,17 +192,17 @@
448 192
449 193 if ( $is_help ) {
450 194 $origin = get_http_origin();
451 195
452 - if ( ! empty( $origin ) && 'GET' === $this->method ) {
196 + if ( ! empty( $origin ) && 'GET' == $this->method ) {
453 197 header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
454 198 }
455 199
456 200 $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
457 - // Show help for all matching endpoints regardless of method.
201 + // Show help for all matching endpoints regardless of method
458 202 $methods = $allowed_methods;
459 203 $find_all_matching_endpoints = true;
460 - // How deep to truncate each endpoint's path to see if it matches this help request.
204 + // How deep to truncate each endpoint's path to see if it matches this help request
461 205 $depth = substr_count( $this->path, '/' ) + 1;
462 206 if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
463 207 $help_content_type = 'json';
464 208 } else {
@@ -463,30 +207,30 @@
463 207 $help_content_type = 'json';
464 208 } else {
465 209 $help_content_type = 'html';
466 210 }
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 211 } 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;
212 + if ( in_array( $this->method, $allowed_methods ) ) {
213 + // Only serve requested method
214 + $methods = array( $this->method );
215 + $find_all_matching_endpoints = false;
216 + } else {
217 + // We don't allow this requested method - find matching endpoints and send 405
218 + $methods = $allowed_methods;
219 + $find_all_matching_endpoints = true;
220 + $four_oh_five = true;
221 + }
476 222 }
477 223
478 - // Find which endpoint to serve.
224 + // Find which endpoint to serve
479 225 $found = false;
480 226 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 227 $endpoint_path_versions = unserialize( $endpoint_path_versions );
484 228 $endpoint_path = $endpoint_path_versions[0];
485 229 $endpoint_min_version = $endpoint_path_versions[1];
486 230 $endpoint_max_version = $endpoint_path_versions[2];
487 231
488 - // Make sure max_version is not less than min_version.
232 + // Make sure max_version is not less than min_version
489 233 if ( version_compare( $endpoint_max_version, $endpoint_min_version, '<' ) ) {
490 234 $endpoint_max_version = $endpoint_min_version;
491 235 }
492 236
@@ -494,16 +238,16 @@
494 238 if ( ! isset( $endpoints_by_method[ $method ] ) ) {
495 239 continue;
496 240 }
497 241
498 - // Normalize.
242 + // Normalize
499 243 $endpoint_path = untrailingslashit( $endpoint_path );
500 244 if ( $is_help ) {
501 - // Truncate path at help depth.
502 - $endpoint_path = implode( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
245 + // Truncate path at help depth
246 + $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
503 247 }
504 248
505 - // Generate regular expression from sprintf().
249 + // Generate regular expression from sprintf()
506 250 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
507 251
508 252 if ( ! preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
509 253 // This endpoint does not match the requested path.
@@ -519,9 +263,9 @@
519 263
520 264 if ( $find_all_matching_endpoints ) {
521 265 $matching_endpoints[] = array( $endpoints_by_method[ $method ], $path_pieces );
522 266 } else {
523 - // The method parameters are now in $path_pieces.
267 + // The method parameters are now in $path_pieces
524 268 $endpoint = $endpoints_by_method[ $method ];
525 269 break 2;
526 270 }
527 271 }
@@ -536,9 +280,9 @@
536 280 foreach ( $matching_endpoints as $matching_endpoint ) {
537 281 $allowed_methods[] = $matching_endpoint[0]->method;
538 282 }
539 283
540 - header( 'Allow: ' . strtoupper( implode( ',', array_unique( $allowed_methods ) ) ) );
284 + header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
541 285 return $this->output(
542 286 405,
543 287 array(
544 288 'error' => 'not_allowed',
@@ -596,28 +340,14 @@
596 340
597 341 return $this->output( $output_status_code, $response, 'application/json', $this->extra_headers );
598 342 }
599 343
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 ) {
344 + function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
608 345 $this->endpoint = $endpoint;
609 346 return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
610 347 }
611 348
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' ) {
349 + function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
620 350 $exit = $this->exit;
621 351 $this->exit = false;
622 352 if ( is_wp_error( $response ) ) {
623 353 $this->output_error( $response );
@@ -629,30 +359,14 @@
629 359 $this->finish_request();
630 360 }
631 361 }
632 362
633 - /**
634 - * Set output status code.
635 - *
636 - * @param int $code HTTP status code.
637 - */
638 - public function set_output_status_code( $code = 200 ) {
363 + function set_output_status_code( $code = 200 ) {
639 364 $this->output_status_code = $code;
640 365 }
641 366
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.
367 + function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) {
368 + // In case output() was called before the callback returned
655 369 if ( $this->did_output ) {
656 370 if ( $this->exit ) {
657 371 exit;
658 372 }
@@ -660,30 +374,23 @@
660 374 }
661 375 $this->did_output = true;
662 376
663 377 // 400s and 404s are allowed for all origins
664 - if ( 404 === $status_code || 400 === $status_code ) {
378 + if ( 404 == $status_code || 400 == $status_code ) {
665 379 header( 'Access-Control-Allow-Origin: *' );
666 380 }
667 381
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' );
672 - }
673 -
674 - if ( $response === null ) {
382 + if ( is_null( $response ) ) {
675 383 $response = new stdClass();
676 384 }
677 385
678 - if ( 'text/plain' === $content_type ||
679 - 'text/html' === $content_type ) {
386 + if ( 'text/plain' === $content_type ) {
680 387 status_header( (int) $status_code );
681 - header( 'Content-Type: ' . $content_type );
388 + header( 'Content-Type: text/plain' );
682 389 foreach ( $extra as $key => $value ) {
683 390 header( "$key: $value" );
684 391 }
685 - echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
392 + echo $response;
686 393 if ( $this->exit ) {
687 394 exit;
688 395 }
689 396
@@ -726,13 +433,13 @@
726 433
727 434 if ( $callback ) {
728 435 // Mitigate Rosetta Flash [1] by setting the Content-Type-Options: nosniff header
729 436 // and by prepending the JSONP response with a JS comment.
730 - // [1] <https://blog.miki.it/2014/7/8/abusing-jsonp-with-rosetta-flash/index.html>.
731 - echo "/**/$callback("; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSONP output, not HTML.
437 + // [1] https://blog.miki.it/2014/7/8/abusing-jsonp-with-rosetta-flash/index.html
438 + echo "/**/$callback(";
732 439
733 440 }
734 - echo $this->json_encode( $response ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML.
441 + echo $this->json_encode( $response );
735 442 if ( $callback ) {
736 443 echo ');';
737 444 }
738 445
@@ -742,19 +449,13 @@
742 449
743 450 return $content_type;
744 451 }
745 452
746 - /**
747 - * Serialize an error.
748 - *
749 - * @param WP_Error $error Error.
750 - * @return array with 'status_code' and 'errors' data.
751 - */
752 453 public static function serializable_error( $error ) {
753 454
754 455 $status_code = $error->get_error_data();
755 456
756 - if ( is_array( $status_code ) && isset( $status_code['status_code'] ) ) {
457 + if ( is_array( $status_code ) ) {
757 458 $status_code = $status_code['status_code'];
758 459 }
759 460
760 461 if ( ! $status_code ) {
@@ -764,10 +465,9 @@
764 465 'error' => $error->get_error_code(),
765 466 'message' => $error->get_error_message(),
766 467 );
767 468
768 - $additional_data = $error->get_error_data( 'additional_data' );
769 - if ( $additional_data ) {
469 + if ( $additional_data = $error->get_error_data( 'additional_data' ) ) {
770 470 $response['data'] = $additional_data;
771 471 }
772 472
773 473 return array(
@@ -775,27 +475,15 @@
775 475 'errors' => $response,
776 476 );
777 477 }
778 478
779 - /**
780 - * Output an error.
781 - *
782 - * @param WP_Error $error Error.
783 - * @return string Content type (assuming it didn't exit).
784 - */
785 - public function output_error( $error ) {
786 - $error_response = static::serializable_error( $error );
479 + function output_error( $error ) {
480 + $error_response = $this->serializable_error( $error );
787 481
788 482 return $this->output( $error_response['status_code'], $error_response['errors'] );
789 483 }
790 484
791 - /**
792 - * Filter fields in a response.
793 - *
794 - * @param array|object $response Response.
795 - * @return array|object Filtered response.
796 - */
797 - public function filter_fields( $response ) {
485 + function filter_fields( $response ) {
798 486 if ( empty( $this->query['fields'] ) || ( is_array( $response ) && ! empty( $response['error'] ) ) || ! empty( $this->endpoint->custom_fields_filtering ) ) {
799 487 return $response;
800 488 }
801 489
@@ -833,9 +521,8 @@
833 521
834 522 foreach ( $response[ $key_to_filter ] as $key => $values ) {
835 523 if ( is_object( $values ) ) {
836 524 if ( is_object( $response[ $key_to_filter ] ) ) {
837 - // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found -- False positive.
838 525 $response[ $key_to_filter ]->$key = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
839 526 } elseif ( is_array( $response[ $key_to_filter ] ) ) {
840 527 $response[ $key_to_filter ][ $key ] = (object) array_intersect_key( ( (array) $values ), array_flip( $fields ) );
841 528 }
@@ -858,19 +545,9 @@
858 545
859 546 return $response;
860 547 }
861 548
862 - /**
863 - * Filter for `home_url`.
864 - *
865 - * If `$original_scheme` is null, turns an https URL to http.
866 - *
867 - * @param string $url The complete home URL including scheme and path.
868 - * @param string $path Path relative to the home URL. Blank string if no path is specified.
869 - * @param string|null $original_scheme Scheme to give the home URL context. Accepts 'http', 'https', 'relative', 'rest', or null.
870 - * @return string URL.
871 - */
872 - public function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
549 + function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
873 550 if ( $original_scheme ) {
874 551 return $url;
875 552 }
876 553
@@ -876,78 +553,36 @@
876 553
877 554 return preg_replace( '#^https:#', 'http:', $url );
878 555 }
879 556
880 - /**
881 - * Decode HTML special characters in comment content.
882 - *
883 - * @param string $comment_content Comment content.
884 - * @return string
885 - */
886 - public function comment_edit_pre( $comment_content ) {
557 + function comment_edit_pre( $comment_content ) {
887 558 return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
888 559 }
889 560
890 - /**
891 - * JSON encode.
892 - *
893 - * @param mixed $data Data.
894 - * @return string|false
895 - */
896 - public function json_encode( $data ) {
561 + function json_encode( $data ) {
897 562 return wp_json_encode( $data );
898 563 }
899 564
900 - /**
901 - * Test if a string ends with a string.
902 - *
903 - * @param string $haystack String to check.
904 - * @param string $needle Suffix to check.
905 - * @return bool
906 - */
907 - public function ends_with( $haystack, $needle ) {
908 - return substr( $haystack, -strlen( $needle ) ) === $needle;
565 + function ends_with( $haystack, $needle ) {
566 + return $needle === substr( $haystack, -strlen( $needle ) );
909 567 }
910 568
911 - /**
912 - * Returns the site's blog_id in the WP.com ecosystem
913 - *
914 - * @return int
915 - */
916 - public function get_blog_id_for_output() {
569 + // Returns the site's blog_id in the WP.com ecosystem
570 + function get_blog_id_for_output() {
917 571 return $this->token_details['blog_id'];
918 572 }
919 573
920 - /**
921 - * Returns the site's local blog_id.
922 - *
923 - * @param int $blog_id Blog ID.
924 - * @return int
925 - */
926 - public function get_blog_id( $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
574 + // Returns the site's local blog_id
575 + function get_blog_id( $blog_id ) {
927 576 return $GLOBALS['blog_id'];
928 577 }
929 578
930 - /**
931 - * Switch to blog and validate user.
932 - *
933 - * @param int $blog_id Blog ID.
934 - * @param bool $verify_token_for_blog Whether to verify the token.
935 - * @return int Blog ID.
936 - */
937 - public function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
579 + function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
938 580 if ( $this->is_restricted_blog( $blog_id ) ) {
939 581 return new WP_Error( 'unauthorized', 'User cannot access this restricted blog', 403 );
940 582 }
941 - /**
942 - * If this is a private site we check for 2 things:
943 - * 1. In case of user based authentication, we need to check if the logged-in user has the 'read' capability.
944 - * 2. In case of site based authentication, make sure the endpoint accepts it.
945 - */
946 - if ( ( new Status() )->is_private_site() &&
947 - ! current_user_can( 'read' ) &&
948 - ! $this->endpoint->accepts_site_based_authentication()
949 - ) {
583 +
584 + if ( -1 == get_option( 'blog_public' ) && ! current_user_can( 'read' ) ) {
950 585 return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
951 586 }
952 587
953 588 return $blog_id;
@@ -952,15 +587,10 @@
952 587
953 588 return $blog_id;
954 589 }
955 590
956 - /**
957 - * Returns true if the specified blog ID is a restricted blog
958 - *
959 - * @param int $blog_id Blog ID.
960 - * @return bool
961 - */
962 - public function is_restricted_blog( $blog_id ) {
591 + // Returns true if the specified blog ID is a restricted blog
592 + function is_restricted_blog( $blog_id ) {
963 593 /**
964 594 * Filters all REST API access and return a 403 unauthorized response for all Restricted blog IDs.
965 595 *
966 596 * @module json-api
@@ -969,99 +599,47 @@
969 599 *
970 600 * @param array $array Array of Blog IDs.
971 601 */
972 602 $restricted_blog_ids = apply_filters( 'wpcom_json_api_restricted_blog_ids', array() );
973 - 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.
603 + return true === in_array( $blog_id, $restricted_blog_ids );
974 604 }
975 605
976 - /**
977 - * Post like count.
978 - *
979 - * @param int $blog_id Blog ID.
980 - * @param int $post_id Post ID.
981 - * @return int
982 - */
983 - public function post_like_count( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
606 + function post_like_count( $blog_id, $post_id ) {
984 607 return 0;
985 608 }
986 609
987 - /**
988 - * Is liked?
989 - *
990 - * @param int $blog_id Blog ID.
991 - * @param int $post_id Post ID.
992 - * @return bool
993 - */
994 - public function is_liked( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
610 + function is_liked( $blog_id, $post_id ) {
995 611 return false;
996 612 }
997 613
998 - /**
999 - * Is reblogged?
1000 - *
1001 - * @param int $blog_id Blog ID.
1002 - * @param int $post_id Post ID.
1003 - * @return bool
1004 - */
1005 - public function is_reblogged( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
614 + function is_reblogged( $blog_id, $post_id ) {
1006 615 return false;
1007 616 }
1008 617
1009 - /**
1010 - * Is following?
1011 - *
1012 - * @param int $blog_id Blog ID.
1013 - * @return bool
1014 - */
1015 - public function is_following( $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
618 + function is_following( $blog_id ) {
1016 619 return false;
1017 620 }
1018 621
1019 - /**
1020 - * Add global ID.
1021 - *
1022 - * @param int $blog_id Blog ID.
1023 - * @param int $post_id Post ID.
1024 - * @return string
1025 - */
1026 - public function add_global_ID( $blog_id, $post_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable, WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
622 + function add_global_ID( $blog_id, $post_id ) {
1027 623 return '';
1028 624 }
1029 625
1030 - /**
1031 - * Return a count of comment likes.
1032 - * This method is overridden by a child class in WPCOM.
1033 - *
1034 - * @since 13.5
1035 - * @return int
1036 - */
1037 - public function comment_like_count() {
1038 - func_get_args(); // @phan-suppress-current-line PhanPluginUseReturnValueInternalKnown -- This is just here so Phan realizes the wpcom version does this.
1039 - return 0;
1040 - }
1041 -
1042 - /**
1043 - * Get avatar URL.
1044 - *
1045 - * @param string $email Email.
1046 - * @param array $args Args for `get_avatar_url()`.
1047 - * @return string|false
1048 - */
1049 - public function get_avatar_url( $email, $args = null ) {
626 + function get_avatar_url( $email, $avatar_size = null ) {
1050 627 if ( function_exists( 'wpcom_get_avatar_url' ) ) {
1051 - $ret = wpcom_get_avatar_url( $email, $args['size'] ?? 96, $args['default'] ?? '', false, $args['force_default'] ?? false );
1052 - return $ret ? $ret[0] : false;
628 + return null === $avatar_size
629 + ? wpcom_get_avatar_url( $email )
630 + : wpcom_get_avatar_url( $email, $avatar_size );
1053 631 } else {
1054 - return null === $args
632 + return null === $avatar_size
1055 633 ? get_avatar_url( $email )
1056 - : get_avatar_url( $email, $args );
634 + : get_avatar_url( $email, $avatar_size );
1057 635 }
1058 636 }
1059 637
1060 638 /**
1061 - * Counts the number of comments on a site, including certain comment types.
639 + * Counts the number of comments on a site, excluding certain comment types.
1062 640 *
1063 - * @param int $post_id Post ID.
641 + * @param $post_id int Post ID.
1064 642 * @return array Array of counts, matching the output of https://developer.wordpress.org/reference/functions/get_comment_count/.
1065 643 */
1066 644 public function wp_count_comments( $post_id ) {
1067 645 global $wpdb;
@@ -1074,44 +652,30 @@
1074 652 'all' => 0,
1075 653 );
1076 654
1077 655 /**
1078 - * Exclude certain comment types from comment counts in the REST API.
1079 - *
1080 - * @since 6.9.0
1081 - * @deprecated 11.1
1082 - * @module json-api
1083 - *
1084 - * @param array Array of comment types to exclude (default: 'order_note', 'webhook_delivery', 'review', 'action_log')
1085 - */
1086 - $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
1087 -
1088 - /**
1089 - * Include certain comment types in comment counts in the REST API.
1090 - * Note: the default array of comment types includes an empty string,
1091 - * to support comments posted before WP 5.5, that used an empty string as comment type.
1092 - *
1093 - * @since 11.1
1094 - * @module json-api
1095 - *
1096 - * @param array Array of comment types to include (default: 'comment', 'pingback', 'trackback')
1097 - */
1098 - $include = apply_filters(
1099 - 'jetpack_api_include_comment_types_count',
1100 - array( 'comment', 'pingback', 'trackback', '' )
656 + * Exclude certain comment types from comment counts in the REST API.
657 + *
658 + * @since 6.9.0
659 + * @module json-api
660 + *
661 + * @param array Array of comment types to exclude (default: 'order_note', 'webhook_delivery', 'review', 'action_log')
662 + */
663 + $exclude = apply_filters(
664 + 'jetpack_api_exclude_comment_types_count',
665 + array( 'order_note', 'webhook_delivery', 'review', 'action_log' )
1101 666 );
1102 667
1103 - if ( empty( $include ) ) {
668 + if ( empty( $exclude ) ) {
1104 669 return wp_count_comments( $post_id );
1105 670 }
1106 671
1107 - array_walk( $include, 'esc_sql' );
672 + array_walk( $exclude, 'esc_sql' );
1108 673 $where = sprintf(
1109 - "WHERE comment_type IN ( '%s' )",
1110 - implode( "','", $include )
674 + "WHERE comment_type NOT IN ( '%s' )",
675 + implode( "','", $exclude )
1111 676 );
1112 677
1113 - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- `$where` is built with escaping just above.
1114 678 $count = $wpdb->get_results(
1115 679 "SELECT comment_approved, COUNT(*) AS num_comments
1116 680 FROM $wpdb->comments
1117 681 {$where}
@@ -1117,9 +681,8 @@
1117 681 {$where}
1118 682 GROUP BY comment_approved
1119 683 "
1120 684 );
1121 - // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1122 685
1123 686 $approved = array(
1124 687 '0' => 'moderated',
1125 688 '1' => 'approved',
@@ -1127,9 +690,9 @@
1127 690 'trash' => 'trash',
1128 691 'post-trashed' => 'post-trashed',
1129 692 );
1130 693
1131 - // <https://developer.wordpress.org/reference/functions/get_comment_count/#source>
694 + // https://developer.wordpress.org/reference/functions/get_comment_count/#source
1132 695 foreach ( $count as $row ) {
1133 696 if ( ! in_array( $row->comment_approved, array( 'post-trashed', 'trash', 'spam' ), true ) ) {
1134 697 $counts['all'] += $row->num_comments;
1135 698 $counts['total_comments'] += $row->num_comments;
@@ -1152,30 +715,19 @@
1152 715 return $counts;
1153 716 }
1154 717
1155 718 /**
1156 - * Traps `wp_die()` calls and outputs a JSON response instead.
719 + * traps `wp_die()` calls and outputs a JSON response instead.
1157 720 * The result is always output, never returned.
1158 721 *
1159 722 * @param string|null $error_code Call with string to start the trapping. Call with null to stop.
1160 723 * @param int $http_status HTTP status code, 400 by default.
1161 724 */
1162 - public function trap_wp_die( $error_code = null, $http_status = 400 ) {
1163 - // Determine the filter name; based on the conditionals inside the wp_die function.
1164 - if ( wp_is_json_request() ) {
1165 - $die_handler = 'wp_die_json_handler';
1166 - } elseif ( wp_is_jsonp_request() ) {
1167 - $die_handler = 'wp_die_jsonp_handler';
1168 - } elseif ( wp_is_xml_request() ) {
1169 - $die_handler = 'wp_die_xml_handler';
1170 - } else {
1171 - $die_handler = 'wp_die_handler';
1172 - }
1173 -
1174 - if ( $error_code === null ) {
725 + function trap_wp_die( $error_code = null, $http_status = 400 ) {
726 + if ( is_null( $error_code ) ) {
1175 727 $this->trapped_error = null;
1176 - // Stop trapping.
1177 - remove_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
728 + // Stop trapping
729 + remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
1178 730 return;
1179 731 }
1180 732
1181 733 // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
@@ -1182,10 +734,12 @@
1182 734 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
1183 735 if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
1184 736 return;
1185 737 }
1186 - } elseif ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
1187 - return;
738 + } else {
739 + if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
740 + return;
741 + }
1188 742 }
1189 743
1190 744 $this->trapped_error = array(
1191 745 'status' => $http_status,
@@ -1191,30 +745,17 @@
1191 745 'status' => $http_status,
1192 746 'code' => $error_code,
1193 747 'message' => '',
1194 748 );
1195 - // Start trapping.
1196 - add_filter( $die_handler, array( $this, 'wp_die_handler_callback' ) );
749 + // Start trapping
750 + add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
1197 751 }
1198 752
1199 - /**
1200 - * Filter function for `wp_die_handler` and similar filters.
1201 - *
1202 - * @return callable
1203 - */
1204 - public function wp_die_handler_callback() {
753 + function wp_die_handler_callback() {
1205 754 return array( $this, 'wp_die_handler' );
1206 755 }
1207 756
1208 - /**
1209 - * Handler for `wp_die` calls.
1210 - *
1211 - * @param string|WP_Error $message As for `wp_die()`.
1212 - * @param string|int $title As for `wp_die()`.
1213 - * @param string|array|int $args As for `wp_die()`.
1214 - * @return never
1215 - */
1216 - public function wp_die_handler( $message, $title = '', $args = array() ) {
757 + function wp_die_handler( $message, $title = '', $args = array() ) {
1217 758 // Allow wp_die calls to override HTTP status code...
1218 759 $args = wp_parse_args(
1219 760 $args,
1220 761 array(
@@ -1222,9 +763,9 @@
1222 763 )
1223 764 );
1224 765
1225 766 // ... unless it's 500
1226 - if ( 500 !== (int) $args['response'] ) {
767 + if ( (int) $args['response'] !== 500 ) {
1227 768 $this->trapped_error['status'] = $args['response'];
1228 769 }
1229 770
1230 771 if ( $title ) {
@@ -1243,17 +784,14 @@
1243 784 break;
1244 785 }
1245 786
1246 787 // We still want to exit so that code execution stops where it should.
1247 - // Attach the JSON output to the WordPress shutdown handler.
788 + // Attach the JSON output to the WordPress shutdown handler
1248 789 add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
1249 790 exit;
1250 791 }
1251 792
1252 - /**
1253 - * Output the trapped error.
1254 - */
1255 - public function output_trapped_error() {
793 + function output_trapped_error() {
1256 794 $this->exit = false; // We're already exiting once. Don't do it twice.
1257 795 $this->output(
1258 796 $this->trapped_error['status'],
1259 797 (object) array(
@@ -1262,12 +800,9 @@
1262 800 )
1263 801 );
1264 802 }
1265 803
1266 - /**
1267 - * Finish the request.
1268 - */
1269 - public function finish_request() {
804 + function finish_request() {
1270 805 if ( function_exists( 'fastcgi_finish_request' ) ) {
1271 806 return fastcgi_finish_request();
1272 807 }
1273 808 }