PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0.1
Jetpack – WP Security, Backup, Speed, & Growth v16.0.1
16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-sync / src / class-actions.php
jetpack / jetpack_vendor / automattic / jetpack-sync / src Last commit date
modules 3 weeks ago replicastore 3 months ago sync-queue 5 months ago class-actions.php 1 month ago class-activity-log-event.php 3 weeks ago class-data-settings.php 1 month ago class-dedicated-sender.php 7 months ago class-default-filter-settings.php 4 years ago class-defaults.php 2 days ago class-functions.php 1 month ago class-health.php 4 months ago class-json-deflate-array-codec.php 7 months ago class-listener.php 1 month ago class-lock.php 4 years ago class-main.php 2 months ago class-modules.php 1 year ago class-package-version.php 2 days ago class-queue-buffer.php 4 years ago class-queue.php 1 month ago class-replicastore.php 1 month ago class-rest-endpoints.php 4 months ago class-rest-sender.php 1 month ago class-sender.php 1 month ago class-server.php 4 months ago class-settings.php 3 months ago class-simple-codec.php 7 months ago class-users.php 3 months ago class-utils.php 4 years ago interface-codec.php 4 years ago interface-replicastore.php 1 month ago
class-actions.php
1296 lines
1 <?php
2 /**
3 * A class that defines syncable actions for Jetpack.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8 namespace Automattic\Jetpack\Sync;
9
10 use Automattic\Jetpack\Connection\Client;
11 use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
12 use Automattic\Jetpack\Constants;
13 use Automattic\Jetpack\Identity_Crisis;
14 use Automattic\Jetpack\Status;
15 use Automattic\Jetpack\Sync\Modules\WooCommerce_HPOS_Orders;
16 use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
17 use WP_Error;
18
19 /**
20 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
21 * when to send, when to perform a full sync, etc.
22 *
23 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
24 */
25 class Actions {
26
27 /**
28 * Name of the retry-after option prefix.
29 *
30 * @access public
31 *
32 * @var string
33 */
34 const RETRY_AFTER_PREFIX = 'jp_sync_retry_after_';
35
36 /**
37 * Name of the error log option prefix.
38 *
39 * @access public
40 *
41 * @var string
42 */
43 const ERROR_LOG_PREFIX = 'jp_sync_error_log_';
44
45 /**
46 * Name of the last successful sync option prefix.
47 *
48 * @access public
49 *
50 * @var string
51 */
52 const LAST_SUCCESS_PREFIX = 'jp_sync_last_success_';
53
54 /**
55 * A variable to hold a sync sender object.
56 *
57 * @access public
58 * @static
59 *
60 * @var \Automattic\Jetpack\Sync\Sender
61 */
62 public static $sender = null;
63
64 /**
65 * A variable to hold a sync listener object.
66 *
67 * @access public
68 * @static
69 *
70 * @var \Automattic\Jetpack\Sync\Listener
71 */
72 public static $listener = null;
73
74 /**
75 * Name of the sync cron schedule.
76 *
77 * @access public
78 *
79 * @var string
80 */
81 const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
82
83 /**
84 * Interval between the last and the next sync cron action.
85 *
86 * @access public
87 *
88 * @var int
89 */
90 const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
91
92 /**
93 * Initialize Sync for cron jobs, set up listeners for WordPress Actions,
94 * and set up a shut-down action for sending actions to WordPress.com
95 * If dedicated Sync is enabled and this is a dedicated Sync request
96 * up an init action for sending actions to WordPress.com instead.
97 *
98 * @access public
99 * @static
100 */
101 public static function init() {
102 // Everything below this point should only happen if we're a valid sync site.
103 if ( ! self::sync_allowed() ) {
104 return;
105 }
106
107 // If dedicated Sync is enabled and this is a dedicated Sync request, no need to
108 // initialize Sync for cron jobs or set up a shut-down action for sending actions to WordPress.com.
109 // We only need to set up an init action for sending actions to WordPress.com and exit early.
110 // Note: We also need to initialize the listener so that callable and constant changes, eg actions that
111 // rely on 'jetpack_sync_before_send_queue_sync' are picked up and added to the queue if needed.
112 if ( Settings::is_dedicated_sync_enabled() && Dedicated_Sender::is_dedicated_sync_request() ) {
113 self::initialize_listener();
114 add_action( 'init', array( __CLASS__, 'add_dedicated_sync_sender_init' ), 200 );
115 return;
116 }
117
118 if ( self::sync_via_cron_allowed() ) {
119 add_action( 'init', array( __CLASS__, 'init_sync_cron_jobs' ), 1 );
120 } elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
121 self::clear_sync_cron_jobs();
122 }
123 // When importing via cron, do not sync.
124 add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
125
126 // Sync connected user role changes to WordPress.com.
127 Users::init();
128
129 // Publicize filter to prevent publicizing blacklisted post types.
130 add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
131
132 /**
133 * Fires on every request before default loading sync listener code.
134 * Return false to not load sync listener code that monitors common
135 * WP actions to be serialized.
136 *
137 * By default this returns true for cron jobs, non-GET-requests, or requests where the
138 * user is logged-in.
139 *
140 * @since 1.6.3
141 * @since-jetpack 4.2.0
142 *
143 * @param bool should we load sync listener code for this request
144 */
145 if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
146 self::initialize_listener();
147 }
148
149 add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
150 }
151
152 /**
153 * Prepares sync to send actions on shutdown for the current request.
154 *
155 * @access public
156 * @static
157 */
158 public static function add_sender_shutdown() {
159 /**
160 * Fires on every request before default loading sync sender code.
161 * Return false to not load sync sender code that serializes pending
162 * data and sends it to WPCOM for processing.
163 *
164 * By default this returns true for cron jobs, POST requests, admin requests, or requests
165 * by users who can manage_options.
166 *
167 * @since 1.6.3
168 * @since-jetpack 4.2.0
169 *
170 * @param bool should we load sync sender code for this request
171 */
172 if ( apply_filters(
173 'jetpack_sync_sender_should_load',
174 self::should_initialize_sender()
175 ) ) {
176 self::initialize_sender();
177 add_action( 'shutdown', array( self::$sender, 'do_sync' ), 9998 );
178
179 if ( self::should_initialize_sender( true ) ) {
180 add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
181 }
182 }
183 }
184
185 /**
186 * Immediately sends actions on init for the current dedicated Sync request.
187 *
188 * @access public
189 * @static
190 */
191 public static function add_dedicated_sync_sender_init() {
192 if ( apply_filters(
193 'jetpack_sync_sender_should_load',
194 true
195 ) ) {
196 self::initialize_sender();
197 self::$sender->do_dedicated_sync_and_exit();
198 }
199 }
200
201 /**
202 * Define JETPACK_SYNC_READ_ONLY constant if not defined.
203 * This notifies sync to not run in shutdown if it was initialized during init.
204 *
205 * @access public
206 * @static
207 */
208 public static function mark_sync_read_only() {
209 Constants::set_constant( 'JETPACK_SYNC_READ_ONLY', true );
210 }
211
212 /**
213 * Decides if the sender should run on shutdown for this request.
214 *
215 * @access public
216 * @static
217 *
218 * @param bool $full_sync Whether the Full Sync sender should run on shutdown for this request.
219 *
220 * @return bool
221 */
222 public static function should_initialize_sender( $full_sync = false ) {
223
224 // Allow for explicit disable of Sync from request param jetpack_sync_read_only.
225 if ( isset( $_REQUEST['jetpack_sync_read_only'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
226 self::mark_sync_read_only();
227 return false;
228 }
229
230 if ( Constants::is_true( 'DOING_CRON' ) ) {
231 return self::sync_via_cron_allowed();
232 }
233
234 /**
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.
237 */
238 if ( false === $full_sync && Settings::is_dedicated_sync_enabled() ) {
239 return true;
240 }
241
242 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
243 return true;
244 }
245
246 if ( current_user_can( 'manage_options' ) ) {
247 return true;
248 }
249
250 if ( is_admin() ) {
251 return true;
252 }
253
254 if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
255 return true;
256 }
257
258 if ( Constants::get_constant( 'WP_CLI' ) ) {
259 return true;
260 }
261
262 return false;
263 }
264
265 /**
266 * Decides if the sender should run on shutdown when actions are queued.
267 *
268 * @access public
269 * @static
270 *
271 * @param bool $enable Should we initilize sender.
272 * @return bool
273 */
274 public static function should_initialize_sender_enqueue( $enable ) {
275
276 // If $enabled is false don't modify it, only check cron if enabled.
277 if ( false === $enable ) {
278 return $enable;
279 }
280
281 if ( Constants::is_true( 'DOING_CRON' ) ) {
282 return self::sync_via_cron_allowed();
283 }
284
285 return true;
286 }
287
288 /**
289 * Decides if sync should run at all during this request.
290 *
291 * @access public
292 * @static
293 *
294 * @return bool
295 */
296 public static function sync_allowed() {
297 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
298 return false;
299 }
300
301 if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
302 return true;
303 }
304
305 if ( ! Settings::is_sync_enabled() ) {
306 return false;
307 }
308
309 if ( ( new Status() )->is_offline_mode() ) {
310 return false;
311 }
312
313 $connection = new Jetpack_Connection();
314 if ( ! $connection->is_connected() ) {
315 if ( ! doing_action( 'jetpack_site_registered' ) ) {
316 return false;
317 }
318 }
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
325 return true;
326 }
327
328 /**
329 * Helper function to get details as to why sync is not allowed, if it is not allowed.
330 *
331 * @return array
332 */
333 public static function get_debug_details() {
334 $debug = array();
335 $debug['debug_details']['sync_allowed'] = self::sync_allowed();
336 $debug['debug_details']['sync_health'] = Health::get_status();
337 if ( false === $debug['debug_details']['sync_allowed'] ) {
338 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
339 $debug['debug_details']['is_wpcom'] = true;
340 }
341 if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
342 $debug['debug_details']['PHPUNIT_JETPACK_TESTSUITE'] = true;
343 }
344 if ( ! Settings::is_sync_enabled() ) {
345 $debug['debug_details']['is_sync_enabled'] = false;
346 $debug['debug_details']['jetpack_sync_disable'] = Settings::get_setting( 'disable' );
347 $debug['debug_details']['jetpack_sync_network_disable'] = Settings::get_setting( 'network_disable' );
348 }
349 if ( ( new Status() )->is_offline_mode() ) {
350 $debug['debug_details']['is_offline_mode'] = true;
351 }
352 if ( ( new Status() )->in_safe_mode() ) {
353 $debug['debug_details']['in_safe_mode'] = true;
354 }
355 $connection = new Jetpack_Connection();
356 if ( ! $connection->is_connected() ) {
357 $debug['debug_details']['active_connection'] = false;
358 }
359 }
360
361 // Sync locks.
362 $debug['debug_details']['dedicated_sync_enabled'] = Settings::is_dedicated_sync_enabled();
363
364 $queue = self::$sender->get_sync_queue();
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';
370
371 $debug['debug_details']['sync_locks'] = array(
372 'retry_time_sync' => get_option( self::RETRY_AFTER_PREFIX . 'sync' ),
373 'retry_time_full_sync' => get_option( self::RETRY_AFTER_PREFIX . 'full_sync' ),
374 'next_sync_time_sync' => self::$sender->get_next_sync_time( 'sync' ),
375 'next_sync_time_full_sync' => self::$sender->get_next_sync_time( 'full_sync' ),
376 'queue_locked_sync' => $queue->is_locked(),
377 'queue_locked_full_sync' => $full_queue->is_locked(),
378 'dedicated_sync_request_lock' => \Jetpack_Options::get_raw_option( $dedicated_sync_lock_expires_name, null ),
379 'dedicated_sync_temporary_disable_flag' => get_transient( Dedicated_Sender::DEDICATED_SYNC_TEMPORARY_DISABLE_FLAG ),
380 );
381
382 // Sync Logs.
383 $debug['debug_details']['last_succesful_sync'] = get_option( self::LAST_SUCCESS_PREFIX . 'sync', '' );
384 $debug['debug_details']['sync_error_log'] = get_option( self::ERROR_LOG_PREFIX . 'sync', '' );
385
386 return $debug;
387 }
388
389 /**
390 * Determines if syncing during a cron job is allowed.
391 *
392 * @access public
393 * @static
394 *
395 * @return bool|int
396 */
397 public static function sync_via_cron_allowed() {
398 return ( Settings::get_setting( 'sync_via_cron' ) );
399 }
400
401 /**
402 * Decides if the given post should be Publicized based on its type.
403 *
404 * @access public
405 * @static
406 *
407 * @param bool $should_publicize Publicize status prior to this filter running.
408 * @param \WP_Post $post The post to test for Publicizability.
409 * @return bool
410 */
411 public static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
412 if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
413 return false;
414 }
415
416 return $should_publicize;
417 }
418
419 /**
420 * Set an importing flag to `true` in sync settings.
421 *
422 * @access public
423 * @static
424 */
425 public static function set_is_importing_true() {
426 Settings::set_importing( true );
427 }
428
429 /**
430 * Sends data to WordPress.com via an XMLRPC or a REST API request based on the settings.
431 *
432 * @access public
433 * @static
434 *
435 * @param object $data Data relating to a sync action.
436 * @param string $codec_name The name of the codec that encodes the data.
437 * @param float $sent_timestamp Current server time so we can compensate for clock differences.
438 * @param string $queue_id The queue the action belongs to, sync or full_sync.
439 * @param float $checkout_duration Time spent retrieving queue items from the DB.
440 * @param float $preprocess_duration Time spent converting queue items into data to send.
441 * @param int $queue_size The size of the sync queue at the time of processing.
442 * @param string $buffer_id The ID of the Queue buffer checked out for processing.
443 * @return mixed|WP_Error The result of the sending request.
444 */
445 public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration, $queue_size = null, $buffer_id = null ) {
446
447 $query_args = array(
448
449 'sync' => '1', // Add an extra parameter to the URL so we can tell it's a sync action.
450 'codec' => $codec_name,
451 'timestamp' => $sent_timestamp,
452 'queue' => $queue_id,
453 'cd' => sprintf( '%.4f', $checkout_duration ),
454 'pd' => sprintf( '%.4f', $preprocess_duration ),
455 'queue_size' => $queue_size,
456 'buffer_id' => $buffer_id,
457 // TODO this will be extended in the future. Might be good to extract in a separate method to support future entries too.
458 'sync_flow_type' => Settings::is_dedicated_sync_enabled() ? 'dedicated' : 'default',
459 'storage_type' => Settings::is_custom_queue_table_enabled() ? 'custom' : 'options',
460 );
461
462 $query_args['timeout'] = Settings::is_doing_cron() ? 30 : 20;
463
464 if ( 'immediate-send' === $queue_id ) {
465 $query_args['timeout'] = 30;
466 }
467
468 /**
469 * Filters query parameters appended to the Sync request URL sent to WordPress.com.
470 *
471 * @since 1.6.3
472 * @since-jetpack 4.7.0
473 *
474 * @param array $query_args associative array of query parameters.
475 */
476 $query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
477
478 $retry_after_header = false;
479 $dedicated_sync_header = false;
480
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'],
490 );
491
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() );
499
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 }
508
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();
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 {
530 // if unexpected value default to 3 minutes.
531 update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + 180, false );
532 }
533 }
534
535 // Enable/Disable Dedicated Sync flow via response headers.
536 if ( false !== $dedicated_sync_header ) {
537 Dedicated_Sender::maybe_change_dedicated_sync_status_from_wpcom_header( $dedicated_sync_header );
538 }
539
540 if ( is_wp_error( $response ) ) {
541 $error = $response;
542 if ( false === $retry_after_header ) {
543 // We received a non standard response from WP.com, lets backoff from sending requests for 1 minute.
544 update_option( self::RETRY_AFTER_PREFIX . $queue_id, microtime( true ) + 60, false );
545 }
546 // Record Sync Errors.
547 $error_log = get_option( self::ERROR_LOG_PREFIX . $queue_id, array() );
548 if ( ! is_array( $error_log ) ) {
549 $error_log = array();
550 }
551 // Trim existing array to last 4 entries.
552 if ( 5 <= count( $error_log ) ) {
553 $error_log = array_slice( $error_log, -4, null, true );
554 }
555 // Add new error indexed to time.
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
564 // Update the error log.
565 update_option( self::ERROR_LOG_PREFIX . $queue_id, $error_log );
566 return $error;
567 }
568
569 // Check if WordPress.com IDC mitigation blocked the sync request.
570 if ( Identity_Crisis::init()->check_response_for_idc( $response ) ) {
571 return new WP_Error(
572 'sync_error_idc',
573 esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack-sync' )
574 );
575 }
576
577 if ( isset( $response['processed_items'] ) ) { // Return only processed items.
578 $response = $response['processed_items'];
579 }
580
581 // Record last successful sync.
582 update_option( self::LAST_SUCCESS_PREFIX . $queue_id, microtime( true ), false );
583
584 return $response;
585 }
586
587 /**
588 * Kicks off the initial sync.
589 *
590 * @access public
591 * @static
592 *
593 * @return bool|null False if sync is not allowed.
594 */
595 public static function do_initial_sync() {
596 // Let's not sync if we are not supposed to.
597 if ( ! self::sync_allowed() ) {
598 return false;
599 }
600
601 // Don't start new sync if a full sync is in process.
602 $full_sync_module = Modules::get_module( 'full-sync' );
603 '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
604 if ( $full_sync_module && $full_sync_module->is_started() && ! $full_sync_module->is_finished() ) {
605 return false;
606 }
607
608 $initial_sync_config = array(
609 'options' => true,
610 'functions' => true,
611 'constants' => true,
612 'users' => array( get_current_user_id() ),
613 'network_options' => true,
614 );
615
616 self::do_full_sync( $initial_sync_config, 'initial_sync' );
617 }
618
619 /**
620 * Do an initial full sync only if one has not already been started.
621 *
622 * @return bool|null False if the initial full sync was already started, otherwise null.
623 */
624 public static function do_only_first_initial_sync() {
625 $full_sync_module = Modules::get_module( 'full-sync' );
626 '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
627 if ( $full_sync_module && $full_sync_module->is_started() ) {
628 return false;
629 }
630
631 static::do_initial_sync();
632 }
633
634 /**
635 * Kicks off a full sync.
636 *
637 * @access public
638 * @static
639 *
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.
642 * @return bool True if full sync was successfully started.
643 */
644 public static function do_full_sync( $modules = null, $context = null ) {
645 if ( ! self::sync_allowed() ) {
646 return false;
647 }
648
649 $full_sync_module = Modules::get_module( 'full-sync' );
650 '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $full_sync_module';
651
652 if ( ! $full_sync_module ) {
653 return false;
654 }
655
656 self::initialize_listener();
657
658 $full_sync_module->start( $modules, $context );
659
660 return true;
661 }
662
663 /**
664 * Adds a cron schedule for regular syncing via cron, unless the schedule already exists.
665 *
666 * @access public
667 * @static
668 *
669 * @param array $schedules The list of WordPress cron schedules prior to this filter.
670 * @return array A list of WordPress cron schedules with the Jetpack sync interval added.
671 */
672 public static function jetpack_cron_schedule( $schedules ) {
673 if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
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 );
677 $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
678 'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
679 'display' => $display,
680 );
681 }
682 return $schedules;
683 }
684
685 /**
686 * Starts an incremental sync via cron.
687 *
688 * @access public
689 * @static
690 */
691 public static function do_cron_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;
731 }
732
733 /**
734 * Starts a full sync via cron.
735 *
736 * @access public
737 * @static
738 */
739 public static function do_cron_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;
765 }
766
767 /**
768 * Try to send actions until we run out of things to send,
769 * or have to wait more than 15s before sending again,
770 * or we hit a lock or some other sending issue
771 *
772 * @access public
773 * @static
774 *
775 * @param string $type Sync type. Can be `sync` or `full_sync`.
776 */
777 public static function do_cron_sync_by_type( $type ) {
778 if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
779 return;
780 }
781
782 self::initialize_sender();
783
784 $time_limit = Settings::get_setting( 'cron_sync_time_limit' );
785 $start_time = time();
786 $executions = 0;
787
788 do {
789 $next_sync_time = self::$sender->get_next_sync_time( $type );
790
791 if ( $next_sync_time ) {
792 $delay = $next_sync_time - time() + 1;
793 if ( $delay > 15 ) {
794 break;
795 } elseif ( $delay > 0 ) {
796 sleep( (int) $delay );
797 }
798 }
799
800 // Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
801 // For more context see p1HpG7-9pe-p2.
802 if ( 'full_sync' === $type && $executions >= 1 ) {
803 break;
804 }
805
806 /**
807 * Only try to sync once if Dedicated Sync is enabled. Dedicated Sync has its own requeueing mechanism
808 * that will re-run it if there are items in the queue at the end.
809 */
810 if ( 'sync' === $type && $executions >= 1 && Settings::is_dedicated_sync_enabled() ) {
811 break;
812 }
813
814 $result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
815
816 // # of send actions performed.
817 ++$executions;
818
819 } while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
820
821 return $executions;
822 }
823
824 /**
825 * Initialize the sync listener.
826 *
827 * @access public
828 * @static
829 */
830 public static function initialize_listener() {
831 self::$listener = Listener::get_instance();
832 }
833
834 /**
835 * Initializes the sync sender.
836 *
837 * @access public
838 * @static
839 */
840 public static function initialize_sender() {
841 self::$sender = Sender::get_instance();
842 add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 8 );
843 }
844
845 /**
846 * Initializes sync for WooCommerce.
847 *
848 * @access public
849 * @static
850 */
851 public static function initialize_woocommerce() {
852 if ( ! class_exists( 'WooCommerce' ) ) {
853 return;
854 }
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 }
864 }
865
866 /**
867 * Initializes sync for Jetpack Search.
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 *
881 * @access public
882 * @static
883 */
884 public static function initialize_search() {
885 if ( ! class_exists( 'Automattic\\Jetpack\\Search\\Module_Control' ) ) {
886 return;
887 }
888 add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_search_sync_module' ) );
889 }
890
891 /**
892 * Add Search updates to Sync Filters.
893 *
894 * @access public
895 * @static
896 *
897 * @param array $sync_modules The list of sync modules declared prior to this filter.
898 * @return array A list of sync modules that now includes Search's modules.
899 */
900 public static function add_search_sync_module( $sync_modules ) {
901 $sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\Search';
902 return $sync_modules;
903 }
904
905 /**
906 * Adds Woo's sync modules to existing modules for sending.
907 *
908 * @access public
909 * @static
910 *
911 * @param array $sync_modules The list of sync modules declared prior to this filter.
912 * @return array A list of sync modules that now includes Woo's modules.
913 */
914 public static function add_woocommerce_sync_module( $sync_modules ) {
915 $sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
916 return $sync_modules;
917 }
918
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 /**
952 * Initializes sync for WP Super Cache.
953 *
954 * @access public
955 * @static
956 */
957 public static function initialize_wp_super_cache() {
958 if ( ! function_exists( 'wp_cache_is_enabled' ) ) {
959 return;
960 }
961 add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
962 }
963
964 /**
965 * Adds WP Super Cache's sync modules to existing modules for sending.
966 *
967 * @access public
968 * @static
969 *
970 * @param array $sync_modules The list of sync modules declared prior to this filer.
971 * @return array A list of sync modules that now includes WP Super Cache's modules.
972 */
973 public static function add_wp_super_cache_sync_module( $sync_modules ) {
974 $sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
975 return $sync_modules;
976 }
977
978 /**
979 * Sanitizes the name of sync's cron schedule.
980 *
981 * @access public
982 * @static
983 *
984 * @param string $schedule The name of a WordPress cron schedule.
985 * @return string The sanitized name of sync's cron schedule.
986 */
987 public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
988 $schedule = sanitize_key( $schedule );
989 $schedules = wp_get_schedules();
990
991 // Make sure that the schedule has actually been registered using the `cron_intervals` filter.
992 if ( isset( $schedules[ $schedule ] ) ) {
993 return $schedule;
994 }
995
996 return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
997 }
998
999 /**
1000 * Allows offsetting of start times for sync cron jobs.
1001 *
1002 * @access public
1003 * @static
1004 *
1005 * @param string $schedule The name of a cron schedule.
1006 * @param string $hook The hook that this method is responding to.
1007 * @return int The offset for the sync cron schedule.
1008 */
1009 public static function get_start_time_offset( $schedule = '', $hook = '' ) {
1010 $start_time_offset = is_multisite()
1011 ? wp_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
1012 : 0;
1013
1014 /**
1015 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
1016 * cron jobs across multiple sites in a network.
1017 *
1018 * @since 1.6.3
1019 * @since-jetpack 4.5.0
1020 *
1021 * @param int $start_time_offset
1022 * @param string $hook
1023 * @param string $schedule
1024 */
1025 return (int) apply_filters(
1026 'jetpack_sync_cron_start_time_offset',
1027 $start_time_offset,
1028 $hook,
1029 $schedule
1030 );
1031 }
1032
1033 /**
1034 * Decides if a sync cron should be scheduled.
1035 *
1036 * @access public
1037 * @static
1038 *
1039 * @param string $schedule The name of a cron schedule.
1040 * @param string $hook The hook that this method is responding to.
1041 */
1042 public static function maybe_schedule_sync_cron( $schedule, $hook ) {
1043 if ( ! $hook ) {
1044 return;
1045 }
1046 $schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
1047
1048 $start_time = time() + self::get_start_time_offset( $schedule, $hook );
1049 if ( ! wp_next_scheduled( $hook ) ) {
1050 // Schedule a job to send pending queue items once a minute.
1051 wp_schedule_event( $start_time, $schedule, $hook );
1052 } elseif ( wp_get_schedule( $hook ) !== $schedule ) {
1053 // If the schedule has changed, update the schedule.
1054 wp_clear_scheduled_hook( $hook );
1055 wp_schedule_event( $start_time, $schedule, $hook );
1056 }
1057 }
1058
1059 /**
1060 * Clears Jetpack sync cron jobs.
1061 *
1062 * @access public
1063 * @static
1064 */
1065 public static function clear_sync_cron_jobs() {
1066 wp_clear_scheduled_hook( 'jetpack_sync_cron' );
1067 wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
1068 }
1069
1070 /**
1071 * Initializes Jetpack sync cron jobs.
1072 *
1073 * @access public
1074 * @static
1075 */
1076 public static function init_sync_cron_jobs() {
1077 add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
1078
1079 add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
1080 add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
1081
1082 /**
1083 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
1084 *
1085 * @since 1.6.3
1086 * @since-jetpack 4.3.2
1087 *
1088 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
1089 */
1090 $incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
1091 self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
1092
1093 /**
1094 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
1095 *
1096 * @since 1.6.3
1097 * @since-jetpack 4.3.2
1098 *
1099 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
1100 */
1101 $full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
1102 self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
1103 }
1104
1105 /**
1106 * Perform maintenance when a plugin upgrade occurs.
1107 *
1108 * @access public
1109 * @static
1110 *
1111 * @param string $new_version New version of the plugin.
1112 * @param string $old_version Old version of the plugin.
1113 */
1114 public static function cleanup_on_upgrade( $new_version = '', $old_version = '' ) {
1115 if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
1116 wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
1117 }
1118
1119 $is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
1120 if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
1121 self::clear_sync_cron_jobs();
1122 Settings::update_settings(
1123 array(
1124 'render_filtered_content' => Defaults::$default_render_filtered_content,
1125 )
1126 );
1127 }
1128
1129 Health::on_jetpack_upgraded();
1130 }
1131
1132 /**
1133 * Get syncing status for the given fields.
1134 *
1135 * @access public
1136 * @static
1137 *
1138 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
1139 * @return array An associative array with the status report.
1140 */
1141 public static function get_sync_status( $fields = null ) {
1142 self::initialize_sender();
1143
1144 $sync_module = Modules::get_module( 'full-sync' );
1145 '@phan-var Modules\Full_Sync_Immediately|Modules\Full_Sync $sync_module';
1146 $queue = self::$sender->get_sync_queue();
1147
1148 // _get_cron_array can be false
1149 $cron_timestamps = ( _get_cron_array() ) ? array_keys( _get_cron_array() ) : array();
1150 $next_cron = ( ! empty( $cron_timestamps ) ) ? $cron_timestamps[0] - time() : '';
1151
1152 $checksums = array();
1153 $debug = array();
1154
1155 if ( ! empty( $fields ) ) {
1156 $store = new Replicastore();
1157 $fields_params = array_map( 'trim', explode( ',', $fields ) );
1158
1159 if ( in_array( 'posts_checksum', $fields_params, true ) ) {
1160 $checksums['posts_checksum'] = $store->posts_checksum();
1161 }
1162 if ( in_array( 'comments_checksum', $fields_params, true ) ) {
1163 $checksums['comments_checksum'] = $store->comments_checksum();
1164 }
1165 if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
1166 $checksums['post_meta_checksum'] = $store->post_meta_checksum();
1167 }
1168 if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
1169 $checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
1170 }
1171
1172 if ( in_array( 'debug_details', $fields_params, true ) ) {
1173 $debug = self::get_debug_details();
1174 }
1175 }
1176
1177 $full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
1178
1179 $full_queue = self::$sender->get_full_sync_queue();
1180
1181 $result = array_merge(
1182 $full_sync_status,
1183 $checksums,
1184 $debug,
1185 array(
1186 'cron_size' => count( $cron_timestamps ),
1187 'next_cron' => $next_cron,
1188 'queue_size' => $queue->size(),
1189 'queue_lag' => $queue->lag(),
1190 'queue_next_sync' => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
1191 'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
1192 )
1193 );
1194
1195 // Verify $sync_module is not false.
1196 if ( $sync_module && ! $sync_module instanceof Modules\Full_Sync_Immediately ) {
1197 $result['full_queue_size'] = $full_queue->size();
1198 $result['full_queue_lag'] = $full_queue->lag();
1199 }
1200 return $result;
1201 }
1202
1203 /**
1204 * Reset Sync locks.
1205 *
1206 * @access public
1207 * @static
1208 * @since 1.43.0
1209 *
1210 * @param bool $unlock_queues Whether to unlock Sync queues. Defaults to true.
1211 */
1212 public static function reset_sync_locks( $unlock_queues = true ) {
1213 // Next sync locks.
1214 delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_sync' );
1215 delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_full_sync' );
1216 delete_option( Sender::NEXT_SYNC_TIME_OPTION_NAME . '_full-sync-enqueue' );
1217 // Retry after locks.
1218 delete_option( self::RETRY_AFTER_PREFIX . 'sync' );
1219 delete_option( self::RETRY_AFTER_PREFIX . 'full_sync' );
1220 // Dedicated sync locks.
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
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 );
1229
1230 // Queue locks.
1231 // Note that we are just unlocking the queues here, not reseting them.
1232 if ( $unlock_queues ) {
1233 $sync_queue = new Queue( 'sync' );
1234 $sync_queue->unlock();
1235
1236 $full_sync_queue = new Queue( 'full_sync' );
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;
1293 }
1294 }
1295 }
1296