Completed
Push — update/setup-wizard-free-creat... ( a13f63...9ba60b )
by Jeremy
50:18 queued 42:19
created

Actions::get_debug_details()   B

Complexity

Conditions 9
Paths 65

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 65
nop 0
dl 0
loc 30
rs 8.0555
c 0
b 0
f 0
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
use Automattic\Jetpack\Sync\Health;
14
use Automattic\Jetpack\Sync\Modules;
15
16
/**
17
 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
18
 * when to send, when to perform a full sync, etc.
19
 *
20
 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
21
 */
22
class Actions {
23
	/**
24
	 * A variable to hold a sync sender object.
25
	 *
26
	 * @access public
27
	 * @static
28
	 *
29
	 * @var Automattic\Jetpack\Sync\Sender
30
	 */
31
	public static $sender = null;
32
33
	/**
34
	 * A variable to hold a sync listener object.
35
	 *
36
	 * @access public
37
	 * @static
38
	 *
39
	 * @var Automattic\Jetpack\Sync\Listener
40
	 */
41
	public static $listener = null;
42
43
	/**
44
	 * Name of the sync cron schedule.
45
	 *
46
	 * @access public
47
	 *
48
	 * @var string
49
	 */
50
	const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
51
52
	/**
53
	 * Interval between the last and the next sync cron action.
54
	 *
55
	 * @access public
56
	 *
57
	 * @var int
58
	 */
59
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
60
61
	/**
62
	 * Initialize Sync for cron jobs, set up listeners for WordPress Actions,
63
	 * and set up a shut-down action for sending actions to WordPress.com
64
	 *
65
	 * @access public
66
	 * @static
67
	 */
68
	public static function init() {
69
		// Everything below this point should only happen if we're a valid sync site.
70
		if ( ! self::sync_allowed() ) {
71
			return;
72
		}
73
74
		if ( self::sync_via_cron_allowed() ) {
75
			self::init_sync_cron_jobs();
76
		} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
77
			self::clear_sync_cron_jobs();
78
		}
79
		// When importing via cron, do not sync.
80
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
81
82
		// Sync connected user role changes to WordPress.com.
83
		Users::init();
84
85
		// Publicize filter to prevent publicizing blacklisted post types.
86
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
87
88
		/**
89
		 * Fires on every request before default loading sync listener code.
90
		 * Return false to not load sync listener code that monitors common
91
		 * WP actions to be serialized.
92
		 *
93
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
94
		 * user is logged-in.
95
		 *
96
		 * @since 4.2.0
97
		 *
98
		 * @param bool should we load sync listener code for this request
99
		 */
100
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
101
			self::initialize_listener();
102
		}
103
104
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
105
	}
106
107
	/**
108
	 * Prepares sync to send actions on shutdown for the current request.
109
	 *
110
	 * @access public
111
	 * @static
112
	 */
113
	public static function add_sender_shutdown() {
114
		/**
115
		 * Fires on every request before default loading sync sender code.
116
		 * Return false to not load sync sender code that serializes pending
117
		 * data and sends it to WPCOM for processing.
118
		 *
119
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
120
		 * by users who can manage_options.
121
		 *
122
		 * @since 4.2.0
123
		 *
124
		 * @param bool should we load sync sender code for this request
125
		 */
126
		if ( apply_filters(
127
			'jetpack_sync_sender_should_load',
128
			self::should_initialize_sender()
129
		) ) {
130
			self::initialize_sender();
131
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
132
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
133
		}
134
	}
135
136
	/**
137
	 * Decides if the sender should run on shutdown for this request.
138
	 *
139
	 * @access public
140
	 * @static
141
	 *
142
	 * @return bool
143
	 */
144
	public static function should_initialize_sender() {
145
		if ( Constants::is_true( 'DOING_CRON' ) ) {
146
			return self::sync_via_cron_allowed();
147
		}
148
149
		if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
150
			return true;
151
		}
152
153
		if ( current_user_can( 'manage_options' ) ) {
154
			return true;
155
		}
156
157
		if ( is_admin() ) {
158
			return true;
159
		}
160
161
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
162
			return true;
163
		}
164
165
		if ( Constants::get_constant( 'WP_CLI' ) ) {
166
			return true;
167
		}
168
169
		return false;
170
	}
171
172
	/**
173
	 * Decides if the sender should run on shutdown when actions are queued.
174
	 *
175
	 * @access public
176
	 * @static
177
	 *
178
	 * @return bool
179
	 */
180
	public static function should_initialize_sender_enqueue() {
181
		if ( Constants::is_true( 'DOING_CRON' ) ) {
182
			return self::sync_via_cron_allowed();
183
		}
184
185
		return true;
186
	}
187
188
	/**
189
	 * Decides if sync should run at all during this request.
190
	 *
191
	 * @access public
192
	 * @static
193
	 *
194
	 * @return bool
195
	 */
196
	public static function sync_allowed() {
197
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
198
			return false;
199
		}
200
201
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
202
			return true;
203
		}
204
205
		if ( ! Settings::is_sync_enabled() ) {
206
			return false;
207
		}
208
209
		if ( ( new Status() )->is_offline_mode() ) {
210
			return false;
211
		}
212
213
		if ( ( new Status() )->is_staging_site() ) {
214
			return false;
215
		}
216
217
		$connection = new Jetpack_Connection();
218
		if ( ! $connection->is_active() ) {
219
			if ( ! doing_action( 'jetpack_user_authorized' ) ) {
220
				return false;
221
			}
222
		}
223
224
		return true;
225
	}
226
227
	/**
228
	 * Helper function to get details as to why sync is not allowed, if it is not allowed.
229
	 *
230
	 * @return array
231
	 */
232
	public static function get_debug_details() {
233
		$debug                                  = array();
234
		$debug['debug_details']['sync_allowed'] = self::sync_allowed();
235
		$debug['debug_details']['sync_health']  = Health::get_status();
236
		if ( false === $debug['debug_details']['sync_allowed'] ) {
237
			if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
238
				$debug['debug_details']['is_wpcom'] = true;
239
			}
240
			if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
241
				$debug['debug_details']['PHPUNIT_JETPACK_TESTSUITE'] = true;
242
			}
243
			if ( ! Settings::is_sync_enabled() ) {
244
				$debug['debug_details']['is_sync_enabled']              = false;
245
				$debug['debug_details']['jetpack_sync_disable']         = Settings::get_setting( 'disable' );
246
				$debug['debug_details']['jetpack_sync_network_disable'] = Settings::get_setting( 'network_disable' );
247
			}
248
			if ( ( new Status() )->is_offline_mode() ) {
249
				$debug['debug_details']['is_offline_mode'] = true;
250
			}
251
			if ( ( new Status() )->is_staging_site() ) {
252
				$debug['debug_details']['is_staging_site'] = true;
253
			}
254
			$connection = new Jetpack_Connection();
255
			if ( ! $connection->is_active() ) {
256
				$debug['debug_details']['active_connection'] = false;
257
			}
258
		}
259
		return $debug;
260
261
	}
262
263
	/**
264
	 * Determines if syncing during a cron job is allowed.
265
	 *
266
	 * @access public
267
	 * @static
268
	 *
269
	 * @return bool|int
270
	 */
271
	public static function sync_via_cron_allowed() {
272
		return ( Settings::get_setting( 'sync_via_cron' ) );
273
	}
274
275
	/**
276
	 * Decides if the given post should be Publicized based on its type.
277
	 *
278
	 * @access public
279
	 * @static
280
	 *
281
	 * @param bool     $should_publicize  Publicize status prior to this filter running.
282
	 * @param \WP_Post $post              The post to test for Publicizability.
283
	 * @return bool
284
	 */
285
	public static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
286
		if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
287
			return false;
288
		}
289
290
		return $should_publicize;
291
	}
292
293
	/**
294
	 * Set an importing flag to `true` in sync settings.
295
	 *
296
	 * @access public
297
	 * @static
298
	 */
299
	public static function set_is_importing_true() {
300
		Settings::set_importing( true );
301
	}
302
303
	/**
304
	 * Sends data to WordPress.com via an XMLRPC request.
305
	 *
306
	 * @access public
307
	 * @static
308
	 *
309
	 * @param object $data                   Data relating to a sync action.
310
	 * @param string $codec_name             The name of the codec that encodes the data.
311
	 * @param float  $sent_timestamp         Current server time so we can compensate for clock differences.
312
	 * @param string $queue_id               The queue the action belongs to, sync or full_sync.
313
	 * @param float  $checkout_duration      Time spent retrieving queue items from the DB.
314
	 * @param float  $preprocess_duration    Time spent converting queue items into data to send.
315
	 * @param int    $queue_size             The size of the sync queue at the time of processing.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $queue_size not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
316
	 * @param string $buffer_id              The ID of the Queue buffer checked out for processing.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $buffer_id not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
317
	 * @return Jetpack_Error|mixed|WP_Error  The result of the sending request.
318
	 */
319
	public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration, $queue_size = null, $buffer_id = null ) {
320
321
		$query_args = array(
322
			'sync'       => '1',             // Add an extra parameter to the URL so we can tell it's a sync action.
323
			'codec'      => $codec_name,
324
			'timestamp'  => $sent_timestamp,
325
			'queue'      => $queue_id,
326
			'home'       => Functions::home_url(),  // Send home url option to check for Identity Crisis server-side.
327
			'siteurl'    => Functions::site_url(),  // Send siteurl option to check for Identity Crisis server-side.
328
			'cd'         => sprintf( '%.4f', $checkout_duration ),
329
			'pd'         => sprintf( '%.4f', $preprocess_duration ),
330
			'queue_size' => $queue_size,
331
			'buffer_id'  => $buffer_id,
332
		);
333
334
		// Has the site opted in to IDC mitigation?
335
		if ( \Jetpack::sync_idc_optin() ) {
336
			$query_args['idc'] = true;
337
		}
338
339
		if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
340
			$query_args['migrate_for_idc'] = true;
341
		}
342
343
		$query_args['timeout'] = Settings::is_doing_cron() ? 30 : 15;
344
345
		/**
346
		 * Filters query parameters appended to the Sync request URL sent to WordPress.com.
347
		 *
348
		 * @since 4.7.0
349
		 *
350
		 * @param array $query_args associative array of query parameters.
351
		 */
352
		$query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
353
354
		$connection = new Jetpack_Connection();
355
		$url        = add_query_arg( $query_args, $connection->xmlrpc_api_url() );
356
357
		// If we're currently updating to Jetpack 7.7, the IXR client may be missing briefly
358
		// because since 7.7 it's being autoloaded with Composer.
359
		if ( ! class_exists( '\\Jetpack_IXR_Client' ) ) {
360
			return new \WP_Error(
361
				'ixr_client_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'ixr_client_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
362
				esc_html__( 'Sync has been aborted because the IXR client is missing.', 'jetpack' )
363
			);
364
		}
365
366
		$rpc = new \Jetpack_IXR_Client(
367
			array(
368
				'url'     => $url,
369
				'timeout' => $query_args['timeout'],
370
			)
371
		);
372
373
		$result = $rpc->query( 'jetpack.syncActions', $data );
374
375
		if ( ! $result ) {
376
			return $rpc->get_jetpack_error();
377
		}
378
379
		$response = $rpc->getResponse();
380
381
		// Check if WordPress.com IDC mitigation blocked the sync request.
382
		if ( is_array( $response ) && isset( $response['error_code'] ) ) {
383
			$error_code              = $response['error_code'];
384
			$allowed_idc_error_codes = array(
385
				'jetpack_url_mismatch',
386
				'jetpack_home_url_mismatch',
387
				'jetpack_site_url_mismatch',
388
			);
389
390
			if ( in_array( $error_code, $allowed_idc_error_codes, true ) ) {
391
				\Jetpack_Options::update_option(
392
					'sync_error_idc',
393
					\Jetpack::get_sync_error_idc_option( $response )
394
				);
395
			}
396
397
			return new \WP_Error(
398
				'sync_error_idc',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'sync_error_idc'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
399
				esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
400
			);
401
		}
402
403
		return $response;
404
	}
405
406
	/**
407
	 * Kicks off the initial sync.
408
	 *
409
	 * @access public
410
	 * @static
411
	 *
412
	 * @return bool|null False if sync is not allowed.
413
	 */
414
	public static function do_initial_sync() {
415
		// Lets not sync if we are not suppose to.
416
		if ( ! self::sync_allowed() ) {
417
			return false;
418
		}
419
420
		// Don't start new sync if a full sync is in process.
421
		$full_sync_module = Modules::get_module( 'full-sync' );
422
		if ( $full_sync_module && $full_sync_module->is_started() && ! $full_sync_module->is_finished() ) {
423
			return false;
424
		}
425
426
		$initial_sync_config = array(
427
			'options'         => true,
428
			'functions'       => true,
429
			'constants'       => true,
430
			'users'           => array( get_current_user_id() ),
431
			'network_options' => true,
432
		);
433
434
		self::do_full_sync( $initial_sync_config );
435
	}
436
437
	/**
438
	 * Kicks off a full sync.
439
	 *
440
	 * @access public
441
	 * @static
442
	 *
443
	 * @param array $modules  The sync modules should be included in this full sync. All will be included if null.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $modules not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
444
	 * @return bool           True if full sync was successfully started.
445
	 */
446
	public static function do_full_sync( $modules = null ) {
447
		if ( ! self::sync_allowed() ) {
448
			return false;
449
		}
450
451
		$full_sync_module = Modules::get_module( 'full-sync' );
452
453
		if ( ! $full_sync_module ) {
454
			return false;
455
		}
456
457
		self::initialize_listener();
458
459
		$full_sync_module->start( $modules );
460
461
		return true;
462
	}
463
464
	/**
465
	 * Adds a cron schedule for regular syncing via cron, unless the schedule already exists.
466
	 *
467
	 * @access public
468
	 * @static
469
	 *
470
	 * @param array $schedules  The list of WordPress cron schedules prior to this filter.
471
	 * @return array            A list of WordPress cron schedules with the Jetpack sync interval added.
472
	 */
473
	public static function jetpack_cron_schedule( $schedules ) {
474
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
475
			$minutes = intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
476
			$display = ( 1 === $minutes ) ?
477
				__( 'Every minute', 'jetpack' ) :
478
				/* translators: %d is an integer indicating the number of minutes. */
479
				sprintf( __( 'Every %d minutes', 'jetpack' ), $minutes );
480
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
481
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
482
				'display'  => $display,
483
			);
484
		}
485
		return $schedules;
486
	}
487
488
	/**
489
	 * Starts an incremental sync via cron.
490
	 *
491
	 * @access public
492
	 * @static
493
	 */
494
	public static function do_cron_sync() {
495
		self::do_cron_sync_by_type( 'sync' );
496
	}
497
498
	/**
499
	 * Starts a full sync via cron.
500
	 *
501
	 * @access public
502
	 * @static
503
	 */
504
	public static function do_cron_full_sync() {
505
		self::do_cron_sync_by_type( 'full_sync' );
506
	}
507
508
	/**
509
	 * Try to send actions until we run out of things to send,
510
	 * or have to wait more than 15s before sending again,
511
	 * or we hit a lock or some other sending issue
512
	 *
513
	 * @access public
514
	 * @static
515
	 *
516
	 * @param string $type Sync type. Can be `sync` or `full_sync`.
517
	 */
518
	public static function do_cron_sync_by_type( $type ) {
519
		if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
520
			return;
521
		}
522
523
		self::initialize_sender();
524
525
		$time_limit = Settings::get_setting( 'cron_sync_time_limit' );
526
		$start_time = time();
527
		$executions = 0;
528
529
		do {
530
			$next_sync_time = self::$sender->get_next_sync_time( $type );
531
532
			if ( $next_sync_time ) {
533
				$delay = $next_sync_time - time() + 1;
534
				if ( $delay > 15 ) {
535
					break;
536
				} elseif ( $delay > 0 ) {
537
					sleep( $delay );
538
				}
539
			}
540
541
			// Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
542
			// For more context see p1HpG7-9pe-p2.
543
			if ( 'full_sync' === $type && $executions >= 1 ) {
544
				break;
545
			}
546
547
			$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
548
549
			// # of send actions performed.
550
			$executions ++;
551
552
		} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
553
554
		return $executions;
555
	}
556
557
	/**
558
	 * Initialize the sync listener.
559
	 *
560
	 * @access public
561
	 * @static
562
	 */
563
	public static function initialize_listener() {
564
		self::$listener = Listener::get_instance();
0 ignored issues
show
Documentation Bug introduced by
It seems like \Automattic\Jetpack\Sync\Listener::get_instance() of type object<Automattic\Jetpack\Sync\Listener> is incompatible with the declared type object<Automattic\Jetpac...\Jetpack\Sync\Listener> of property $listener.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
565
	}
566
567
	/**
568
	 * Initializes the sync sender.
569
	 *
570
	 * @access public
571
	 * @static
572
	 */
573
	public static function initialize_sender() {
574
		self::$sender = Sender::get_instance();
0 ignored issues
show
Documentation Bug introduced by
It seems like \Automattic\Jetpack\Sync\Sender::get_instance() of type object<Automattic\Jetpack\Sync\Sender> is incompatible with the declared type object<Automattic\Jetpac...ic\Jetpack\Sync\Sender> of property $sender.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
575
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 8 );
576
	}
577
578
	/**
579
	 * Initializes sync for WooCommerce.
580
	 *
581
	 * @access public
582
	 * @static
583
	 */
584
	public static function initialize_woocommerce() {
585
		if ( false === class_exists( 'WooCommerce' ) ) {
586
			return;
587
		}
588
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
589
	}
590
591
	/**
592
	 * Adds Woo's sync modules to existing modules for sending.
593
	 *
594
	 * @access public
595
	 * @static
596
	 *
597
	 * @param array $sync_modules The list of sync modules declared prior to this filter.
598
	 * @return array A list of sync modules that now includes Woo's modules.
599
	 */
600
	public static function add_woocommerce_sync_module( $sync_modules ) {
601
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
602
		return $sync_modules;
603
	}
604
605
	/**
606
	 * Initializes sync for WP Super Cache.
607
	 *
608
	 * @access public
609
	 * @static
610
	 */
611
	public static function initialize_wp_super_cache() {
612
		if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
613
			return;
614
		}
615
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
616
	}
617
618
	/**
619
	 * Adds WP Super Cache's sync modules to existing modules for sending.
620
	 *
621
	 * @access public
622
	 * @static
623
	 *
624
	 * @param array $sync_modules The list of sync modules declared prior to this filer.
625
	 * @return array A list of sync modules that now includes WP Super Cache's modules.
626
	 */
627
	public static function add_wp_super_cache_sync_module( $sync_modules ) {
628
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
629
		return $sync_modules;
630
	}
631
632
	/**
633
	 * Sanitizes the name of sync's cron schedule.
634
	 *
635
	 * @access public
636
	 * @static
637
	 *
638
	 * @param string $schedule The name of a WordPress cron schedule.
639
	 * @return string The sanitized name of sync's cron schedule.
640
	 */
641
	public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
642
		$schedule  = sanitize_key( $schedule );
643
		$schedules = wp_get_schedules();
644
645
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
646
		if ( isset( $schedules[ $schedule ] ) ) {
647
			return $schedule;
648
		}
649
650
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
651
	}
652
653
	/**
654
	 * Allows offsetting of start times for sync cron jobs.
655
	 *
656
	 * @access public
657
	 * @static
658
	 *
659
	 * @param string $schedule The name of a cron schedule.
660
	 * @param string $hook     The hook that this method is responding to.
661
	 * @return int The offset for the sync cron schedule.
662
	 */
663
	public static function get_start_time_offset( $schedule = '', $hook = '' ) {
664
		$start_time_offset = is_multisite()
665
			? wp_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
666
			: 0;
667
668
		/**
669
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
670
		 * cron jobs across multiple sites in a network.
671
		 *
672
		 * @since 4.5.0
673
		 *
674
		 * @param int    $start_time_offset
675
		 * @param string $hook
676
		 * @param string $schedule
677
		 */
678
		return intval(
679
			apply_filters(
680
				'jetpack_sync_cron_start_time_offset',
681
				$start_time_offset,
682
				$hook,
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $hook.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
683
				$schedule
684
			)
685
		);
686
	}
687
688
	/**
689
	 * Decides if a sync cron should be scheduled.
690
	 *
691
	 * @access public
692
	 * @static
693
	 *
694
	 * @param string $schedule The name of a cron schedule.
695
	 * @param string $hook     The hook that this method is responding to.
696
	 */
697
	public static function maybe_schedule_sync_cron( $schedule, $hook ) {
698
		if ( ! $hook ) {
699
			return;
700
		}
701
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
702
703
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
704
		if ( ! wp_next_scheduled( $hook ) ) {
705
			// Schedule a job to send pending queue items once a minute.
706
			wp_schedule_event( $start_time, $schedule, $hook );
707
		} elseif ( wp_get_schedule( $hook ) !== $schedule ) {
708
			// If the schedule has changed, update the schedule.
709
			wp_clear_scheduled_hook( $hook );
710
			wp_schedule_event( $start_time, $schedule, $hook );
711
		}
712
	}
713
714
	/**
715
	 * Clears Jetpack sync cron jobs.
716
	 *
717
	 * @access public
718
	 * @static
719
	 */
720
	public static function clear_sync_cron_jobs() {
721
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
722
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
723
	}
724
725
	/**
726
	 * Initializes Jetpack sync cron jobs.
727
	 *
728
	 * @access public
729
	 * @static
730
	 */
731
	public static function init_sync_cron_jobs() {
732
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
733
734
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
735
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
736
737
		/**
738
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
739
		 *
740
		 * @since 4.3.2
741
		 *
742
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
743
		 */
744
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
745
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
746
747
		/**
748
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
749
		 *
750
		 * @since 4.3.2
751
		 *
752
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
753
		 */
754
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
755
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
756
	}
757
758
	/**
759
	 * Perform maintenance when a plugin upgrade occurs.
760
	 *
761
	 * @access public
762
	 * @static
763
	 *
764
	 * @param string $new_version New version of the plugin.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $new_version not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
765
	 * @param string $old_version Old version of the plugin.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $old_version not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
766
	 */
767
	public static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
768
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
769
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
770
		}
771
772
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
773
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
774
			self::clear_sync_cron_jobs();
775
			Settings::update_settings(
776
				array(
777
					'render_filtered_content' => Defaults::$default_render_filtered_content,
778
				)
779
			);
780
		}
781
782
		Health::on_jetpack_upgraded();
783
	}
784
785
	/**
786
	 * Get syncing status for the given fields.
787
	 *
788
	 * @access public
789
	 * @static
790
	 *
791
	 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
792
	 * @return array An associative array with the status report.
793
	 */
794
	public static function get_sync_status( $fields = null ) {
795
		self::initialize_sender();
796
797
		$sync_module = Modules::get_module( 'full-sync' );
798
		$queue       = self::$sender->get_sync_queue();
799
800
		// _get_cron_array can be false
801
		$cron_timestamps = ( _get_cron_array() ) ? array_keys( _get_cron_array() ) : array();
802
		$next_cron       = ( ! empty( $cron_timestamps ) ) ? $cron_timestamps[0] - time() : '';
803
804
		$checksums = array();
805
		$debug     = array();
806
807
		if ( ! empty( $fields ) ) {
808
			$store         = new Replicastore();
809
			$fields_params = array_map( 'trim', explode( ',', $fields ) );
810
811
			if ( in_array( 'posts_checksum', $fields_params, true ) ) {
812
				$checksums['posts_checksum'] = $store->posts_checksum();
813
			}
814
			if ( in_array( 'comments_checksum', $fields_params, true ) ) {
815
				$checksums['comments_checksum'] = $store->comments_checksum();
816
			}
817
			if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
818
				$checksums['post_meta_checksum'] = $store->post_meta_checksum();
819
			}
820
			if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
821
				$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
822
			}
823
824
			if ( in_array( 'debug_details', $fields_params, true ) ) {
825
				$debug = self::get_debug_details();
826
			}
827
		}
828
829
		$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
830
831
		$full_queue = self::$sender->get_full_sync_queue();
832
833
		$result = array_merge(
834
			$full_sync_status,
835
			$checksums,
836
			$debug,
837
			array(
838
				'cron_size'            => count( $cron_timestamps ),
839
				'next_cron'            => $next_cron,
840
				'queue_size'           => $queue->size(),
841
				'queue_lag'            => $queue->lag(),
842
				'queue_next_sync'      => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
843
				'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
844
			)
845
		);
846
847
		// Verify $sync_module is not false.
848
		if ( ( $sync_module ) && false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
849
			$result['full_queue_size'] = $full_queue->size();
850
			$result['full_queue_lag']  = $full_queue->lag();
851
		}
852
		return $result;
853
	}
854
}
855