← All changes
|
jetpack_vendor/automattic/jetpack-sync/src/class-actions.php
+136
-34
13.6.2
→
16.3-beta
View file →
| @@ -115,9 +115,9 @@ | ||
| 115 | 115 | return; |
| 116 | 116 | } |
| 117 | 117 | |
| 118 | 118 | if ( self::sync_via_cron_allowed() ) { |
| 119 | - self::init_sync_cron_jobs(); | |
| 119 | + add_action( 'init', array( __CLASS__, 'init_sync_cron_jobs' ), 1 ); | |
| 120 | 120 | } elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) { |
| 121 | 121 | self::clear_sync_cron_jobs(); |
| 122 | 122 | } |
| 123 | 123 | // When importing via cron, do not sync. |
| @@ -174,9 +174,12 @@ | ||
| 174 | 174 | self::should_initialize_sender() |
| 175 | 175 | ) ) { |
| 176 | 176 | self::initialize_sender(); |
| 177 | 177 | add_action( 'shutdown', array( self::$sender, 'do_sync' ), 9998 ); |
| 178 | - 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 | + } | |
| 179 | 182 | } |
| 180 | 183 | } |
| 181 | 184 | |
| 182 | 185 | /** |
| @@ -211,11 +214,13 @@ | ||
| 211 | 214 | * |
| 212 | 215 | * @access public |
| 213 | 216 | * @static |
| 214 | 217 | * |
| 218 | + * @param bool $full_sync Whether the Full Sync sender should run on shutdown for this request. | |
| 219 | + * | |
| 215 | 220 | * @return bool |
| 216 | 221 | */ |
| 217 | - public static function should_initialize_sender() { | |
| 222 | + public static function should_initialize_sender( $full_sync = false ) { | |
| 218 | 223 | |
| 219 | 224 | // Allow for explicit disable of Sync from request param jetpack_sync_read_only. |
| 220 | 225 | if ( isset( $_REQUEST['jetpack_sync_read_only'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification |
| 221 | 226 | self::mark_sync_read_only(); |
| @@ -226,11 +231,12 @@ | ||
| 226 | 231 | return self::sync_via_cron_allowed(); |
| 227 | 232 | } |
| 228 | 233 | |
| 229 | 234 | /** |
| 230 | - * 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. | |
| 231 | 237 | */ |
| 232 | - if ( Settings::is_dedicated_sync_enabled() ) { | |
| 238 | + if ( false === $full_sync && Settings::is_dedicated_sync_enabled() ) { | |
| 233 | 239 | return true; |
| 234 | 240 | } |
| 235 | 241 | |
| 236 | 242 | if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) { |
| @@ -303,12 +309,8 @@ | ||
| 303 | 309 | if ( ( new Status() )->is_offline_mode() ) { |
| 304 | 310 | return false; |
| 305 | 311 | } |
| 306 | 312 | |
| 307 | - if ( ( new Status() )->in_safe_mode() ) { | |
| 308 | - return false; | |
| 309 | - } | |
| 310 | - | |
| 311 | 313 | $connection = new Jetpack_Connection(); |
| 312 | 314 | if ( ! $connection->is_connected() ) { |
| 313 | 315 | if ( ! doing_action( 'jetpack_site_registered' ) ) { |
| 314 | 316 | return false; |
| @@ -314,8 +316,13 @@ | ||
| 314 | 316 | return false; |
| 315 | 317 | } |
| 316 | 318 | } |
| 317 | 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 | + | |
| 318 | 325 | return true; |
| 319 | 326 | } |
| 320 | 327 | |
| 321 | 328 | /** |
| @@ -355,8 +362,12 @@ | ||
| 355 | 362 | $debug['debug_details']['dedicated_sync_enabled'] = Settings::is_dedicated_sync_enabled(); |
| 356 | 363 | |
| 357 | 364 | $queue = self::$sender->get_sync_queue(); |
| 358 | 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'; | |
| 359 | 370 | |
| 360 | 371 | $debug['debug_details']['sync_locks'] = array( |
| 361 | 372 | 'retry_time_sync' => get_option( self::RETRY_AFTER_PREFIX . 'sync' ), |
| 362 | 373 | 'retry_time_full_sync' => get_option( self::RETRY_AFTER_PREFIX . 'full_sync' ), |
| @@ -363,9 +374,9 @@ | ||
| 363 | 374 | 'next_sync_time_sync' => self::$sender->get_next_sync_time( 'sync' ), |
| 364 | 375 | 'next_sync_time_full_sync' => self::$sender->get_next_sync_time( 'full_sync' ), |
| 365 | 376 | 'queue_locked_sync' => $queue->is_locked(), |
| 366 | 377 | 'queue_locked_full_sync' => $full_queue->is_locked(), |
| 367 | - '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 ), | |
| 368 | 379 | 'dedicated_sync_temporary_disable_flag' => get_transient( Dedicated_Sender::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG ), |
| 369 | 380 | ); |
| 370 | 381 | |
| 371 | 382 | // Sync Logs. |
| @@ -601,9 +612,9 @@ | ||
| 601 | 612 | 'users' => array( get_current_user_id() ), |
| 602 | 613 | 'network_options' => true, |
| 603 | 614 | ); |
| 604 | 615 | |
| 605 | - self::do_full_sync( $initial_sync_config ); | |
| 616 | + self::do_full_sync( $initial_sync_config, 'initial_sync' ); | |
| 606 | 617 | } |
| 607 | 618 | |
| 608 | 619 | /** |
| 609 | 620 | * Do an initial full sync only if one has not already been started. |
| @@ -626,11 +637,12 @@ | ||
| 626 | 637 | * @access public |
| 627 | 638 | * @static |
| 628 | 639 | * |
| 629 | 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. | |
| 630 | 642 | * @return bool True if full sync was successfully started. |
| 631 | 643 | */ |
| 632 | - public static function do_full_sync( $modules = null ) { | |
| 644 | + public static function do_full_sync( $modules = null, $context = null ) { | |
| 633 | 645 | if ( ! self::sync_allowed() ) { |
| 634 | 646 | return false; |
| 635 | 647 | } |
| 636 | 648 | |
| @@ -642,9 +654,9 @@ | ||
| 642 | 654 | } |
| 643 | 655 | |
| 644 | 656 | self::initialize_listener(); |
| 645 | 657 | |
| 646 | - $full_sync_module->start( $modules ); | |
| 658 | + $full_sync_module->start( $modules, $context ); | |
| 647 | 659 | |
| 648 | 660 | return true; |
| 649 | 661 | } |
| 650 | 662 | |
| @@ -658,13 +670,11 @@ | ||
| 658 | 670 | * @return array A list of WordPress cron schedules with the Jetpack sync interval added. |
| 659 | 671 | */ |
| 660 | 672 | public static function jetpack_cron_schedule( $schedules ) { |
| 661 | 673 | if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) { |
| 662 | - $minutes = (int) ( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 ); | |
| 663 | - $display = ( 1 === $minutes ) ? | |
| 664 | - __( 'Every minute', 'jetpack-sync' ) : | |
| 665 | - /* translators: %d is an integer indicating the number of minutes. */ | |
| 666 | - 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 ); | |
| 667 | 677 | $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array( |
| 668 | 678 | 'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE, |
| 669 | 679 | 'display' => $display, |
| 670 | 680 | ); |
| @@ -678,9 +688,47 @@ | ||
| 678 | 688 | * @access public |
| 679 | 689 | * @static |
| 680 | 690 | */ |
| 681 | 691 | public static function do_cron_sync() { |
| 682 | - 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; | |
| 683 | 731 | } |
| 684 | 732 | |
| 685 | 733 | /** |
| 686 | 734 | * Starts a full sync via cron. |
| @@ -688,9 +736,33 @@ | ||
| 688 | 736 | * @access public |
| 689 | 737 | * @static |
| 690 | 738 | */ |
| 691 | 739 | public static function do_cron_full_sync() { |
| 692 | - 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; | |
| 693 | 765 | } |
| 694 | 766 | |
| 695 | 767 | /** |
| 696 | 768 | * Try to send actions until we run out of things to send, |
| @@ -720,9 +792,9 @@ | ||
| 720 | 792 | $delay = $next_sync_time - time() + 1; |
| 721 | 793 | if ( $delay > 15 ) { |
| 722 | 794 | break; |
| 723 | 795 | } elseif ( $delay > 0 ) { |
| 724 | - sleep( $delay ); | |
| 796 | + sleep( (int) $delay ); | |
| 725 | 797 | } |
| 726 | 798 | } |
| 727 | 799 | |
| 728 | 800 | // Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved. |
| @@ -776,9 +848,9 @@ | ||
| 776 | 848 | * @access public |
| 777 | 849 | * @static |
| 778 | 850 | */ |
| 779 | 851 | public static function initialize_woocommerce() { |
| 780 | - if ( false === class_exists( 'WooCommerce' ) ) { | |
| 852 | + if ( ! class_exists( 'WooCommerce' ) ) { | |
| 781 | 853 | return; |
| 782 | 854 | } |
| 783 | 855 | add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) ); |
| 784 | 856 | |
| @@ -791,21 +863,30 @@ | ||
| 791 | 863 | } |
| 792 | 864 | } |
| 793 | 865 | |
| 794 | 866 | /** |
| 795 | - * Initializes sync for Instant Search. | |
| 867 | + * Initializes sync for Jetpack Search. | |
| 796 | 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 | + * | |
| 797 | 881 | * @access public |
| 798 | 882 | * @static |
| 799 | 883 | */ |
| 800 | 884 | public static function initialize_search() { |
| 801 | - if ( false === class_exists( 'Automattic\\Jetpack\\Search\\Module_Control' ) ) { | |
| 885 | + if ( ! class_exists( 'Automattic\\Jetpack\\Search\\Module_Control' ) ) { | |
| 802 | 886 | return; |
| 803 | 887 | } |
| 804 | - $search_module = new \Automattic\Jetpack\Search\Module_Control(); | |
| 805 | - if ( $search_module->is_instant_search_enabled() ) { | |
| 806 | - add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_search_sync_module' ) ); | |
| 807 | - } | |
| 888 | + add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_search_sync_module' ) ); | |
| 808 | 889 | } |
| 809 | 890 | |
| 810 | 891 | /** |
| 811 | 892 | * Add Search updates to Sync Filters. |
| @@ -850,8 +931,25 @@ | ||
| 850 | 931 | return $sync_modules; |
| 851 | 932 | } |
| 852 | 933 | |
| 853 | 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 | + /** | |
| 854 | 952 | * Initializes sync for WP Super Cache. |
| 855 | 953 | * |
| 856 | 954 | * @access public |
| 857 | 955 | * @static |
| @@ -856,9 +954,9 @@ | ||
| 856 | 954 | * @access public |
| 857 | 955 | * @static |
| 858 | 956 | */ |
| 859 | 957 | public static function initialize_wp_super_cache() { |
| 860 | - if ( false === function_exists( 'wp_cache_is_enabled' ) ) { | |
| 958 | + if ( ! function_exists( 'wp_cache_is_enabled' ) ) { | |
| 861 | 959 | return; |
| 862 | 960 | } |
| 863 | 961 | add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) ); |
| 864 | 962 | } |
| @@ -1119,9 +1217,13 @@ | ||
| 1119 | 1217 | // Retry after locks. |
| 1120 | 1218 | delete_option( self::RETRY_AFTER_PREFIX . 'sync' ); |
| 1121 | 1219 | delete_option( self::RETRY_AFTER_PREFIX . 'full_sync' ); |
| 1122 | 1220 | // Dedicated sync locks. |
| 1123 | - \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 | + | |
| 1124 | 1226 | delete_transient( Dedicated_Sender::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG ); |
| 1125 | 1227 | // Lock for disabling Sync sending temporarily. |
| 1126 | 1228 | delete_transient( Sender::TEMP_SYNC_DISABLE_TRANSIENT_NAME ); |
| 1127 | 1229 | |
| @@ -1147,9 +1249,9 @@ | ||
| 1147 | 1249 | $jsonl_data = implode( |
| 1148 | 1250 | "\n", |
| 1149 | 1251 | array_map( |
| 1150 | 1252 | function ( $key, $value ) { |
| 1151 | - return wp_json_encode( array( $key => $value ) ); | |
| 1253 | + return wp_json_encode( array( $key => $value ), JSON_UNESCAPED_SLASHES ); | |
| 1152 | 1254 | }, |
| 1153 | 1255 | array_keys( (array) $data ), |
| 1154 | 1256 | array_values( (array) $data ) |
| 1155 | 1257 | ) |
| @@ -1171,14 +1273,14 @@ | ||
| 1171 | 1273 | return $response; |
| 1172 | 1274 | } |
| 1173 | 1275 | $decoded_response = json_decode( $response_body, true ); |
| 1174 | 1276 | |
| 1175 | - if ( false === is_array( $decoded_response ) ) { | |
| 1277 | + if ( ! is_array( $decoded_response ) ) { | |
| 1176 | 1278 | return new WP_Error( 'sync_rest_api_response_decoding_failed', 'Sync REST API response decoding failed', $response_body ); |
| 1177 | 1279 | } |
| 1178 | 1280 | |
| 1179 | - if ( $response_code !== 200 || false === isset( $decoded_response['processed_items'] ) ) { | |
| 1180 | - if ( is_array( $decoded_response ) && isset( $decoded_response['code'] ) && isset( $decoded_response['message'] ) ) { | |
| 1281 | + if ( $response_code !== 200 || ! isset( $decoded_response['processed_items'] ) ) { | |
| 1282 | + if ( isset( $decoded_response['code'] ) && isset( $decoded_response['message'] ) ) { | |
| 1181 | 1283 | return new WP_Error( |
| 1182 | 1284 | 'jetpack_sync_send_error_' . $decoded_response['code'], |
| 1183 | 1285 | $decoded_response['message'], |
| 1184 | 1286 | $decoded_response['data'] ?? null |