Automattic /
jetpack
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * The Jetpack Connection error class file. |
||
| 4 | * |
||
| 5 | * @package automattic/jetpack-connection |
||
| 6 | */ |
||
| 7 | |||
| 8 | namespace Automattic\Jetpack\Connection; |
||
| 9 | |||
| 10 | /** |
||
| 11 | * The Jetpack Connection Errors that handles errors |
||
| 12 | * |
||
| 13 | * This class handles the following workflow: |
||
| 14 | * |
||
| 15 | * 1. A XML-RCP request with an invalid signature triggers a error |
||
| 16 | * 2. Applies a gate to only process each error code once an hour to avoid overflow |
||
| 17 | * 3. It stores the error on the database, but we don't know yet if this is a valid error, because |
||
| 18 | * we can't confirm it came from WP.com. |
||
| 19 | * 4. It encrypts the error details and send it to thw wp.com server |
||
| 20 | * 5. wp.com checks it and, if valid, sends a new request back to this site using the verify_xml_rpc_error REST endpoint |
||
| 21 | * 6. This endpoint add this error to the Verified errors in the database |
||
| 22 | * 7. Triggers a workflow depending on the error (display user an error message, do some self healing, etc.) |
||
| 23 | * |
||
| 24 | * Errors are stored in the database as options in the following format: |
||
| 25 | * |
||
| 26 | * [ |
||
| 27 | * $error_code => [ |
||
| 28 | * $user_id => [ |
||
| 29 | * $error_details |
||
| 30 | * ] |
||
| 31 | * ] |
||
| 32 | * ] |
||
| 33 | * |
||
| 34 | * For each error code we store a maximum of 5 errors for 5 different user ids. |
||
| 35 | * |
||
| 36 | * An user ID can be |
||
| 37 | * * 0 for blog tokens |
||
| 38 | * * positive integer for user tokens |
||
| 39 | * * 'invalid' for malformed tokens |
||
| 40 | * |
||
| 41 | * @since 8.7.0 |
||
| 42 | */ |
||
| 43 | class Error_Handler { |
||
| 44 | |||
| 45 | /** |
||
| 46 | * The name of the option that stores the errors |
||
| 47 | * |
||
| 48 | * @since 8.7.0 |
||
| 49 | * |
||
| 50 | * @var string |
||
| 51 | */ |
||
| 52 | const STORED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_errors'; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * The name of the option that stores the errors |
||
| 56 | * |
||
| 57 | * @since 8.7.0 |
||
| 58 | * |
||
| 59 | * @var string |
||
| 60 | */ |
||
| 61 | const STORED_VERIFIED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_verified_errors'; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * The prefix of the transient that controls the gate for each error code |
||
| 65 | * |
||
| 66 | * @since 8.7.0 |
||
| 67 | * |
||
| 68 | * @var string |
||
| 69 | */ |
||
| 70 | const ERROR_REPORTING_GATE = 'jetpack_connection_error_reporting_gate_'; |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Time in seconds a test should live in the database before being discarded |
||
| 74 | * |
||
| 75 | * @since 8.7.0 |
||
| 76 | */ |
||
| 77 | const ERROR_LIFE_TIME = DAY_IN_SECONDS; |
||
| 78 | /** |
||
| 79 | * List of known errors. Only error codes in this list will be handled |
||
| 80 | * |
||
| 81 | * @since 8.7.0 |
||
| 82 | * |
||
| 83 | * @var array |
||
| 84 | */ |
||
| 85 | public $known_errors = array( |
||
| 86 | 'malformed_token', |
||
| 87 | 'malformed_user_id', |
||
| 88 | 'unknown_user', |
||
| 89 | 'no_user_tokens', |
||
| 90 | 'empty_master_user_option', |
||
| 91 | 'no_token_for_user', |
||
| 92 | 'token_malformed', |
||
| 93 | 'user_id_mismatch', |
||
| 94 | 'no_possible_tokens', |
||
| 95 | 'no_valid_token', |
||
| 96 | 'unknown_token', |
||
| 97 | 'could_not_sign', |
||
| 98 | 'invalid_scheme', |
||
| 99 | 'invalid_secret', |
||
| 100 | 'invalid_token', |
||
| 101 | 'token_mismatch', |
||
| 102 | 'invalid_body', |
||
| 103 | 'invalid_signature', |
||
| 104 | 'invalid_body_hash', |
||
| 105 | 'invalid_nonce', |
||
| 106 | 'signature_mismatch', |
||
| 107 | ); |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Holds the instance of this singleton class |
||
| 111 | * |
||
| 112 | * @since 8.7.0 |
||
| 113 | * |
||
| 114 | * @var Error_Handler $instance |
||
| 115 | */ |
||
| 116 | public static $instance = null; |
||
| 117 | |||
| 118 | /** |
||
| 119 | * Initialize instance, hookds and load verified errors handlers |
||
| 120 | * |
||
| 121 | * @since 8.7.0 |
||
| 122 | */ |
||
| 123 | private function __construct() { |
||
| 124 | defined( 'JETPACK__ERRORS_PUBLIC_KEY' ) || define( 'JETPACK__ERRORS_PUBLIC_KEY', 'KdZY80axKX+nWzfrOcizf0jqiFHnrWCl9X8yuaClKgM=' ); |
||
| 125 | |||
| 126 | add_action( 'rest_api_init', array( $this, 'register_verify_error_endpoint' ) ); |
||
| 127 | |||
| 128 | $this->handle_verified_errors(); |
||
| 129 | |||
| 130 | // If the site gets reconnected, clear errors. |
||
| 131 | add_action( 'jetpack_site_registered', array( $this, 'delete_all_errors' ) ); |
||
| 132 | add_action( 'jetpack_get_site_data_success', array( $this, 'delete_all_errors' ) ); |
||
| 133 | } |
||
| 134 | |||
| 135 | /** |
||
| 136 | * Gets the list of verified errors and act upon them |
||
| 137 | * |
||
| 138 | * @since 8.7.0 |
||
| 139 | * |
||
| 140 | * @return void |
||
| 141 | */ |
||
| 142 | public function handle_verified_errors() { |
||
| 143 | $verified_errors = $this->get_verified_errors(); |
||
| 144 | foreach ( $verified_errors as $error_code => $user_errors ) { |
||
| 145 | |||
| 146 | switch ( $error_code ) { |
||
| 147 | case 'malformed_token': |
||
| 148 | case 'token_malformed': |
||
| 149 | case 'no_possible_tokens': |
||
| 150 | case 'no_valid_token': |
||
| 151 | case 'unknown_token': |
||
| 152 | case 'could_not_sign': |
||
| 153 | case 'invalid_token': |
||
| 154 | case 'token_mismatch': |
||
| 155 | case 'invalid_signature': |
||
| 156 | case 'signature_mismatch': |
||
| 157 | new Error_Handlers\Invalid_Blog_Token( $user_errors ); |
||
| 158 | break; |
||
| 159 | } |
||
| 160 | } |
||
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * Gets the instance of this singleton class |
||
| 165 | * |
||
| 166 | * @since 8.7.0 |
||
| 167 | * |
||
| 168 | * @return Error_Handler $instance |
||
| 169 | */ |
||
| 170 | public static function get_instance() { |
||
| 171 | if ( is_null( self::$instance ) ) { |
||
| 172 | self::$instance = new self(); |
||
| 173 | } |
||
| 174 | return self::$instance; |
||
| 175 | } |
||
| 176 | |||
| 177 | /** |
||
| 178 | * Keep track of a connection error that was encountered |
||
| 179 | * |
||
| 180 | * @since 8.7.0 |
||
| 181 | * |
||
| 182 | * @param \WP_Error $error the error object. |
||
| 183 | * @param boolean $force Force the report, even if should_report_error is false. |
||
| 184 | * @return void |
||
| 185 | */ |
||
| 186 | public function report_error( \WP_Error $error, $force = false ) { |
||
| 187 | if ( in_array( $error->get_error_code(), $this->known_errors, true ) && $this->should_report_error( $error ) || $force ) { |
||
| 188 | $stored_error = $this->store_error( $error ); |
||
| 189 | if ( $stored_error ) { |
||
| 190 | $this->send_error_to_wpcom( $stored_error ); |
||
| 191 | } |
||
| 192 | } |
||
| 193 | } |
||
| 194 | |||
| 195 | /** |
||
| 196 | * Checks the status of the gate |
||
| 197 | * |
||
| 198 | * This protects the site (and WPCOM) against over loads. |
||
| 199 | * |
||
| 200 | * @since 8.7.0 |
||
| 201 | * |
||
| 202 | * @param \WP_Error $error the error object. |
||
| 203 | * @return boolean $should_report True if gate is open and the error should be reported. |
||
| 204 | */ |
||
| 205 | public function should_report_error( \WP_Error $error ) { |
||
| 206 | |||
| 207 | if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) { |
||
| 208 | return true; |
||
| 209 | } |
||
| 210 | |||
| 211 | /** |
||
| 212 | * Whether to bypass the gate for XML-RPC error handling |
||
| 213 | * |
||
| 214 | * By default, we only process XML-RPC errors once an hour for each error code. |
||
| 215 | * This is done to avoid overflows. If you need to disable this gate, you can set this variable to true. |
||
| 216 | * |
||
| 217 | * This filter is useful for unit testing |
||
| 218 | * |
||
| 219 | * @since 8.7.0 |
||
| 220 | * |
||
| 221 | * @param boolean $bypass_gate whether to bypass the gate. Default is false, do not bypass. |
||
| 222 | */ |
||
| 223 | $bypass_gate = apply_filters( 'jetpack_connection_bypass_error_reporting_gate', false ); |
||
| 224 | if ( true === $bypass_gate ) { |
||
| 225 | return true; |
||
| 226 | } |
||
| 227 | |||
| 228 | $transient = self::ERROR_REPORTING_GATE . $error->get_error_code(); |
||
| 229 | |||
| 230 | if ( get_transient( $transient ) ) { |
||
| 231 | return false; |
||
| 232 | } |
||
| 233 | |||
| 234 | set_transient( $transient, true, HOUR_IN_SECONDS ); |
||
| 235 | return true; |
||
| 236 | } |
||
| 237 | |||
| 238 | /** |
||
| 239 | * Stores the error in the database so we know there is an issue and can inform the user |
||
| 240 | * |
||
| 241 | * @since 8.7.0 |
||
| 242 | * |
||
| 243 | * @param \WP_Error $error the error object. |
||
| 244 | * @return boolean|array False if stored errors were not updated and the error array if it was successfully stored. |
||
| 245 | */ |
||
| 246 | public function store_error( \WP_Error $error ) { |
||
| 247 | |||
| 248 | $stored_errors = $this->get_stored_errors(); |
||
| 249 | $error_array = $this->wp_error_to_array( $error ); |
||
| 250 | $error_code = $error->get_error_code(); |
||
| 251 | $user_id = $error_array['user_id']; |
||
| 252 | |||
| 253 | if ( ! isset( $stored_errors[ $error_code ] ) || ! is_array( $stored_errors[ $error_code ] ) ) { |
||
| 254 | $stored_errors[ $error_code ] = array(); |
||
| 255 | } |
||
| 256 | |||
| 257 | $stored_errors[ $error_code ][ $user_id ] = $error_array; |
||
| 258 | |||
| 259 | // Let's store a maximum of 5 different user ids for each error code. |
||
| 260 | if ( count( $stored_errors[ $error_code ] ) > 5 ) { |
||
| 261 | // array_shift will destroy keys here because they are numeric, so manually remove first item. |
||
| 262 | $keys = array_keys( $stored_errors[ $error_code ] ); |
||
| 263 | unset( $stored_errors[ $error_code ][ $keys[0] ] ); |
||
| 264 | } |
||
| 265 | |||
| 266 | if ( update_option( self::STORED_ERRORS_OPTION, $stored_errors ) ) { |
||
| 267 | return $error_array; |
||
| 268 | } |
||
| 269 | |||
| 270 | return false; |
||
| 271 | |||
| 272 | } |
||
| 273 | |||
| 274 | /** |
||
| 275 | * Converts a WP_Error object in the array representation we store in the database |
||
| 276 | * |
||
| 277 | * @since 8.7.0 |
||
| 278 | * |
||
| 279 | * @param \WP_Error $error the error object. |
||
| 280 | * @return boolean|array False if error is invalid or the error array |
||
| 281 | */ |
||
| 282 | public function wp_error_to_array( \WP_Error $error ) { |
||
| 283 | |||
| 284 | $data = $error->get_error_data(); |
||
| 285 | |||
| 286 | if ( ! isset( $data['signature_details'] ) || ! is_array( $data['signature_details'] ) ) { |
||
| 287 | return false; |
||
| 288 | } |
||
| 289 | |||
| 290 | $data = $data['signature_details']; |
||
| 291 | |||
| 292 | if ( ! isset( $data['token'] ) || empty( $data['token'] ) ) { |
||
| 293 | return false; |
||
| 294 | } |
||
| 295 | |||
| 296 | $user_id = $this->get_user_id_from_token( $data['token'] ); |
||
| 297 | |||
| 298 | $error_array = array( |
||
| 299 | 'error_code' => $error->get_error_code(), |
||
| 300 | 'user_id' => $user_id, |
||
| 301 | 'error_message' => $error->get_error_message(), |
||
| 302 | 'error_data' => $data, |
||
| 303 | 'timestamp' => time(), |
||
| 304 | 'nonce' => wp_generate_password( 10, false ), |
||
| 305 | ); |
||
| 306 | |||
| 307 | if ( $this->track_lost_active_master_user( $error->get_error_code(), $data['token'], $user_id ) ) { |
||
|
0 ignored issues
–
show
|
|||
| 308 | $error_array['error_message'] = 'Site has a deleted but active master user token'; |
||
| 309 | } |
||
| 310 | |||
| 311 | return $error_array; |
||
| 312 | |||
| 313 | } |
||
| 314 | |||
| 315 | /** |
||
| 316 | * This is been used to track blogs with deleted master user but whose tokens are still actively being used |
||
| 317 | * |
||
| 318 | * See p9dueE-1GB-p2 |
||
| 319 | * |
||
| 320 | * This tracking should be removed as long as we no longer need, possibly in 8.9 |
||
| 321 | * |
||
| 322 | * @since 8.8.1 |
||
| 323 | * |
||
| 324 | * @param string $error_code The error code. |
||
| 325 | * @param string $token The token that triggered the error. |
||
| 326 | * @param integer $user_id The user ID used to make the request that triggered the error. |
||
| 327 | * @return boolean |
||
| 328 | */ |
||
| 329 | private function track_lost_active_master_user( $error_code, $token, $user_id ) { |
||
| 330 | if ( 'unknown_user' === $error_code ) { |
||
| 331 | $manager = new Manager(); |
||
| 332 | // If the Unknown user is the master user (master user has been deleted). |
||
| 333 | if ( $manager->is_missing_connection_owner() && (int) $user_id === (int) $manager->get_connection_owner_id() ) { |
||
| 334 | $user_token = $manager->get_access_token( JETPACK_MASTER_USER ); |
||
|
0 ignored issues
–
show
JETPACK_MASTER_USER is of type boolean, but the function expects a false|integer.
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
| 335 | // If there's still a token stored for the deleted master user. |
||
| 336 | if ( $user_token && is_object( $user_token ) && isset( $user_token->secret ) ) { |
||
| 337 | $token_parts = explode( ':', wp_unslash( $token ) ); |
||
| 338 | // If the token stored for the deleted master user matches the token user by wpcom to make the request. |
||
| 339 | // This means that requests FROM this site TO wpcom using the JETPACK_MASTER_USER constant are still working. |
||
| 340 | if ( isset( $token_parts[0] ) && ! empty( $token_parts[0] ) && false !== strpos( $user_token->secret, $token_parts[0] ) ) { |
||
| 341 | return true; |
||
| 342 | } |
||
| 343 | } |
||
| 344 | } |
||
| 345 | } |
||
| 346 | return false; |
||
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Sends the error to WP.com to be verified |
||
| 351 | * |
||
| 352 | * @since 8.7.0 |
||
| 353 | * |
||
| 354 | * @param array $error_array The array representation of the error as it is stored in the database. |
||
| 355 | * @return bool |
||
| 356 | */ |
||
| 357 | public function send_error_to_wpcom( $error_array ) { |
||
| 358 | |||
| 359 | $blog_id = \Jetpack_Options::get_option( 'id' ); |
||
| 360 | |||
| 361 | $encrypted_data = $this->encrypt_data_to_wpcom( $error_array ); |
||
| 362 | |||
| 363 | if ( false === $encrypted_data ) { |
||
| 364 | return false; |
||
| 365 | } |
||
| 366 | |||
| 367 | $args = array( |
||
| 368 | 'body' => array( |
||
| 369 | 'error_data' => $encrypted_data, |
||
| 370 | ), |
||
| 371 | ); |
||
| 372 | |||
| 373 | // send encrypted data to WP.com Public-API v2. |
||
| 374 | wp_remote_post( "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/jetpack-report-error/", $args ); |
||
| 375 | return true; |
||
| 376 | } |
||
| 377 | |||
| 378 | /** |
||
| 379 | * Encrypt data to be sent over to WP.com |
||
| 380 | * |
||
| 381 | * @since 8.7.0 |
||
| 382 | * |
||
| 383 | * @param array|string $data the data to be encoded. |
||
| 384 | * @return boolean|string The encoded string on success, false on failure |
||
| 385 | */ |
||
| 386 | public function encrypt_data_to_wpcom( $data ) { |
||
| 387 | |||
| 388 | try { |
||
| 389 | // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode |
||
| 390 | // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode |
||
| 391 | $encrypted_data = base64_encode( sodium_crypto_box_seal( wp_json_encode( $data ), base64_decode( JETPACK__ERRORS_PUBLIC_KEY ) ) ); |
||
| 392 | // phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode |
||
| 393 | // phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode |
||
| 394 | } catch ( \SodiumException $e ) { |
||
| 395 | // error encrypting data. |
||
| 396 | return false; |
||
| 397 | } |
||
| 398 | |||
| 399 | return $encrypted_data; |
||
| 400 | |||
| 401 | } |
||
| 402 | |||
| 403 | /** |
||
| 404 | * Extracts the user ID from a token |
||
| 405 | * |
||
| 406 | * @since 8.7.0 |
||
| 407 | * |
||
| 408 | * @param string $token the token used to make the xml-rpc request. |
||
| 409 | * @return string $the user id or `invalid` if user id not present. |
||
| 410 | */ |
||
| 411 | public function get_user_id_from_token( $token ) { |
||
| 412 | $parsed_token = explode( ':', wp_unslash( $token ) ); |
||
| 413 | |||
| 414 | if ( isset( $parsed_token[2] ) && ! empty( $parsed_token[2] ) && ctype_digit( $parsed_token[2] ) ) { |
||
| 415 | $user_id = $parsed_token[2]; |
||
| 416 | } else { |
||
| 417 | $user_id = 'invalid'; |
||
| 418 | } |
||
| 419 | |||
| 420 | return $user_id; |
||
| 421 | |||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Gets the reported errors stored in the database |
||
| 426 | * |
||
| 427 | * @since 8.7.0 |
||
| 428 | * |
||
| 429 | * @return array $errors |
||
| 430 | */ |
||
| 431 | View Code Duplication | public function get_stored_errors() { |
|
| 432 | |||
| 433 | $stored_errors = get_option( self::STORED_ERRORS_OPTION ); |
||
| 434 | |||
| 435 | if ( ! is_array( $stored_errors ) ) { |
||
| 436 | $stored_errors = array(); |
||
| 437 | } |
||
| 438 | |||
| 439 | $stored_errors = $this->garbage_collector( $stored_errors ); |
||
| 440 | |||
| 441 | return $stored_errors; |
||
| 442 | } |
||
| 443 | |||
| 444 | /** |
||
| 445 | * Gets the verified errors stored in the database |
||
| 446 | * |
||
| 447 | * @since 8.7.0 |
||
| 448 | * |
||
| 449 | * @return array $errors |
||
| 450 | */ |
||
| 451 | View Code Duplication | public function get_verified_errors() { |
|
| 452 | |||
| 453 | $verified_errors = get_option( self::STORED_VERIFIED_ERRORS_OPTION ); |
||
| 454 | |||
| 455 | if ( ! is_array( $verified_errors ) ) { |
||
| 456 | $verified_errors = array(); |
||
| 457 | } |
||
| 458 | |||
| 459 | $verified_errors = $this->garbage_collector( $verified_errors ); |
||
| 460 | |||
| 461 | return $verified_errors; |
||
| 462 | } |
||
| 463 | |||
| 464 | /** |
||
| 465 | * Removes expired errors from the array |
||
| 466 | * |
||
| 467 | * This method is called by get_stored_errors and get_verified errors and filters their result |
||
| 468 | * Whenever a new error is stored to the database or verified, this will be triggered and the |
||
| 469 | * expired error will be permantently removed from the database |
||
| 470 | * |
||
| 471 | * @since 8.7.0 |
||
| 472 | * |
||
| 473 | * @param array $errors array of errors as stored in the database. |
||
| 474 | * @return array |
||
| 475 | */ |
||
| 476 | private function garbage_collector( $errors ) { |
||
| 477 | foreach ( $errors as $error_code => $users ) { |
||
| 478 | foreach ( $users as $user_id => $error ) { |
||
| 479 | if ( self::ERROR_LIFE_TIME < time() - (int) $error['timestamp'] ) { |
||
| 480 | unset( $errors[ $error_code ][ $user_id ] ); |
||
| 481 | } |
||
| 482 | } |
||
| 483 | } |
||
| 484 | // Clear empty error codes. |
||
| 485 | $errors = array_filter( |
||
| 486 | $errors, |
||
| 487 | function( $user_errors ) { |
||
| 488 | return ! empty( $user_errors ); |
||
| 489 | } |
||
| 490 | ); |
||
| 491 | return $errors; |
||
| 492 | } |
||
| 493 | |||
| 494 | /** |
||
| 495 | * Delete all stored and verified errors from the database |
||
| 496 | * |
||
| 497 | * @since 8.7.0 |
||
| 498 | * |
||
| 499 | * @return void |
||
| 500 | */ |
||
| 501 | public function delete_all_errors() { |
||
| 502 | $this->delete_stored_errors(); |
||
| 503 | $this->delete_verified_errors(); |
||
| 504 | } |
||
| 505 | |||
| 506 | /** |
||
| 507 | * Delete the reported errors stored in the database |
||
| 508 | * |
||
| 509 | * @since 8.7.0 |
||
| 510 | * |
||
| 511 | * @return boolean True, if option is successfully deleted. False on failure. |
||
| 512 | */ |
||
| 513 | public function delete_stored_errors() { |
||
| 514 | return delete_option( self::STORED_ERRORS_OPTION ); |
||
| 515 | } |
||
| 516 | |||
| 517 | /** |
||
| 518 | * Delete the verified errors stored in the database |
||
| 519 | * |
||
| 520 | * @since 8.7.0 |
||
| 521 | * |
||
| 522 | * @return boolean True, if option is successfully deleted. False on failure. |
||
| 523 | */ |
||
| 524 | public function delete_verified_errors() { |
||
| 525 | return delete_option( self::STORED_VERIFIED_ERRORS_OPTION ); |
||
| 526 | } |
||
| 527 | |||
| 528 | /** |
||
| 529 | * Gets an error based on the nonce |
||
| 530 | * |
||
| 531 | * Receives a nonce and finds the related error. |
||
| 532 | * |
||
| 533 | * @since 8.7.0 |
||
| 534 | * |
||
| 535 | * @param string $nonce The nonce created for the error we want to get. |
||
| 536 | * @return null|array Returns the error array representation or null if error not found. |
||
| 537 | */ |
||
| 538 | public function get_error_by_nonce( $nonce ) { |
||
| 539 | $errors = $this->get_stored_errors(); |
||
| 540 | foreach ( $errors as $user_group ) { |
||
| 541 | foreach ( $user_group as $error ) { |
||
| 542 | if ( $error['nonce'] === $nonce ) { |
||
| 543 | return $error; |
||
| 544 | } |
||
| 545 | } |
||
| 546 | } |
||
| 547 | return null; |
||
| 548 | } |
||
| 549 | |||
| 550 | /** |
||
| 551 | * Adds an error to the verified error list |
||
| 552 | * |
||
| 553 | * @since 8.7.0 |
||
| 554 | * |
||
| 555 | * @param array $error The error array, as it was saved in the unverified errors list. |
||
| 556 | * @return void |
||
| 557 | */ |
||
| 558 | public function verify_error( $error ) { |
||
| 559 | |||
| 560 | $verified_errors = $this->get_verified_errors(); |
||
| 561 | $error_code = $error['error_code']; |
||
| 562 | $user_id = $error['user_id']; |
||
| 563 | |||
| 564 | if ( ! isset( $verified_errors[ $error_code ] ) ) { |
||
| 565 | $verified_errors[ $error_code ] = array(); |
||
| 566 | } |
||
| 567 | |||
| 568 | $verified_errors[ $error_code ][ $user_id ] = $error; |
||
| 569 | |||
| 570 | update_option( self::STORED_VERIFIED_ERRORS_OPTION, $verified_errors ); |
||
| 571 | |||
| 572 | } |
||
| 573 | |||
| 574 | /** |
||
| 575 | * Register REST API end point for error hanlding. |
||
| 576 | * |
||
| 577 | * @since 8.7.0 |
||
| 578 | * |
||
| 579 | * @return void |
||
| 580 | */ |
||
| 581 | public function register_verify_error_endpoint() { |
||
| 582 | register_rest_route( |
||
| 583 | 'jetpack/v4', |
||
| 584 | '/verify_xmlrpc_error', |
||
| 585 | array( |
||
| 586 | 'methods' => \WP_REST_Server::CREATABLE, |
||
| 587 | 'callback' => array( $this, 'verify_xml_rpc_error' ), |
||
| 588 | 'permission_callback' => '__return_true', |
||
| 589 | 'args' => array( |
||
| 590 | 'nonce' => array( |
||
| 591 | 'required' => true, |
||
| 592 | 'type' => 'string', |
||
| 593 | ), |
||
| 594 | ), |
||
| 595 | ) |
||
| 596 | ); |
||
| 597 | } |
||
| 598 | |||
| 599 | /** |
||
| 600 | * Handles verification that a xml rpc error is legit and came from WordPres.com |
||
| 601 | * |
||
| 602 | * @since 8.7.0 |
||
| 603 | * |
||
| 604 | * @param \WP_REST_Request $request The request sent to the WP REST API. |
||
| 605 | * |
||
| 606 | * @return boolean |
||
| 607 | */ |
||
| 608 | public function verify_xml_rpc_error( \WP_REST_Request $request ) { |
||
| 609 | |||
| 610 | $error = $this->get_error_by_nonce( $request['nonce'] ); |
||
| 611 | |||
| 612 | if ( $error ) { |
||
| 613 | $this->verify_error( $error ); |
||
| 614 | return new \WP_REST_Response( true, 200 ); |
||
| 615 | } |
||
| 616 | |||
| 617 | return new \WP_REST_Response( false, 200 ); |
||
| 618 | |||
| 619 | } |
||
| 620 | |||
| 621 | } |
||
| 622 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.