Completed
Push — fix/resolve-security-vulnerabi... ( 372367...c122d8 )
by Yaroslav
197:31 queued 189:03
created

packages/sync/src/class-actions.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Manager as Jetpack_Connection;
11
use Automattic\Jetpack\Constants;
12
use Automattic\Jetpack\Status;
13
14
/**
15
 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
16
 * when to send, when to perform a full sync, etc.
17
 *
18
 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
19
 */
20
class Actions {
21
	/**
22
	 * A variable to hold a sync sender object.
23
	 *
24
	 * @access public
25
	 * @static
26
	 *
27
	 * @var Automattic\Jetpack\Sync\Sender
28
	 */
29
	public static $sender = null;
30
31
	/**
32
	 * A variable to hold a sync listener object.
33
	 *
34
	 * @access public
35
	 * @static
36
	 *
37
	 * @var Automattic\Jetpack\Sync\Listener
38
	 */
39
	public static $listener = null;
40
41
	/**
42
	 * Name of the sync cron schedule.
43
	 *
44
	 * @access public
45
	 *
46
	 * @var string
47
	 */
48
	const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
49
50
	/**
51
	 * Interval between the last and the next sync cron action.
52
	 *
53
	 * @access public
54
	 *
55
	 * @var int
56
	 */
57
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
58
59
	/**
60
	 * Initialize Sync for cron jobs, set up listeners for WordPress Actions,
61
	 * and set up a shut-down action for sending actions to WordPress.com
62
	 *
63
	 * @access public
64
	 * @static
65
	 */
66
	public static function init() {
67
		// Everything below this point should only happen if we're a valid sync site.
68
		if ( ! self::sync_allowed() ) {
69
			return;
70
		}
71
72
		if ( self::sync_via_cron_allowed() ) {
73
			self::init_sync_cron_jobs();
74
		} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
75
			self::clear_sync_cron_jobs();
76
		}
77
		// When importing via cron, do not sync.
78
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
79
80
		// Sync connected user role changes to WordPress.com.
81
		Users::init();
82
83
		// Publicize filter to prevent publicizing blacklisted post types.
84
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
85
86
		/**
87
		 * Fires on every request before default loading sync listener code.
88
		 * Return false to not load sync listener code that monitors common
89
		 * WP actions to be serialized.
90
		 *
91
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
92
		 * user is logged-in.
93
		 *
94
		 * @since 4.2.0
95
		 *
96
		 * @param bool should we load sync listener code for this request
97
		 */
98
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
99
			self::initialize_listener();
100
		}
101
102
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
103
	}
104
105
	/**
106
	 * Prepares sync to send actions on shutdown for the current request.
107
	 *
108
	 * @access public
109
	 * @static
110
	 */
111
	public static function add_sender_shutdown() {
112
		/**
113
		 * Fires on every request before default loading sync sender code.
114
		 * Return false to not load sync sender code that serializes pending
115
		 * data and sends it to WPCOM for processing.
116
		 *
117
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
118
		 * by users who can manage_options.
119
		 *
120
		 * @since 4.2.0
121
		 *
122
		 * @param bool should we load sync sender code for this request
123
		 */
124
		if ( apply_filters(
125
			'jetpack_sync_sender_should_load',
126
			self::should_initialize_sender()
127
		) ) {
128
			self::initialize_sender();
129
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
130
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
131
		}
132
	}
133
134
	/**
135
	 * Define JETPACK_SYNC_READ_ONLY constant if not defined.
136
	 * This notifies sync to not run in shutdown if it was initialized during init.
137
	 *
138
	 * @access public
139
	 * @static
140
	 */
141
	public static function mark_sync_read_only() {
142
		Constants::set_constant( 'JETPACK_SYNC_READ_ONLY', true );
0 ignored issues
show
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
	}
144
145
	/**
146
	 * Decides if the sender should run on shutdown for this request.
147
	 *
148
	 * @access public
149
	 * @static
150
	 *
151
	 * @return bool
152
	 */
153
	public static function should_initialize_sender() {
154
155
		// Allow for explicit disable of Sync from request param jetpack_sync_read_only.
156
		if ( isset( $_REQUEST['jetpack_sync_read_only'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
157
			self::mark_sync_read_only();
158
			return false;
159
		}
160
161
		if ( Constants::is_true( 'DOING_CRON' ) ) {
162
			return self::sync_via_cron_allowed();
163
		}
164
165
		if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
166
			return true;
167
		}
168
169
		if ( current_user_can( 'manage_options' ) ) {
170
			return true;
171
		}
172
173
		if ( is_admin() ) {
174
			return true;
175
		}
176
177
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
178
			return true;
179
		}
180
181
		if ( Constants::get_constant( 'WP_CLI' ) ) {
182
			return true;
183
		}
184
185
		return false;
186
	}
187
188
	/**
189
	 * Decides if the sender should run on shutdown when actions are queued.
190
	 *
191
	 * @access public
192
	 * @static
193
	 *
194
	 * @return bool
195
	 */
196
	public static function should_initialize_sender_enqueue() {
197
		if ( Constants::is_true( 'DOING_CRON' ) ) {
198
			return self::sync_via_cron_allowed();
199
		}
200
201
		return true;
202
	}
203
204
	/**
205
	 * Decides if sync should run at all during this request.
206
	 *
207
	 * @access public
208
	 * @static
209
	 *
210
	 * @return bool
211
	 */
212
	public static function sync_allowed() {
213
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
214
			return false;
215
		}
216
217
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
218
			return true;
219
		}
220
221
		if ( ! Settings::is_sync_enabled() ) {
222
			return false;
223
		}
224
225
		if ( ( new Status() )->is_offline_mode() ) {
226
			return false;
227
		}
228
229
		if ( ( new Status() )->is_staging_site() ) {
230
			return false;
231
		}
232
233
		$connection = new Jetpack_Connection();
234
		if ( ! $connection->is_active() ) {
235
			if ( ! doing_action( 'jetpack_user_authorized' ) ) {
236
				return false;
237
			}
238
		}
239
240
		return true;
241
	}
242
243
	/**
244
	 * Helper function to get details as to why sync is not allowed, if it is not allowed.
245
	 *
246
	 * @return array
247
	 */
248
	public static function get_debug_details() {
249
		$debug                                  = array();
250
		$debug['debug_details']['sync_allowed'] = self::sync_allowed();
251
		$debug['debug_details']['sync_health']  = Health::get_status();
252
		if ( false === $debug['debug_details']['sync_allowed'] ) {
253
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
254
				$debug['debug_details']['is_wpcom'] = true;
255
			}
256
			if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
257
				$debug['debug_details']['PHPUNIT_JETPACK_TESTSUITE'] = true;
258
			}
259
			if ( ! Settings::is_sync_enabled() ) {
260
				$debug['debug_details']['is_sync_enabled']              = false;
261
				$debug['debug_details']['jetpack_sync_disable']         = Settings::get_setting( 'disable' );
262
				$debug['debug_details']['jetpack_sync_network_disable'] = Settings::get_setting( 'network_disable' );
263
			}
264
			if ( ( new Status() )->is_offline_mode() ) {
265
				$debug['debug_details']['is_offline_mode'] = true;
266
			}
267
			if ( ( new Status() )->is_staging_site() ) {
268
				$debug['debug_details']['is_staging_site'] = true;
269
			}
270
			$connection = new Jetpack_Connection();
271
			if ( ! $connection->is_active() ) {
272
				$debug['debug_details']['active_connection'] = false;
273
			}
274
		}
275
		return $debug;
276
277
	}
278
279
	/**
280
	 * Determines if syncing during a cron job is allowed.
281
	 *
282
	 * @access public
283
	 * @static
284
	 *
285
	 * @return bool|int
286
	 */
287
	public static function sync_via_cron_allowed() {
288
		return ( Settings::get_setting( 'sync_via_cron' ) );
289
	}
290
291
	/**
292
	 * Decides if the given post should be Publicized based on its type.
293
	 *
294
	 * @access public
295
	 * @static
296
	 *
297
	 * @param bool     $should_publicize  Publicize status prior to this filter running.
298
	 * @param \WP_Post $post              The post to test for Publicizability.
299
	 * @return bool
300
	 */
301
	public static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
302
		if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
303
			return false;
304
		}
305
306
		return $should_publicize;
307
	}
308
309
	/**
310
	 * Set an importing flag to `true` in sync settings.
311
	 *
312
	 * @access public
313
	 * @static
314
	 */
315
	public static function set_is_importing_true() {
316
		Settings::set_importing( true );
317
	}
318
319
	/**
320
	 * Sends data to WordPress.com via an XMLRPC request.
321
	 *
322
	 * @access public
323
	 * @static
324
	 *
325
	 * @param object $data                   Data relating to a sync action.
326
	 * @param string $codec_name             The name of the codec that encodes the data.
327
	 * @param float  $sent_timestamp         Current server time so we can compensate for clock differences.
328
	 * @param string $queue_id               The queue the action belongs to, sync or full_sync.
329
	 * @param float  $checkout_duration      Time spent retrieving queue items from the DB.
330
	 * @param float  $preprocess_duration    Time spent converting queue items into data to send.
331
	 * @param int    $queue_size             The size of the sync queue at the time of processing.
332
	 * @param string $buffer_id              The ID of the Queue buffer checked out for processing.
333
	 * @return Jetpack_Error|mixed|WP_Error  The result of the sending request.
334
	 */
335
	public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration, $queue_size = null, $buffer_id = null ) {
336
337
		$query_args = array(
338
			'sync'       => '1',             // Add an extra parameter to the URL so we can tell it's a sync action.
339
			'codec'      => $codec_name,
340
			'timestamp'  => $sent_timestamp,
341
			'queue'      => $queue_id,
342
			'home'       => Functions::home_url(),  // Send home url option to check for Identity Crisis server-side.
343
			'siteurl'    => Functions::site_url(),  // Send siteurl option to check for Identity Crisis server-side.
344
			'cd'         => sprintf( '%.4f', $checkout_duration ),
345
			'pd'         => sprintf( '%.4f', $preprocess_duration ),
346
			'queue_size' => $queue_size,
347
			'buffer_id'  => $buffer_id,
348
		);
349
350
		// Has the site opted in to IDC mitigation?
351
		if ( \Jetpack::sync_idc_optin() ) {
352
			$query_args['idc'] = true;
353
		}
354
355
		if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
356
			$query_args['migrate_for_idc'] = true;
357
		}
358
359
		$query_args['timeout'] = Settings::is_doing_cron() ? 30 : 15;
360
361
		/**
362
		 * Filters query parameters appended to the Sync request URL sent to WordPress.com.
363
		 *
364
		 * @since 4.7.0
365
		 *
366
		 * @param array $query_args associative array of query parameters.
367
		 */
368
		$query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
369
370
		$connection = new Jetpack_Connection();
371
		$url        = add_query_arg( $query_args, $connection->xmlrpc_api_url() );
372
373
		// If we're currently updating to Jetpack 7.7, the IXR client may be missing briefly
374
		// because since 7.7 it's being autoloaded with Composer.
375
		if ( ! class_exists( '\\Jetpack_IXR_Client' ) ) {
376
			return new \WP_Error(
377
				'ixr_client_missing',
378
				esc_html__( 'Sync has been aborted because the IXR client is missing.', 'jetpack' )
379
			);
380
		}
381
382
		$rpc = new \Jetpack_IXR_Client(
383
			array(
384
				'url'     => $url,
385
				'timeout' => $query_args['timeout'],
386
			)
387
		);
388
389
		$result = $rpc->query( 'jetpack.syncActions', $data );
390
391
		if ( ! $result ) {
392
			return $rpc->get_jetpack_error();
393
		}
394
395
		$response = $rpc->getResponse();
396
397
		// Check if WordPress.com IDC mitigation blocked the sync request.
398
		if ( is_array( $response ) && isset( $response['error_code'] ) ) {
399
			$error_code              = $response['error_code'];
400
			$allowed_idc_error_codes = array(
401
				'jetpack_url_mismatch',
402
				'jetpack_home_url_mismatch',
403
				'jetpack_site_url_mismatch',
404
			);
405
406
			if ( in_array( $error_code, $allowed_idc_error_codes, true ) ) {
407
				\Jetpack_Options::update_option(
408
					'sync_error_idc',
409
					\Jetpack::get_sync_error_idc_option( $response )
410
				);
411
			}
412
413
			return new \WP_Error(
414
				'sync_error_idc',
415
				esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
416
			);
417
		}
418
419
		return $response;
420
	}
421
422
	/**
423
	 * Kicks off the initial sync.
424
	 *
425
	 * @access public
426
	 * @static
427
	 *
428
	 * @return bool|null False if sync is not allowed.
429
	 */
430
	public static function do_initial_sync() {
431
		// Lets not sync if we are not suppose to.
432
		if ( ! self::sync_allowed() ) {
433
			return false;
434
		}
435
436
		// Don't start new sync if a full sync is in process.
437
		$full_sync_module = Modules::get_module( 'full-sync' );
438
		if ( $full_sync_module && $full_sync_module->is_started() && ! $full_sync_module->is_finished() ) {
439
			return false;
440
		}
441
442
		$initial_sync_config = array(
443
			'options'         => true,
444
			'functions'       => true,
445
			'constants'       => true,
446
			'users'           => array( get_current_user_id() ),
447
			'network_options' => true,
448
		);
449
450
		self::do_full_sync( $initial_sync_config );
451
	}
452
453
	/**
454
	 * Kicks off a full sync.
455
	 *
456
	 * @access public
457
	 * @static
458
	 *
459
	 * @param array $modules  The sync modules should be included in this full sync. All will be included if null.
460
	 * @return bool           True if full sync was successfully started.
461
	 */
462
	public static function do_full_sync( $modules = null ) {
463
		if ( ! self::sync_allowed() ) {
464
			return false;
465
		}
466
467
		$full_sync_module = Modules::get_module( 'full-sync' );
468
469
		if ( ! $full_sync_module ) {
470
			return false;
471
		}
472
473
		self::initialize_listener();
474
475
		$full_sync_module->start( $modules );
476
477
		return true;
478
	}
479
480
	/**
481
	 * Adds a cron schedule for regular syncing via cron, unless the schedule already exists.
482
	 *
483
	 * @access public
484
	 * @static
485
	 *
486
	 * @param array $schedules  The list of WordPress cron schedules prior to this filter.
487
	 * @return array            A list of WordPress cron schedules with the Jetpack sync interval added.
488
	 */
489
	public static function jetpack_cron_schedule( $schedules ) {
490
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
491
			$minutes = intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
492
			$display = ( 1 === $minutes ) ?
493
				__( 'Every minute', 'jetpack' ) :
494
				/* translators: %d is an integer indicating the number of minutes. */
495
				sprintf( __( 'Every %d minutes', 'jetpack' ), $minutes );
496
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
497
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
498
				'display'  => $display,
499
			);
500
		}
501
		return $schedules;
502
	}
503
504
	/**
505
	 * Starts an incremental sync via cron.
506
	 *
507
	 * @access public
508
	 * @static
509
	 */
510
	public static function do_cron_sync() {
511
		self::do_cron_sync_by_type( 'sync' );
512
	}
513
514
	/**
515
	 * Starts a full sync via cron.
516
	 *
517
	 * @access public
518
	 * @static
519
	 */
520
	public static function do_cron_full_sync() {
521
		self::do_cron_sync_by_type( 'full_sync' );
522
	}
523
524
	/**
525
	 * Try to send actions until we run out of things to send,
526
	 * or have to wait more than 15s before sending again,
527
	 * or we hit a lock or some other sending issue
528
	 *
529
	 * @access public
530
	 * @static
531
	 *
532
	 * @param string $type Sync type. Can be `sync` or `full_sync`.
533
	 */
534
	public static function do_cron_sync_by_type( $type ) {
535
		if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
536
			return;
537
		}
538
539
		self::initialize_sender();
540
541
		$time_limit = Settings::get_setting( 'cron_sync_time_limit' );
542
		$start_time = time();
543
		$executions = 0;
544
545
		do {
546
			$next_sync_time = self::$sender->get_next_sync_time( $type );
547
548
			if ( $next_sync_time ) {
549
				$delay = $next_sync_time - time() + 1;
550
				if ( $delay > 15 ) {
551
					break;
552
				} elseif ( $delay > 0 ) {
553
					sleep( $delay );
554
				}
555
			}
556
557
			// Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
558
			// For more context see p1HpG7-9pe-p2.
559
			if ( 'full_sync' === $type && $executions >= 1 ) {
560
				break;
561
			}
562
563
			$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
564
565
			// # of send actions performed.
566
			$executions ++;
567
568
		} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
569
570
		return $executions;
571
	}
572
573
	/**
574
	 * Initialize the sync listener.
575
	 *
576
	 * @access public
577
	 * @static
578
	 */
579
	public static function initialize_listener() {
580
		self::$listener = Listener::get_instance();
581
	}
582
583
	/**
584
	 * Initializes the sync sender.
585
	 *
586
	 * @access public
587
	 * @static
588
	 */
589
	public static function initialize_sender() {
590
		self::$sender = Sender::get_instance();
591
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 8 );
592
	}
593
594
	/**
595
	 * Initializes sync for WooCommerce.
596
	 *
597
	 * @access public
598
	 * @static
599
	 */
600
	public static function initialize_woocommerce() {
601
		if ( false === class_exists( 'WooCommerce' ) ) {
602
			return;
603
		}
604
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
605
	}
606
607
	/**
608
	 * Adds Woo's sync modules to existing modules for sending.
609
	 *
610
	 * @access public
611
	 * @static
612
	 *
613
	 * @param array $sync_modules The list of sync modules declared prior to this filter.
614
	 * @return array A list of sync modules that now includes Woo's modules.
615
	 */
616
	public static function add_woocommerce_sync_module( $sync_modules ) {
617
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
618
		return $sync_modules;
619
	}
620
621
	/**
622
	 * Initializes sync for WP Super Cache.
623
	 *
624
	 * @access public
625
	 * @static
626
	 */
627
	public static function initialize_wp_super_cache() {
628
		if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
629
			return;
630
		}
631
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
632
	}
633
634
	/**
635
	 * Adds WP Super Cache's sync modules to existing modules for sending.
636
	 *
637
	 * @access public
638
	 * @static
639
	 *
640
	 * @param array $sync_modules The list of sync modules declared prior to this filer.
641
	 * @return array A list of sync modules that now includes WP Super Cache's modules.
642
	 */
643
	public static function add_wp_super_cache_sync_module( $sync_modules ) {
644
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
645
		return $sync_modules;
646
	}
647
648
	/**
649
	 * Sanitizes the name of sync's cron schedule.
650
	 *
651
	 * @access public
652
	 * @static
653
	 *
654
	 * @param string $schedule The name of a WordPress cron schedule.
655
	 * @return string The sanitized name of sync's cron schedule.
656
	 */
657
	public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
658
		$schedule  = sanitize_key( $schedule );
659
		$schedules = wp_get_schedules();
660
661
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
662
		if ( isset( $schedules[ $schedule ] ) ) {
663
			return $schedule;
664
		}
665
666
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
667
	}
668
669
	/**
670
	 * Allows offsetting of start times for sync cron jobs.
671
	 *
672
	 * @access public
673
	 * @static
674
	 *
675
	 * @param string $schedule The name of a cron schedule.
676
	 * @param string $hook     The hook that this method is responding to.
677
	 * @return int The offset for the sync cron schedule.
678
	 */
679
	public static function get_start_time_offset( $schedule = '', $hook = '' ) {
680
		$start_time_offset = is_multisite()
681
			? wp_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
682
			: 0;
683
684
		/**
685
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
686
		 * cron jobs across multiple sites in a network.
687
		 *
688
		 * @since 4.5.0
689
		 *
690
		 * @param int    $start_time_offset
691
		 * @param string $hook
692
		 * @param string $schedule
693
		 */
694
		return intval(
695
			apply_filters(
696
				'jetpack_sync_cron_start_time_offset',
697
				$start_time_offset,
698
				$hook,
699
				$schedule
700
			)
701
		);
702
	}
703
704
	/**
705
	 * Decides if a sync cron should be scheduled.
706
	 *
707
	 * @access public
708
	 * @static
709
	 *
710
	 * @param string $schedule The name of a cron schedule.
711
	 * @param string $hook     The hook that this method is responding to.
712
	 */
713
	public static function maybe_schedule_sync_cron( $schedule, $hook ) {
714
		if ( ! $hook ) {
715
			return;
716
		}
717
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
718
719
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
720
		if ( ! wp_next_scheduled( $hook ) ) {
721
			// Schedule a job to send pending queue items once a minute.
722
			wp_schedule_event( $start_time, $schedule, $hook );
723
		} elseif ( wp_get_schedule( $hook ) !== $schedule ) {
724
			// If the schedule has changed, update the schedule.
725
			wp_clear_scheduled_hook( $hook );
726
			wp_schedule_event( $start_time, $schedule, $hook );
727
		}
728
	}
729
730
	/**
731
	 * Clears Jetpack sync cron jobs.
732
	 *
733
	 * @access public
734
	 * @static
735
	 */
736
	public static function clear_sync_cron_jobs() {
737
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
738
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
739
	}
740
741
	/**
742
	 * Initializes Jetpack sync cron jobs.
743
	 *
744
	 * @access public
745
	 * @static
746
	 */
747
	public static function init_sync_cron_jobs() {
748
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
749
750
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
751
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
752
753
		/**
754
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
755
		 *
756
		 * @since 4.3.2
757
		 *
758
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
759
		 */
760
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
761
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
762
763
		/**
764
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
765
		 *
766
		 * @since 4.3.2
767
		 *
768
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
769
		 */
770
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
771
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
772
	}
773
774
	/**
775
	 * Perform maintenance when a plugin upgrade occurs.
776
	 *
777
	 * @access public
778
	 * @static
779
	 *
780
	 * @param string $new_version New version of the plugin.
781
	 * @param string $old_version Old version of the plugin.
782
	 */
783
	public static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
784
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
785
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
786
		}
787
788
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
789
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
790
			self::clear_sync_cron_jobs();
791
			Settings::update_settings(
792
				array(
793
					'render_filtered_content' => Defaults::$default_render_filtered_content,
794
				)
795
			);
796
		}
797
798
		Health::on_jetpack_upgraded();
799
	}
800
801
	/**
802
	 * Get syncing status for the given fields.
803
	 *
804
	 * @access public
805
	 * @static
806
	 *
807
	 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
808
	 * @return array An associative array with the status report.
809
	 */
810
	public static function get_sync_status( $fields = null ) {
811
		self::initialize_sender();
812
813
		$sync_module = Modules::get_module( 'full-sync' );
814
		$queue       = self::$sender->get_sync_queue();
815
816
		// _get_cron_array can be false
817
		$cron_timestamps = ( _get_cron_array() ) ? array_keys( _get_cron_array() ) : array();
818
		$next_cron       = ( ! empty( $cron_timestamps ) ) ? $cron_timestamps[0] - time() : '';
819
820
		$checksums = array();
821
		$debug     = array();
822
823
		if ( ! empty( $fields ) ) {
824
			$store         = new Replicastore();
825
			$fields_params = array_map( 'trim', explode( ',', $fields ) );
826
827
			if ( in_array( 'posts_checksum', $fields_params, true ) ) {
828
				$checksums['posts_checksum'] = $store->posts_checksum();
829
			}
830
			if ( in_array( 'comments_checksum', $fields_params, true ) ) {
831
				$checksums['comments_checksum'] = $store->comments_checksum();
832
			}
833
			if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
834
				$checksums['post_meta_checksum'] = $store->post_meta_checksum();
835
			}
836
			if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
837
				$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
838
			}
839
840
			if ( in_array( 'debug_details', $fields_params, true ) ) {
841
				$debug = self::get_debug_details();
842
			}
843
		}
844
845
		$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
846
847
		$full_queue = self::$sender->get_full_sync_queue();
848
849
		$result = array_merge(
850
			$full_sync_status,
851
			$checksums,
852
			$debug,
853
			array(
854
				'cron_size'            => count( $cron_timestamps ),
855
				'next_cron'            => $next_cron,
856
				'queue_size'           => $queue->size(),
857
				'queue_lag'            => $queue->lag(),
858
				'queue_next_sync'      => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
859
				'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
860
			)
861
		);
862
863
		// Verify $sync_module is not false.
864
		if ( ( $sync_module ) && false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
865
			$result['full_queue_size'] = $full_queue->size();
866
			$result['full_queue_lag']  = $full_queue->lag();
867
		}
868
		return $result;
869
	}
870
}
871