WooCommerce Payments – Fully Integrated Solution Built and Supported by Woo - Version 1.4.0

Version Description

  • 2020-09-02 =
  • Add - Initial support for WooCommerce Subscriptions: Signing up for subscriptions, scheduled payments, and customer-initiated payment method changes.
  • Add - Added a link to transaction details from order screens.
  • Add - Allow merchant to edit statement descriptor.
  • Fix - Do not redirect to the onboarding page after completing the WC4.5-beta wizard.
  • Fix - Save order metadata before the payment is completed to avoid missing payments.
  • Update - Bumped the minimum Jetpack requirement to version 8.2.
Download this release

Release Info

Developer automattic
Plugin Icon 128x128 WooCommerce Payments – Fully Integrated Solution Built and Supported by Woo
Version 1.4.0
Comparing to
See all releases

Code changes from version 1.3.0 to 1.4.0

Files changed (40) hide show
  1. changelog.txt +8 -0
  2. includes/class-wc-payment-gateway-wcpay.php +399 -196
  3. includes/class-wc-payments-account.php +57 -1
  4. includes/class-wc-payments-token-service.php +6 -4
  5. includes/class-wc-payments.php +9 -2
  6. includes/compat/subscriptions/class-wc-payment-gateway-wcpay-subscriptions-compat.php +139 -0
  7. includes/data-types/class-payment-information.php +176 -0
  8. includes/wc-payment-api/class-wc-payments-api-client.php +25 -2
  9. includes/wc-payment-api/class-wc-payments-http.php +7 -4
  10. readme.txt +10 -2
  11. vendor/autoload.php +1 -1
  12. vendor/autoload_packages.php +2 -2
  13. vendor/automattic/jetpack-config/src/class-config.php +17 -7
  14. vendor/automattic/jetpack-connection/.gitignore +1 -0
  15. vendor/automattic/jetpack-connection/composer.json +8 -5
  16. vendor/automattic/jetpack-connection/docs/register-site.md +92 -5
  17. vendor/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php +3 -2
  18. vendor/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php +6 -3
  19. vendor/automattic/jetpack-connection/legacy/class-jetpack-xmlrpc-server.php +25 -19
  20. vendor/automattic/jetpack-connection/src/class-error-handler.php +621 -0
  21. vendor/automattic/jetpack-connection/src/class-manager.php +237 -49
  22. vendor/automattic/jetpack-connection/src/class-plugin-storage.php +55 -18
  23. vendor/automattic/jetpack-connection/src/class-plugin.php +28 -1
  24. vendor/automattic/jetpack-connection/src/class-rest-connector.php +234 -6
  25. vendor/automattic/jetpack-connection/src/error-handlers/class-invalid-blog-token.php +94 -0
  26. vendor/automattic/jetpack-constants/phpunit.xml +7 -0
  27. vendor/automattic/jetpack-options/composer.json +1 -1
  28. vendor/automattic/jetpack-options/legacy/class-jetpack-options.php +26 -3
  29. vendor/automattic/jetpack-roles/phpunit.xml +7 -0
  30. vendor/automattic/jetpack-status/README.md +34 -0
  31. vendor/automattic/jetpack-status/composer.json +31 -0
  32. vendor/automattic/jetpack-status/src/class-status.php +235 -0
  33. vendor/composer/ClassLoader.php +1 -1
  34. vendor/composer/autoload_classmap.php +3 -0
  35. vendor/composer/autoload_classmap_package.php +28 -16
  36. vendor/composer/autoload_files_package.php +1 -1
  37. vendor/composer/autoload_real.php +7 -10
  38. vendor/composer/autoload_static.php +7 -4
  39. vendor/composer/installed.json +70 -34
  40. woocommerce-payments.php +35 -2
changelog.txt CHANGED
@@ -1,5 +1,13 @@
1
  *** WooCommerce Payments Changelog ***
2
 
 
 
 
 
 
 
 
 
3
  = 1.3.0 - 2020-08-17 =
4
  * Add - Support for saved cards.
5
  * Add - Search bar for transactions.
1
  *** WooCommerce Payments Changelog ***
2
 
3
+ = 1.4.0 - 2020-09-02 =
4
+ * Add - Initial support for WooCommerce Subscriptions: Signing up for subscriptions, scheduled payments, and customer-initiated payment method changes.
5
+ * Add - Added a link to transaction details from order screens.
6
+ * Add - Allow merchant to edit statement descriptor.
7
+ * Fix - Do not redirect to the onboarding page after completing the WC4.5-beta wizard.
8
+ * Fix - Save order metadata before the payment is completed to avoid missing payments.
9
+ * Update - Bumped the minimum Jetpack requirement to version 8.2.
10
+
11
  = 1.3.0 - 2020-08-17 =
12
  * Add - Support for saved cards.
13
  * Add - Search bar for transactions.
includes/class-wc-payment-gateway-wcpay.php CHANGED
@@ -10,6 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
10
  }
11
 
12
  use WCPay\Logger;
 
13
  use WCPay\Exceptions\WC_Payments_Intent_Authentication_Exception;
14
  use WCPay\Tracker;
15
 
@@ -99,27 +100,32 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
99
 
100
  // Define setting fields.
101
  $this->form_fields = [
102
- 'enabled' => [
103
  'title' => __( 'Enable/disable', 'woocommerce-payments' ),
104
  'label' => __( 'Enable WooCommerce Payments', 'woocommerce-payments' ),
105
  'type' => 'checkbox',
106
  'description' => '',
107
  'default' => 'no',
108
  ],
109
- 'account_details' => [
110
  'type' => 'account_actions',
111
  ],
112
- 'account_status' => [
113
  'type' => 'account_status',
114
  ],
115
- 'manual_capture' => [
 
 
 
 
 
116
  'title' => __( 'Manual capture', 'woocommerce-payments' ),
117
  'label' => __( 'Issue an authorization on checkout, and capture later.', 'woocommerce-payments' ),
118
  'type' => 'checkbox',
119
  'description' => __( 'Charge must be captured within 7 days of authorization, otherwise the authorization and order will be canceled.', 'woocommerce-payments' ),
120
  'default' => 'no',
121
  ],
122
- 'test_mode' => [
123
  'title' => __( 'Test mode', 'woocommerce-payments' ),
124
  'label' => __( 'Enable test mode', 'woocommerce-payments' ),
125
  'type' => 'checkbox',
@@ -127,7 +133,7 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
127
  'default' => 'no',
128
  'desc_tip' => true,
129
  ],
130
- 'enable_logging' => [
131
  'title' => __( 'Debug log', 'woocommerce-payments' ),
132
  'label' => __( 'When enabled debug notes will be added to the log.', 'woocommerce-payments' ),
133
  'type' => 'checkbox',
@@ -146,7 +152,9 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
146
  // Load the settings.
147
  $this->init_settings();
148
 
 
149
  add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
 
150
  add_action( 'woocommerce_order_actions', [ $this, 'add_order_actions' ] );
151
  add_action( 'woocommerce_order_action_capture_charge', [ $this, 'capture_charge' ] );
152
  add_action( 'woocommerce_order_action_cancel_authorization', [ $this, 'cancel_authorization' ] );
@@ -388,223 +396,263 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
388
  /**
389
  * Process the payment for a given order.
390
  *
391
- * @param int $order_id Order ID to process the payment for.
 
392
  *
393
  * @return array|null An array with result of payment and redirect URL, or nothing.
394
  */
395
- public function process_payment( $order_id ) {
396
  $order = wc_get_order( $order_id );
397
- return $this->process_payment_for_order( $order, WC()->cart );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  }
399
 
400
  /**
401
  * Process the payment for a given order.
402
  *
403
- * @param WC_Order $order Order to process the payment for.
404
- * @param WC_Cart $cart The WC_Cart object.
 
 
 
405
  *
406
  * @return array|null An array with result of payment and redirect URL, or nothing.
 
407
  */
408
- public function process_payment_for_order( $order, $cart ) {
409
- try {
410
- $order_id = $order->get_id();
411
- $amount = $order->get_total();
412
-
413
- if ( $amount > 0 ) {
414
- // Get the payment method from the request (generated when the user entered their card details).
415
- $payment_method = $this->get_payment_method_from_request();
416
- $manual_capture = 'yes' === $this->get_option( 'manual_capture' );
417
- $name = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() );
418
- $email = sanitize_email( $order->get_billing_email() );
419
- $save_payment_method = ! empty( $_POST[ 'wc-' . self::GATEWAY_ID . '-new-payment-method' ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
420
- $is_saved_method = empty( $_POST['wcpay-payment-method'] ) && ! empty( $_POST[ 'wc-' . self::GATEWAY_ID . '-payment-token' ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
421
-
422
- // Determine the customer making the payment, create one if we don't have one already.
423
- $user = wp_get_current_user();
424
- $customer_id = $this->customer_service->get_customer_id_by_user_id( $user->ID );
425
-
426
- if ( null === $customer_id ) {
427
- // Create a new customer.
428
- $customer_id = $this->customer_service->create_customer_for_user( $user, $name, $email );
429
- } else {
430
- // Update the existing customer with the current details. In the event the old customer can't be
431
- // found a new one is created, so we update the customer ID here as well.
432
- $customer_id = $this->customer_service->update_customer_for_user( $customer_id, $user, $name, $email );
433
- }
434
 
435
- // Update saved payment method information with checkout values, as some saved methods might not have billing details.
436
- if ( $is_saved_method ) {
437
- try {
438
- $this->customer_service->update_payment_method_with_billing_details_from_order( $payment_method, $order );
439
- } catch ( Exception $e ) {
440
- // If updating the payment method fails, log the error message but catch the error to avoid crashing the checkout flow.
441
- Logger::log( 'Error when updating saved payment method: ' . $e->getMessage() );
442
- }
443
- }
444
 
445
- $metadata = [
446
- 'customer_name' => $name,
447
- 'customer_email' => $email,
448
- 'site_url' => esc_url( get_site_url() ),
449
- 'order_id' => $order->get_id(),
450
- ];
451
-
452
- // Create intention, try to confirm it & capture the charge (if 3DS is not required).
453
- $intent = $this->payments_api_client->create_and_confirm_intention(
454
- WC_Payments_Utils::prepare_amount( $amount, 'USD' ),
455
- 'usd',
456
- $payment_method,
457
- $customer_id,
458
- $manual_capture,
459
- $save_payment_method,
460
- $metadata,
461
- $this->get_level3_data_from_order( $order )
462
- );
463
 
464
- $intent_id = $intent->get_id();
465
- $status = $intent->get_status();
 
 
 
 
 
 
 
466
 
467
- if ( $save_payment_method && ( 'succeeded' === $status || 'requires_capture' === $status ) ) {
468
- try {
469
- $this->token_service->add_payment_method_to_user( $payment_method, wp_get_current_user() );
470
- } catch ( Exception $e ) {
471
- // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow.
472
- Logger::log( 'Error when saving payment method: ' . $e->getMessage() );
473
- }
474
- }
475
 
476
- switch ( $status ) {
477
- case 'succeeded':
478
- $note = sprintf(
479
- WC_Payments_Utils::esc_interpolated_html(
480
- /* translators: %1: the successfully charged amount, %2: transaction ID of the payment */
481
- __( 'A payment of %1$s was <strong>successfully charged</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
482
- [
483
- 'strong' => '<strong>',
484
- 'code' => '<code>',
485
- ]
486
- ),
487
- wc_price( $amount ),
488
- $intent_id
489
- );
490
 
491
- $order->update_meta_data( '_intent_id', $intent_id );
492
- $order->update_meta_data( '_charge_id', $intent->get_charge_id() );
493
- $order->update_meta_data( '_intention_status', $status );
494
- $order->save();
 
 
 
 
 
 
 
 
 
495
 
496
- $order->add_order_note( $note );
497
- $order->payment_complete( $intent_id );
498
- break;
499
- case 'requires_capture':
500
- $note = sprintf(
501
- WC_Payments_Utils::esc_interpolated_html(
502
- /* translators: %1: the authorized amount, %2: transaction ID of the payment */
503
- __( 'A payment of %1$s was <strong>authorized</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
504
- [
505
- 'strong' => '<strong>',
506
- 'code' => '<code>',
507
- ]
508
- ),
509
- wc_price( $amount ),
510
- $intent_id
511
- );
512
 
513
- $order->update_status( 'on-hold', $note );
514
- $order->set_transaction_id( $intent_id );
515
-
516
- $order->update_meta_data( '_intent_id', $intent_id );
517
- $order->update_meta_data( '_charge_id', $intent->get_charge_id() );
518
- $order->update_meta_data( '_intention_status', $status );
519
- $order->save();
520
-
521
- break;
522
- case 'requires_action':
523
- // Add a note in case the customer does not complete the payment (exits the page),
524
- // so the store owner has some information about what happened to create an order.
525
- $note = sprintf(
526
- WC_Payments_Utils::esc_interpolated_html(
527
- /* translators: %1: the authorized amount, %2: transaction ID of the payment */
528
- __( 'A payment of %1$s was <strong>started</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
529
- [
530
- 'strong' => '<strong>',
531
- 'code' => '<code>',
532
- ]
533
- ),
534
- wc_price( $amount ),
535
- $intent_id
536
- );
537
- $order->add_order_note( $note );
538
 
539
- $order->update_meta_data( '_intent_id', $intent_id );
540
- $order->update_meta_data( '_intention_status', $status );
541
- $order->save();
542
-
543
- return [
544
- 'result' => 'success',
545
- // Include a new nonce for update_order_status to ensure the update order
546
- // status call works when a guest user creates an account during checkout.
547
- 'redirect' => sprintf(
548
- '#wcpay-confirm-pi:%s:%s:%s',
549
- $order_id,
550
- $intent->get_client_secret(),
551
- wp_create_nonce( 'wcpay_update_order_status_nonce' )
552
- ),
553
- ];
554
  }
555
- } else {
556
- $order->payment_complete();
557
  }
558
 
559
- wc_reduce_stock_levels( $order_id );
560
- $cart->empty_cart();
 
 
561
 
562
- return [
563
- 'result' => 'success',
564
- 'redirect' => $this->get_return_url( $order ),
565
- ];
566
- } catch ( Exception $e ) {
567
- // TODO: Create plugin specific exceptions so that we can be smarter about what we create notices for.
568
- wc_add_notice( $e->getMessage(), 'error' );
 
 
 
 
 
 
 
569
 
570
- $order->update_status( 'failed' );
 
 
 
571
 
572
- return [
573
- 'result' => 'fail',
574
- 'redirect' => '',
575
- ];
576
- }
577
- }
 
 
 
 
 
 
 
 
 
 
 
 
578
 
579
- /**
580
- * Extract the payment method from the request's POST variables
581
- *
582
- * @return string
583
- * @throws Exception - If no payment method is found.
584
- */
585
- private function get_payment_method_from_request() {
586
- // phpcs:disable WordPress.Security.NonceVerification.Missing
587
- if ( empty( $_POST['wcpay-payment-method'] ) && empty( $_POST[ 'wc-' . self::GATEWAY_ID . '-payment-token' ] ) ) {
588
- // If no payment method is set then stop here with an error.
589
- throw new Exception( __( 'Payment method not found.', 'woocommerce-payments' ) );
590
- }
591
 
592
- $payment_method = ! empty( $_POST['wcpay-payment-method'] ) ? wc_clean( $_POST['wcpay-payment-method'] ) : null; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
 
 
 
593
 
594
- if ( empty( $payment_method ) ) {
595
- $token_id = wc_clean( $_POST[ 'wc-' . self::GATEWAY_ID . '-payment-token' ] ); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
596
- $token = WC_Payment_Tokens::get( $token_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
- if ( ! $token || self::GATEWAY_ID !== $token->get_gateway_id() || $token->get_user_id() !== get_current_user_id() ) {
599
- throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-payments' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  }
 
601
 
602
- $payment_method = $token->get_token();
 
 
603
  }
604
 
605
- // phpcs:enable WordPress.Security.NonceVerification.Missing
 
 
 
 
606
 
607
- return $payment_method;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  }
609
 
610
  /**
@@ -717,6 +765,127 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
717
  return ob_get_clean();
718
  }
719
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  /**
721
  * Generate markup for account actions
722
  */
@@ -995,6 +1164,12 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
995
  $intent_id
996
  );
997
  $order->add_order_note( $note );
 
 
 
 
 
 
998
  $order->payment_complete( $intent_id );
999
  break;
1000
  case 'requires_capture':
@@ -1013,6 +1188,12 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1013
  // Save the note separately because if there is no change in status
1014
  // then the note is not saved using WC_Order::update_status.
1015
  $order->add_order_note( $note );
 
 
 
 
 
 
1016
  $order->update_status( 'on-hold' );
1017
  $order->set_transaction_id( $intent_id );
1018
  break;
@@ -1037,16 +1218,12 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1037
  }
1038
 
1039
  if ( 'succeeded' === $status || 'requires_capture' === $status ) {
1040
- // The order is successful, so update it to reflect that.
1041
- $order->update_meta_data( '_charge_id', $intent->get_charge_id() );
1042
- $order->update_meta_data( '_intention_status', $status );
1043
- $order->save();
1044
-
1045
  wc_reduce_stock_levels( $order_id );
1046
  WC()->cart->empty_cart();
1047
 
1048
  if ( ! empty( $payment_method_id ) ) {
1049
  try {
 
1050
  $this->token_service->add_payment_method_to_user( $payment_method_id, wp_get_current_user() );
1051
  } catch ( Exception $e ) {
1052
  // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow.
@@ -1112,13 +1289,13 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1112
  public function add_payment_method() {
1113
  try {
1114
 
1115
- // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1116
  if ( ! isset( $_POST['wcpay-setup-intent'] ) ) {
1117
  throw new Exception( __( 'A WooCommerce Payments payment method was not provided', 'woocommerce-payments' ) );
1118
  }
1119
 
 
1120
  $setup_intent_id = ! empty( $_POST['wcpay-setup-intent'] ) ? wc_clean( $_POST['wcpay-setup-intent'] ) : false;
1121
- // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1122
 
1123
  $customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
1124
 
@@ -1154,7 +1331,8 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1154
  * @throws Exception - When an error occurs in setup intent creation.
1155
  */
1156
  public function create_setup_intent() {
1157
- $payment_method = $this->get_payment_method_from_request();
 
1158
 
1159
  // Determine the customer adding the payment method, create one if we don't have one already.
1160
  $user = wp_get_current_user();
@@ -1164,7 +1342,7 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1164
  }
1165
 
1166
  return $this->payments_api_client->create_setup_intent(
1167
- $payment_method,
1168
  $customer_id
1169
  );
1170
  }
@@ -1195,4 +1373,29 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
1195
  );
1196
  }
1197
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  }
10
  }
11
 
12
  use WCPay\Logger;
13
+ use WCPay\DataTypes\Payment_Information;
14
  use WCPay\Exceptions\WC_Payments_Intent_Authentication_Exception;
15
  use WCPay\Tracker;
16
 
100
 
101
  // Define setting fields.
102
  $this->form_fields = [
103
+ 'enabled' => [
104
  'title' => __( 'Enable/disable', 'woocommerce-payments' ),
105
  'label' => __( 'Enable WooCommerce Payments', 'woocommerce-payments' ),
106
  'type' => 'checkbox',
107
  'description' => '',
108
  'default' => 'no',
109
  ],
110
+ 'account_details' => [
111
  'type' => 'account_actions',
112
  ],
113
+ 'account_status' => [
114
  'type' => 'account_status',
115
  ],
116
+ 'account_statement_descriptor' => [
117
+ 'type' => 'account_statement_descriptor',
118
+ 'title' => __( 'Customer bank statement', 'woocommerce-payments' ),
119
+ 'description' => __( 'Edit the way your store name appears on your customers’ bank statements.', 'woocommerce-payments' ),
120
+ ],
121
+ 'manual_capture' => [
122
  'title' => __( 'Manual capture', 'woocommerce-payments' ),
123
  'label' => __( 'Issue an authorization on checkout, and capture later.', 'woocommerce-payments' ),
124
  'type' => 'checkbox',
125
  'description' => __( 'Charge must be captured within 7 days of authorization, otherwise the authorization and order will be canceled.', 'woocommerce-payments' ),
126
  'default' => 'no',
127
  ],
128
+ 'test_mode' => [
129
  'title' => __( 'Test mode', 'woocommerce-payments' ),
130
  'label' => __( 'Enable test mode', 'woocommerce-payments' ),
131
  'type' => 'checkbox',
133
  'default' => 'no',
134
  'desc_tip' => true,
135
  ],
136
+ 'enable_logging' => [
137
  'title' => __( 'Debug log', 'woocommerce-payments' ),
138
  'label' => __( 'When enabled debug notes will be added to the log.', 'woocommerce-payments' ),
139
  'type' => 'checkbox',
152
  // Load the settings.
153
  $this->init_settings();
154
 
155
+ add_filter( 'woocommerce_settings_api_sanitized_fields_' . $this->id, [ $this, 'sanitize_plugin_settings' ] );
156
  add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
157
+ add_action( 'admin_notices', [ $this, 'display_errors' ], 9999 );
158
  add_action( 'woocommerce_order_actions', [ $this, 'add_order_actions' ] );
159
  add_action( 'woocommerce_order_action_capture_charge', [ $this, 'capture_charge' ] );
160
  add_action( 'woocommerce_order_action_cancel_authorization', [ $this, 'cancel_authorization' ] );
396
  /**
397
  * Process the payment for a given order.
398
  *
399
+ * @param int $order_id Order ID to process the payment for.
400
+ * @param bool $force_save_payment_method Whether this is a one-off payment (false) or it's the first installment of a recurring payment (true).
401
  *
402
  * @return array|null An array with result of payment and redirect URL, or nothing.
403
  */
404
+ public function process_payment( $order_id, $force_save_payment_method = false ) {
405
  $order = wc_get_order( $order_id );
406
+
407
+ try {
408
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
409
+ $payment_information = Payment_Information::from_payment_request( $_POST );
410
+ $manual_capture = 'yes' === $this->get_option( 'manual_capture' );
411
+
412
+ return $this->process_payment_for_order( $order, WC()->cart, $payment_information, $manual_capture, $force_save_payment_method );
413
+ } catch ( Exception $e ) {
414
+ // TODO: Create plugin specific exceptions so that we can be smarter about what we create notices for.
415
+ wc_add_notice( $e->getMessage(), 'error' );
416
+
417
+ $order->update_status( 'failed' );
418
+
419
+ return [
420
+ 'result' => 'fail',
421
+ 'redirect' => '',
422
+ ];
423
+ }
424
  }
425
 
426
  /**
427
  * Process the payment for a given order.
428
  *
429
+ * @param WC_Order $order Order.
430
+ * @param WC_Cart $cart Cart.
431
+ * @param WC_Payment_Information $payment_information Payment info.
432
+ * @param bool $manual_capture Indicates whether this payment is merchant-initiated (true) or customer-initated (false).
433
+ * @param bool $force_save_payment_method Whether this is a one-off payment (false) or it's the first installment of a recurring payment (true).
434
  *
435
  * @return array|null An array with result of payment and redirect URL, or nothing.
436
+ * @throws WC_Payments_API_Exception Error processing the payment.
437
  */
438
+ public function process_payment_for_order( $order, $cart, $payment_information, $manual_capture, $force_save_payment_method = false ) {
439
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
440
+ $save_payment_method = ! $payment_information->is_using_saved_payment_method() && ( ! empty( $_POST[ 'wc-' . self::GATEWAY_ID . '-new-payment-method' ] ) || $force_save_payment_method );
441
+
442
+ $order_id = $order->get_id();
443
+ $amount = $order->get_total();
444
+ $user = $order->get_user() ?? wp_get_current_user();
445
+ $name = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() );
446
+ $email = sanitize_email( $order->get_billing_email() );
447
+ $metadata = [
448
+ 'customer_name' => $name,
449
+ 'customer_email' => $email,
450
+ 'site_url' => esc_url( get_site_url() ),
451
+ 'order_id' => $order_id,
452
+ ];
 
 
 
 
 
 
 
 
 
 
 
453
 
454
+ // We only force save the card during subscriptions.
455
+ // TODO: This is a bit flawed; make these 2 functionalities distinct.
456
+ if ( $force_save_payment_method ) {
457
+ $metadata['payment_type'] = 'recurring';
458
+ }
 
 
 
 
459
 
460
+ // Determine the customer making the payment, create one if we don't have one already.
461
+ $customer_id = $this->customer_service->get_customer_id_by_user_id( $user->ID );
462
+
463
+ if ( null === $customer_id ) {
464
+ // Create a new customer.
465
+ $customer_id = $this->customer_service->create_customer_for_user( $user, $name, $email );
466
+ } else {
467
+ // Update the existing customer with the current details. In the event the old customer can't be
468
+ // found a new one is created, so we update the customer ID here as well.
469
+ $customer_id = $this->customer_service->update_customer_for_user( $customer_id, $user, $name, $email );
470
+ }
 
 
 
 
 
 
 
471
 
472
+ // Update saved payment method information with checkout values, as some saved methods might not have billing details.
473
+ if ( $payment_information->is_using_saved_payment_method() ) {
474
+ try {
475
+ $this->customer_service->update_payment_method_with_billing_details_from_order( $payment_information->get_payment_method(), $order );
476
+ } catch ( Exception $e ) {
477
+ // If updating the payment method fails, log the error message but catch the error to avoid crashing the checkout flow.
478
+ Logger::log( 'Error when updating saved payment method: ' . $e->getMessage() );
479
+ }
480
+ }
481
 
482
+ $intent_failed = false;
 
 
 
 
 
 
 
483
 
484
+ // In case amount is 0 and we're not saving the payment method, we won't be using intents and can confirm the order payment.
485
+ if ( 0 === $amount && ! $save_payment_method ) {
486
+ $order->payment_complete();
487
+ }
 
 
 
 
 
 
 
 
 
 
488
 
489
+ if ( $amount > 0 ) {
490
+ // Create intention, try to confirm it & capture the charge (if 3DS is not required).
491
+ $intent = $this->payments_api_client->create_and_confirm_intention(
492
+ WC_Payments_Utils::prepare_amount( $amount, 'USD' ),
493
+ 'usd',
494
+ $payment_information->get_payment_method(),
495
+ $customer_id,
496
+ $manual_capture,
497
+ $save_payment_method,
498
+ $metadata,
499
+ $this->get_level3_data_from_order( $order ),
500
+ $payment_information->is_merchant_initiated()
501
+ );
502
 
503
+ $intent_id = $intent->get_id();
504
+ $status = $intent->get_status();
505
+ $charge_id = $intent->get_charge_id();
506
+ } else {
507
+ // For $0 orders, we need to save the payment method using a setup intent.
508
+ $intent = $this->payments_api_client->create_setup_intent(
509
+ $payment_information->get_payment_method(),
510
+ $customer_id,
511
+ 'true'
512
+ );
 
 
 
 
 
 
513
 
514
+ $intent_id = $intent['id'];
515
+ $status = $intent['status'];
516
+ $charge_id = '';
517
+
518
+ // In SCA cases the setup intent status might be requires_action and we should display the authentication modal.
519
+ // For now, since we're not supporting SCA cards, we can ignore that status.
520
+ if ( 'succeeded' !== $status ) {
521
+ throw new Exception( __( 'Failed to add the provided payment method. Please try again later', 'woocommerce-payments' ) );
522
+ }
523
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
 
525
+ if ( ! empty( $intent ) ) {
526
+ if ( 'succeeded' !== $status && 'requires_capture' !== $status ) {
527
+ $intent_failed = true;
528
+ }
529
+
530
+ if ( $save_payment_method && ! $intent_failed ) {
531
+ try {
532
+ $token = $this->token_service->add_payment_method_to_user( $payment_information->get_payment_method(), $user );
533
+ $payment_information->set_token( $token );
534
+ } catch ( Exception $e ) {
535
+ // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow.
536
+ Logger::log( 'Error when saving payment method: ' . $e->getMessage() );
 
 
 
537
  }
 
 
538
  }
539
 
540
+ if ( $payment_information->is_using_saved_payment_method() ) {
541
+ $token = $payment_information->get_payment_token();
542
+ $this->add_token_to_order( $order, $token );
543
+ }
544
 
545
+ switch ( $status ) {
546
+ case 'succeeded':
547
+ $note = sprintf(
548
+ WC_Payments_Utils::esc_interpolated_html(
549
+ /* translators: %1: the successfully charged amount, %2: transaction ID of the payment */
550
+ __( 'A payment of %1$s was <strong>successfully charged</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
551
+ [
552
+ 'strong' => '<strong>',
553
+ 'code' => '<code>',
554
+ ]
555
+ ),
556
+ wc_price( $amount ),
557
+ $intent_id
558
+ );
559
 
560
+ $order->update_meta_data( '_intent_id', $intent_id );
561
+ $order->update_meta_data( '_charge_id', $charge_id );
562
+ $order->update_meta_data( '_intention_status', $status );
563
+ $order->save();
564
 
565
+ if ( $amount > 0 ) {
566
+ $order->add_order_note( $note );
567
+ }
568
+ $order->payment_complete( $intent_id );
569
+ break;
570
+ case 'requires_capture':
571
+ $note = sprintf(
572
+ WC_Payments_Utils::esc_interpolated_html(
573
+ /* translators: %1: the authorized amount, %2: transaction ID of the payment */
574
+ __( 'A payment of %1$s was <strong>authorized</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
575
+ [
576
+ 'strong' => '<strong>',
577
+ 'code' => '<code>',
578
+ ]
579
+ ),
580
+ wc_price( $amount ),
581
+ $intent_id
582
+ );
583
 
584
+ $order->update_status( 'on-hold', $note );
585
+ $order->set_transaction_id( $intent_id );
 
 
 
 
 
 
 
 
 
 
586
 
587
+ $order->update_meta_data( '_intent_id', $intent_id );
588
+ $order->update_meta_data( '_charge_id', $charge_id );
589
+ $order->update_meta_data( '_intention_status', $status );
590
+ $order->save();
591
 
592
+ break;
593
+ case 'requires_action':
594
+ // Add a note in case the customer does not complete the payment (exits the page),
595
+ // so the store owner has some information about what happened to create an order.
596
+ $note = sprintf(
597
+ WC_Payments_Utils::esc_interpolated_html(
598
+ /* translators: %1: the authorized amount, %2: transaction ID of the payment */
599
+ __( 'A payment of %1$s was <strong>started</strong> using WooCommerce Payments (<code>%2$s</code>).', 'woocommerce-payments' ),
600
+ [
601
+ 'strong' => '<strong>',
602
+ 'code' => '<code>',
603
+ ]
604
+ ),
605
+ wc_price( $amount ),
606
+ $intent_id
607
+ );
608
+ $order->add_order_note( $note );
609
 
610
+ $order->update_meta_data( '_intent_id', $intent_id );
611
+ $order->update_meta_data( '_intention_status', $status );
612
+ $order->save();
613
+
614
+ return [
615
+ 'result' => 'success',
616
+ // Include a new nonce for update_order_status to ensure the update order
617
+ // status call works when a guest user creates an account during checkout.
618
+ 'redirect' => sprintf(
619
+ '#wcpay-confirm-pi:%s:%s:%s',
620
+ $order_id,
621
+ $intent->get_client_secret(),
622
+ wp_create_nonce( 'wcpay_update_order_status_nonce' )
623
+ ),
624
+ ];
625
  }
626
+ }
627
 
628
+ wc_reduce_stock_levels( $order_id );
629
+ if ( isset( $cart ) ) {
630
+ $cart->empty_cart();
631
  }
632
 
633
+ return [
634
+ 'result' => 'success',
635
+ 'redirect' => $this->get_return_url( $order ),
636
+ ];
637
+ }
638
 
639
+ /**
640
+ * Saves the payment token to the order.
641
+ *
642
+ * @param WC_Order $order The order.
643
+ * @param WC_Payment_Token $token The token to save.
644
+ */
645
+ protected function add_token_to_order( $order, $token ) {
646
+ $order_tokens = $order->get_payment_tokens();
647
+
648
+ // This could lead to tokens being saved twice in an order's payment tokens, but it is needed so that shoppers
649
+ // may re-use a previous card for the same subscription, as we consider the last token to be the active one.
650
+ // We can't remove the previous entry for the token because WC_Order does not support removal of tokens [1] and
651
+ // we can't delete the token as it might be used somewhere else.
652
+ // [1] https://github.com/woocommerce/woocommerce/issues/11857.
653
+ if ( $token->get_id() !== end( $order_tokens ) ) {
654
+ $order->add_payment_token( $token );
655
+ }
656
  }
657
 
658
  /**
765
  return ob_get_clean();
766
  }
767
 
768
+ /**
769
+ * Generates markup for account statement descriptor field.
770
+ *
771
+ * @param string $key Field key.
772
+ * @param array $data Field data.
773
+ *
774
+ * @return string
775
+ */
776
+ public function generate_account_statement_descriptor_html( $key, $data ) {
777
+ if ( ! $this->is_connected() ) {
778
+ return '';
779
+ }
780
+
781
+ return parent::generate_text_html( $key, $data );
782
+ }
783
+
784
+ /**
785
+ * Get option from DB or connected account.
786
+ *
787
+ * Overrides parent method to retrieve some options from connected account.
788
+ *
789
+ * @param string $key Option key.
790
+ * @param mixed $empty_value Value when empty.
791
+ * @return string The value specified for the option or a default value for the option.
792
+ */
793
+ public function get_option( $key, $empty_value = null ) {
794
+ switch ( $key ) {
795
+ case 'account_statement_descriptor':
796
+ return $this->get_account_statement_descriptor();
797
+ default:
798
+ return parent::get_option( $key, $empty_value );
799
+ }
800
+ }
801
+
802
+ /**
803
+ * Sanitizes plugin settings before saving them in site's DB.
804
+ *
805
+ * Filters out some values stored in connected account.
806
+ *
807
+ * @param array $settings Plugin settings.
808
+ * @return array Sanitized settings.
809
+ */
810
+ public function sanitize_plugin_settings( $settings ) {
811
+ if ( isset( $settings['account_statement_descriptor'] ) ) {
812
+ $this->update_statement_descriptor( $settings['account_statement_descriptor'] );
813
+ unset( $settings['account_statement_descriptor'] );
814
+ }
815
+
816
+ return $settings;
817
+ }
818
+
819
+ /**
820
+ * Gets connected account statement descriptor.
821
+ *
822
+ * @param mixed $empty_value Empty value to return when not connected or fails to fetch account descriptor.
823
+ *
824
+ * @return string Statement descriptor of default value.
825
+ */
826
+ private function get_account_statement_descriptor( $empty_value = null ) {
827
+ try {
828
+ if ( ! $this->is_connected() ) {
829
+ return $empty_value;
830
+ }
831
+
832
+ return $this->account->get_statement_descriptor();
833
+ } catch ( Exception $e ) {
834
+ Logger::error( 'Failed to get account statement descriptor.' . $e );
835
+ return $empty_value;
836
+ }
837
+ }
838
+
839
+ /**
840
+ * Handles statement descriptor update when plugin settings saved.
841
+ *
842
+ * Adds error message to display in admin notices in case of failure.
843
+ *
844
+ * @param string $statement_descriptor Statement descriptor value.
845
+ */
846
+ private function update_statement_descriptor( $statement_descriptor ) {
847
+ if ( empty( $statement_descriptor ) ) {
848
+ return;
849
+ }
850
+
851
+ $account_settings = [
852
+ 'statement_descriptor' => $statement_descriptor,
853
+ ];
854
+ $error_message = $this->account->update_stripe_account( $account_settings );
855
+
856
+ if ( is_string( $error_message ) ) {
857
+ $msg = __( 'Failed to update Statement descriptor. ', 'woocommerce-payments' ) . $error_message;
858
+ $this->add_error( $msg );
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Validates statement descriptor value
864
+ *
865
+ * @param string $key Field key.
866
+ * @param string $value Posted Value.
867
+ *
868
+ * @return string Sanitized statement descriptor.
869
+ * @throws Exception When statement descriptor is invalid.
870
+ */
871
+ public function validate_account_statement_descriptor_field( $key, $value ) {
872
+ // Validation can be done with a single regex but splitting into multiple for better readability.
873
+ $valid_length = '/^.{5,22}$/';
874
+ $has_one_letter = '/^.*[a-zA-Z]+/';
875
+ $no_specials = '/^[^*"\'<>]*$/';
876
+
877
+ if (
878
+ ! preg_match( $valid_length, $value ) ||
879
+ ! preg_match( $has_one_letter, $value ) ||
880
+ ! preg_match( $no_specials, $value )
881
+ ) {
882
+ throw new Exception( __( 'Customer bank statement is invalid. Statement should be between 5 and 22 characters long, contain at least single Latin character and does not contain special characters: \' " * &lt; &gt;', 'woocommerce-payments' ) );
883
+ }
884
+
885
+ // Perform text validation after own checks to prevent special characters like < > escaped before own validation.
886
+ return $this->validate_text_field( $key, $value );
887
+ }
888
+
889
  /**
890
  * Generate markup for account actions
891
  */
1164
  $intent_id
1165
  );
1166
  $order->add_order_note( $note );
1167
+
1168
+ // The order is successful, so update it to reflect that.
1169
+ $order->update_meta_data( '_charge_id', $intent->get_charge_id() );
1170
+ $order->update_meta_data( '_intention_status', $status );
1171
+ $order->save();
1172
+
1173
  $order->payment_complete( $intent_id );
1174
  break;
1175
  case 'requires_capture':
1188
  // Save the note separately because if there is no change in status
1189
  // then the note is not saved using WC_Order::update_status.
1190
  $order->add_order_note( $note );
1191
+
1192
+ // The order is successful, so update it to reflect that.
1193
+ $order->update_meta_data( '_charge_id', $intent->get_charge_id() );
1194
+ $order->update_meta_data( '_intention_status', $status );
1195
+ $order->save();
1196
+
1197
  $order->update_status( 'on-hold' );
1198
  $order->set_transaction_id( $intent_id );
1199
  break;
1218
  }
1219
 
1220
  if ( 'succeeded' === $status || 'requires_capture' === $status ) {
 
 
 
 
 
1221
  wc_reduce_stock_levels( $order_id );
1222
  WC()->cart->empty_cart();
1223
 
1224
  if ( ! empty( $payment_method_id ) ) {
1225
  try {
1226
+ // TODO: Add token to subscriptions related to this order.
1227
  $this->token_service->add_payment_method_to_user( $payment_method_id, wp_get_current_user() );
1228
  } catch ( Exception $e ) {
1229
  // If saving the token fails, log the error message but catch the error to avoid crashing the checkout flow.
1289
  public function add_payment_method() {
1290
  try {
1291
 
1292
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1293
  if ( ! isset( $_POST['wcpay-setup-intent'] ) ) {
1294
  throw new Exception( __( 'A WooCommerce Payments payment method was not provided', 'woocommerce-payments' ) );
1295
  }
1296
 
1297
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1298
  $setup_intent_id = ! empty( $_POST['wcpay-setup-intent'] ) ? wc_clean( $_POST['wcpay-setup-intent'] ) : false;
 
1299
 
1300
  $customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
1301
 
1331
  * @throws Exception - When an error occurs in setup intent creation.
1332
  */
1333
  public function create_setup_intent() {
1334
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1335
+ $payment_information = Payment_Information::from_payment_request( $_POST );
1336
 
1337
  // Determine the customer adding the payment method, create one if we don't have one already.
1338
  $user = wp_get_current_user();
1342
  }
1343
 
1344
  return $this->payments_api_client->create_setup_intent(
1345
+ $payment_information->get_payment_method(),
1346
  $customer_id
1347
  );
1348
  }
1373
  );
1374
  }
1375
  }
1376
+
1377
+ /**
1378
+ * Add a url to the admin order page that links directly to the transactions detail view.
1379
+ *
1380
+ * @since 1.4.0
1381
+ *
1382
+ * @param WC_Order $order The context passed into this function when the user view the order details page in WordPress admin.
1383
+ * @return string
1384
+ */
1385
+ public function get_transaction_url( $order ) {
1386
+ $charge_id = $order->get_meta( '_charge_id' );
1387
+
1388
+ if ( empty( $charge_id ) ) {
1389
+ return '';
1390
+ }
1391
+
1392
+ return add_query_arg(
1393
+ [
1394
+ 'page' => 'wc-admin',
1395
+ 'path' => '/payments/transactions/details&',
1396
+ 'id' => $charge_id,
1397
+ ],
1398
+ admin_url( 'admin.php' )
1399
+ );
1400
+ }
1401
  }
includes/class-wc-payments-account.php CHANGED
@@ -162,6 +162,17 @@ class WC_Payments_Account {
162
  ];
163
  }
164
 
 
 
 
 
 
 
 
 
 
 
 
165
  /**
166
  * Utility function to immediately redirect to the main "Welcome to WooCommerce Payments" onboarding page.
167
  * Note that this function immediately ends the execution.
@@ -226,6 +237,11 @@ class WC_Payments_Account {
226
  return false;
227
  }
228
 
 
 
 
 
 
229
  return true;
230
  }
231
 
@@ -567,7 +583,7 @@ class WC_Payments_Account {
567
  delete_transient( self::ACCOUNT_TRANSIENT );
568
  $this->get_cached_account_data();
569
  } catch ( Exception $e ) {
570
- WCPay\Logger::error( "Failed to refresh account data. Error: $e" );
571
  }
572
  }
573
 
@@ -617,4 +633,44 @@ class WC_Payments_Account {
617
  }
618
  );
619
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  }
162
  ];
163
  }
164
 
165
+ /**
166
+ * Gets the account statement descriptor for rendering on the settings page.
167
+ *
168
+ * @return string Account statement descriptor.
169
+ * @throws WC_Payments_API_Exception Bubbles up from get_cached_account_data.
170
+ */
171
+ public function get_statement_descriptor() {
172
+ $account = $this->get_cached_account_data();
173
+ return isset( $account['statement_descriptor'] ) ? $account['statement_descriptor'] : '';
174
+ }
175
+
176
  /**
177
  * Utility function to immediately redirect to the main "Welcome to WooCommerce Payments" onboarding page.
178
  * Note that this function immediately ends the execution.
237
  return false;
238
  }
239
 
240
+ // Don't redirect if the user is on Jetpack pages.
241
+ if ( 'jetpack' === $current_page ) {
242
+ return false;
243
+ }
244
+
245
  return true;
246
  }
247
 
583
  delete_transient( self::ACCOUNT_TRANSIENT );
584
  $this->get_cached_account_data();
585
  } catch ( Exception $e ) {
586
+ Logger::error( "Failed to refresh account data. Error: $e" );
587
  }
588
  }
589
 
633
  }
634
  );
635
  }
636
+
637
+ /**
638
+ * Updates Stripe account settings.
639
+ *
640
+ * @param array $stripe_account_settings Settings to update.
641
+ *
642
+ * @return null|string Error message if update failed.
643
+ */
644
+ public function update_stripe_account( $stripe_account_settings ) {
645
+ try {
646
+ if ( ! $this->settings_changed( $stripe_account_settings ) ) {
647
+ Logger::info( 'Skip updating account settings. Nothing is changed.' );
648
+ return;
649
+ }
650
+ $updated_account = $this->payments_api_client->update_account( $stripe_account_settings );
651
+ $this->cache_account( $updated_account );
652
+ } catch ( Exception $e ) {
653
+ Logger::error( 'Failed to update Stripe account ' . $e );
654
+ return $e->getMessage();
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Checks if account settings changed.
660
+ *
661
+ * @param array $changes Account settings changes.
662
+ *
663
+ * @return bool True if at least one parameter value is changed.
664
+ */
665
+ private function settings_changed( $changes = [] ) {
666
+ $account = get_transient( self::ACCOUNT_TRANSIENT );
667
+
668
+ // Consider changes as valid if we don't have cached account data.
669
+ if ( ! $this->is_valid_cached_account( $account ) ) {
670
+ return true;
671
+ }
672
+
673
+ $diff = array_diff_assoc( $changes, $account );
674
+ return ! empty( $diff );
675
+ }
676
  }
includes/class-wc-payments-token-service.php CHANGED
@@ -51,6 +51,7 @@ class WC_Payments_Token_Service {
51
  *
52
  * @param array $payment_method Payment method to be added.
53
  * @param WP_User $user User to attach payment method to.
 
54
  */
55
  public function add_token_to_user( $payment_method, $user ) {
56
  // Clear cached payment methods.
@@ -74,10 +75,11 @@ class WC_Payments_Token_Service {
74
  *
75
  * @param string $payment_method_id Payment method to be added.
76
  * @param WP_User $user User to attach payment method to.
 
77
  */
78
  public function add_payment_method_to_user( $payment_method_id, $user ) {
79
  $payment_method_object = $this->payments_api_client->get_payment_method( $payment_method_id );
80
- return $this->add_token_to_user( $payment_method_object, wp_get_current_user() );
81
  }
82
 
83
  /**
@@ -93,7 +95,7 @@ class WC_Payments_Token_Service {
93
  return $tokens;
94
  }
95
 
96
- $customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
97
 
98
  if ( null === $customer_id ) {
99
  return $tokens;
@@ -110,7 +112,7 @@ class WC_Payments_Token_Service {
110
  foreach ( $payment_methods as $payment_method ) {
111
  if ( isset( $payment_method['type'] ) && 'card' === $payment_method['type'] ) {
112
  if ( ! in_array( $payment_method['id'], $stored_tokens, true ) ) {
113
- $token = $this->add_token_to_user( $payment_method, wp_get_current_user() );
114
  $tokens[ $token->get_id() ] = $token;
115
  }
116
  }
@@ -145,7 +147,7 @@ class WC_Payments_Token_Service {
145
  */
146
  public function woocommerce_payment_token_set_default( $token_id, $token ) {
147
  if ( WC_Payment_Gateway_WCPay::GATEWAY_ID === $token->get_gateway_id() ) {
148
- $customer_id = $this->customer_service->get_customer_id_by_user_id( get_current_user_id() );
149
  if ( $customer_id ) {
150
  $this->customer_service->set_default_payment_method_for_customer( $customer_id, $token->get_token() );
151
  // Clear cached payment methods.
51
  *
52
  * @param array $payment_method Payment method to be added.
53
  * @param WP_User $user User to attach payment method to.
54
+ * @return WC_Payment_Token_CC The WC object for the payment token.
55
  */
56
  public function add_token_to_user( $payment_method, $user ) {
57
  // Clear cached payment methods.
75
  *
76
  * @param string $payment_method_id Payment method to be added.
77
  * @param WP_User $user User to attach payment method to.
78
+ * @return WC_Payment_Token_CC The newly created token.
79
  */
80
  public function add_payment_method_to_user( $payment_method_id, $user ) {
81
  $payment_method_object = $this->payments_api_client->get_payment_method( $payment_method_id );
82
+ return $this->add_token_to_user( $payment_method_object, $user );
83
  }
84
 
85
  /**
95
  return $tokens;
96
  }
97
 
98
+ $customer_id = $this->customer_service->get_customer_id_by_user_id( $user_id );
99
 
100
  if ( null === $customer_id ) {
101
  return $tokens;
112
  foreach ( $payment_methods as $payment_method ) {
113
  if ( isset( $payment_method['type'] ) && 'card' === $payment_method['type'] ) {
114
  if ( ! in_array( $payment_method['id'], $stored_tokens, true ) ) {
115
+ $token = $this->add_token_to_user( $payment_method, get_user_by( 'id', $user_id ) );
116
  $tokens[ $token->get_id() ] = $token;
117
  }
118
  }
147
  */
148
  public function woocommerce_payment_token_set_default( $token_id, $token ) {
149
  if ( WC_Payment_Gateway_WCPay::GATEWAY_ID === $token->get_gateway_id() ) {
150
+ $customer_id = $this->customer_service->get_customer_id_by_user_id( $token->get_user_id() );
151
  if ( $customer_id ) {
152
  $this->customer_service->set_default_payment_method_for_customer( $customer_id, $token->get_token() );
153
  // Clear cached payment methods.
includes/class-wc-payments.php CHANGED
@@ -94,13 +94,20 @@ class WC_Payments {
94
  include_once dirname( __FILE__ ) . '/class-logger.php';
95
  include_once dirname( __FILE__ ) . '/class-wc-payment-gateway-wcpay.php';
96
  include_once dirname( __FILE__ ) . '/class-wc-payments-token-service.php';
97
- include_once WCPAY_ABSPATH . 'includes/exceptions/class-wc-payments-intent-authentication-exception.php';
 
98
 
99
  self::$account = new WC_Payments_Account( self::$api_client );
100
  self::$customer_service = new WC_Payments_Customer_Service( self::$api_client );
101
  self::$token_service = new WC_Payments_Token_Service( self::$api_client, self::$customer_service );
102
 
103
- self::$gateway = new WC_Payment_Gateway_WCPay( self::$api_client, self::$account, self::$customer_service, self::$token_service );
 
 
 
 
 
 
104
 
105
  add_filter( 'woocommerce_payment_gateways', [ __CLASS__, 'register_gateway' ] );
106
  add_filter( 'option_woocommerce_gateway_order', [ __CLASS__, 'set_gateway_top_of_list' ], 2 );
94
  include_once dirname( __FILE__ ) . '/class-logger.php';
95
  include_once dirname( __FILE__ ) . '/class-wc-payment-gateway-wcpay.php';
96
  include_once dirname( __FILE__ ) . '/class-wc-payments-token-service.php';
97
+ include_once dirname( __FILE__ ) . '/exceptions/class-wc-payments-intent-authentication-exception.php';
98
+ include_once dirname( __FILE__ ) . '/data-types/class-payment-information.php';
99
 
100
  self::$account = new WC_Payments_Account( self::$api_client );
101
  self::$customer_service = new WC_Payments_Customer_Service( self::$api_client );
102
  self::$token_service = new WC_Payments_Token_Service( self::$api_client, self::$customer_service );
103
 
104
+ $gateway_class = 'WC_Payment_Gateway_WCPay';
105
+ if ( class_exists( 'WC_Subscriptions' ) && version_compare( WC_Subscriptions::$version, '3.0.0', '>=' ) ) {
106
+ include_once dirname( __FILE__ ) . '/compat/subscriptions/class-wc-payment-gateway-wcpay-subscriptions-compat.php';
107
+ $gateway_class = 'WC_Payment_Gateway_WCPay_Subscriptions_Compat';
108
+ }
109
+
110
+ self::$gateway = new $gateway_class( self::$api_client, self::$account, self::$customer_service, self::$token_service );
111
 
112
  add_filter( 'woocommerce_payment_gateways', [ __CLASS__, 'register_gateway' ] );
113
  add_filter( 'option_woocommerce_gateway_order', [ __CLASS__, 'set_gateway_top_of_list' ], 2 );
includes/compat/subscriptions/class-wc-payment-gateway-wcpay-subscriptions-compat.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class WC_Payment_Gateway_WCPay_Subscriptions_Compat
4
+ *
5
+ * @package WooCommerce\Payments
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly.
10
+ }
11
+
12
+ use WCPay\Logger;
13
+ use WCPay\DataTypes\Payment_Information;
14
+
15
+ /**
16
+ * Gateway class for WooCommerce Payments, with added compatibility with WooCommerce Subscriptions.
17
+ */
18
+ class WC_Payment_Gateway_WCPay_Subscriptions_Compat extends WC_Payment_Gateway_WCPay {
19
+
20
+ /**
21
+ * WC_Payment_Gateway_WCPay_Subscriptions_Compat constructor.
22
+ *
23
+ * @param array ...$args Arguments passed to the main gateway's constructor.
24
+ */
25
+ public function __construct( ...$args ) {
26
+ parent::__construct( ...$args );
27
+
28
+ $this->supports = array_merge(
29
+ $this->supports,
30
+ [
31
+ 'subscriptions',
32
+ 'subscription_cancellation',
33
+ 'subscription_suspension',
34
+ 'subscription_reactivation',
35
+ 'subscription_amount_changes',
36
+ 'subscription_date_changes',
37
+ 'subscription_payment_method_change',
38
+ 'subscription_payment_method_change_customer',
39
+ 'multiple_subscriptions',
40
+ ]
41
+ );
42
+
43
+ add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, [ $this, 'scheduled_subscription_payment' ], 10, 2 );
44
+ add_action( 'woocommerce_subscription_failing_payment_method_updated_' . $this->id, [ $this, 'update_failing_payment_method' ], 10, 2 );
45
+ add_filter( 'wc_payments_display_save_payment_method_checkbox', [ $this, 'display_save_payment_method_checkbox' ], 10 );
46
+ }
47
+
48
+ /**
49
+ * Process the payment for a given order.
50
+ *
51
+ * @param int $order_id Order ID to process the payment for.
52
+ * @param bool $is_recurring_payment Whether this is a one-off payment (false) or it's the first installment of a recurring payment (true).
53
+ *
54
+ * @return array|null An array with result of payment and redirect URL, or nothing.
55
+ */
56
+ public function process_payment( $order_id, $is_recurring_payment = false ) {
57
+ return parent::process_payment( $order_id, wcs_order_contains_subscription( $order_id ) );
58
+ }
59
+
60
+ /**
61
+ * Returns a boolean value indicating whether the save payment checkbox should be
62
+ * displayed during checkout.
63
+ *
64
+ * Returns `false` if the cart currently has a subscriptions. Returns the value in
65
+ * `$display` otherwise.
66
+ *
67
+ * @param bool $display Bool indicating whether to show the save payment checkbox in the absence of subscriptions.
68
+ *
69
+ * @return bool Indicates whether the save payment method checkbox should be displayed or not.
70
+ */
71
+ public function display_save_payment_method_checkbox( $display ) {
72
+ if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
73
+ return false;
74
+ }
75
+ // Only render the "Save payment method" checkbox if there are no subscription products in the cart.
76
+ return $display;
77
+ }
78
+
79
+ /**
80
+ * Process a scheduled subscription payment.
81
+ *
82
+ * @param float $amount The amount to charge.
83
+ * @param WC_Order $renewal_order A WC_Order object created to record the renewal payment.
84
+ */
85
+ public function scheduled_subscription_payment( $amount, $renewal_order ) {
86
+ $order_tokens = $renewal_order->get_payment_tokens();
87
+ $token_id = end( $order_tokens );
88
+ $token = ! $token_id ? null : WC_Payment_Tokens::get( $token_id );
89
+ if ( is_null( $token ) ) {
90
+ Logger::error( 'There is no saved payment token for order #' . $renewal_order->get_id() );
91
+ $renewal_order->update_status( 'failed' );
92
+ return;
93
+ }
94
+
95
+ $payment_information = new Payment_Information( '', $token, true );
96
+
97
+ try {
98
+ // TODO: make `force_saved_card` and adding the 'recurring' metadata 2 distinct features.
99
+ $this->process_payment_for_order( $renewal_order, null, $payment_information, false, true );
100
+ } catch ( WC_Payments_API_Exception $e ) {
101
+ Logger::error( 'Error processing subscription renewal: ' . $e->getMessage() );
102
+
103
+ $renewal_order->update_status( 'failed' );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Adds the payment token from a failed renewal order to the provided subscription.
109
+ *
110
+ * @param WC_Subscription $subscription The subscription to be updated.
111
+ * @param WC_Order $renewal_order The failed renewal order.
112
+ */
113
+ public function update_failing_payment_method( $subscription, $renewal_order ) {
114
+ $renewal_order_tokens = $renewal_order->get_payment_tokens();
115
+ $renewal_token_id = end( $renewal_order_tokens );
116
+ $renewal_token = ! $renewal_token_id ? null : WC_Payment_Tokens::get( $renewal_token_id );
117
+ if ( is_null( $renewal_token ) ) {
118
+ Logger::error( 'Failing subscription could not be updated: there is no saved payment token for order #' . $renewal_order->get_id() );
119
+ return;
120
+ }
121
+ $subscription->add_payment_token( $renewal_token );
122
+ }
123
+
124
+ /**
125
+ * Saves the payment token to the order.
126
+ *
127
+ * @param WC_Order $order The order.
128
+ * @param WC_Payment_Token $token The token to save.
129
+ */
130
+ protected function add_token_to_order( $order, $token ) {
131
+ parent::add_token_to_order( $order, $token );
132
+
133
+ // Set payment token for subscriptions, so it can be used for renewals.
134
+ $subscriptions = wcs_get_subscriptions_for_order( $order->get_id() );
135
+ foreach ( $subscriptions as $subscription ) {
136
+ parent::add_token_to_order( $subscription, $token );
137
+ }
138
+ }
139
+ }
includes/data-types/class-payment-information.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class Payment_Information
4
+ *
5
+ * @package WooCommerce\Payments
6
+ */
7
+
8
+ namespace WCPay\DataTypes;
9
+
10
+ if ( ! defined( 'ABSPATH' ) ) {
11
+ exit; // Exit if accessed directly.
12
+ }
13
+
14
+ /**
15
+ * Mostly a wrapper containing information on a single payment.
16
+ */
17
+ class Payment_Information {
18
+ /**
19
+ * The ID of the payment method used for this payment.
20
+ *
21
+ * @var string
22
+ */
23
+ private $payment_method;
24
+
25
+ /**
26
+ * The payment token used for this payment.
27
+ *
28
+ * @var \WC_Payment_Token/NULL
29
+ */
30
+ private $token;
31
+
32
+ /**
33
+ * Indicates whether the payment is merchant-initiated (true) or customer-initiated (false).
34
+ *
35
+ * @var bool
36
+ */
37
+ private $off_session;
38
+
39
+ /**
40
+ * Payment information constructor.
41
+ *
42
+ * @param string $payment_method The ID of the payment method used for this payment.
43
+ * @param \WC_Payment_Token $token The payment token used for this payment.
44
+ * @param bool $off_session Indicates whether the payment is merchant-initiated (true) or customer-initiated (false).
45
+ *
46
+ * @throws \Exception - If no payment method is found in the provided request.
47
+ */
48
+ public function __construct(
49
+ string $payment_method,
50
+ \WC_Payment_Token $token = null,
51
+ bool $off_session = false
52
+ ) {
53
+ if ( empty( $payment_method ) && empty( $token ) ) {
54
+ throw new \Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-payments' ) );
55
+ }
56
+
57
+ $this->payment_method = $payment_method;
58
+ $this->token = $token;
59
+ $this->off_session = $off_session;
60
+ }
61
+
62
+ /**
63
+ * Returns true if payment was initiated by the merchant, false otherwise.
64
+ *
65
+ * @return bool True if payment was initiated by the merchant, false otherwise.
66
+ */
67
+ public function is_merchant_initiated(): bool {
68
+ return $this->off_session;
69
+ }
70
+
71
+ /**
72
+ * Returns the payment method ID.
73
+ *
74
+ * @return string The payment method ID.
75
+ */
76
+ public function get_payment_method(): string {
77
+ // Use the token if we have it.
78
+ if ( $this->is_using_saved_payment_method() ) {
79
+ return $this->token->get_token();
80
+ }
81
+
82
+ return $this->payment_method;
83
+ }
84
+
85
+ /**
86
+ * Returns the payment token.
87
+ *
88
+ * TODO: Once php requirement is bumped to >= 7.1.0 change return type to ?\WC_Payment_Token
89
+ * since the return type is nullable, as per
90
+ * https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
91
+ *
92
+ * @return \WC_Payment_Token/NULL The payment token.
93
+ */
94
+ public function get_payment_token(): \WC_Payment_Token {
95
+ return $this->token;
96
+ }
97
+
98
+ /**
99
+ * Update the payment token associated with this payment.
100
+ *
101
+ * @param \WC_Payment_Token $token The new payment token.
102
+ */
103
+ public function set_token( \WC_Payment_Token $token ) {
104
+ $this->token = $token;
105
+ }
106
+
107
+ /**
108
+ * Returns true if the payment token is not empty, false otherwise.
109
+ *
110
+ * @return bool True if payment token is not empty, false otherwise.
111
+ */
112
+ public function is_using_saved_payment_method(): bool {
113
+ return ! empty( $this->token );
114
+ }
115
+
116
+ /**
117
+ * Payment information constructor.
118
+ *
119
+ * @param array $request Associative array containing payment request information.
120
+ * @param bool $off_session Indicates whether the payment is merchant-initiated (true) or customer-initiated (false).
121
+ *
122
+ * @throws Exception - If no payment method is found in the provided request.
123
+ */
124
+ public static function from_payment_request(
125
+ array $request,
126
+ bool $off_session = false
127
+ ): Payment_Information {
128
+ $payment_method = self::get_payment_method_from_request( $request );
129
+ $token = self::get_token_from_request( $request );
130
+ $off_session = $off_session;
131
+
132
+ return new Payment_Information( $payment_method, $token, $off_session );
133
+ }
134
+
135
+ /**
136
+ * Extracts the payment method from the provided request.
137
+ *
138
+ * @param array $request Associative array containing payment request information.
139
+ *
140
+ * @return string
141
+ */
142
+ public static function get_payment_method_from_request( array $request ): string {
143
+ //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
144
+ return ! empty( $request['wcpay-payment-method'] ) ? wc_clean( $request['wcpay-payment-method'] ) : '';
145
+ }
146
+
147
+ /**
148
+ * Extract the payment token from the provided request.
149
+ *
150
+ * TODO: Once php requirement is bumped to >= 7.1.0 set return type to ?\WC_Payment_Token
151
+ * since the return type is nullable, as per
152
+ * https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
153
+ *
154
+ * @param array $request Associative array containing payment request information.
155
+ *
156
+ * @return \WC_Payment_Token|NULL
157
+ */
158
+ public static function get_token_from_request( array $request ) {
159
+ if (
160
+ ! isset( $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ] ) ||
161
+ 'new' === $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ]
162
+ ) {
163
+ return null;
164
+ }
165
+
166
+ //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
167
+ $token = \WC_Payment_Tokens::get( wc_clean( $request[ 'wc-' . \WC_Payment_Gateway_WCPay::GATEWAY_ID . '-payment-token' ] ) );
168
+
169
+ // If the token doesn't belong to this gateway or the current user it's invalid.
170
+ if ( ! $token || \WC_Payment_Gateway_WCPay::GATEWAY_ID !== $token->get_gateway_id() || $token->get_user_id() !== get_current_user_id() ) {
171
+ return null;
172
+ }
173
+
174
+ return $token;
175
+ }
176
+ }
includes/wc-payment-api/class-wc-payments-api-client.php CHANGED
@@ -121,6 +121,7 @@ class WC_Payments_API_Client {
121
  * @param bool $save_payment_method - Whether to save payment method for future purchases.
122
  * @param array $metadata - Meta data values to be sent along with payment intent creation.
123
  * @param array $level3 - Level 3 data.
 
124
  *
125
  * @return WC_Payments_API_Intention
126
  * @throws WC_Payments_API_Exception - Exception thrown on intention creation failure.
@@ -133,7 +134,8 @@ class WC_Payments_API_Client {
133
  $manual_capture = false,
134
  $save_payment_method = false,
135
  $metadata = [],
136
- $level3 = []
 
137
  ) {
138
  // TODO: There's scope to have amount and currency bundled up into an object.
139
  $request = [];
@@ -146,6 +148,10 @@ class WC_Payments_API_Client {
146
  $request['metadata'] = $metadata;
147
  $request['level3'] = $level3;
148
 
 
 
 
 
149
  if ( $save_payment_method ) {
150
  $request['setup_future_usage'] = 'off_session';
151
  }
@@ -254,14 +260,16 @@ class WC_Payments_API_Client {
254
  *
255
  * @param string $payment_method_id - ID of payment method to be saved.
256
  * @param string $customer_id - ID of the customer.
 
257
  *
258
  * @return array
259
  * @throws WC_Payments_API_Exception - Exception thrown on setup intent creation failure.
260
  */
261
- public function create_setup_intent( $payment_method_id, $customer_id ) {
262
  $request = [
263
  'payment_method' => $payment_method_id,
264
  'customer' => $customer_id,
 
265
  ];
266
 
267
  return $this->request( $request, self::SETUP_INTENTS_API, self::POST );
@@ -612,6 +620,21 @@ class WC_Payments_API_Client {
612
  );
613
  }
614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  /**
616
  * Get data needed to initialize the OAuth flow
617
  *
121
  * @param bool $save_payment_method - Whether to save payment method for future purchases.
122
  * @param array $metadata - Meta data values to be sent along with payment intent creation.
123
  * @param array $level3 - Level 3 data.
124
+ * @param bool $off_session - Whether the payment is off-session (merchant-initiated), or on-session (customer-initiated).
125
  *
126
  * @return WC_Payments_API_Intention
127
  * @throws WC_Payments_API_Exception - Exception thrown on intention creation failure.
134
  $manual_capture = false,
135
  $save_payment_method = false,
136
  $metadata = [],
137
+ $level3 = [],
138
+ $off_session = false
139
  ) {
140
  // TODO: There's scope to have amount and currency bundled up into an object.
141
  $request = [];
148
  $request['metadata'] = $metadata;
149
  $request['level3'] = $level3;
150
 
151
+ if ( $off_session ) {
152
+ $request['off_session'] = true;
153
+ }
154
+
155
  if ( $save_payment_method ) {
156
  $request['setup_future_usage'] = 'off_session';
157
  }
260
  *
261
  * @param string $payment_method_id - ID of payment method to be saved.
262
  * @param string $customer_id - ID of the customer.
263
+ * @param bool $confirm - Flag to confirm the intent on creation if true.
264
  *
265
  * @return array
266
  * @throws WC_Payments_API_Exception - Exception thrown on setup intent creation failure.
267
  */
268
+ public function create_setup_intent( $payment_method_id, $customer_id, $confirm = 'false' ) {
269
  $request = [
270
  'payment_method' => $payment_method_id,
271
  'customer' => $customer_id,
272
+ 'confirm' => $confirm,
273
  ];
274
 
275
  return $this->request( $request, self::SETUP_INTENTS_API, self::POST );
620
  );
621
  }
622
 
623
+ /**
624
+ * Update Stripe account data
625
+ *
626
+ * @param array $stripe_account_settings Settings to update.
627
+ *
628
+ * @return array Updated account data.
629
+ */
630
+ public function update_account( $stripe_account_settings ) {
631
+ return $this->request(
632
+ $stripe_account_settings,
633
+ self::ACCOUNTS_API,
634
+ self::POST
635
+ );
636
+ }
637
+
638
  /**
639
  * Get data needed to initialize the OAuth flow
640
  *
includes/wc-payment-api/class-wc-payments-http.php CHANGED
@@ -101,7 +101,7 @@ class WC_Payments_Http {
101
  * @return bool true if Jetpack connection has access token.
102
  */
103
  public function is_connected() {
104
- return $this->connection_manager->is_active();
105
  }
106
 
107
  /**
@@ -113,15 +113,18 @@ class WC_Payments_Http {
113
  * @throws WC_Payments_API_Exception - Exception thrown on failure.
114
  */
115
  public function start_connection( $redirect ) {
116
- // First, register the site to wp.com.
117
- if ( ! $this->connection_manager->get_access_token() ) {
 
 
 
118
  $result = $this->connection_manager->register();
119
  if ( is_wp_error( $result ) ) {
120
  throw new WC_Payments_API_Exception( $result->get_error_message(), 'wcpay_jetpack_register_site_failed', 500 );
121
  }
122
  }
123
 
124
- // Second, redirect the user to the Jetpack user connection flow.
125
  add_filter( 'jetpack_use_iframe_authorization_flow', '__return_false' );
126
  // Same logic as in WC-Admin.
127
  $calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, [ 'development', 'wpcalypso', 'horizon', 'stage' ], true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
101
  * @return bool true if Jetpack connection has access token.
102
  */
103
  public function is_connected() {
104
+ return $this->connection_manager->is_plugin_enabled() && $this->connection_manager->is_active();
105
  }
106
 
107
  /**
113
  * @throws WC_Payments_API_Exception - Exception thrown on failure.
114
  */
115
  public function start_connection( $redirect ) {
116
+ // Mark the plugin as enabled in case it had been soft-disconnected.
117
+ $this->connection_manager->enable_plugin();
118
+
119
+ // Register the site to wp.com.
120
+ if ( ! $this->connection_manager->is_registered() ) {
121
  $result = $this->connection_manager->register();
122
  if ( is_wp_error( $result ) ) {
123
  throw new WC_Payments_API_Exception( $result->get_error_message(), 'wcpay_jetpack_register_site_failed', 500 );
124
  }
125
  }
126
 
127
+ // Redirect the user to the Jetpack user connection flow.
128
  add_filter( 'jetpack_use_iframe_authorization_flow', '__return_false' );
129
  // Same logic as in WC-Admin.
130
  $calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, [ 'development', 'wpcalypso', 'horizon', 'stage' ], true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === WooCommerce Payments ===
2
- Contributors: automattic
3
  Tags: woocommerce, payment, payment request, credit card, automattic
4
  Requires at least: 5.3
5
  Tested up to: 5.4
6
  Requires PHP: 7.0
7
- Stable tag: 1.3.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -90,6 +90,14 @@ You can read our Terms of Service [here](https://en.wordpress.com/tos).
90
 
91
  == Changelog ==
92
 
 
 
 
 
 
 
 
 
93
  = 1.3.0 - 2020-08-17 =
94
  * Add - Support for saved cards.
95
  * Add - Search bar for transactions.
1
  === WooCommerce Payments ===
2
+ Contributors: woocommerce, automattic
3
  Tags: woocommerce, payment, payment request, credit card, automattic
4
  Requires at least: 5.3
5
  Tested up to: 5.4
6
  Requires PHP: 7.0
7
+ Stable tag: 1.4.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
90
 
91
  == Changelog ==
92
 
93
+ = 1.4.0 - 2020-09-02 =
94
+ * Add - Initial support for WooCommerce Subscriptions: Signing up for subscriptions, scheduled payments, and customer-initiated payment method changes.
95
+ * Add - Added a link to transaction details from order screens.
96
+ * Add - Allow merchant to edit statement descriptor.
97
+ * Fix - Do not redirect to the onboarding page after completing the WC4.5-beta wizard.
98
+ * Fix - Save order metadata before the payment is completed to avoid missing payments.
99
+ * Update - Bumped the minimum Jetpack requirement to version 8.2.
100
+
101
  = 1.3.0 - 2020-08-17 =
102
  * Add - Support for saved cards.
103
  * Add - Search bar for transactions.
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
- return ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4::getLoader();
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit0a651f92f6dec0685e6f92fb5fc9b1f9::getLoader();
vendor/autoload_packages.php CHANGED
@@ -179,7 +179,7 @@ if ( ! function_exists( __NAMESPACE__ . '\autoloader' ) ) {
179
  /**
180
  * Prepare all the classes for autoloading.
181
  */
182
- function enqueue_packages_996f9e0187d1cf069c402189f248054f() {
183
  $class_map = require_once dirname( __FILE__ ) . '/composer/autoload_classmap_package.php';
184
  foreach ( $class_map as $class_name => $class_info ) {
185
  enqueue_package_class( $class_name, $class_info['version'], $class_info['path'] );
@@ -203,4 +203,4 @@ function enqueue_packages_996f9e0187d1cf069c402189f248054f() {
203
  file_loader(); // Either WordPress is not loaded or plugin is doing it wrong. Either way we'll load the files so nothing breaks.
204
  }
205
  }
206
- enqueue_packages_996f9e0187d1cf069c402189f248054f();
179
  /**
180
  * Prepare all the classes for autoloading.
181
  */
182
+ function enqueue_packages_1c17c81d6bb0e789d15762cdc7617239() {
183
  $class_map = require_once dirname( __FILE__ ) . '/composer/autoload_classmap_package.php';
184
  foreach ( $class_map as $class_name => $class_info ) {
185
  enqueue_package_class( $class_name, $class_info['version'], $class_info['path'] );
203
  file_loader(); // Either WordPress is not loaded or plugin is doing it wrong. Either way we'll load the files so nothing breaks.
204
  }
205
  }
206
+ enqueue_packages_1c17c81d6bb0e789d15762cdc7617239();
vendor/automattic/jetpack-config/src/class-config.php CHANGED
@@ -8,7 +8,8 @@
8
  namespace Automattic\Jetpack;
9
 
10
  use Automattic\Jetpack\Connection\Manager;
11
- use Automattic\Jetpack\JITMS\JITM;
 
12
  use Automattic\Jetpack\Connection\Plugin;
13
  use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
14
  use Automattic\Jetpack\Sync\Main as Sync_Main;
@@ -91,8 +92,10 @@ class Config {
91
  }
92
 
93
  if ( $this->config['jitm'] ) {
94
- $this->ensure_class( 'Automattic\Jetpack\JITMS\JITM' )
95
- && $this->ensure_feature( 'jitm' );
 
 
96
  }
97
  }
98
 
@@ -100,13 +103,15 @@ class Config {
100
  * Returns true if the required class is available and alerts the user if it's not available
101
  * in case the site is in debug mode.
102
  *
103
- * @param String $classname a fully qualified class name.
 
 
104
  * @return Boolean whether the class is available.
105
  */
106
- protected function ensure_class( $classname ) {
107
  $available = class_exists( $classname );
108
 
109
- if ( ! $available && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
110
  trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
111
  sprintf(
112
  /* translators: %1$s is a PHP class name. */
@@ -190,7 +195,12 @@ class Config {
190
  * Enables the JITM feature.
191
  */
192
  protected function enable_jitm() {
193
- JITM::configure();
 
 
 
 
 
194
 
195
  return true;
196
  }
8
  namespace Automattic\Jetpack;
9
 
10
  use Automattic\Jetpack\Connection\Manager;
11
+ use Automattic\Jetpack\JITMS\JITM as JITMS_JITM;
12
+ use Automattic\Jetpack\JITM as JITM;
13
  use Automattic\Jetpack\Connection\Plugin;
14
  use Automattic\Jetpack\Plugin\Tracking as Plugin_Tracking;
15
  use Automattic\Jetpack\Sync\Main as Sync_Main;
92
  }
93
 
94
  if ( $this->config['jitm'] ) {
95
+ // Check for the JITM class in both namespaces. The namespace was changed in jetpack-jitm v1.6.
96
+ ( $this->ensure_class( 'Automattic\Jetpack\JITMS\JITM', false )
97
+ || $this->ensure_class( 'Automattic\Jetpack\JITM' ) )
98
+ && $this->ensure_feature( 'jitm' );
99
  }
100
  }
101
 
103
  * Returns true if the required class is available and alerts the user if it's not available
104
  * in case the site is in debug mode.
105
  *
106
+ * @param String $classname a fully qualified class name.
107
+ * @param Boolean $log_notice whether the E_USER_NOTICE should be generated if the class is not found.
108
+ *
109
  * @return Boolean whether the class is available.
110
  */
111
+ protected function ensure_class( $classname, $log_notice = true ) {
112
  $available = class_exists( $classname );
113
 
114
+ if ( $log_notice && ! $available && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
115
  trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
116
  sprintf(
117
  /* translators: %1$s is a PHP class name. */
195
  * Enables the JITM feature.
196
  */
197
  protected function enable_jitm() {
198
+ if ( class_exists( 'Automattic\Jetpack\JITMS\JITM' ) ) {
199
+ JITMS_JITM::configure();
200
+ } else {
201
+ // Provides compatibility with jetpack-jitm <v1.6.
202
+ JITM::configure();
203
+ }
204
 
205
  return true;
206
  }
vendor/automattic/jetpack-connection/.gitignore ADDED
@@ -0,0 +1 @@
 
1
+ wordpress
vendor/automattic/jetpack-connection/composer.json CHANGED
@@ -4,13 +4,15 @@
4
  "type": "library",
5
  "license": "GPL-2.0-or-later",
6
  "require": {
7
- "automattic/jetpack-constants": "1.2.0",
8
- "automattic/jetpack-options": "1.5.0",
9
- "automattic/jetpack-roles": "1.0.4"
 
10
  },
11
  "require-dev": {
12
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5",
13
- "php-mock/php-mock": "^2.1"
 
14
  },
15
  "autoload": {
16
  "files": [
@@ -25,7 +27,8 @@
25
  "phpunit": [
26
  "@composer install",
27
  "./vendor/phpunit/phpunit/phpunit --colors=always"
28
- ]
 
29
  },
30
  "repositories": [
31
  {
4
  "type": "library",
5
  "license": "GPL-2.0-or-later",
6
  "require": {
7
+ "automattic/jetpack-constants": "1.4.0",
8
+ "automattic/jetpack-options": "1.7.0",
9
+ "automattic/jetpack-roles": "1.2.0",
10
+ "automattic/jetpack-status": "1.3.0"
11
  },
12
  "require-dev": {
13
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5",
14
+ "php-mock/php-mock": "^2.1",
15
+ "automattic/wordbless": "@dev"
16
  },
17
  "autoload": {
18
  "files": [
27
  "phpunit": [
28
  "@composer install",
29
  "./vendor/phpunit/phpunit/phpunit --colors=always"
30
+ ],
31
+ "post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
32
  },
33
  "repositories": [
34
  {
vendor/automattic/jetpack-connection/docs/register-site.md CHANGED
@@ -69,7 +69,15 @@ add_action( 'admin_post_register_site', 'your_plugin_register_site' );
69
 
70
  function your_plugin_register_site() {
71
  check_admin_referer( 'register-site' );
72
- ( new Manager( 'plugin-slug' ) )->register();
 
 
 
 
 
 
 
 
73
 
74
  // This is where you could put your error handling, redirects, or whatever decorations you need.
75
  }
@@ -101,12 +109,91 @@ use Automattic\Jetpack\Connection\Manager;
101
  function your_plugin_disconnect_site() {
102
  check_admin_referer( 'disconnect-site' );
103
 
104
- // This will destroy the blog tokens on both this site, and the tokens stored on wordpress.com
105
- ( new Manager( 'plugin-slug' ) )->disconnect_site_wpcom();
106
 
107
- // Clear all the tokens!
108
- ( new Manager( 'plugin-slug' ) )->delete_all_connection_tokens();
 
 
109
 
110
  // Your error handling and decorations
111
  }
112
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  function your_plugin_register_site() {
71
  check_admin_referer( 'register-site' );
72
+ $manager = new Manager( 'plugin-slug' );
73
+
74
+ // Mark the plugin connection as enabled, in case it was disabled earlier.
75
+ $manager->enable_plugin();
76
+
77
+ // If the token doesn't exist (see "Soft and Hard Disconnect" section below), we need to register the site.
78
+ if ( ! $manager->get_access_token() ) {
79
+ $manager->register();
80
+ }
81
 
82
  // This is where you could put your error handling, redirects, or whatever decorations you need.
83
  }
109
  function your_plugin_disconnect_site() {
110
  check_admin_referer( 'disconnect-site' );
111
 
112
+ $manager = new Manager( 'plugin-slug' );
 
113
 
114
+ // Mark the plugin connection as disabled.
115
+ // If there are no other plugins using the connection, destroy blog and user tokens,
116
+ // as well as the tokens stored in wordpress.com
117
+ $manager->remove_connection();
118
 
119
  // Your error handling and decorations
120
  }
121
  ```
122
+
123
+
124
+ #### Custom Disconnect
125
+
126
+ Method `$manager->remove_connection()` essentially calls `disable_plugin()`, `disconnect_site_wpcom()`, and `delete_all_connection_tokens()`.
127
+ Its purpose is to simplify the disconnection process.
128
+ If you need to customize the disconnect process, or perform a partial disconnect, you can call these methods one by one.
129
+
130
+ ```php
131
+ // Mark the plugin connection as disabled.
132
+
133
+ $manager = new Manager( 'plugin-slug' );
134
+
135
+ // Mark the plugin connection as disabled.
136
+ $manager->disable_plugin();
137
+
138
+ // If no other plugins use the connection, this will destroy the blog tokens on both this site, and the tokens stored on wordpress.com
139
+ $manager->disconnect_site_wpcom();
140
+
141
+ // If no other plugins use the connection, this will clear all the tokens!
142
+ $manager->delete_all_connection_tokens();
143
+ ```
144
+
145
+ ### Soft and Hard Disconnect
146
+
147
+ There are two types of disconnection happening when you request a disconnect: *Soft Disconnect* and *Hard Disconnect*.
148
+ The package API takes care of that under the hood, so in most cases there won't be any need to differentiate them in your code.
149
+ Below is some basic information on how they differ.
150
+
151
+ #### Soft Disconnect
152
+
153
+ Soft disconnect means that all the tokens are preserved, and the connection for other plugins stays active.
154
+ Technically speaking, soft disconnect happens when you call `$manager->disable_plugin();`.
155
+ Next time you try to use the connection, you call `$manager->is_plugin_enabled()`, which will return `false`.
156
+
157
+ Calling `$manager->disconnect_site_wpcom()` and `$manager->delete_all_connection_tokens()` afterwards is still needed.
158
+ These calls will determine if the aforementioned plugin is the only one using the connection, and perform *soft* or *hard* disconnect accordingly.
159
+
160
+ #### Hard Disconnect
161
+
162
+ If there are no other plugins using the connection, or all of them have already been *softly disconnected*, the package will perform the *hard disconnect*.
163
+ In that case methods `disconnect_site_wpcom()` and `delete_all_connection_tokens()` will actually remove the tokens and run the `deregister` API request.
164
+
165
+ You can explicitly request hard disconnect by providing the argument `$ignore_connected_plugins`:
166
+ ```php
167
+ $manager = new Manager( 'plugin-slug' );
168
+
169
+ $manager->disable_plugin();
170
+
171
+ // The `$ignore_connected_plugins` argument is set to `true`, so the Connection Manager will perform the hard disconnect.
172
+ $manager->disconnect_site_wpcom( true );
173
+ $manager->delete_all_connection_tokens( true );
174
+ ```
175
+
176
+ #### Using the connection
177
+ If the plugin was *softly* disconnected, the access tokens will still be accessible.
178
+ However, the user explicitly requested the plugin to be disabled, so you need to check for that before you utilize the connection in any way:
179
+ ```php
180
+ $manager = new Manager( 'plugin-slug' );
181
+
182
+ if ( $manager->is_plugin_enabled() && $manager->get_access_token() ) {
183
+ // Perform the API requests.
184
+ } else {
185
+ // Assume the plugin is disconnected, no matter if the tokens actually exist.
186
+ }
187
+ ```
188
+
189
+ #### Reconnecting
190
+ Whether a *soft* or *hard* disconnect was performed, the plugin will be marked as "disconnected", so if the connect is being reestablished, you need to call `$manager->enable_plugin()` to remove that flag.
191
+ If the plugin was *softly* disconnected, removing the flag is enough for it to work. Otherwise, you'll need to register the website again:
192
+ ```php
193
+ $manager = new Manager( 'plugin-slug' );
194
+ $manager->enable_plugin();
195
+
196
+ if ( ! $manager->get_access_token() ) {
197
+ $manager->register();
198
+ }
199
+ ```
vendor/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php CHANGED
@@ -49,10 +49,11 @@ class Jetpack_IXR_Client extends IXR_Client {
49
  /**
50
  * Perform the IXR request.
51
  *
 
 
52
  * @return bool True if request succeeded, false otherwise.
53
  */
54
- public function query() {
55
- $args = func_get_args();
56
  $method = array_shift( $args );
57
  $request = new IXR_Request( $method, $args );
58
  $xml = trim( $request->getXml() );
49
  /**
50
  * Perform the IXR request.
51
  *
52
+ * @param string[] ...$args IXR args.
53
+ *
54
  * @return bool True if request succeeded, false otherwise.
55
  */
56
+ public function query( ...$args ) {
 
57
  $method = array_shift( $args );
58
  $request = new IXR_Request( $method, $args );
59
  $xml = trim( $request->getXml() );
vendor/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php CHANGED
@@ -23,9 +23,10 @@ class Jetpack_IXR_ClientMulticall extends Jetpack_IXR_Client {
23
  * Add a IXR call to the client.
24
  * First argument is the method name.
25
  * The rest of the arguments are the params specified to the method.
 
 
26
  */
27
- public function addCall() {
28
- $args = func_get_args();
29
  $method_name = array_shift( $args );
30
  $struct = array(
31
  'methodName' => $method_name,
@@ -37,9 +38,11 @@ class Jetpack_IXR_ClientMulticall extends Jetpack_IXR_Client {
37
  /**
38
  * Perform the IXR multicall request.
39
  *
 
 
40
  * @return bool True if request succeeded, false otherwise.
41
  */
42
- public function query() {
43
  usort( $this->calls, array( $this, 'sort_calls' ) );
44
 
45
  // Prepare multicall, then call the parent::query() method.
23
  * Add a IXR call to the client.
24
  * First argument is the method name.
25
  * The rest of the arguments are the params specified to the method.
26
+ *
27
+ * @param string[] ...$args IXR args.
28
  */
29
+ public function addCall( ...$args ) {
 
30
  $method_name = array_shift( $args );
31
  $struct = array(
32
  'methodName' => $method_name,
38
  /**
39
  * Perform the IXR multicall request.
40
  *
41
+ * @param string[] ...$args IXR args.
42
+ *
43
  * @return bool True if request succeeded, false otherwise.
44
  */
45
+ public function query( ...$args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
46
  usort( $this->calls, array( $this, 'sort_calls' ) );
47
 
48
  // Prepare multicall, then call the parent::query() method.
vendor/automattic/jetpack-connection/legacy/class-jetpack-xmlrpc-server.php CHANGED
@@ -56,29 +56,35 @@ class Jetpack_XMLRPC_Server {
56
  */
57
  public function xmlrpc_methods( $core_methods ) {
58
  $jetpack_methods = array(
59
- 'jetpack.jsonAPI' => array( $this, 'json_api' ),
60
  'jetpack.verifyAction' => array( $this, 'verify_action' ),
61
  'jetpack.getUser' => array( $this, 'get_user' ),
62
  'jetpack.remoteRegister' => array( $this, 'remote_register' ),
63
  'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
64
  );
65
 
 
 
 
 
66
  $this->user = $this->login();
67
 
68
  if ( $this->user ) {
69
  $jetpack_methods = array_merge(
70
  $jetpack_methods,
71
  array(
72
- 'jetpack.testConnection' => array( $this, 'test_connection' ),
73
- 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
74
- 'jetpack.featuresAvailable' => array( $this, 'features_available' ),
75
- 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ),
76
- 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ),
77
- 'jetpack.unlinkUser' => array( $this, 'unlink_user' ),
78
- 'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
79
  )
80
  );
81
 
 
 
 
 
 
 
82
  if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
83
  $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
84
  $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
@@ -159,7 +165,7 @@ class Jetpack_XMLRPC_Server {
159
 
160
  if ( ! $user_id ) {
161
  return $this->error(
162
- new Jetpack_Error(
163
  'invalid_user',
164
  __( 'Invalid user identifier.', 'jetpack' ),
165
  400
@@ -172,7 +178,7 @@ class Jetpack_XMLRPC_Server {
172
 
173
  if ( ! $user ) {
174
  return $this->error(
175
- new Jetpack_Error(
176
  'user_unknown',
177
  __( 'User not found.', 'jetpack' ),
178
  404
@@ -230,18 +236,18 @@ class Jetpack_XMLRPC_Server {
230
  foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
231
  if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
232
  return $this->error(
233
- new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
234
  'remote_authorize'
235
  );
236
  }
237
  }
238
 
239
  if ( ! $user ) {
240
- return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
241
  }
242
 
243
  if ( $this->connection->is_active() && $this->connection->is_user_connected( $request['state'] ) ) {
244
- return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
245
  }
246
 
247
  $verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
@@ -293,7 +299,7 @@ class Jetpack_XMLRPC_Server {
293
 
294
  if ( empty( $request['nonce'] ) ) {
295
  return $this->error(
296
- new Jetpack_Error(
297
  'nonce_missing',
298
  __( 'The required "nonce" parameter is missing.', 'jetpack' ),
299
  400
@@ -319,7 +325,7 @@ class Jetpack_XMLRPC_Server {
319
  'OK' !== trim( wp_remote_retrieve_body( $response ) )
320
  ) {
321
  return $this->error(
322
- new Jetpack_Error(
323
  'invalid_nonce',
324
  __( 'There was an issue validating this request.', 'jetpack' ),
325
  400
@@ -338,7 +344,7 @@ class Jetpack_XMLRPC_Server {
338
  return $this->error( $registered, 'remote_register' );
339
  } elseif ( ! $registered ) {
340
  return $this->error(
341
- new Jetpack_Error(
342
  'registration_error',
343
  __( 'There was an unspecified error registering the site', 'jetpack' ),
344
  400
@@ -506,7 +512,7 @@ class Jetpack_XMLRPC_Server {
506
  private function fetch_and_verify_local_user( $request ) {
507
  if ( empty( $request['local_user'] ) ) {
508
  return $this->error(
509
- new Jetpack_Error(
510
  'local_user_missing',
511
  __( 'The required "local_user" parameter is missing.', 'jetpack' ),
512
  400
@@ -589,13 +595,13 @@ class Jetpack_XMLRPC_Server {
589
  $user = wp_authenticate( 'username', 'password' );
590
  if ( is_wp_error( $user ) ) {
591
  if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
592
- $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
593
  } else {
594
  $this->error = $user;
595
  }
596
  return false;
597
  } elseif ( ! $user ) { // Shouldn't happen.
598
- $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
599
  return false;
600
  }
601
 
56
  */
57
  public function xmlrpc_methods( $core_methods ) {
58
  $jetpack_methods = array(
 
59
  'jetpack.verifyAction' => array( $this, 'verify_action' ),
60
  'jetpack.getUser' => array( $this, 'get_user' ),
61
  'jetpack.remoteRegister' => array( $this, 'remote_register' ),
62
  'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
63
  );
64
 
65
+ if ( class_exists( 'Jetpack' ) ) {
66
+ $jetpack_methods['jetpack.jsonAPI'] = array( $this, 'json_api' );
67
+ }
68
+
69
  $this->user = $this->login();
70
 
71
  if ( $this->user ) {
72
  $jetpack_methods = array_merge(
73
  $jetpack_methods,
74
  array(
75
+ 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
76
+ 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ),
77
+ 'jetpack.unlinkUser' => array( $this, 'unlink_user' ),
78
+ 'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
 
 
 
79
  )
80
  );
81
 
82
+ if ( class_exists( 'Jetpack' ) ) {
83
+ $jetpack_methods['jetpack.testConnection'] = array( $this, 'test_connection' );
84
+ $jetpack_methods['jetpack.featuresAvailable'] = array( $this, 'features_available' );
85
+ $jetpack_methods['jetpack.featuresEnabled'] = array( $this, 'features_enabled' );
86
+ }
87
+
88
  if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
89
  $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
90
  $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
165
 
166
  if ( ! $user_id ) {
167
  return $this->error(
168
+ new \WP_Error(
169
  'invalid_user',
170
  __( 'Invalid user identifier.', 'jetpack' ),
171
  400
178
 
179
  if ( ! $user ) {
180
  return $this->error(
181
+ new \WP_Error(
182
  'user_unknown',
183
  __( 'User not found.', 'jetpack' ),
184
  404
236
  foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
237
  if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
238
  return $this->error(
239
+ new \WP_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
240
  'remote_authorize'
241
  );
242
  }
243
  }
244
 
245
  if ( ! $user ) {
246
+ return $this->error( new \WP_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
247
  }
248
 
249
  if ( $this->connection->is_active() && $this->connection->is_user_connected( $request['state'] ) ) {
250
+ return $this->error( new \WP_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
251
  }
252
 
253
  $verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
299
 
300
  if ( empty( $request['nonce'] ) ) {
301
  return $this->error(
302
+ new \WP_Error(
303
  'nonce_missing',
304
  __( 'The required "nonce" parameter is missing.', 'jetpack' ),
305
  400
325
  'OK' !== trim( wp_remote_retrieve_body( $response ) )
326
  ) {
327
  return $this->error(
328
+ new \WP_Error(
329
  'invalid_nonce',
330
  __( 'There was an issue validating this request.', 'jetpack' ),
331
  400
344
  return $this->error( $registered, 'remote_register' );
345
  } elseif ( ! $registered ) {
346
  return $this->error(
347
+ new \WP_Error(
348
  'registration_error',
349
  __( 'There was an unspecified error registering the site', 'jetpack' ),
350
  400
512
  private function fetch_and_verify_local_user( $request ) {
513
  if ( empty( $request['local_user'] ) ) {
514
  return $this->error(
515
+ new \WP_Error(
516
  'local_user_missing',
517
  __( 'The required "local_user" parameter is missing.', 'jetpack' ),
518
  400
595
  $user = wp_authenticate( 'username', 'password' );
596
  if ( is_wp_error( $user ) ) {
597
  if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
598
+ $this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
599
  } else {
600
  $this->error = $user;
601
  }
602
  return false;
603
  } elseif ( ! $user ) { // Shouldn't happen.
604
+ $this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
605
  return false;
606
  }
607
 
vendor/automattic/jetpack-connection/src/class-error-handler.php ADDED
@@ -0,0 +1,621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ) ) {
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 );
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
+ 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
+ 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
+ }
vendor/automattic/jetpack-connection/src/class-manager.php CHANGED
@@ -9,7 +9,9 @@ namespace Automattic\Jetpack\Connection;
9
 
10
  use Automattic\Jetpack\Constants;
11
  use Automattic\Jetpack\Roles;
 
12
  use Automattic\Jetpack\Tracking;
 
13
 
14
  /**
15
  * The Jetpack Connection Manager class that is used as a single gateway between WordPress.com
@@ -74,32 +76,35 @@ class Manager {
74
  public static function configure() {
75
  $manager = new self();
76
 
 
 
 
 
 
 
 
77
  $manager->setup_xmlrpc_handlers(
78
  $_GET, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
79
  $manager->is_active(),
80
  $manager->verify_xml_rpc_signature()
81
  );
82
 
 
 
83
  if ( $manager->is_active() ) {
84
  add_filter( 'xmlrpc_methods', array( $manager, 'public_xmlrpc_methods' ) );
85
- } else {
86
- add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ) );
87
  }
88
 
 
 
89
  add_action( 'jetpack_clean_nonces', array( $manager, 'clean_nonces' ) );
90
  if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
91
  wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
92
  }
93
 
94
- add_filter(
95
- 'jetpack_constant_default_value',
96
- __NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
97
- 10,
98
- 2
99
- );
100
-
101
  add_action( 'plugins_loaded', __NAMESPACE__ . '\Plugin_Storage::configure', 100 );
102
 
 
103
  }
104
 
105
  /**
@@ -279,7 +284,7 @@ class Manager {
279
  * @param String $password password string.
280
  * @return WP_User|Mixed authenticated user or error.
281
  */
282
- public function authenticate_jetpack( $user, $username, $password ) {
283
  if ( is_a( $user, '\\WP_User' ) ) {
284
  return $user;
285
  }
@@ -316,19 +321,14 @@ class Manager {
316
  /**
317
  * Action for logging XMLRPC signature verification errors. This data is sensitive.
318
  *
319
- * Error codes:
320
- * - malformed_token
321
- * - malformed_user_id
322
- * - unknown_token
323
- * - could_not_sign
324
- * - invalid_nonce
325
- * - signature_mismatch
326
- *
327
  * @since 7.5.0
328
  *
329
  * @param WP_Error $signature_verification_error The verification error
330
  */
331
  do_action( 'jetpack_verify_signature_error', $this->xmlrpc_verification );
 
 
 
332
  }
333
  }
334
 
@@ -519,9 +519,9 @@ class Manager {
519
  * @return bool
520
  */
521
  public function is_registered() {
522
- $blog_id = \Jetpack_Options::get_option( 'id' );
523
- $has_token = $this->is_active();
524
- return $blog_id && $has_token;
525
  }
526
 
527
  /**
@@ -728,6 +728,10 @@ class Manager {
728
 
729
  \Jetpack_Options::update_option( 'user_tokens', $tokens );
730
 
 
 
 
 
731
  /**
732
  * Fires after the current user has been unlinked from WordPress.com.
733
  *
@@ -804,7 +808,7 @@ class Manager {
804
  * WordPress.com.
805
  *
806
  * @param String $api_endpoint (optional) an API endpoint to use, defaults to 'register'.
807
- * @return Integer zero on success, or a bitmask on failure.
808
  */
809
  public function register( $api_endpoint = 'register' ) {
810
  add_action( 'pre_update_jetpack_option_register', array( '\\Jetpack_Options', 'delete_option' ) );
@@ -843,20 +847,21 @@ class Manager {
843
  $body = apply_filters(
844
  'jetpack_register_request_body',
845
  array(
846
- 'siteurl' => site_url(),
847
- 'home' => home_url(),
848
- 'gmt_offset' => $gmt_offset,
849
- 'timezone_string' => (string) get_option( 'timezone_string' ),
850
- 'site_name' => (string) get_option( 'blogname' ),
851
- 'secret_1' => $secrets['secret_1'],
852
- 'secret_2' => $secrets['secret_2'],
853
- 'site_lang' => get_locale(),
854
- 'timeout' => $timeout,
855
- 'stats_id' => $stats_id,
856
- 'state' => get_current_user_id(),
857
- 'site_created' => $this->get_assumed_site_creation_date(),
858
- 'jetpack_version' => Constants::get_constant( 'JETPACK__VERSION' ),
859
- 'ABSPATH' => Constants::get_constant( 'ABSPATH' ),
 
860
  )
861
  );
862
 
@@ -950,7 +955,7 @@ class Manager {
950
  * @since 2.6
951
  *
952
  * @param Mixed $response the response object, or the error object.
953
- * @return string|WP_Error A JSON object on success or Jetpack_Error on failures
954
  **/
955
  protected function validate_remote_register_response( $response ) {
956
  if ( is_wp_error( $response ) ) {
@@ -1097,6 +1102,46 @@ class Manager {
1097
  }
1098
  }
1099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
  /**
1101
  * Builds the timeout limit for queries talking with the wpcom servers.
1102
  *
@@ -1319,8 +1364,30 @@ class Manager {
1319
 
1320
  /**
1321
  * Deletes all connection tokens and transients from the local Jetpack site.
 
 
 
 
 
 
 
 
1322
  */
1323
- public function delete_all_connection_tokens() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1324
  \Jetpack_Options::delete_option(
1325
  array(
1326
  'blog_token',
@@ -1337,14 +1404,74 @@ class Manager {
1337
  // Delete cached connected user data.
1338
  $transient_key = 'jetpack_connected_user_data_' . get_current_user_id();
1339
  delete_transient( $transient_key );
 
 
 
 
 
1340
  }
1341
 
1342
  /**
1343
  * Tells WordPress.com to disconnect the site and clear all tokens from cached site.
 
 
 
 
 
 
 
 
1344
  */
1345
- public function disconnect_site_wpcom() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1346
  $xml = new \Jetpack_IXR_Client();
1347
  $xml->query( 'jetpack.deregister', get_current_user_id() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1348
  }
1349
 
1350
  /**
@@ -2025,23 +2152,26 @@ class Manager {
2025
 
2026
  if ( $user_id ) {
2027
  if ( ! $user_tokens ) {
2028
- return $suppress_errors ? false : new \WP_Error( 'no_user_tokens' );
2029
  }
2030
  if ( self::JETPACK_MASTER_USER === $user_id ) {
2031
  $user_id = \Jetpack_Options::get_option( 'master_user' );
2032
  if ( ! $user_id ) {
2033
- return $suppress_errors ? false : new \WP_Error( 'empty_master_user_option' );
2034
  }
2035
  }
2036
  if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
2037
- return $suppress_errors ? false : new \WP_Error( 'no_token_for_user', sprintf( 'No token for user %d', $user_id ) );
 
2038
  }
2039
  $user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
2040
  if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
2041
- return $suppress_errors ? false : new \WP_Error( 'token_malformed', sprintf( 'Token for user %d is malformed', $user_id ) );
 
2042
  }
2043
  if ( $user_token_chunks[2] !== (string) $user_id ) {
2044
- return $suppress_errors ? false : new \WP_Error( 'user_id_mismatch', sprintf( 'Requesting user_id %d does not match token user_id %d', $user_id, $user_token_chunks[2] ) );
 
2045
  }
2046
  $possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
2047
  } else {
@@ -2071,7 +2201,8 @@ class Manager {
2071
  }
2072
 
2073
  if ( ! $possible_tokens ) {
2074
- return $suppress_errors ? false : new \WP_Error( 'no_possible_tokens' );
 
2075
  }
2076
 
2077
  $valid_token = false;
@@ -2096,7 +2227,12 @@ class Manager {
2096
  }
2097
 
2098
  if ( ! $valid_token ) {
2099
- return $suppress_errors ? false : new \WP_Error( 'no_valid_token' );
 
 
 
 
 
2100
  }
2101
 
2102
  return (object) array(
@@ -2268,12 +2404,64 @@ class Manager {
2268
  }
2269
 
2270
  /**
2271
- * Get all connected plugins information.
 
 
 
2272
  *
2273
- * @return array|\WP_Error
2274
  */
2275
  public function get_connected_plugins() {
2276
- return Plugin_Storage::get_all();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2277
  }
2278
 
2279
  }
9
 
10
  use Automattic\Jetpack\Constants;
11
  use Automattic\Jetpack\Roles;
12
+ use Automattic\Jetpack\Status;
13
  use Automattic\Jetpack\Tracking;
14
+ use WP_Error;
15
 
16
  /**
17
  * The Jetpack Connection Manager class that is used as a single gateway between WordPress.com
76
  public static function configure() {
77
  $manager = new self();
78
 
79
+ add_filter(
80
+ 'jetpack_constant_default_value',
81
+ __NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
82
+ 10,
83
+ 2
84
+ );
85
+
86
  $manager->setup_xmlrpc_handlers(
87
  $_GET, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
88
  $manager->is_active(),
89
  $manager->verify_xml_rpc_signature()
90
  );
91
 
92
+ $manager->error_handler = Error_Handler::get_instance();
93
+
94
  if ( $manager->is_active() ) {
95
  add_filter( 'xmlrpc_methods', array( $manager, 'public_xmlrpc_methods' ) );
 
 
96
  }
97
 
98
+ add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ) );
99
+
100
  add_action( 'jetpack_clean_nonces', array( $manager, 'clean_nonces' ) );
101
  if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
102
  wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
103
  }
104
 
 
 
 
 
 
 
 
105
  add_action( 'plugins_loaded', __NAMESPACE__ . '\Plugin_Storage::configure', 100 );
106
 
107
+ add_filter( 'map_meta_cap', array( $manager, 'jetpack_connection_custom_caps' ), 1, 4 );
108
  }
109
 
110
  /**
284
  * @param String $password password string.
285
  * @return WP_User|Mixed authenticated user or error.
286
  */
287
+ public function authenticate_jetpack( $user, $username, $password ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
288
  if ( is_a( $user, '\\WP_User' ) ) {
289
  return $user;
290
  }
321
  /**
322
  * Action for logging XMLRPC signature verification errors. This data is sensitive.
323
  *
 
 
 
 
 
 
 
 
324
  * @since 7.5.0
325
  *
326
  * @param WP_Error $signature_verification_error The verification error
327
  */
328
  do_action( 'jetpack_verify_signature_error', $this->xmlrpc_verification );
329
+
330
+ Error_Handler::get_instance()->report_error( $this->xmlrpc_verification );
331
+
332
  }
333
  }
334
 
519
  * @return bool
520
  */
521
  public function is_registered() {
522
+ $has_blog_id = (bool) \Jetpack_Options::get_option( 'id' );
523
+ $has_blog_token = (bool) $this->get_access_token( false );
524
+ return $has_blog_id && $has_blog_token;
525
  }
526
 
527
  /**
728
 
729
  \Jetpack_Options::update_option( 'user_tokens', $tokens );
730
 
731
+ // Delete cached connected user data.
732
+ $transient_key = "jetpack_connected_user_data_$user_id";
733
+ delete_transient( $transient_key );
734
+
735
  /**
736
  * Fires after the current user has been unlinked from WordPress.com.
737
  *
808
  * WordPress.com.
809
  *
810
  * @param String $api_endpoint (optional) an API endpoint to use, defaults to 'register'.
811
+ * @return true|WP_Error The error object.
812
  */
813
  public function register( $api_endpoint = 'register' ) {
814
  add_action( 'pre_update_jetpack_option_register', array( '\\Jetpack_Options', 'delete_option' ) );
847
  $body = apply_filters(
848
  'jetpack_register_request_body',
849
  array(
850
+ 'siteurl' => site_url(),
851
+ 'home' => home_url(),
852
+ 'gmt_offset' => $gmt_offset,
853
+ 'timezone_string' => (string) get_option( 'timezone_string' ),
854
+ 'site_name' => (string) get_option( 'blogname' ),
855
+ 'secret_1' => $secrets['secret_1'],
856
+ 'secret_2' => $secrets['secret_2'],
857
+ 'site_lang' => get_locale(),
858
+ 'timeout' => $timeout,
859
+ 'stats_id' => $stats_id,
860
+ 'state' => get_current_user_id(),
861
+ 'site_created' => $this->get_assumed_site_creation_date(),
862
+ 'jetpack_version' => Constants::get_constant( 'JETPACK__VERSION' ),
863
+ 'ABSPATH' => Constants::get_constant( 'ABSPATH' ),
864
+ 'current_user_email' => wp_get_current_user()->user_email,
865
  )
866
  );
867
 
955
  * @since 2.6
956
  *
957
  * @param Mixed $response the response object, or the error object.
958
+ * @return string|WP_Error A JSON object on success or WP_Error on failures
959
  **/
960
  protected function validate_remote_register_response( $response ) {
961
  if ( is_wp_error( $response ) ) {
1102
  }
1103
  }
1104
 
1105
+ /**
1106
+ * Sets the Connection custom capabilities.
1107
+ *
1108
+ * @param string[] $caps Array of the user's capabilities.
1109
+ * @param string $cap Capability name.
1110
+ * @param int $user_id The user ID.
1111
+ * @param array $args Adds the context to the cap. Typically the object ID.
1112
+ */
1113
+ public function jetpack_connection_custom_caps( $caps, $cap, $user_id, $args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1114
+ $is_offline_mode = ( new Status() )->is_offline_mode();
1115
+ switch ( $cap ) {
1116
+ case 'jetpack_connect':
1117
+ case 'jetpack_reconnect':
1118
+ if ( $is_offline_mode ) {
1119
+ $caps = array( 'do_not_allow' );
1120
+ break;
1121
+ }
1122
+ // Pass through. If it's not offline mode, these should match disconnect.
1123
+ // Let users disconnect if it's offline mode, just in case things glitch.
1124
+ case 'jetpack_disconnect':
1125
+ /**
1126
+ * Filters the jetpack_disconnect capability.
1127
+ *
1128
+ * @since 8.7.0
1129
+ *
1130
+ * @param array An array containing the capability name.
1131
+ */
1132
+ $caps = apply_filters( 'jetpack_disconnect_cap', array( 'manage_options' ) );
1133
+ break;
1134
+ case 'jetpack_connect_user':
1135
+ if ( $is_offline_mode ) {
1136
+ $caps = array( 'do_not_allow' );
1137
+ break;
1138
+ }
1139
+ $caps = array( 'read' );
1140
+ break;
1141
+ }
1142
+ return $caps;
1143
+ }
1144
+
1145
  /**
1146
  * Builds the timeout limit for queries talking with the wpcom servers.
1147
  *
1364
 
1365
  /**
1366
  * Deletes all connection tokens and transients from the local Jetpack site.
1367
+ * If the plugin object has been provided in the constructor, the function first checks
1368
+ * whether it's the only active connection.
1369
+ * If there are any other connections, the function will do nothing and return `false`
1370
+ * (unless `$ignore_connected_plugins` is set to `true`).
1371
+ *
1372
+ * @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
1373
+ *
1374
+ * @return bool True if disconnected successfully, false otherwise.
1375
  */
1376
+ public function delete_all_connection_tokens( $ignore_connected_plugins = false ) {
1377
+ if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) {
1378
+ return false;
1379
+ }
1380
+
1381
+ /**
1382
+ * Fires upon the disconnect attempt.
1383
+ * Return `false` to prevent the disconnect.
1384
+ *
1385
+ * @since 8.7.0
1386
+ */
1387
+ if ( ! apply_filters( 'jetpack_connection_delete_all_tokens', true, $this ) ) {
1388
+ return false;
1389
+ }
1390
+
1391
  \Jetpack_Options::delete_option(
1392
  array(
1393
  'blog_token',
1404
  // Delete cached connected user data.
1405
  $transient_key = 'jetpack_connected_user_data_' . get_current_user_id();
1406
  delete_transient( $transient_key );
1407
+
1408
+ // Delete all XML-RPC errors.
1409
+ Error_Handler::get_instance()->delete_all_errors();
1410
+
1411
+ return true;
1412
  }
1413
 
1414
  /**
1415
  * Tells WordPress.com to disconnect the site and clear all tokens from cached site.
1416
+ * If the plugin object has been provided in the constructor, the function first check
1417
+ * whether it's the only active connection.
1418
+ * If there are any other connections, the function will do nothing and return `false`
1419
+ * (unless `$ignore_connected_plugins` is set to `true`).
1420
+ *
1421
+ * @param bool $ignore_connected_plugins Delete the tokens even if there are other connected plugins.
1422
+ *
1423
+ * @return bool True if disconnected successfully, false otherwise.
1424
  */
1425
+ public function disconnect_site_wpcom( $ignore_connected_plugins = false ) {
1426
+ if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) {
1427
+ return false;
1428
+ }
1429
+
1430
+ /**
1431
+ * Fires upon the disconnect attempt.
1432
+ * Return `false` to prevent the disconnect.
1433
+ *
1434
+ * @since 8.7.0
1435
+ */
1436
+ if ( ! apply_filters( 'jetpack_connection_disconnect_site_wpcom', true, $this ) ) {
1437
+ return false;
1438
+ }
1439
+
1440
  $xml = new \Jetpack_IXR_Client();
1441
  $xml->query( 'jetpack.deregister', get_current_user_id() );
1442
+
1443
+ return true;
1444
+ }
1445
+
1446
+ /**
1447
+ * Disconnect the plugin and remove the tokens.
1448
+ * This function will automatically perform "soft" or "hard" disconnect depending on whether other plugins are using the connection.
1449
+ * This is a proxy method to simplify the Connection package API.
1450
+ *
1451
+ * @see Manager::disable_plugin()
1452
+ * @see Manager::disconnect_site_wpcom()
1453
+ * @see Manager::delete_all_connection_tokens()
1454
+ *
1455
+ * @return bool
1456
+ */
1457
+ public function remove_connection() {
1458
+ $this->disable_plugin();
1459
+ $this->disconnect_site_wpcom();
1460
+ $this->delete_all_connection_tokens();
1461
+
1462
+ return true;
1463
+ }
1464
+
1465
+ /**
1466
+ * Completely clearing up the connection, and initiating reconnect.
1467
+ *
1468
+ * @return true|WP_Error True if reconnected successfully, a `WP_Error` object otherwise.
1469
+ */
1470
+ public function reconnect() {
1471
+ $this->disconnect_site_wpcom( true );
1472
+ $this->delete_all_connection_tokens( true );
1473
+
1474
+ return $this->register();
1475
  }
1476
 
1477
  /**
2152
 
2153
  if ( $user_id ) {
2154
  if ( ! $user_tokens ) {
2155
+ return $suppress_errors ? false : new \WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack' ) );
2156
  }
2157
  if ( self::JETPACK_MASTER_USER === $user_id ) {
2158
  $user_id = \Jetpack_Options::get_option( 'master_user' );
2159
  if ( ! $user_id ) {
2160
+ return $suppress_errors ? false : new \WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack' ) );
2161
  }
2162
  }
2163
  if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
2164
+ // translators: %s is the user ID.
2165
+ return $suppress_errors ? false : new \WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack' ), $user_id ) );
2166
  }
2167
  $user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
2168
  if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
2169
+ // translators: %s is the user ID.
2170
+ return $suppress_errors ? false : new \WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack' ), $user_id ) );
2171
  }
2172
  if ( $user_token_chunks[2] !== (string) $user_id ) {
2173
+ // translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token.
2174
+ return $suppress_errors ? false : new \WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack' ), $user_id, $user_token_chunks[2] ) );
2175
  }
2176
  $possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
2177
  } else {
2201
  }
2202
 
2203
  if ( ! $possible_tokens ) {
2204
+ // If no user tokens were found, it would have failed earlier, so this is about blog token.
2205
+ return $suppress_errors ? false : new \WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack' ) );
2206
  }
2207
 
2208
  $valid_token = false;
2227
  }
2228
 
2229
  if ( ! $valid_token ) {
2230
+ if ( $user_id ) {
2231
+ // translators: %d is the user ID.
2232
+ return $suppress_errors ? false : new \WP_Error( 'no_valid_token', sprintf( __( 'Invalid token for user %d', 'jetpack' ), $user_id ) );
2233
+ } else {
2234
+ return $suppress_errors ? false : new \WP_Error( 'no_valid_token', __( 'Invalid blog token', 'jetpack' ) );
2235
+ }
2236
  }
2237
 
2238
  return (object) array(
2404
  }
2405
 
2406
  /**
2407
+ * Get all connected plugins information, excluding those disconnected by user.
2408
+ * WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
2409
+ * Even if you don't use Jetpack Config, it may be introduced later by other plugins,
2410
+ * so please make sure not to run the method too early in the code.
2411
  *
2412
+ * @return array|WP_Error
2413
  */
2414
  public function get_connected_plugins() {
2415
+ $maybe_plugins = Plugin_Storage::get_all( true );
2416
+
2417
+ if ( $maybe_plugins instanceof WP_Error ) {
2418
+ return $maybe_plugins;
2419
+ }
2420
+
2421
+ return $maybe_plugins;
2422
+ }
2423
+
2424
+ /**
2425
+ * Force plugin disconnect. After its called, the plugin will not be allowed to use the connection.
2426
+ * Note: this method does not remove any access tokens.
2427
+ *
2428
+ * @return bool
2429
+ */
2430
+ public function disable_plugin() {
2431
+ if ( ! $this->plugin ) {
2432
+ return false;
2433
+ }
2434
+
2435
+ return $this->plugin->disable();
2436
+ }
2437
+
2438
+ /**
2439
+ * Force plugin reconnect after user-initiated disconnect.
2440
+ * After its called, the plugin will be allowed to use the connection again.
2441
+ * Note: this method does not initialize access tokens.
2442
+ *
2443
+ * @return bool
2444
+ */
2445
+ public function enable_plugin() {
2446
+ if ( ! $this->plugin ) {
2447
+ return false;
2448
+ }
2449
+
2450
+ return $this->plugin->enable();
2451
+ }
2452
+
2453
+ /**
2454
+ * Whether the plugin is allowed to use the connection, or it's been disconnected by user.
2455
+ * If no plugin slug was passed into the constructor, always returns true.
2456
+ *
2457
+ * @return bool
2458
+ */
2459
+ public function is_plugin_enabled() {
2460
+ if ( ! $this->plugin ) {
2461
+ return true;
2462
+ }
2463
+
2464
+ return $this->plugin->is_enabled();
2465
  }
2466
 
2467
  }
vendor/automattic/jetpack-connection/src/class-plugin-storage.php CHANGED
@@ -7,20 +7,17 @@
7
 
8
  namespace Automattic\Jetpack\Connection;
9
 
10
- use Automattic\Jetpack\Config;
11
  use WP_Error;
12
 
13
  /**
14
- * The class serves a single purpose - to store the data that plugins use the connection, along with some auxiliary information.
15
- * Well, we don't really store all that. The information is provided on runtime,
16
- * so all we need to do is to save the data into the class property and retrieve it from there on demand.
17
- *
18
- * @todo Adapt for multisite installations.
19
  */
20
  class Plugin_Storage {
21
 
22
  const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
23
 
 
 
24
  /**
25
  * Whether this class was configured for the first time or not.
26
  *
@@ -42,14 +39,6 @@ class Plugin_Storage {
42
  */
43
  private static $plugins = array();
44
 
45
- /**
46
- * Whether the plugins were configured.
47
- * To make sure we don't call the configuration process again and again.
48
- *
49
- * @var bool
50
- */
51
- private static $plugins_configuration_finished = false;
52
-
53
  /**
54
  * Add or update the plugin information in the storage.
55
  *
@@ -62,7 +51,7 @@ class Plugin_Storage {
62
  self::$plugins[ $slug ] = $args;
63
 
64
  // if plugin is not in the list of active plugins, refresh the list.
65
- if ( ! array_key_exists( $slug, get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) ) ) {
66
  self::$refresh_connected_plugins = true;
67
  }
68
 
@@ -95,16 +84,18 @@ class Plugin_Storage {
95
  * Even if you don't use Jetpack Config, it may be introduced later by other plugins,
96
  * so please make sure not to run the method too early in the code.
97
  *
 
 
98
  * @return array|WP_Error
99
  */
100
- public static function get_all() {
101
  $maybe_error = self::ensure_configured();
102
 
103
  if ( $maybe_error instanceof WP_Error ) {
104
  return $maybe_error;
105
  }
106
 
107
- return self::$plugins;
108
  }
109
 
110
  /**
@@ -156,7 +147,7 @@ class Plugin_Storage {
156
  }
157
 
158
  // If a plugin was activated or deactivated.
159
- $number_of_plugins_differ = count( self::$plugins ) !== count( get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
160
 
161
  if ( $number_of_plugins_differ || true === self::$refresh_connected_plugins ) {
162
  self::update_active_plugins_option();
@@ -176,4 +167,50 @@ class Plugin_Storage {
176
  update_option( self::ACTIVE_PLUGINS_OPTION_NAME, self::$plugins );
177
  }
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
7
 
8
  namespace Automattic\Jetpack\Connection;
9
 
 
10
  use WP_Error;
11
 
12
  /**
13
+ * The class serves a single purpose - to store the data which plugins use the connection, along with some auxiliary information.
 
 
 
 
14
  */
15
  class Plugin_Storage {
16
 
17
  const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
18
 
19
+ const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
20
+
21
  /**
22
  * Whether this class was configured for the first time or not.
23
  *
39
  */
40
  private static $plugins = array();
41
 
 
 
 
 
 
 
 
 
42
  /**
43
  * Add or update the plugin information in the storage.
44
  *
51
  self::$plugins[ $slug ] = $args;
52
 
53
  // if plugin is not in the list of active plugins, refresh the list.
54
+ if ( ! array_key_exists( $slug, (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) ) ) {
55
  self::$refresh_connected_plugins = true;
56
  }
57
 
84
  * Even if you don't use Jetpack Config, it may be introduced later by other plugins,
85
  * so please make sure not to run the method too early in the code.
86
  *
87
+ * @param bool $connected_only Exclude plugins that were explicitly disconnected.
88
+ *
89
  * @return array|WP_Error
90
  */
91
+ public static function get_all( $connected_only = false ) {
92
  $maybe_error = self::ensure_configured();
93
 
94
  if ( $maybe_error instanceof WP_Error ) {
95
  return $maybe_error;
96
  }
97
 
98
+ return $connected_only ? array_diff_key( self::$plugins, array_flip( self::get_all_disabled_plugins() ) ) : self::$plugins;
99
  }
100
 
101
  /**
147
  }
148
 
149
  // If a plugin was activated or deactivated.
150
+ $number_of_plugins_differ = count( self::$plugins ) !== count( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
151
 
152
  if ( $number_of_plugins_differ || true === self::$refresh_connected_plugins ) {
153
  self::update_active_plugins_option();
167
  update_option( self::ACTIVE_PLUGINS_OPTION_NAME, self::$plugins );
168
  }
169
 
170
+ /**
171
+ * Add the plugin to the set of disconnected ones.
172
+ *
173
+ * @param string $slug Plugin slug.
174
+ *
175
+ * @return bool
176
+ */
177
+ public static function disable_plugin( $slug ) {
178
+ $disconnects = self::get_all_disabled_plugins();
179
+
180
+ if ( ! in_array( $slug, $disconnects, true ) ) {
181
+ $disconnects[] = $slug;
182
+ update_option( self::PLUGINS_DISABLED_OPTION_NAME, $disconnects );
183
+ }
184
+
185
+ return true;
186
+ }
187
+
188
+ /**
189
+ * Remove the plugin from the set of disconnected ones.
190
+ *
191
+ * @param string $slug Plugin slug.
192
+ *
193
+ * @return bool
194
+ */
195
+ public static function enable_plugin( $slug ) {
196
+ $disconnects = self::get_all_disabled_plugins();
197
+
198
+ $slug_index = array_search( $slug, $disconnects, true );
199
+ if ( false !== $slug_index ) {
200
+ unset( $disconnects[ $slug_index ] );
201
+ update_option( self::PLUGINS_DISABLED_OPTION_NAME, $disconnects );
202
+ }
203
+
204
+ return true;
205
+ }
206
+
207
+ /**
208
+ * Get all plugins that were disconnected by user.
209
+ *
210
+ * @return array
211
+ */
212
+ public static function get_all_disabled_plugins() {
213
+ return (array) get_option( self::PLUGINS_DISABLED_OPTION_NAME, array() );
214
+ }
215
+
216
  }
vendor/automattic/jetpack-connection/src/class-plugin.php CHANGED
@@ -74,9 +74,36 @@ class Plugin {
74
  * @return bool
75
  */
76
  public function is_only() {
77
- $plugins = Plugin_Storage::get_all();
78
 
79
  return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
74
  * @return bool
75
  */
76
  public function is_only() {
77
+ $plugins = Plugin_Storage::get_all( true );
78
 
79
  return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
80
  }
81
 
82
+ /**
83
+ * Add the plugin to the set of disconnected ones.
84
+ *
85
+ * @return bool
86
+ */
87
+ public function disable() {
88
+ return Plugin_Storage::disable_plugin( $this->slug );
89
+ }
90
+
91
+ /**
92
+ * Remove the plugin from the set of disconnected ones.
93
+ *
94
+ * @return bool
95
+ */
96
+ public function enable() {
97
+ return Plugin_Storage::enable_plugin( $this->slug );
98
+ }
99
+
100
+ /**
101
+ * Whether this plugin is allowed to use the connection.
102
+ *
103
+ * @return bool
104
+ */
105
+ public function is_enabled() {
106
+ return ! in_array( $this->slug, Plugin_Storage::get_all_disabled_plugins(), true );
107
+ }
108
+
109
  }
vendor/automattic/jetpack-connection/src/class-rest-connector.php CHANGED
@@ -7,6 +7,13 @@
7
 
8
  namespace Automattic\Jetpack\Connection;
9
 
 
 
 
 
 
 
 
10
  /**
11
  * Registers the REST routes for Connections.
12
  */
@@ -18,6 +25,13 @@ class REST_Connector {
18
  */
19
  private $connection;
20
 
 
 
 
 
 
 
 
21
  /**
22
  * Constructor.
23
  *
@@ -26,13 +40,72 @@ class REST_Connector {
26
  public function __construct( Manager $connection ) {
27
  $this->connection = $connection;
28
 
29
- // Register a site.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  register_rest_route(
31
  'jetpack/v4',
32
- '/verify_registration',
33
  array(
34
- 'methods' => \WP_REST_Server::EDITABLE,
35
- 'callback' => array( $this, 'verify_registration' ),
 
 
 
 
 
 
 
36
  )
37
  );
38
  }
@@ -42,13 +115,168 @@ class REST_Connector {
42
  *
43
  * @since 5.4.0
44
  *
45
- * @param \WP_REST_Request $request The request sent to the WP REST API.
46
  *
47
  * @return string|WP_Error
48
  */
49
- public function verify_registration( \WP_REST_Request $request ) {
50
  $registration_data = array( $request['secret_1'], $request['state'] );
51
 
52
  return $this->connection->handle_registration( $registration_data );
53
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
7
 
8
  namespace Automattic\Jetpack\Connection;
9
 
10
+ use Automattic\Jetpack\Status;
11
+ use Jetpack_XMLRPC_Server;
12
+ use WP_Error;
13
+ use WP_REST_Request;
14
+ use WP_REST_Response;
15
+ use WP_REST_Server;
16
+
17
  /**
18
  * Registers the REST routes for Connections.
19
  */
25
  */
26
  private $connection;
27
 
28
+ /**
29
+ * This property stores the localized "Insufficient Permissions" error message.
30
+ *
31
+ * @var string Generic error message when user is not allowed to perform an action.
32
+ */
33
+ private static $user_permissions_error_msg;
34
+
35
  /**
36
  * Constructor.
37
  *
40
  public function __construct( Manager $connection ) {
41
  $this->connection = $connection;
42
 
43
+ self::$user_permissions_error_msg = esc_html__(
44
+ 'You do not have the correct user permissions to perform this action.
45
+ Please contact your site admin if you think this is a mistake.',
46
+ 'jetpack'
47
+ );
48
+
49
+ if ( ! $this->connection->is_active() ) {
50
+ // Register a site.
51
+ register_rest_route(
52
+ 'jetpack/v4',
53
+ '/verify_registration',
54
+ array(
55
+ 'methods' => WP_REST_Server::EDITABLE,
56
+ 'callback' => array( $this, 'verify_registration' ),
57
+ 'permission_callback' => '__return_true',
58
+ )
59
+ );
60
+ }
61
+
62
+ // Authorize a remote user.
63
+ register_rest_route(
64
+ 'jetpack/v4',
65
+ '/remote_authorize',
66
+ array(
67
+ 'methods' => WP_REST_Server::EDITABLE,
68
+ 'callback' => __CLASS__ . '::remote_authorize',
69
+ 'permission_callback' => '__return_true',
70
+ )
71
+ );
72
+
73
+ // Get current connection status of Jetpack.
74
+ register_rest_route(
75
+ 'jetpack/v4',
76
+ '/connection',
77
+ array(
78
+ 'methods' => WP_REST_Server::READABLE,
79
+ 'callback' => __CLASS__ . '::connection_status',
80
+ 'permission_callback' => '__return_true',
81
+ )
82
+ );
83
+
84
+ // Get list of plugins that use the Jetpack connection.
85
+ register_rest_route(
86
+ 'jetpack/v4',
87
+ '/connection/plugins',
88
+ array(
89
+ 'methods' => WP_REST_Server::READABLE,
90
+ 'callback' => array( $this, 'get_connection_plugins' ),
91
+ 'permission_callback' => __CLASS__ . '::activate_plugins_permission_check',
92
+ )
93
+ );
94
+
95
+ // Full or partial reconnect in case of connection issues.
96
  register_rest_route(
97
  'jetpack/v4',
98
+ '/connection/reconnect',
99
  array(
100
+ 'methods' => WP_REST_Server::EDITABLE,
101
+ 'callback' => array( $this, 'connection_reconnect' ),
102
+ 'args' => array(
103
+ 'action' => array(
104
+ 'type' => 'string',
105
+ 'required' => true,
106
+ ),
107
+ ),
108
+ 'permission_callback' => __CLASS__ . '::jetpack_disconnect_permission_check',
109
  )
110
  );
111
  }
115
  *
116
  * @since 5.4.0
117
  *
118
+ * @param WP_REST_Request $request The request sent to the WP REST API.
119
  *
120
  * @return string|WP_Error
121
  */
122
+ public function verify_registration( WP_REST_Request $request ) {
123
  $registration_data = array( $request['secret_1'], $request['state'] );
124
 
125
  return $this->connection->handle_registration( $registration_data );
126
  }
127
+
128
+ /**
129
+ * Handles verification that a site is registered
130
+ *
131
+ * @since 5.4.0
132
+ *
133
+ * @param WP_REST_Request $request The request sent to the WP REST API.
134
+ *
135
+ * @return array|wp-error
136
+ */
137
+ public static function remote_authorize( $request ) {
138
+ $xmlrpc_server = new Jetpack_XMLRPC_Server();
139
+ $result = $xmlrpc_server->remote_authorize( $request );
140
+
141
+ if ( is_a( $result, 'IXR_Error' ) ) {
142
+ $result = new WP_Error( $result->code, $result->message );
143
+ }
144
+
145
+ return $result;
146
+ }
147
+
148
+ /**
149
+ * Get connection status for this Jetpack site.
150
+ *
151
+ * @since 4.3.0
152
+ *
153
+ * @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
154
+ *
155
+ * @return WP_REST_Response|array Connection information.
156
+ */
157
+ public static function connection_status( $rest_response = true ) {
158
+ $status = new Status();
159
+ $connection = new Manager();
160
+
161
+ $connection_status = array(
162
+ 'isActive' => $connection->is_active(),
163
+ 'isStaging' => $status->is_staging_site(),
164
+ 'isRegistered' => $connection->is_registered(),
165
+ 'offlineMode' => array(
166
+ 'isActive' => $status->is_offline_mode(),
167
+ 'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
168
+ 'url' => $status->is_local_site(),
169
+ /** This filter is documented in packages/status/src/class-status.php */
170
+ 'filter' => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
171
+ 'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
172
+ ),
173
+ 'isPublic' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
174
+ );
175
+
176
+ if ( $rest_response ) {
177
+ return rest_ensure_response(
178
+ $connection_status
179
+ );
180
+ } else {
181
+ return $connection_status;
182
+ }
183
+ }
184
+
185
+
186
+ /**
187
+ * Get plugins connected to the Jetpack.
188
+ *
189
+ * @since 8.6.0
190
+ *
191
+ * @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
192
+ */
193
+ public function get_connection_plugins() {
194
+ $plugins = $this->connection->get_connected_plugins();
195
+
196
+ if ( is_wp_error( $plugins ) ) {
197
+ return $plugins;
198
+ }
199
+
200
+ array_walk(
201
+ $plugins,
202
+ function( &$data, $slug ) {
203
+ $data['slug'] = $slug;
204
+ }
205
+ );
206
+
207
+ return rest_ensure_response( array_values( $plugins ) );
208
+ }
209
+
210
+ /**
211
+ * Verify that user can view Jetpack admin page and can activate plugins.
212
+ *
213
+ * @since 8.8.0
214
+ *
215
+ * @return bool|WP_Error Whether user has the capability 'activate_plugins'.
216
+ */
217
+ public static function activate_plugins_permission_check() {
218
+ if ( current_user_can( 'activate_plugins' ) ) {
219
+ return true;
220
+ }
221
+
222
+ return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
223
+ }
224
+
225
+ /**
226
+ * Verify that user is allowed to disconnect Jetpack.
227
+ *
228
+ * @since 8.8.0
229
+ *
230
+ * @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'.
231
+ */
232
+ public static function jetpack_disconnect_permission_check() {
233
+ if ( current_user_can( 'jetpack_disconnect' ) ) {
234
+ return true;
235
+ }
236
+
237
+ return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
238
+ }
239
+
240
+ /**
241
+ * Returns generic error message when user is not allowed to perform an action.
242
+ *
243
+ * @return string The error message.
244
+ */
245
+ public static function get_user_permissions_error_msg() {
246
+ return self::$user_permissions_error_msg;
247
+ }
248
+
249
+ /**
250
+ * The endpoint tried to partially or fully reconnect the website to WP.com.
251
+ *
252
+ * @since 8.8.0
253
+ *
254
+ * @param WP_REST_Request $request The request sent to the WP REST API.
255
+ *
256
+ * @return \WP_REST_Response|WP_Error
257
+ */
258
+ public function connection_reconnect( WP_REST_Request $request ) {
259
+ $params = $request->get_json_params();
260
+
261
+ $response = array();
262
+
263
+ switch ( $params['action'] ) {
264
+ case 'reconnect':
265
+ $result = $this->connection->reconnect();
266
+
267
+ if ( true === $result ) {
268
+ $response['status'] = 'in_progress';
269
+ $response['authorizeUrl'] = $this->connection->get_authorization_url();
270
+ } elseif ( is_wp_error( $result ) ) {
271
+ $response = $result;
272
+ }
273
+ break;
274
+ default:
275
+ $response = new WP_Error( 'Unknown action' );
276
+ break;
277
+ }
278
+
279
+ return rest_ensure_response( $response );
280
+ }
281
+
282
  }
vendor/automattic/jetpack-connection/src/error-handlers/class-invalid-blog-token.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The Jetpack Connection error handler class for invalid blog tokens
4
+ *
5
+ * @package automattic/jetpack-connection
6
+ */
7
+
8
+ namespace Automattic\Jetpack\Connection\Error_Handlers;
9
+
10
+ /**
11
+ * This class handles all the error codes that indicates a broken blog token and
12
+ * suggests the user to reconnect.
13
+ *
14
+ * @since 8.7.0
15
+ */
16
+ class Invalid_Blog_Token {
17
+
18
+ /**
19
+ * Set up hooks
20
+ *
21
+ * @since 8.7.0
22
+ *
23
+ * @param array $errors The array containing verified errors stored in the database.
24
+ */
25
+ public function __construct( $errors ) {
26
+
27
+ /**
28
+ * Filters the message to be displayed in the admin notices area when there's a invalid blog token xmlrpc error
29
+ *
30
+ * Return an empty value to disable the message.
31
+ *
32
+ * @since 8.7.0
33
+ *
34
+ * @param string $message The error message.
35
+ */
36
+ $this->message = apply_filters( 'jetpack_connection_invalid_blog_token_admin_notice', __( 'Your connection with WordPress.com seems to be broken. If you\'re experiencing issues, please try reconnecting.', 'jetpack' ) );
37
+
38
+ if ( empty( $this->message ) ) {
39
+ return;
40
+ }
41
+
42
+ // In this class, we will only handle errors with the blog token, so ignoring if there are only errors with user tokens.
43
+ if ( ! isset( $errors[0] ) && ! isset( $errors['invalid'] ) ) {
44
+ return;
45
+ }
46
+
47
+ add_action( 'react_connection_errors_initial_state', array( $this, 'jetpack_react_dashboard_error' ) );
48
+ // do not add admin notice to the jetpack dashboard.
49
+ global $pagenow;
50
+ if ( 'admin.php' === $pagenow || isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { // phpcs:ignore
51
+ return;
52
+ }
53
+ add_action( 'admin_notices', array( $this, 'admin_notice' ) );
54
+
55
+ }
56
+
57
+ /**
58
+ * Prints an admin notice for the blog token error
59
+ *
60
+ * @since 8.7.0
61
+ *
62
+ * @return void
63
+ */
64
+ public function admin_notice() {
65
+
66
+ if ( ! current_user_can( 'jetpack_connect' ) ) {
67
+ return;
68
+ }
69
+
70
+ ?>
71
+ <div class="notice notice-error is-dismissible jetpack-message jp-connect" style="display:block !important;">
72
+ <p><?php echo esc_html( $this->message ); ?></p>
73
+ </div>
74
+ <?php
75
+ }
76
+
77
+ /**
78
+ * Adds the error message to the Jetpack React Dashboard
79
+ *
80
+ * @param array $errors The array of errors.
81
+ * @return array
82
+ */
83
+ public function jetpack_react_dashboard_error( $errors ) {
84
+
85
+ $errors[] = array(
86
+ 'code' => 'invalid_blog_token',
87
+ 'message' => $this->message,
88
+ 'action' => 'reconnect',
89
+ );
90
+ return $errors;
91
+ }
92
+
93
+
94
+ }
vendor/automattic/jetpack-constants/phpunit.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <phpunit bootstrap="tests/php/bootstrap.php" backupGlobals="false" colors="true">
2
+ <testsuites>
3
+ <testsuite name="main">
4
+ <directory prefix="test" suffix=".php">tests/php</directory>
5
+ </testsuite>
6
+ </testsuites>
7
+ </phpunit>
vendor/automattic/jetpack-options/composer.json CHANGED
@@ -4,7 +4,7 @@
4
  "type": "library",
5
  "license": "GPL-2.0-or-later",
6
  "require": {
7
- "automattic/jetpack-constants": "1.2.0"
8
  },
9
  "require-dev": {
10
  "phpunit/phpunit": "7.*.*",
4
  "type": "library",
5
  "license": "GPL-2.0-or-later",
6
  "require": {
7
+ "automattic/jetpack-constants": "1.4.0"
8
  },
9
  "require-dev": {
10
  "phpunit/phpunit": "7.*.*",
vendor/automattic/jetpack-options/legacy/class-jetpack-options.php CHANGED
@@ -167,7 +167,8 @@ class Jetpack_Options {
167
  }
168
 
169
  /**
170
- * Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
 
171
  *
172
  * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
173
  * @param mixed $default (optional).
@@ -175,6 +176,28 @@ class Jetpack_Options {
175
  * @return mixed
176
  */
177
  public static function get_option( $name, $default = false ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  if ( self::is_valid( $name, 'non_compact' ) ) {
179
  if ( self::is_network_option( $name ) ) {
180
  return get_site_option( "jetpack_$name", $default );
@@ -481,7 +504,7 @@ class Jetpack_Options {
481
 
482
  /**
483
  * This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
484
- * Options can be selectively managed via a blacklist by filtering option names via the jetpack_disabled_raw_option filter.
485
  *
486
  * @param string $name Option name.
487
  *
@@ -497,7 +520,7 @@ class Jetpack_Options {
497
  *
498
  * @since 5.5.0
499
  *
500
- * @param array $disabled_raw_options An array of option names that you can selectively blacklist from being managed via direct database queries.
501
  */
502
  $disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
503
  return isset( $disabled_raw_options[ $name ] );
167
  }
168
 
169
  /**
170
+ * Filters the requested option.
171
+ * This is a wrapper around `get_option_from_database` so that we can filter the option.
172
  *
173
  * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
174
  * @param mixed $default (optional).
176
  * @return mixed
177
  */
178
  public static function get_option( $name, $default = false ) {
179
+ /**
180
+ * Filter Jetpack Options.
181
+ * Can be useful in environments when Jetpack is running with a different setup
182
+ *
183
+ * @since 8.8.0
184
+ *
185
+ * @param string $value The value from the database.
186
+ * @param string $name Option name, _without_ `jetpack_%` prefix.
187
+ * @return string $value, unless the filters modify it.
188
+ */
189
+ return apply_filters( 'jetpack_options', self::get_option_from_database( $name, $default ), $name );
190
+ }
191
+
192
+ /**
193
+ * Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
194
+ *
195
+ * @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
196
+ * @param mixed $default (optional).
197
+ *
198
+ * @return mixed
199
+ */
200
+ private static function get_option_from_database( $name, $default = false ) {
201
  if ( self::is_valid( $name, 'non_compact' ) ) {
202
  if ( self::is_network_option( $name ) ) {
203
  return get_site_option( "jetpack_$name", $default );
504
 
505
  /**
506
  * This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
507
+ * Options can be selectively managed via a blocklist by filtering option names via the jetpack_disabled_raw_option filter.
508
  *
509
  * @param string $name Option name.
510
  *
520
  *
521
  * @since 5.5.0
522
  *
523
+ * @param array $disabled_raw_options An array of option names that you can selectively blocklist from being managed via direct database queries.
524
  */
525
  $disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
526
  return isset( $disabled_raw_options[ $name ] );
vendor/automattic/jetpack-roles/phpunit.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <phpunit bootstrap="tests/php/bootstrap.php" backupGlobals="false" colors="true">
2
+ <testsuites>
3
+ <testsuite name="main">
4
+ <directory prefix="test" suffix=".php">tests/php</directory>
5
+ </testsuite>
6
+ </testsuites>
7
+ </phpunit>
vendor/automattic/jetpack-status/README.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Jetpack Status
2
+
3
+ A status class for Jetpack.
4
+
5
+ Used to retrieve information about the current status of Jetpack and the site overall.
6
+
7
+ ### Usage
8
+
9
+ Find out whether the site is in offline mode:
10
+
11
+ ```php
12
+ use Automattic\Jetpack\Status;
13
+
14
+ $status = new Status();
15
+ $is_offline_mode = $status->is_offline_mode();
16
+ ```
17
+
18
+ Find out whether this is a system with multiple networks:
19
+
20
+ ```php
21
+ use Automattic\Jetpack\Status;
22
+
23
+ $status = new Status();
24
+ $is_multi_network = $status->is_multi_network();
25
+ ```
26
+
27
+ Find out whether this site is a single user site:
28
+
29
+ ```php
30
+ use Automattic\Jetpack\Status;
31
+
32
+ $status = new Status();
33
+ $is_single_user_site = $status->is_single_user_site();
34
+ ```
vendor/automattic/jetpack-status/composer.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "automattic/jetpack-status",
3
+ "description": "Used to retrieve information about the current status of Jetpack and the site overall.",
4
+ "type": "library",
5
+ "license": "GPL-2.0-or-later",
6
+ "require": {},
7
+ "require-dev": {
8
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5",
9
+ "php-mock/php-mock": "^2.1",
10
+ "brain/monkey": "2.4.0"
11
+ },
12
+ "autoload": {
13
+ "classmap": [
14
+ "src/"
15
+ ]
16
+ },
17
+ "scripts": {
18
+ "phpunit": [
19
+ "@composer install",
20
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
21
+ ]
22
+ },
23
+ "repositories": [
24
+ {
25
+ "type": "path",
26
+ "url": "../*"
27
+ }
28
+ ],
29
+ "minimum-stability": "dev",
30
+ "prefer-stable": true
31
+ }
vendor/automattic/jetpack-status/src/class-status.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * A status class for Jetpack.
4
+ *
5
+ * @package automattic/jetpack-status
6
+ */
7
+
8
+ namespace Automattic\Jetpack;
9
+
10
+ /**
11
+ * Class Automattic\Jetpack\Status
12
+ *
13
+ * Used to retrieve information about the current status of Jetpack and the site overall.
14
+ */
15
+ class Status {
16
+ /**
17
+ * Is Jetpack in development (offline) mode?
18
+ *
19
+ * @deprecated 8.8.0 Use Status->is_offline_mode().
20
+ *
21
+ * @return bool Whether Jetpack's offline mode is active.
22
+ */
23
+ public function is_development_mode() {
24
+ _deprecated_function( __FUNCTION__, 'Jetpack 8.8.0', 'Automattic\Jetpack\Status->is_offline_mode' );
25
+ return $this->is_offline_mode();
26
+ }
27
+
28
+ /**
29
+ * Is Jetpack in offline mode?
30
+ *
31
+ * This was formerly called "Development Mode", but sites "in development" aren't always offline/localhost.
32
+ *
33
+ * @since 8.8.0
34
+ *
35
+ * @return bool Whether Jetpack's offline mode is active.
36
+ */
37
+ public function is_offline_mode() {
38
+ $offline_mode = false;
39
+
40
+ if ( defined( '\\JETPACK_DEV_DEBUG' ) ) {
41
+ $offline_mode = constant( '\\JETPACK_DEV_DEBUG' );
42
+ } elseif ( defined( '\\WP_LOCAL_DEV' ) ) {
43
+ $offline_mode = constant( '\\WP_LOCAL_DEV' );
44
+ } elseif ( $this->is_local_site() ) {
45
+ $offline_mode = true;
46
+ }
47
+
48
+ /**
49
+ * Filters Jetpack's offline mode.
50
+ *
51
+ * @see https://jetpack.com/support/development-mode/
52
+ * @todo Update documentation ^^.
53
+ *
54
+ * @since 2.2.1
55
+ * @deprecated 8.8.0
56
+ *
57
+ * @param bool $offline_mode Is Jetpack's offline mode active.
58
+ */
59
+ $offline_mode = (bool) apply_filters_deprecated( 'jetpack_development_mode', array( $offline_mode ), '8.8.0', 'jetpack_offline_mode' );
60
+
61
+ /**
62
+ * Filters Jetpack's offline mode.
63
+ *
64
+ * @see https://jetpack.com/support/development-mode/
65
+ * @todo Update documentation ^^.
66
+ *
67
+ * @since 8.8.0
68
+ *
69
+ * @param bool $offline_mode Is Jetpack's offline mode active.
70
+ */
71
+ $offline_mode = (bool) apply_filters( 'jetpack_offline_mode', $offline_mode );
72
+
73
+ return $offline_mode;
74
+ }
75
+
76
+ /**
77
+ * Whether this is a system with a multiple networks.
78
+ * Implemented since there is no core is_multi_network function.
79
+ * Right now there is no way to tell which network is the dominant network on the system.
80
+ *
81
+ * @return boolean
82
+ */
83
+ public function is_multi_network() {
84
+ global $wpdb;
85
+
86
+ // If we don't have a multi site setup no need to do any more.
87
+ if ( ! is_multisite() ) {
88
+ return false;
89
+ }
90
+
91
+ $num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
92
+ if ( $num_sites > 1 ) {
93
+ return true;
94
+ }
95
+
96
+ return false;
97
+ }
98
+
99
+ /**
100
+ * Whether the current site is single user site.
101
+ *
102
+ * @return bool
103
+ */
104
+ public function is_single_user_site() {
105
+ global $wpdb;
106
+
107
+ $some_users = get_transient( 'jetpack_is_single_user' );
108
+ if ( false === $some_users ) {
109
+ $some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
110
+ set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
111
+ }
112
+ return 1 === (int) $some_users;
113
+ }
114
+
115
+ /**
116
+ * If the site is a local site.
117
+ *
118
+ * @since 8.8.0
119
+ *
120
+ * @return bool
121
+ */
122
+ public function is_local_site() {
123
+ // Check for localhost and sites using an IP only first.
124
+ $is_local = site_url() && false === strpos( site_url(), '.' );
125
+
126
+ // Then check for usual usual domains used by local dev tools.
127
+ $known_local = array(
128
+ '#\.local$#i',
129
+ '#\.localhost$#i',
130
+ '#\.test$#i',
131
+ '#\.docksal$#i', // Docksal.
132
+ '#\.docksal\.site$#i', // Docksal.
133
+ '#\.dev\.cc$#i', // ServerPress.
134
+ '#\.lndo\.site$#i', // Lando.
135
+ );
136
+
137
+ if ( ! $is_local ) {
138
+ foreach ( $known_local as $url ) {
139
+ if ( preg_match( $url, site_url() ) ) {
140
+ $is_local = true;
141
+ break;
142
+ }
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Filters is_local_site check.
148
+ *
149
+ * @since 8.8.0
150
+ *
151
+ * @param bool $is_local If the current site is a local site.
152
+ */
153
+ return apply_filters( 'jetpack_is_local_site', $is_local );
154
+ }
155
+
156
+ /**
157
+ * If is a staging site.
158
+ *
159
+ * @todo Add IDC detection to a package.
160
+ *
161
+ * @return bool
162
+ */
163
+ public function is_staging_site() {
164
+ // @todo Remove function_exists when WP 5.5 is the minimum version.
165
+ // Core's wp_get_environment_type allows for a few specific options. We should default to bowing out gracefully for anything other than production.
166
+ $is_staging = function_exists( 'wp_get_environment_type' ) && 'production' !== wp_get_environment_type();
167
+
168
+ $known_staging = array(
169
+ 'urls' => array(
170
+ '#\.staging\.wpengine\.com$#i', // WP Engine.
171
+ '#\.staging\.kinsta\.com$#i', // Kinsta.com.
172
+ '#\.kinsta\.cloud$#i', // Kinsta.com.
173
+ '#\.stage\.site$#i', // DreamPress.
174
+ '#\.newspackstaging\.com$#i', // Newspack.
175
+ '#\.pantheonsite\.io$#i', // Pantheon.
176
+ '#\.flywheelsites\.com$#i', // Flywheel.
177
+ '#\.flywheelstaging\.com$#i', // Flywheel.
178
+ '#\.cloudwaysapps\.com$#i', // Cloudways.
179
+ '#\.azurewebsites\.net$#i', // Azure.
180
+ '#\.wpserveur\.net$#i', // WPServeur.
181
+ '#\-liquidwebsites\.com$#i', // Liquidweb.
182
+ ),
183
+ 'constants' => array(
184
+ 'IS_WPE_SNAPSHOT', // WP Engine.
185
+ 'KINSTA_DEV_ENV', // Kinsta.com.
186
+ 'WPSTAGECOACH_STAGING', // WP Stagecoach.
187
+ 'JETPACK_STAGING_MODE', // Generic.
188
+ 'WP_LOCAL_DEV', // Generic.
189
+ ),
190
+ );
191
+ /**
192
+ * Filters the flags of known staging sites.
193
+ *
194
+ * @since 3.9.0
195
+ *
196
+ * @param array $known_staging {
197
+ * An array of arrays that each are used to check if the current site is staging.
198
+ * @type array $urls URLs of staging sites in regex to check against site_url.
199
+ * @type array $constants PHP constants of known staging/developement environments.
200
+ * }
201
+ */
202
+ $known_staging = apply_filters( 'jetpack_known_staging', $known_staging );
203
+
204
+ if ( isset( $known_staging['urls'] ) ) {
205
+ foreach ( $known_staging['urls'] as $url ) {
206
+ if ( preg_match( $url, site_url() ) ) {
207
+ $is_staging = true;
208
+ break;
209
+ }
210
+ }
211
+ }
212
+
213
+ if ( isset( $known_staging['constants'] ) ) {
214
+ foreach ( $known_staging['constants'] as $constant ) {
215
+ if ( defined( $constant ) && constant( $constant ) ) {
216
+ $is_staging = true;
217
+ }
218
+ }
219
+ }
220
+
221
+ // Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
222
+ if ( ! $is_staging && method_exists( 'Jetpack', 'validate_sync_error_idc_option' ) && \Jetpack::validate_sync_error_idc_option() ) {
223
+ $is_staging = true;
224
+ }
225
+
226
+ /**
227
+ * Filters is_staging_site check.
228
+ *
229
+ * @since 3.9.0
230
+ *
231
+ * @param bool $is_staging If the current site is a staging site.
232
+ */
233
+ return apply_filters( 'jetpack_is_staging_site', $is_staging );
234
+ }
235
+ }
vendor/composer/ClassLoader.php CHANGED
@@ -279,7 +279,7 @@ class ClassLoader
279
  */
280
  public function setApcuPrefix($apcuPrefix)
281
  {
282
- $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
283
  }
284
 
285
  /**
279
  */
280
  public function setApcuPrefix($apcuPrefix)
281
  {
282
+ $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
  }
284
 
285
  /**
vendor/composer/autoload_classmap.php CHANGED
@@ -8,6 +8,8 @@ $baseDir = dirname($vendorDir);
8
  return array(
9
  'Automattic\\Jetpack\\Config' => $vendorDir . '/automattic/jetpack-config/src/class-config.php',
10
  'Automattic\\Jetpack\\Connection\\Client' => $vendorDir . '/automattic/jetpack-connection/src/class-client.php',
 
 
11
  'Automattic\\Jetpack\\Connection\\Manager' => $vendorDir . '/automattic/jetpack-connection/src/class-manager.php',
12
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => $vendorDir . '/automattic/jetpack-connection/src/interface-manager.php',
13
  'Automattic\\Jetpack\\Connection\\Plugin' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin.php',
@@ -17,6 +19,7 @@ return array(
17
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => $vendorDir . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php',
18
  'Automattic\\Jetpack\\Constants' => $vendorDir . '/automattic/jetpack-constants/src/class-constants.php',
19
  'Automattic\\Jetpack\\Roles' => $vendorDir . '/automattic/jetpack-roles/src/class-roles.php',
 
20
  'Jetpack_IXR_Client' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php',
21
  'Jetpack_IXR_ClientMulticall' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php',
22
  'Jetpack_Options' => $vendorDir . '/automattic/jetpack-options/legacy/class-jetpack-options.php',
8
  return array(
9
  'Automattic\\Jetpack\\Config' => $vendorDir . '/automattic/jetpack-config/src/class-config.php',
10
  'Automattic\\Jetpack\\Connection\\Client' => $vendorDir . '/automattic/jetpack-connection/src/class-client.php',
11
+ 'Automattic\\Jetpack\\Connection\\Error_Handler' => $vendorDir . '/automattic/jetpack-connection/src/class-error-handler.php',
12
+ 'Automattic\\Jetpack\\Connection\\Error_Handlers\\Invalid_Blog_Token' => $vendorDir . '/automattic/jetpack-connection/src/error-handlers/class-invalid-blog-token.php',
13
  'Automattic\\Jetpack\\Connection\\Manager' => $vendorDir . '/automattic/jetpack-connection/src/class-manager.php',
14
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => $vendorDir . '/automattic/jetpack-connection/src/interface-manager.php',
15
  'Automattic\\Jetpack\\Connection\\Plugin' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin.php',
19
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => $vendorDir . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php',
20
  'Automattic\\Jetpack\\Constants' => $vendorDir . '/automattic/jetpack-constants/src/class-constants.php',
21
  'Automattic\\Jetpack\\Roles' => $vendorDir . '/automattic/jetpack-roles/src/class-roles.php',
22
+ 'Automattic\\Jetpack\\Status' => $vendorDir . '/automattic/jetpack-status/src/class-status.php',
23
  'Jetpack_IXR_Client' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php',
24
  'Jetpack_IXR_ClientMulticall' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php',
25
  'Jetpack_Options' => $vendorDir . '/automattic/jetpack-options/legacy/class-jetpack-options.php',
vendor/composer/autoload_classmap_package.php CHANGED
@@ -15,67 +15,79 @@ return array(
15
  'path' => $vendorDir . '/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php'
16
  ),
17
  'Jetpack_XMLRPC_Server' => array(
18
- 'version' => '1.13.1.0',
19
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-xmlrpc-server.php'
20
  ),
21
  'Jetpack_IXR_Client' => array(
22
- 'version' => '1.13.1.0',
23
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php'
24
  ),
25
  'Jetpack_Signature' => array(
26
- 'version' => '1.13.1.0',
27
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-signature.php'
28
  ),
29
  'Jetpack_IXR_ClientMulticall' => array(
30
- 'version' => '1.13.1.0',
31
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php'
32
  ),
 
 
 
 
33
  'Automattic\\Jetpack\\Connection\\REST_Connector' => array(
34
- 'version' => '1.13.1.0',
35
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-rest-connector.php'
36
  ),
37
  'Automattic\\Jetpack\\Connection\\Plugin_Storage' => array(
38
- 'version' => '1.13.1.0',
39
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin-storage.php'
40
  ),
41
  'Automattic\\Jetpack\\Connection\\Client' => array(
42
- 'version' => '1.13.1.0',
43
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-client.php'
44
  ),
45
  'Automattic\\Jetpack\\Connection\\Utils' => array(
46
- 'version' => '1.13.1.0',
47
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-utils.php'
48
  ),
49
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => array(
50
- 'version' => '1.13.1.0',
51
  'path' => $vendorDir . '/automattic/jetpack-connection/src/interface-manager.php'
52
  ),
53
  'Automattic\\Jetpack\\Connection\\Plugin' => array(
54
- 'version' => '1.13.1.0',
55
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin.php'
56
  ),
57
  'Automattic\\Jetpack\\Connection\\Manager' => array(
58
- 'version' => '1.13.1.0',
59
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-manager.php'
60
  ),
61
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => array(
62
- 'version' => '1.13.1.0',
63
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php'
64
  ),
 
 
 
 
65
  'Automattic\\Jetpack\\Config' => array(
66
- 'version' => '1.2.0.0',
67
  'path' => $vendorDir . '/automattic/jetpack-config/src/class-config.php'
68
  ),
69
  'Jetpack_Options' => array(
70
- 'version' => '1.5.0.0',
71
  'path' => $vendorDir . '/automattic/jetpack-options/legacy/class-jetpack-options.php'
72
  ),
73
  'Automattic\\Jetpack\\Roles' => array(
74
- 'version' => '1.0.4.0',
75
  'path' => $vendorDir . '/automattic/jetpack-roles/src/class-roles.php'
76
  ),
 
 
 
 
77
  'Automattic\\Jetpack\\Constants' => array(
78
- 'version' => '1.2.0.0',
79
  'path' => $vendorDir . '/automattic/jetpack-constants/src/class-constants.php'
80
  ),
81
  );
15
  'path' => $vendorDir . '/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php'
16
  ),
17
  'Jetpack_XMLRPC_Server' => array(
18
+ 'version' => '1.15.2.0',
19
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-xmlrpc-server.php'
20
  ),
21
  'Jetpack_IXR_Client' => array(
22
+ 'version' => '1.15.2.0',
23
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php'
24
  ),
25
  'Jetpack_Signature' => array(
26
+ 'version' => '1.15.2.0',
27
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-signature.php'
28
  ),
29
  'Jetpack_IXR_ClientMulticall' => array(
30
+ 'version' => '1.15.2.0',
31
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php'
32
  ),
33
+ 'Automattic\\Jetpack\\Connection\\Error_Handler' => array(
34
+ 'version' => '1.15.2.0',
35
+ 'path' => $vendorDir . '/automattic/jetpack-connection/src/class-error-handler.php'
36
+ ),
37
  'Automattic\\Jetpack\\Connection\\REST_Connector' => array(
38
+ 'version' => '1.15.2.0',
39
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-rest-connector.php'
40
  ),
41
  'Automattic\\Jetpack\\Connection\\Plugin_Storage' => array(
42
+ 'version' => '1.15.2.0',
43
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin-storage.php'
44
  ),
45
  'Automattic\\Jetpack\\Connection\\Client' => array(
46
+ 'version' => '1.15.2.0',
47
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-client.php'
48
  ),
49
  'Automattic\\Jetpack\\Connection\\Utils' => array(
50
+ 'version' => '1.15.2.0',
51
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-utils.php'
52
  ),
53
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => array(
54
+ 'version' => '1.15.2.0',
55
  'path' => $vendorDir . '/automattic/jetpack-connection/src/interface-manager.php'
56
  ),
57
  'Automattic\\Jetpack\\Connection\\Plugin' => array(
58
+ 'version' => '1.15.2.0',
59
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-plugin.php'
60
  ),
61
  'Automattic\\Jetpack\\Connection\\Manager' => array(
62
+ 'version' => '1.15.2.0',
63
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-manager.php'
64
  ),
65
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => array(
66
+ 'version' => '1.15.2.0',
67
  'path' => $vendorDir . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php'
68
  ),
69
+ 'Automattic\\Jetpack\\Connection\\Error_Handlers\\Invalid_Blog_Token' => array(
70
+ 'version' => '1.15.2.0',
71
+ 'path' => $vendorDir . '/automattic/jetpack-connection/src/error-handlers/class-invalid-blog-token.php'
72
+ ),
73
  'Automattic\\Jetpack\\Config' => array(
74
+ 'version' => '1.3.0.0',
75
  'path' => $vendorDir . '/automattic/jetpack-config/src/class-config.php'
76
  ),
77
  'Jetpack_Options' => array(
78
+ 'version' => '1.7.0.0',
79
  'path' => $vendorDir . '/automattic/jetpack-options/legacy/class-jetpack-options.php'
80
  ),
81
  'Automattic\\Jetpack\\Roles' => array(
82
+ 'version' => '1.2.0.0',
83
  'path' => $vendorDir . '/automattic/jetpack-roles/src/class-roles.php'
84
  ),
85
+ 'Automattic\\Jetpack\\Status' => array(
86
+ 'version' => '1.3.0.0',
87
+ 'path' => $vendorDir . '/automattic/jetpack-status/src/class-status.php'
88
+ ),
89
  'Automattic\\Jetpack\\Constants' => array(
90
+ 'version' => '1.4.0.0',
91
  'path' => $vendorDir . '/automattic/jetpack-constants/src/class-constants.php'
92
  ),
93
  );
vendor/composer/autoload_files_package.php CHANGED
@@ -7,7 +7,7 @@ $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
  'bce4ecd6aabb2a2948e06d0e2c4ea9a6' => array(
10
- 'version' => '1.13.1.0',
11
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/load-ixr.php'
12
  ),
13
  );
7
 
8
  return array(
9
  'bce4ecd6aabb2a2948e06d0e2c4ea9a6' => array(
10
+ 'version' => '1.15.2.0',
11
  'path' => $vendorDir . '/automattic/jetpack-connection/legacy/load-ixr.php'
12
  ),
13
  );
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4
6
  {
7
  private static $loader;
8
 
@@ -13,24 +13,21 @@ class ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4
13
  }
14
  }
15
 
16
- /**
17
- * @return \Composer\Autoload\ClassLoader
18
- */
19
  public static function getLoader()
20
  {
21
  if (null !== self::$loader) {
22
  return self::$loader;
23
  }
24
 
25
- spl_autoload_register(array('ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4', 'loadClassLoader'), true, true);
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
- spl_autoload_unregister(array('ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4', 'loadClassLoader'));
28
 
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
  if ($useStaticLoader) {
31
  require_once __DIR__ . '/autoload_static.php';
32
 
33
- call_user_func(\Composer\Autoload\ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4::getInitializer($loader));
34
  } else {
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
  foreach ($map as $namespace => $path) {
@@ -51,19 +48,19 @@ class ComposerAutoloaderInita2447e0d68af485c4e75e9838c0a2df4
51
  $loader->register(true);
52
 
53
  if ($useStaticLoader) {
54
- $includeFiles = Composer\Autoload\ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4::$files;
55
  } else {
56
  $includeFiles = require __DIR__ . '/autoload_files.php';
57
  }
58
  foreach ($includeFiles as $fileIdentifier => $file) {
59
- composerRequirea2447e0d68af485c4e75e9838c0a2df4($fileIdentifier, $file);
60
  }
61
 
62
  return $loader;
63
  }
64
  }
65
 
66
- function composerRequirea2447e0d68af485c4e75e9838c0a2df4($fileIdentifier, $file)
67
  {
68
  if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
69
  require $file;
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit0a651f92f6dec0685e6f92fb5fc9b1f9
6
  {
7
  private static $loader;
8
 
13
  }
14
  }
15
 
 
 
 
16
  public static function getLoader()
17
  {
18
  if (null !== self::$loader) {
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit0a651f92f6dec0685e6f92fb5fc9b1f9', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit0a651f92f6dec0685e6f92fb5fc9b1f9', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
  require_once __DIR__ . '/autoload_static.php';
29
 
30
+ call_user_func(\Composer\Autoload\ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9::getInitializer($loader));
31
  } else {
32
  $map = require __DIR__ . '/autoload_namespaces.php';
33
  foreach ($map as $namespace => $path) {
48
  $loader->register(true);
49
 
50
  if ($useStaticLoader) {
51
+ $includeFiles = Composer\Autoload\ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9::$files;
52
  } else {
53
  $includeFiles = require __DIR__ . '/autoload_files.php';
54
  }
55
  foreach ($includeFiles as $fileIdentifier => $file) {
56
+ composerRequire0a651f92f6dec0685e6f92fb5fc9b1f9($fileIdentifier, $file);
57
  }
58
 
59
  return $loader;
60
  }
61
  }
62
 
63
+ function composerRequire0a651f92f6dec0685e6f92fb5fc9b1f9($fileIdentifier, $file)
64
  {
65
  if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
66
  require $file;
vendor/composer/autoload_static.php CHANGED
@@ -4,7 +4,7 @@
4
 
5
  namespace Composer\Autoload;
6
 
7
- class ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4
8
  {
9
  public static $files = array (
10
  'bce4ecd6aabb2a2948e06d0e2c4ea9a6' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/load-ixr.php',
@@ -27,6 +27,8 @@ class ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4
27
  public static $classMap = array (
28
  'Automattic\\Jetpack\\Config' => __DIR__ . '/..' . '/automattic/jetpack-config/src/class-config.php',
29
  'Automattic\\Jetpack\\Connection\\Client' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-client.php',
 
 
30
  'Automattic\\Jetpack\\Connection\\Manager' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-manager.php',
31
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/interface-manager.php',
32
  'Automattic\\Jetpack\\Connection\\Plugin' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-plugin.php',
@@ -36,6 +38,7 @@ class ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4
36
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php',
37
  'Automattic\\Jetpack\\Constants' => __DIR__ . '/..' . '/automattic/jetpack-constants/src/class-constants.php',
38
  'Automattic\\Jetpack\\Roles' => __DIR__ . '/..' . '/automattic/jetpack-roles/src/class-roles.php',
 
39
  'Jetpack_IXR_Client' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php',
40
  'Jetpack_IXR_ClientMulticall' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php',
41
  'Jetpack_Options' => __DIR__ . '/..' . '/automattic/jetpack-options/legacy/class-jetpack-options.php',
@@ -46,9 +49,9 @@ class ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4
46
  public static function getInitializer(ClassLoader $loader)
47
  {
48
  return \Closure::bind(function () use ($loader) {
49
- $loader->prefixLengthsPsr4 = ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4::$prefixLengthsPsr4;
50
- $loader->prefixDirsPsr4 = ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4::$prefixDirsPsr4;
51
- $loader->classMap = ComposerStaticInita2447e0d68af485c4e75e9838c0a2df4::$classMap;
52
 
53
  }, null, ClassLoader::class);
54
  }
4
 
5
  namespace Composer\Autoload;
6
 
7
+ class ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9
8
  {
9
  public static $files = array (
10
  'bce4ecd6aabb2a2948e06d0e2c4ea9a6' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/load-ixr.php',
27
  public static $classMap = array (
28
  'Automattic\\Jetpack\\Config' => __DIR__ . '/..' . '/automattic/jetpack-config/src/class-config.php',
29
  'Automattic\\Jetpack\\Connection\\Client' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-client.php',
30
+ 'Automattic\\Jetpack\\Connection\\Error_Handler' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-error-handler.php',
31
+ 'Automattic\\Jetpack\\Connection\\Error_Handlers\\Invalid_Blog_Token' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/error-handlers/class-invalid-blog-token.php',
32
  'Automattic\\Jetpack\\Connection\\Manager' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-manager.php',
33
  'Automattic\\Jetpack\\Connection\\Manager_Interface' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/interface-manager.php',
34
  'Automattic\\Jetpack\\Connection\\Plugin' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-plugin.php',
38
  'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/class-xmlrpc-connector.php',
39
  'Automattic\\Jetpack\\Constants' => __DIR__ . '/..' . '/automattic/jetpack-constants/src/class-constants.php',
40
  'Automattic\\Jetpack\\Roles' => __DIR__ . '/..' . '/automattic/jetpack-roles/src/class-roles.php',
41
+ 'Automattic\\Jetpack\\Status' => __DIR__ . '/..' . '/automattic/jetpack-status/src/class-status.php',
42
  'Jetpack_IXR_Client' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-client.php',
43
  'Jetpack_IXR_ClientMulticall' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/class-jetpack-ixr-clientmulticall.php',
44
  'Jetpack_Options' => __DIR__ . '/..' . '/automattic/jetpack-options/legacy/class-jetpack-options.php',
49
  public static function getInitializer(ClassLoader $loader)
50
  {
51
  return \Closure::bind(function () use ($loader) {
52
+ $loader->prefixLengthsPsr4 = ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9::$prefixLengthsPsr4;
53
+ $loader->prefixDirsPsr4 = ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9::$prefixDirsPsr4;
54
+ $loader->classMap = ComposerStaticInit0a651f92f6dec0685e6f92fb5fc9b1f9::$classMap;
55
 
56
  }, null, ClassLoader::class);
57
  }
vendor/composer/installed.json CHANGED
@@ -39,20 +39,20 @@
39
  },
40
  {
41
  "name": "automattic/jetpack-config",
42
- "version": "v1.2.0",
43
- "version_normalized": "1.2.0.0",
44
  "source": {
45
  "type": "git",
46
  "url": "https://github.com/Automattic/jetpack-config.git",
47
- "reference": "4c6ea210ba8fa60f43f0d9eb725f70450f0ba210"
48
  },
49
  "dist": {
50
  "type": "zip",
51
- "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/4c6ea210ba8fa60f43f0d9eb725f70450f0ba210",
52
- "reference": "4c6ea210ba8fa60f43f0d9eb725f70450f0ba210",
53
  "shasum": ""
54
  },
55
- "time": "2020-05-20T20:52:06+00:00",
56
  "type": "library",
57
  "installation-source": "dist",
58
  "autoload": {
@@ -68,29 +68,31 @@
68
  },
69
  {
70
  "name": "automattic/jetpack-connection",
71
- "version": "v1.13.1",
72
- "version_normalized": "1.13.1.0",
73
  "source": {
74
  "type": "git",
75
  "url": "https://github.com/Automattic/jetpack-connection.git",
76
- "reference": "9039aaa4ddfac4ae87cbddd8cb8dae39b1ebd620"
77
  },
78
  "dist": {
79
  "type": "zip",
80
- "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/9039aaa4ddfac4ae87cbddd8cb8dae39b1ebd620",
81
- "reference": "9039aaa4ddfac4ae87cbddd8cb8dae39b1ebd620",
82
  "shasum": ""
83
  },
84
  "require": {
85
- "automattic/jetpack-constants": "1.2.0",
86
- "automattic/jetpack-options": "1.5.0",
87
- "automattic/jetpack-roles": "1.0.4"
 
88
  },
89
  "require-dev": {
 
90
  "php-mock/php-mock": "^2.1",
91
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
92
  },
93
- "time": "2020-06-01T09:03:16+00:00",
94
  "type": "library",
95
  "installation-source": "dist",
96
  "autoload": {
@@ -110,24 +112,24 @@
110
  },
111
  {
112
  "name": "automattic/jetpack-constants",
113
- "version": "v1.2.0",
114
- "version_normalized": "1.2.0.0",
115
  "source": {
116
  "type": "git",
117
  "url": "https://github.com/Automattic/jetpack-constants.git",
118
- "reference": "881618defb04134ddba120e7835af1a474a11edc"
119
  },
120
  "dist": {
121
  "type": "zip",
122
- "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/881618defb04134ddba120e7835af1a474a11edc",
123
- "reference": "881618defb04134ddba120e7835af1a474a11edc",
124
  "shasum": ""
125
  },
126
  "require-dev": {
127
  "php-mock/php-mock": "^2.1",
128
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
129
  },
130
- "time": "2020-04-15T18:58:53+00:00",
131
  "type": "library",
132
  "installation-source": "dist",
133
  "autoload": {
@@ -143,27 +145,27 @@
143
  },
144
  {
145
  "name": "automattic/jetpack-options",
146
- "version": "v1.5.0",
147
- "version_normalized": "1.5.0.0",
148
  "source": {
149
  "type": "git",
150
  "url": "https://github.com/Automattic/jetpack-options.git",
151
- "reference": "5c9d947ab62bc786adc36b7071b0e3a7dc7470d1"
152
  },
153
  "dist": {
154
  "type": "zip",
155
- "url": "https://api.github.com/repos/Automattic/jetpack-options/zipball/5c9d947ab62bc786adc36b7071b0e3a7dc7470d1",
156
- "reference": "5c9d947ab62bc786adc36b7071b0e3a7dc7470d1",
157
  "shasum": ""
158
  },
159
  "require": {
160
- "automattic/jetpack-constants": "1.2.0"
161
  },
162
  "require-dev": {
163
  "10up/wp_mock": "0.4.2",
164
  "phpunit/phpunit": "7.*.*"
165
  },
166
- "time": "2020-05-26T13:35:15+00:00",
167
  "type": "library",
168
  "installation-source": "dist",
169
  "autoload": {
@@ -179,24 +181,24 @@
179
  },
180
  {
181
  "name": "automattic/jetpack-roles",
182
- "version": "v1.0.4",
183
- "version_normalized": "1.0.4.0",
184
  "source": {
185
  "type": "git",
186
  "url": "https://github.com/Automattic/jetpack-roles.git",
187
- "reference": "0cdcff4fdc489c79f20a361c084ec48e326ce483"
188
  },
189
  "dist": {
190
  "type": "zip",
191
- "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/0cdcff4fdc489c79f20a361c084ec48e326ce483",
192
- "reference": "0cdcff4fdc489c79f20a361c084ec48e326ce483",
193
  "shasum": ""
194
  },
195
  "require-dev": {
196
  "php-mock/php-mock": "^2.1",
197
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
198
  },
199
- "time": "2019-11-08T21:16:05+00:00",
200
  "type": "library",
201
  "installation-source": "dist",
202
  "autoload": {
@@ -209,5 +211,39 @@
209
  "GPL-2.0-or-later"
210
  ],
211
  "description": "Utilities, related with user roles and capabilities."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  }
213
  ]
39
  },
40
  {
41
  "name": "automattic/jetpack-config",
42
+ "version": "v1.3.0",
43
+ "version_normalized": "1.3.0.0",
44
  "source": {
45
  "type": "git",
46
  "url": "https://github.com/Automattic/jetpack-config.git",
47
+ "reference": "a7ae4b6f32c964fb7ea0f715aa5ba272a1f06113"
48
  },
49
  "dist": {
50
  "type": "zip",
51
+ "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/a7ae4b6f32c964fb7ea0f715aa5ba272a1f06113",
52
+ "reference": "a7ae4b6f32c964fb7ea0f715aa5ba272a1f06113",
53
  "shasum": ""
54
  },
55
+ "time": "2020-06-26T07:31:13+00:00",
56
  "type": "library",
57
  "installation-source": "dist",
58
  "autoload": {
68
  },
69
  {
70
  "name": "automattic/jetpack-connection",
71
+ "version": "v1.15.2",
72
+ "version_normalized": "1.15.2.0",
73
  "source": {
74
  "type": "git",
75
  "url": "https://github.com/Automattic/jetpack-connection.git",
76
+ "reference": "eff65e640bcec826962475e87dcb236741a2a762"
77
  },
78
  "dist": {
79
  "type": "zip",
80
+ "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/eff65e640bcec826962475e87dcb236741a2a762",
81
+ "reference": "eff65e640bcec826962475e87dcb236741a2a762",
82
  "shasum": ""
83
  },
84
  "require": {
85
+ "automattic/jetpack-constants": "1.4.0",
86
+ "automattic/jetpack-options": "1.7.0",
87
+ "automattic/jetpack-roles": "1.2.0",
88
+ "automattic/jetpack-status": "1.3.0"
89
  },
90
  "require-dev": {
91
+ "automattic/wordbless": "@dev",
92
  "php-mock/php-mock": "^2.1",
93
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
94
  },
95
+ "time": "2020-08-10T15:51:57+00:00",
96
  "type": "library",
97
  "installation-source": "dist",
98
  "autoload": {
112
  },
113
  {
114
  "name": "automattic/jetpack-constants",
115
+ "version": "v1.4.0",
116
+ "version_normalized": "1.4.0.0",
117
  "source": {
118
  "type": "git",
119
  "url": "https://github.com/Automattic/jetpack-constants.git",
120
+ "reference": "b4210d56948529b43785ce31e0055f435eac1f9f"
121
  },
122
  "dist": {
123
  "type": "zip",
124
+ "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/b4210d56948529b43785ce31e0055f435eac1f9f",
125
+ "reference": "b4210d56948529b43785ce31e0055f435eac1f9f",
126
  "shasum": ""
127
  },
128
  "require-dev": {
129
  "php-mock/php-mock": "^2.1",
130
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
131
  },
132
+ "time": "2020-07-01T15:55:35+00:00",
133
  "type": "library",
134
  "installation-source": "dist",
135
  "autoload": {
145
  },
146
  {
147
  "name": "automattic/jetpack-options",
148
+ "version": "v1.7.0",
149
+ "version_normalized": "1.7.0.0",
150
  "source": {
151
  "type": "git",
152
  "url": "https://github.com/Automattic/jetpack-options.git",
153
+ "reference": "8006d330476d93a1cb768f30a875f0b17d16f7f6"
154
  },
155
  "dist": {
156
  "type": "zip",
157
+ "url": "https://api.github.com/repos/Automattic/jetpack-options/zipball/8006d330476d93a1cb768f30a875f0b17d16f7f6",
158
+ "reference": "8006d330476d93a1cb768f30a875f0b17d16f7f6",
159
  "shasum": ""
160
  },
161
  "require": {
162
+ "automattic/jetpack-constants": "1.4.0"
163
  },
164
  "require-dev": {
165
  "10up/wp_mock": "0.4.2",
166
  "phpunit/phpunit": "7.*.*"
167
  },
168
+ "time": "2020-07-28T14:03:34+00:00",
169
  "type": "library",
170
  "installation-source": "dist",
171
  "autoload": {
181
  },
182
  {
183
  "name": "automattic/jetpack-roles",
184
+ "version": "v1.2.0",
185
+ "version_normalized": "1.2.0.0",
186
  "source": {
187
  "type": "git",
188
  "url": "https://github.com/Automattic/jetpack-roles.git",
189
+ "reference": "0148108451db7ee5bfb68669671fc50ddb6d1474"
190
  },
191
  "dist": {
192
  "type": "zip",
193
+ "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/0148108451db7ee5bfb68669671fc50ddb6d1474",
194
+ "reference": "0148108451db7ee5bfb68669671fc50ddb6d1474",
195
  "shasum": ""
196
  },
197
  "require-dev": {
198
  "php-mock/php-mock": "^2.1",
199
  "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
200
  },
201
+ "time": "2020-07-01T15:55:45+00:00",
202
  "type": "library",
203
  "installation-source": "dist",
204
  "autoload": {
211
  "GPL-2.0-or-later"
212
  ],
213
  "description": "Utilities, related with user roles and capabilities."
214
+ },
215
+ {
216
+ "name": "automattic/jetpack-status",
217
+ "version": "v1.3.0",
218
+ "version_normalized": "1.3.0.0",
219
+ "source": {
220
+ "type": "git",
221
+ "url": "https://github.com/Automattic/jetpack-status.git",
222
+ "reference": "09bd04d677832f348d3b9d520eecd856c2f0cafb"
223
+ },
224
+ "dist": {
225
+ "type": "zip",
226
+ "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/09bd04d677832f348d3b9d520eecd856c2f0cafb",
227
+ "reference": "09bd04d677832f348d3b9d520eecd856c2f0cafb",
228
+ "shasum": ""
229
+ },
230
+ "require-dev": {
231
+ "brain/monkey": "2.4.0",
232
+ "php-mock/php-mock": "^2.1",
233
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
234
+ },
235
+ "time": "2020-07-28T08:21:54+00:00",
236
+ "type": "library",
237
+ "installation-source": "dist",
238
+ "autoload": {
239
+ "classmap": [
240
+ "src/"
241
+ ]
242
+ },
243
+ "notification-url": "https://packagist.org/downloads/",
244
+ "license": [
245
+ "GPL-2.0-or-later"
246
+ ],
247
+ "description": "Used to retrieve information about the current status of Jetpack and the site overall."
248
  }
249
  ]
woocommerce-payments.php CHANGED
@@ -8,9 +8,9 @@
8
  * Text Domain: woocommerce-payments
9
  * Domain Path: /languages
10
  * WC requires at least: 4.0
11
- * WC tested up to: 4.3
12
  * Requires WP: 5.3
13
- * Version: 1.3.0
14
  *
15
  * @package WooCommerce\Payments
16
  */
@@ -29,6 +29,9 @@ require_once WCPAY_ABSPATH . 'vendor/autoload_packages.php';
29
  * Initialize the Jetpack connection functionality.
30
  */
31
  function wcpay_jetpack_init() {
 
 
 
32
  $jetpack_config = new Automattic\Jetpack\Config();
33
  $jetpack_config->ensure(
34
  'connection',
@@ -52,4 +55,34 @@ function wcpay_init() {
52
  }
53
 
54
  // Make sure this is run *after* WooCommerce has a chance to initialize its packages (wc-admin, etc). That is run with priority 10.
 
55
  add_action( 'plugins_loaded', 'wcpay_init', 11 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  * Text Domain: woocommerce-payments
9
  * Domain Path: /languages
10
  * WC requires at least: 4.0
11
+ * WC tested up to: 4.4
12
  * Requires WP: 5.3
13
+ * Version: 1.4.0
14
  *
15
  * @package WooCommerce\Payments
16
  */
29
  * Initialize the Jetpack connection functionality.
30
  */
31
  function wcpay_jetpack_init() {
32
+ if ( ! wcpay_check_old_jetpack_version() ) {
33
+ return;
34
+ }
35
  $jetpack_config = new Automattic\Jetpack\Config();
36
  $jetpack_config->ensure(
37
  'connection',
55
  }
56
 
57
  // Make sure this is run *after* WooCommerce has a chance to initialize its packages (wc-admin, etc). That is run with priority 10.
58
+ // If you change the priority of this action, you'll need to change it in the wcpay_check_old_jetpack_version function too.
59
  add_action( 'plugins_loaded', 'wcpay_init', 11 );
60
+
61
+ /**
62
+ * Check if WCPay is installed alongside an old version of Jetpack (8.1 or earlier). Due to the autoloader code in those old
63
+ * versions, the Jetpack Config initialization code would just crash the site.
64
+ * TODO: Remove this when Jetpack 8.1 (Released on January 2020) is so old we don't think anyone will run into this problem anymore.
65
+ *
66
+ * @return bool True if the plugin can keep initializing itself, false otherwise.
67
+ */
68
+ function wcpay_check_old_jetpack_version() {
69
+ if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '8.2', '<' ) ) {
70
+ add_filter( 'admin_notices', 'wcpay_show_old_jetpack_notice' );
71
+ // Prevent the rest of the plugin from initializing.
72
+ remove_action( 'plugins_loaded', 'wcpay_init', 11 );
73
+ return false;
74
+ }
75
+ return true;
76
+ }
77
+
78
+ /**
79
+ * Display an error notice if the installed Jetpack version is too old to even start initializing the plugin.
80
+ */
81
+ function wcpay_show_old_jetpack_notice() {
82
+ ?>
83
+ <div class="notice wcpay-notice notice-error">
84
+ <p><b><?php echo esc_html( __( 'WooCommerce Payments', 'woocommerce-payments' ) ); ?></b></p>
85
+ <p><?php echo esc_html( __( 'The version of Jetpack installed is too old to be used with WooCommerce Payments. WooCommerce Payments has been disabled. Please deactivate or update Jetpack.', 'woocommerce-payments' ) ); ?></p>
86
+ </div>
87
+ <?php
88
+ }