PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/class-actions.php +290 -70 12.5.2 → 16.3-beta View file →
@@ -6,12 +6,15 @@
6 6 */
7 7
8 8 namespace Automattic\Jetpack\Sync;
9 9
10 +use Automattic\Jetpack\Connection\Client;
10 11 use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
11 12 use Automattic\Jetpack\Constants;
12 13 use Automattic\Jetpack\Identity_Crisis;
13 14 use Automattic\Jetpack\Status;
15 +use Automattic\Jetpack\Sync\Modules\WooCommerce_HPOS_Orders;
16 +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
14 17 use WP_Error;
15 18
16 19 /**
17 20 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
@@ -53,9 +56,9 @@
53 56 *
54 57 * @access public
55 58 * @static
56 59 *
57 - * @var Automattic\Jetpack\Sync\Sender
60 + * @var \Automattic\Jetpack\Sync\Sender
58 61 */
59 62 public static $sender = null;
60 63
61 64 /**
@@ -63,9 +66,9 @@
63 66 *
64 67 * @access public
65 68 * @static
66 69 *
67 - * @var Automattic\Jetpack\Sync\Listener
70 + * @var \Automattic\Jetpack\Sync\Listener
68 71 */
69 72 public static $listener = null;
70 73
71 74 /**
@@ -107,14 +110,14 @@
107 110 // Note: We also need to initialize the listener so that callable and constant changes, eg actions that
108 111 // rely on 'jetpack_sync_before_send_queue_sync' are picked up and added to the queue if needed.
109 112 if ( Settings::is_dedicated_sync_enabled() && Dedicated_Sender::is_dedicated_sync_request() ) {
110 113 self::initialize_listener();
111 - add_action( 'init', array( __CLASS__, 'add_dedicated_sync_sender_init' ), 90 );
114 + add_action( 'init', array( __CLASS__, 'add_dedicated_sync_sender_init' ), 200 );
112 115 return;
113 116 }
114 117
115 118 if ( self::sync_via_cron_allowed() ) {
116 - self::init_sync_cron_jobs();
119 + add_action( 'init', array( __CLASS__, 'init_sync_cron_jobs' ), 1 );
117 120 } elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
118 121 self::clear_sync_cron_jobs();
119 122 }
120 123 // When importing via cron, do not sync.
@@ -171,9 +174,12 @@
171 174 self::should_initialize_sender()
172 175 ) ) {
173 176 self::initialize_sender();
174 177 add_action( 'shutdown', array( self::$sender, 'do_sync' ), 9998 );
175 - add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
178 +
179 + if ( self::should_initialize_sender( true ) ) {
180 + add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
181 + }
176 182 }
177 183 }
178 184
179 185 /**
@@ -208,11 +214,13 @@
208 214 *
209 215 * @access public
210 216 * @static
211 217 *
218 + * @param bool $full_sync Whether the Full Sync sender should run on shutdown for this request.
219 + *
212 220 * @return bool
213 221 */
214 - public static function should_initialize_sender() {
222 + public static function should_initialize_sender( $full_sync = false ) {
215 223
216 224 // Allow for explicit disable of Sync from request param jetpack_sync_read_only.
217 225 if ( isset( $_REQUEST['jetpack_sync_read_only'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
218 226 self::mark_sync_read_only();
@@ -223,11 +231,12 @@
223 231 return self::sync_via_cron_allowed();
224 232 }
225 233
226 234 /**
227 - * For now, if dedicated Sync is enabled we will always initialize send, even for GET and unauthenticated requests.
235 + * For now, if dedicated Sync is enabled we will always initialize send, even for GET and unauthenticated requests
236 + * but not for Full Sync, since it will still happen on shutdown.
228 237 */
229 - if ( Settings::is_dedicated_sync_enabled() ) {
238 + if ( false === $full_sync && Settings::is_dedicated_sync_enabled() ) {
230 239 return true;
231 240 }
232 241
233 242 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
@@ -300,12 +309,8 @@
300 309 if ( ( new Status() )->is_offline_mode() ) {
301 310 return false;
302 311 }
303 312
304 - if ( ( new Status() )->is_staging_site() ) {
305 - return false;
306 - }
307 -
308 313 $connection = new Jetpack_Connection();
309 314 if ( ! $connection->is_connected() ) {
310 315 if ( ! doing_action( 'jetpack_site_registered' ) ) {
311 316 return false;
@@ -311,8 +316,13 @@
311 316 return false;
312 317 }
313 318 }
314 319
320 + // By now, we know the site is connected, so we can return false if in safe mode.
321 + if ( ( new Status() )->in_safe_mode() ) {
322 + return false;
323 + }
324 +
315 325 return true;
316 326 }
317 327
318 328 /**
@@ -338,10 +348,10 @@
338 348 }
339 349 if ( ( new Status() )->is_offline_mode() ) {
340 350 $debug['debug_details']['is_offline_mode'] = true;
341 351 }
342 - if ( ( new Status() )->is_staging_site() ) {
343 - $debug['debug_details']['is_staging_site'] = true;
352 + if ( ( new Status() )->in_safe_mode() ) {
353 + $debug['debug_details']['in_safe_mode'] = true;
344 354 }
345 355 $connection = new Jetpack_Connection();
346 356 if ( ! $connection->is_connected() ) {
347 357 $debug['debug_details']['active_connection'] = false;
@@ -352,8 +362,12 @@
352 362 $debug['debug_details']['dedicated_sync_enabled'] = Settings::is_dedicated_sync_enabled();
353 363
354 364 $queue = self::$sender->get_sync_queue();
355 365 $full_queue = self::$sender->get_full_sync_queue();
366 + // We are sending the expiry vs the actual dedicated lock value to ensure backwards compatibility
367 + // with previous versions where the lock value was a timestamp.
368 + $dedicated_sync_lock_option_name = Dedicated_Sender::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME;
369 + $dedicated_sync_lock_expires_name = $dedicated_sync_lock_option_name . '_expires';
356 370
357 371 $debug['debug_details']['sync_locks'] = array(
358 372 'retry_time_sync' => get_option( self::RETRY_AFTER_PREFIX . 'sync' ),
359 373 'retry_time_full_sync' => get_option( self::RETRY_AFTER_PREFIX . 'full_sync' ),
@@ -360,9 +374,9 @@
360 374 'next_sync_time_sync' => self::$sender->get_next_sync_time( 'sync' ),
361 375 'next_sync_time_full_sync' => self::$sender->get_next_sync_time( 'full_sync' ),
362 376 'queue_locked_sync' => $queue->is_locked(),
363 377 'queue_locked_full_sync' => $full_queue->is_locked(),
364 - 'dedicated_sync_request_lock' => \Jetpack_Options::get_raw_option( Dedicated_Sender::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null ),
378 + 'dedicated_sync_request_lock' => \Jetpack_Options::get_raw_option( $dedicated_sync_lock_expires_name, null ),
365 379 'dedicated_sync_temporary_disable_flag' => get_transient( Dedicated_Sender::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG ),
366 380 );
367 381
368 382 // Sync Logs.
@@ -412,9 +426,9 @@
412 426 Settings::set_importing( true );
413 427 }
414 428
415 429 /**
416 - * Sends data to WordPress.com via an XMLRPC request.
430 + * Sends data to WordPress.com via an XMLRPC or a REST API request based on the settings.
417 431 *
418 432 * @access public
419 433 * @static
420 434 *
@@ -430,8 +444,9 @@
430 444 */
431 445 public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration, $queue_size = null, $buffer_id = null ) {
432 446
433 447 $query_args = array(
448 +
434 449 'sync' => '1', // Add an extra parameter to the URL so we can tell it's a sync action.
435 450 'codec' => $codec_name,
436 451 'timestamp' => $sent_timestamp,
437 452 'queue' => $queue_id,
@@ -459,35 +474,60 @@
459 474 * @param array $query_args associative array of query parameters.
460 475 */
461 476 $query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
462 477
463 - $connection = new Jetpack_Connection();
464 - $url = add_query_arg( $query_args, $connection->xmlrpc_api_url() );
478 + $retry_after_header = false;
479 + $dedicated_sync_header = false;
465 480
466 - // If we're currently updating to Jetpack 7.7, the IXR client may be missing briefly
467 - // because since 7.7 it's being autoloaded with Composer.
468 - if ( ! class_exists( '\\Jetpack_IXR_Client' ) ) {
469 - return new WP_Error(
470 - 'ixr_client_missing',
471 - esc_html__( 'Sync has been aborted because the IXR client is missing.', 'jetpack-sync' )
481 + // If REST API is enabled, use it.
482 + if ( Settings::is_wpcom_rest_api_enabled() ) {
483 + $jsonl_data = self::prepare_jsonl_data( $data );
484 + $url = '/sites/' . \Jetpack_Options::get_option( 'id' ) . '/jetpack-sync-actions';
485 + $url = add_query_arg( $query_args, $url );
486 + $args = array(
487 + 'method' => 'POST',
488 + 'format' => 'jsonl',
489 + 'timeout' => $query_args['timeout'],
472 490 );
473 - }
474 491
475 - $rpc = new \Jetpack_IXR_Client(
476 - array(
477 - 'url' => $url,
478 - 'timeout' => $query_args['timeout'],
479 - )
480 - );
492 + $response = Client::wpcom_json_api_request_as_blog( $url, '2', $args, $jsonl_data, 'wpcom' );
493 + $retry_after_header = wp_remote_retrieve_header( $response, 'Retry-After' ) ? wp_remote_retrieve_header( $response, 'Retry-After' ) : false;
494 + $dedicated_sync_header = wp_remote_retrieve_header( $response, 'Jetpack-Dedicated-Sync' ) ? wp_remote_retrieve_header( $response, 'Jetpack-Dedicated-Sync' ) : false;
495 + $response = self::process_rest_api_response( $response );
496 + } else { // Use XML-RPC.
497 + $connection = new Jetpack_Connection();
498 + $url = add_query_arg( $query_args, $connection->xmlrpc_api_url() );
481 499
482 - $result = $rpc->query( 'jetpack.syncActions', $data );
500 + // If we're currently updating to Jetpack 7.7, the IXR client may be missing briefly
501 + // because since 7.7 it's being autoloaded with Composer.
502 + if ( ! class_exists( '\\Jetpack_IXR_Client' ) ) {
503 + return new WP_Error(
504 + 'ixr_client_missing',
505 + esc_html__( 'Sync has been aborted because the IXR client is missing.', 'jetpack-sync' )
506 + );
507 + }
483 508
484 - // Adhere to Retry-After headers.
485 - $retry_after = $rpc->get_response_header( 'Retry-After' );
486 - if ( false !== $retry_after ) {
487 - if ( (int) $retry_after > 0 ) {
488 - update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + (int) $retry_after, false );
509 + $rpc = new \Jetpack_IXR_Client(
510 + array(
511 + 'url' => $url,
512 + 'timeout' => $query_args['timeout'],
513 + )
514 + );
515 + $result = $rpc->query( 'jetpack.syncActions', $data );
516 + $retry_after_header = $rpc->get_response_header( 'Retry-After' );
517 + $dedicated_sync_header = $rpc->get_response_header( 'Jetpack-Dedicated-Sync' );
518 + if ( $result ) {
519 + $response = $rpc->getResponse();
489 520 } else {
521 + $response = $rpc->get_jetpack_error();
522 + }
523 + }
524 +
525 + // Adhere to Retry-After headers.
526 + if ( false !== $retry_after_header ) {
527 + if ( (int) $retry_after_header > 0 ) {
528 + update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + (int) $retry_after_header, false );
529 + } else {
490 530 // if unexpected value default to 3 minutes.
491 531 update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + 180, false );
492 532 }
493 533 }
@@ -492,15 +532,15 @@
492 532 }
493 533 }
494 534
495 535 // Enable/Disable Dedicated Sync flow via response headers.
496 - $dedicated_sync_header = $rpc->get_response_header( 'Jetpack-Dedicated-Sync' );
497 536 if ( false !== $dedicated_sync_header ) {
498 537 Dedicated_Sender::maybe_change_dedicated_sync_status_from_wpcom_header( $dedicated_sync_header );
499 538 }
500 539
501 - if ( ! $result ) {
502 - if ( false === $retry_after ) {
540 + if ( is_wp_error( $response ) ) {
541 + $error = $response;
542 + if ( false === $retry_after_header ) {
503 543 // We received a non standard response from WP.com, lets backoff from sending requests for 1 minute.
504 544 update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + 60, false );
505 545 }
506 546 // Record Sync Errors.
@@ -512,20 +552,21 @@
512 552 if ( 5 <= count( $error_log ) ) {
513 553 $error_log = array_slice( $error_log, -4, null, true );
514 554 }
515 555 // Add new error indexed to time.
516 - $error = $rpc->get_jetpack_error();
517 - $error->add_data( $rpc->get_last_response() );
518 - $error_log[ (string) microtime( true ) ] = $error;
556 + if ( isset( $rpc ) && ! empty( $rpc->get_last_response() ) ) {
557 + $error_with_last_response = clone $error;
558 + $error_with_last_response->add_data( $rpc->get_last_response() );
559 + $error_log[ (string) microtime( true ) ] = $error_with_last_response;
560 + } else {
561 + $error_log[ (string) microtime( true ) ] = $error;
562 + }
563 +
519 564 // Update the error log.
520 565 update_option( self::ERROR_LOG_PREFIX . $queue_id, $error_log );
521 -
522 - // return request error.
523 - return $rpc->get_jetpack_error();
566 + return $error;
524 567 }
525 568
526 - $response = $rpc->getResponse();
527 -
528 569 // Check if WordPress.com IDC mitigation blocked the sync request.
529 570 if ( Identity_Crisis::init()->check_response_for_idc( $response ) ) {
530 571 return new WP_Error(
531 572 'sync_error_idc',
@@ -532,8 +573,12 @@
532 573 esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack-sync' )
533 574 );
534 575 }
535 576
577 + if ( isset( $response['processed_items'] ) ) { // Return only processed items.
578 + $response = $response['processed_items'];
579 + }
580 +
536 581 // Record last successful sync.
537 582 update_option( self::LAST_SUCCESS_PREFIX . $queue_id, microtime( true ), false );
538 583
539 584 return $response;
@@ -554,8 +599,9 @@
554 599 }
555 600
556 601 // Don't start new sync if a full sync is in process.
557 602 $full_sync_module = Modules::get_module( 'full-sync' );
603 + '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
558 604 if ( $full_sync_module && $full_sync_module->is_started() && ! $full_sync_module->is_finished() ) {
559 605 return false;
560 606 }
561 607
@@ -566,9 +612,9 @@
566 612 'users' => array( get_current_user_id() ),
567 613 'network_options' => true,
568 614 );
569 615
570 - self::do_full_sync( $initial_sync_config );
616 + self::do_full_sync( $initial_sync_config, 'initial_sync' );
571 617 }
572 618
573 619 /**
574 620 * Do an initial full sync only if one has not already been started.
@@ -576,8 +622,9 @@
576 622 * @return bool|null False if the initial full sync was already started, otherwise null.
577 623 */
578 624 public static function do_only_first_initial_sync() {
579 625 $full_sync_module = Modules::get_module( 'full-sync' );
626 + '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
580 627 if ( $full_sync_module && $full_sync_module->is_started() ) {
581 628 return false;
582 629 }
583 630
@@ -590,16 +637,18 @@
590 637 * @access public
591 638 * @static
592 639 *
593 640 * @param array $modules The sync modules should be included in this full sync. All will be included if null.
641 + * @param mixed $context The context where the full sync was initiated from.
594 642 * @return bool True if full sync was successfully started.
595 643 */
596 - public static function do_full_sync( $modules = null ) {
644 + public static function do_full_sync( $modules = null, $context = null ) {
597 645 if ( ! self::sync_allowed() ) {
598 646 return false;
599 647 }
600 648
601 649 $full_sync_module = Modules::get_module( 'full-sync' );
650 + '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
602 651
603 652 if ( ! $full_sync_module ) {
604 653 return false;
605 654 }
@@ -605,9 +654,9 @@
605 654 }
606 655
607 656 self::initialize_listener();
608 657
609 - $full_sync_module->start( $modules );
658 + $full_sync_module->start( $modules, $context );
610 659
611 660 return true;
612 661 }
613 662
@@ -621,13 +670,11 @@
621 670 * @return array A list of WordPress cron schedules with the Jetpack sync interval added.
622 671 */
623 672 public static function jetpack_cron_schedule( $schedules ) {
624 673 if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
625 - $minutes = (int) ( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
626 - $display = ( 1 === $minutes ) ?
627 - __( 'Every minute', 'jetpack-sync' ) :
628 - /* translators: %d is an integer indicating the number of minutes. */
629 - sprintf( __( 'Every %d minutes', 'jetpack-sync' ), $minutes );
674 + $minutes = ( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
675 + /* translators: %d is an integer indicating the number of minutes. */
676 + $display = sprintf( __( 'Every %d minutes', 'jetpack-sync' ), $minutes );
630 677 $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
631 678 'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
632 679 'display' => $display,
633 680 );
@@ -641,9 +688,47 @@
641 688 * @access public
642 689 * @static
643 690 */
644 691 public static function do_cron_sync() {
645 - self::do_cron_sync_by_type( 'sync' );
692 + if ( ! self::sync_allowed() ) {
693 + return;
694 + }
695 +
696 + self::initialize_sender();
697 +
698 + $time_limit = Settings::get_setting( 'cron_sync_time_limit' );
699 + $start_time = time();
700 + $executions = 0;
701 +
702 + $lock_id = Dedicated_Sender::try_lock_spawn_request();
703 +
704 + do {
705 + $next_sync_time = self::$sender->get_next_sync_time( 'sync' );
706 +
707 + if ( $next_sync_time ) {
708 + $delay = $next_sync_time - time() + 1;
709 + if ( $delay > 15 ) {
710 + break;
711 + } elseif ( $delay > 0 ) {
712 + sleep( (int) $delay );
713 + }
714 + }
715 +
716 + $result = self::$sender->do_sync_and_set_delays( self::$sender->get_sync_queue() );
717 +
718 + if ( is_wp_error( $result ) && in_array( $result->get_error_code(), array( 'unclosed_buffer', 'sync_throttled' ), true ) ) {
719 + $result = true; // Give it some time.
720 + }
721 + // # of send actions performed.
722 + ++$executions;
723 +
724 + } while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
725 +
726 + if ( $lock_id ) {
727 + Dedicated_Sender::try_release_lock_spawn_request( $lock_id );
728 + }
729 +
730 + return $executions;
646 731 }
647 732
648 733 /**
649 734 * Starts a full sync via cron.
@@ -651,9 +736,33 @@
651 736 * @access public
652 737 * @static
653 738 */
654 739 public static function do_cron_full_sync() {
655 - self::do_cron_sync_by_type( 'full_sync' );
740 + if ( ! self::sync_allowed() ) {
741 + return;
742 + }
743 +
744 + self::initialize_sender();
745 +
746 + $executions = 0;
747 +
748 + $next_sync_time = self::$sender->get_next_sync_time( 'full_sync' );
749 +
750 + if ( $next_sync_time ) {
751 + $delay = $next_sync_time - time() + 1;
752 + if ( $delay > 15 ) {
753 + return;
754 + } elseif ( $delay > 0 ) {
755 + sleep( (int) $delay );
756 + }
757 + }
758 +
759 + // Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
760 + // For more context see p1HpG7-9pe-p2.
761 + self::$sender->do_full_sync();
762 + ++$executions;
763 +
764 + return $executions;
656 765 }
657 766
658 767 /**
659 768 * Try to send actions until we run out of things to send,
@@ -683,9 +792,9 @@
683 792 $delay = $next_sync_time - time() + 1;
684 793 if ( $delay > 15 ) {
685 794 break;
686 795 } elseif ( $delay > 0 ) {
687 - sleep( $delay );
796 + sleep( (int) $delay );
688 797 }
689 798 }
690 799
691 800 // Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
@@ -739,28 +848,45 @@
739 848 * @access public
740 849 * @static
741 850 */
742 851 public static function initialize_woocommerce() {
743 - if ( false === class_exists( 'WooCommerce' ) ) {
852 + if ( ! class_exists( 'WooCommerce' ) ) {
744 853 return;
745 854 }
746 855 add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
856 +
857 + if ( ! class_exists( CustomOrdersTableController::class ) ) {
858 + return;
859 + }
860 + $cot_controller = wc_get_container()->get( CustomOrdersTableController::class );
861 + if ( $cot_controller->custom_orders_table_usage_is_enabled() ) {
862 + add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_hpos_order_sync_module' ) );
863 + }
747 864 }
748 865
749 866 /**
750 - * Initializes sync for Instant Search.
867 + * Initializes sync for Jetpack Search.
751 868 *
869 + * The Search sync module owns the option-whitelist entries for
870 + * `instant_search_enabled` and `jetpack_search_experience`. Registration
871 + * is unconditional whenever the Search package is present — gating on
872 + * either `is_instant_search_enabled()` or `is_active()` reintroduces a
873 + * chicken-and-egg, because the very request that flips Search on (or
874 + * flips Instant Search on) must already have the option whitelist in
875 + * place to enqueue the write.
876 + *
877 + * The `class_exists()` guard below tracks package presence (autoloader
878 + * concern), not module activation — `Module_Control` is autoloaded as
879 + * long as the Search package is installed, even when the module is off.
880 + *
752 881 * @access public
753 882 * @static
754 883 */
755 884 public static function initialize_search() {
756 - if ( false === class_exists( 'Automattic\\Jetpack\\Search\\Module_Control' ) ) {
885 + if ( ! class_exists( 'Automattic\\Jetpack\\Search\\Module_Control' ) ) {
757 886 return;
758 887 }
759 - $search_module = new \Automattic\Jetpack\Search\Module_Control();
760 - if ( $search_module->is_instant_search_enabled() ) {
761 - add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_search_sync_module' ) );
762 - }
888 + add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_search_sync_module' ) );
763 889 }
764 890
765 891 /**
766 892 * Add Search updates to Sync Filters.
@@ -790,8 +916,40 @@
790 916 return $sync_modules;
791 917 }
792 918
793 919 /**
920 + * Adds Woo's HPOS sync modules to existing modules for sending.
921 + *
922 + * @param array $sync_modules The list of sync modules declared prior to this filter.
923 + *
924 + * @access public
925 + * @static
926 + *
927 + * @return array A list of sync modules that now includes Woo's HPOS modules.
928 + */
929 + public static function add_woocommerce_hpos_order_sync_module( $sync_modules ) {
930 + $sync_modules[] = WooCommerce_HPOS_Orders::class;
931 + return $sync_modules;
932 + }
933 +
934 + /**
935 + * Adds Woo's Products sync module to existing modules for sending.
936 + *
937 + * Note: This module is currently used for WooCommerce Analytics only.
938 + *
939 + * @param array $sync_modules The list of sync modules declared prior to this filter.
940 + *
941 + * @access public
942 + * @static
943 + *
944 + * @return array A list of sync modules that now includes Woo's Products module.
945 + */
946 + public static function add_woocommerce_products_sync_module( $sync_modules ) {
947 + $sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce_Products';
948 + return $sync_modules;
949 + }
950 +
951 + /**
794 952 * Initializes sync for WP Super Cache.
795 953 *
796 954 * @access public
797 955 * @static
@@ -796,9 +954,9 @@
796 954 * @access public
797 955 * @static
798 956 */
799 957 public static function initialize_wp_super_cache() {
800 - if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
958 + if ( ! function_exists( 'wp_cache_is_enabled' ) ) {
801 959 return;
802 960 }
803 961 add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
804 962 }
@@ -983,9 +1141,10 @@
983 1141 public static function get_sync_status( $fields = null ) {
984 1142 self::initialize_sender();
985 1143
986 1144 $sync_module = Modules::get_module( 'full-sync' );
987 - $queue = self::$sender->get_sync_queue();
1145 + '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $sync_module';
1146 + $queue = self::$sender->get_sync_queue();
988 1147
989 1148 // _get_cron_array can be false
990 1149 $cron_timestamps = ( _get_cron_array() ) ? array_keys( _get_cron_array() ) : array();
991 1150 $next_cron = ( ! empty( $cron_timestamps ) ) ? $cron_timestamps[0] - time() : '';
@@ -1033,9 +1192,9 @@
1033 1192 )
1034 1193 );
1035 1194
1036 1195 // Verify $sync_module is not false.
1037 - if ( ( $sync_module ) && false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
1196 + if ( $sync_module && ! $sync_module instanceof Modules\Full_Sync_Immediately ) {
1038 1197 $result['full_queue_size'] = $full_queue->size();
1039 1198 $result['full_queue_lag'] = $full_queue->lag();
1040 1199 }
1041 1200 return $result;
@@ -1058,10 +1217,16 @@
1058 1217 // Retry after locks.
1059 1218 delete_option( self::RETRY_AFTER_PREFIX . 'sync' );
1060 1219 delete_option( self::RETRY_AFTER_PREFIX . 'full_sync' );
1061 1220 // Dedicated sync locks.
1062 - \Jetpack_Options::delete_raw_option( Dedicated_Sender::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME );
1221 + $dedicated_sync_lock_option = Dedicated_Sender::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME;
1222 + $dedicated_sync_lock_expires_option = $dedicated_sync_lock_option . '_expires';
1223 + \Jetpack_Options::delete_raw_option( $dedicated_sync_lock_option );
1224 + \Jetpack_Options::delete_raw_option( $dedicated_sync_lock_expires_option );
1225 +
1063 1226 delete_transient( Dedicated_Sender::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG );
1227 + // Lock for disabling Sync sending temporarily.
1228 + delete_transient( Sender::TEMP_SYNC_DISABLE_TRANSIENT_NAME );
1064 1229
1065 1230 // Queue locks.
1066 1231 // Note that we are just unlocking the queues here, not reseting them.
1067 1232 if ( $unlock_queues ) {
@@ -1069,7 +1234,62 @@
1069 1234 $sync_queue->unlock();
1070 1235
1071 1236 $full_sync_queue = new Queue( 'full_sync' );
1072 1237 $full_sync_queue->unlock();
1238 + }
1239 + }
1240 +
1241 + /**
1242 + * Prepare JSONL data.
1243 + *
1244 + * @param mixed $data The data to be prepared.
1245 + *
1246 + * @return string The prepared JSONL data.
1247 + */
1248 + private static function prepare_jsonl_data( $data ) {
1249 + $jsonl_data = implode(
1250 + "\n",
1251 + array_map(
1252 + function ( $key, $value ) {
1253 + return wp_json_encode( array( $key => $value ), JSON_UNESCAPED_SLASHES );
1254 + },
1255 + array_keys( (array) $data ),
1256 + array_values( (array) $data )
1257 + )
1258 + );
1259 + return $jsonl_data;
1260 + }
1261 +
1262 + /**
1263 + * Helper method to process the API response.
1264 + *
1265 + * @param mixed $response The response from the API.
1266 + * @return array|Wp_Error Array for successful response or a WP_Error object.
1267 + */
1268 + private static function process_rest_api_response( $response ) {
1269 +
1270 + $response_code = wp_remote_retrieve_response_code( $response );
1271 + $response_body = wp_remote_retrieve_body( $response );
1272 + if ( is_wp_error( $response ) ) {
1273 + return $response;
1274 + }
1275 + $decoded_response = json_decode( $response_body, true );
1276 +
1277 + if ( ! is_array( $decoded_response ) ) {
1278 + return new WP_Error( 'sync_rest_api_response_decoding_failed', 'Sync REST API response decoding failed', $response_body );
1279 + }
1280 +
1281 + if ( $response_code !== 200 || ! isset( $decoded_response['processed_items'] ) ) {
1282 + if ( isset( $decoded_response['code'] ) && isset( $decoded_response['message'] ) ) {
1283 + return new WP_Error(
1284 + 'jetpack_sync_send_error_' . $decoded_response['code'],
1285 + $decoded_response['message'],
1286 + $decoded_response['data'] ?? null
1287 + );
1288 + } else {
1289 + return new WP_Error( $response_code, 'Sync REST API request failed', $response_body );
1290 + }
1291 + } else {
1292 + return $decoded_response;
1073 1293 }
1074 1294 }
1075 1295 }