Completed
Push — update/extend-sync-status-endp... ( 2b99f7 )
by
unknown
13:03 queued 05:26
created

Actions::initialize_sender()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
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
		);
432
433
		if ( is_multisite() ) {
434
			$initial_sync_config['network_options'] = true;
435
		}
436
437
		self::do_full_sync( $initial_sync_config );
438
	}
439
440
	/**
441
	 * Kicks off a full sync.
442
	 *
443
	 * @access public
444
	 * @static
445
	 *
446
	 * @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...
447
	 * @return bool           True if full sync was successfully started.
448
	 */
449
	public static function do_full_sync( $modules = null ) {
450
		if ( ! self::sync_allowed() ) {
451
			return false;
452
		}
453
454
		$full_sync_module = Modules::get_module( 'full-sync' );
455
456
		if ( ! $full_sync_module ) {
457
			return false;
458
		}
459
460
		self::initialize_listener();
461
462
		$full_sync_module->start( $modules );
463
464
		return true;
465
	}
466
467
	/**
468
	 * Adds a cron schedule for regular syncing via cron, unless the schedule already exists.
469
	 *
470
	 * @access public
471
	 * @static
472
	 *
473
	 * @param array $schedules  The list of WordPress cron schedules prior to this filter.
474
	 * @return array            A list of WordPress cron schedules with the Jetpack sync interval added.
475
	 */
476
	public static function jetpack_cron_schedule( $schedules ) {
477
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
478
			$minutes = intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
479
			$display = ( 1 === $minutes ) ?
480
				__( 'Every minute', 'jetpack' ) :
481
				/* translators: %d is an integer indicating the number of minutes. */
482
				sprintf( __( 'Every %d minutes', 'jetpack' ), $minutes );
483
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
484
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
485
				'display'  => $display,
486
			);
487
		}
488
		return $schedules;
489
	}
490
491
	/**
492
	 * Starts an incremental sync via cron.
493
	 *
494
	 * @access public
495
	 * @static
496
	 */
497
	public static function do_cron_sync() {
498
		self::do_cron_sync_by_type( 'sync' );
499
	}
500
501
	/**
502
	 * Starts a full sync via cron.
503
	 *
504
	 * @access public
505
	 * @static
506
	 */
507
	public static function do_cron_full_sync() {
508
		self::do_cron_sync_by_type( 'full_sync' );
509
	}
510
511
	/**
512
	 * Try to send actions until we run out of things to send,
513
	 * or have to wait more than 15s before sending again,
514
	 * or we hit a lock or some other sending issue
515
	 *
516
	 * @access public
517
	 * @static
518
	 *
519
	 * @param string $type Sync type. Can be `sync` or `full_sync`.
520
	 */
521
	public static function do_cron_sync_by_type( $type ) {
522
		if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
523
			return;
524
		}
525
526
		self::initialize_sender();
527
528
		$time_limit = Settings::get_setting( 'cron_sync_time_limit' );
529
		$start_time = time();
530
		$executions = 0;
531
532
		do {
533
			$next_sync_time = self::$sender->get_next_sync_time( $type );
534
535
			if ( $next_sync_time ) {
536
				$delay = $next_sync_time - time() + 1;
537
				if ( $delay > 15 ) {
538
					break;
539
				} elseif ( $delay > 0 ) {
540
					sleep( $delay );
541
				}
542
			}
543
544
			// Explicitly only allow 1 do_full_sync call until issue with Immediate Full Sync is resolved.
545
			// For more context see p1HpG7-9pe-p2.
546
			if ( 'full_sync' === $type && $executions >= 1 ) {
547
				break;
548
			}
549
550
			$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
551
552
			// # of send actions performed.
553
			$executions ++;
554
555
		} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
556
557
		return $executions;
558
	}
559
560
	/**
561
	 * Initialize the sync listener.
562
	 *
563
	 * @access public
564
	 * @static
565
	 */
566
	public static function initialize_listener() {
567
		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...
568
	}
569
570
	/**
571
	 * Initializes the sync sender.
572
	 *
573
	 * @access public
574
	 * @static
575
	 */
576
	public static function initialize_sender() {
577
		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...
578
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 8 );
579
	}
580
581
	/**
582
	 * Initializes sync for WooCommerce.
583
	 *
584
	 * @access public
585
	 * @static
586
	 */
587
	public static function initialize_woocommerce() {
588
		if ( false === class_exists( 'WooCommerce' ) ) {
589
			return;
590
		}
591
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
592
	}
593
594
	/**
595
	 * Adds Woo's sync modules to existing modules for sending.
596
	 *
597
	 * @access public
598
	 * @static
599
	 *
600
	 * @param array $sync_modules The list of sync modules declared prior to this filter.
601
	 * @return array A list of sync modules that now includes Woo's modules.
602
	 */
603
	public static function add_woocommerce_sync_module( $sync_modules ) {
604
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
605
		return $sync_modules;
606
	}
607
608
	/**
609
	 * Initializes sync for WP Super Cache.
610
	 *
611
	 * @access public
612
	 * @static
613
	 */
614
	public static function initialize_wp_super_cache() {
615
		if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
616
			return;
617
		}
618
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
619
	}
620
621
	/**
622
	 * Adds WP Super Cache's sync modules to existing modules for sending.
623
	 *
624
	 * @access public
625
	 * @static
626
	 *
627
	 * @param array $sync_modules The list of sync modules declared prior to this filer.
628
	 * @return array A list of sync modules that now includes WP Super Cache's modules.
629
	 */
630
	public static function add_wp_super_cache_sync_module( $sync_modules ) {
631
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
632
		return $sync_modules;
633
	}
634
635
	/**
636
	 * Sanitizes the name of sync's cron schedule.
637
	 *
638
	 * @access public
639
	 * @static
640
	 *
641
	 * @param string $schedule The name of a WordPress cron schedule.
642
	 * @return string The sanitized name of sync's cron schedule.
643
	 */
644
	public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
645
		$schedule  = sanitize_key( $schedule );
646
		$schedules = wp_get_schedules();
647
648
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
649
		if ( isset( $schedules[ $schedule ] ) ) {
650
			return $schedule;
651
		}
652
653
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
654
	}
655
656
	/**
657
	 * Allows offsetting of start times for sync cron jobs.
658
	 *
659
	 * @access public
660
	 * @static
661
	 *
662
	 * @param string $schedule The name of a cron schedule.
663
	 * @param string $hook     The hook that this method is responding to.
664
	 * @return int The offset for the sync cron schedule.
665
	 */
666
	public static function get_start_time_offset( $schedule = '', $hook = '' ) {
667
		$start_time_offset = is_multisite()
668
			? wp_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
669
			: 0;
670
671
		/**
672
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
673
		 * cron jobs across multiple sites in a network.
674
		 *
675
		 * @since 4.5.0
676
		 *
677
		 * @param int    $start_time_offset
678
		 * @param string $hook
679
		 * @param string $schedule
680
		 */
681
		return intval(
682
			apply_filters(
683
				'jetpack_sync_cron_start_time_offset',
684
				$start_time_offset,
685
				$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...
686
				$schedule
687
			)
688
		);
689
	}
690
691
	/**
692
	 * Decides if a sync cron should be scheduled.
693
	 *
694
	 * @access public
695
	 * @static
696
	 *
697
	 * @param string $schedule The name of a cron schedule.
698
	 * @param string $hook     The hook that this method is responding to.
699
	 */
700
	public static function maybe_schedule_sync_cron( $schedule, $hook ) {
701
		if ( ! $hook ) {
702
			return;
703
		}
704
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
705
706
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
707
		if ( ! wp_next_scheduled( $hook ) ) {
708
			// Schedule a job to send pending queue items once a minute.
709
			wp_schedule_event( $start_time, $schedule, $hook );
710
		} elseif ( wp_get_schedule( $hook ) !== $schedule ) {
711
			// If the schedule has changed, update the schedule.
712
			wp_clear_scheduled_hook( $hook );
713
			wp_schedule_event( $start_time, $schedule, $hook );
714
		}
715
	}
716
717
	/**
718
	 * Clears Jetpack sync cron jobs.
719
	 *
720
	 * @access public
721
	 * @static
722
	 */
723
	public static function clear_sync_cron_jobs() {
724
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
725
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
726
	}
727
728
	/**
729
	 * Initializes Jetpack sync cron jobs.
730
	 *
731
	 * @access public
732
	 * @static
733
	 */
734
	public static function init_sync_cron_jobs() {
735
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
736
737
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
738
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
739
740
		/**
741
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
742
		 *
743
		 * @since 4.3.2
744
		 *
745
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
746
		 */
747
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
748
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
749
750
		/**
751
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
752
		 *
753
		 * @since 4.3.2
754
		 *
755
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
756
		 */
757
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
758
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
759
	}
760
761
	/**
762
	 * Perform maintenance when a plugin upgrade occurs.
763
	 *
764
	 * @access public
765
	 * @static
766
	 *
767
	 * @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...
768
	 * @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...
769
	 */
770
	public static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
771
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
772
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
773
		}
774
775
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
776
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
777
			self::clear_sync_cron_jobs();
778
			Settings::update_settings(
779
				array(
780
					'render_filtered_content' => Defaults::$default_render_filtered_content,
781
				)
782
			);
783
		}
784
785
		Health::on_jetpack_upgraded();
786
	}
787
788
	/**
789
	 * Get syncing status for the given fields.
790
	 *
791
	 * @access public
792
	 * @static
793
	 *
794
	 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
795
	 * @return array An associative array with the status report.
796
	 */
797
	public static function get_sync_status( $fields = null ) {
798
		self::initialize_sender();
799
800
		$sync_module = Modules::get_module( 'full-sync' );
801
		$queue       = self::$sender->get_sync_queue();
802
803
		// _get_cron_array can be false
804
		$cron_timestamps = ( _get_cron_array() ) ? array_keys( _get_cron_array() ) : array();
805
		$next_cron       = ( ! empty( $cron_timestamps ) ) ? $cron_timestamps[0] - time() : '';
806
807
		$checksums = array();
808
		$debug     = array();
809
810
		if ( ! empty( $fields ) ) {
811
			$store         = new Replicastore();
812
			$fields_params = array_map( 'trim', explode( ',', $fields ) );
813
814
			if ( in_array( 'posts_checksum', $fields_params, true ) ) {
815
				$checksums['posts_checksum'] = $store->posts_checksum();
816
			}
817
			if ( in_array( 'comments_checksum', $fields_params, true ) ) {
818
				$checksums['comments_checksum'] = $store->comments_checksum();
819
			}
820
			if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
821
				$checksums['post_meta_checksum'] = $store->post_meta_checksum();
822
			}
823
			if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
824
				$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
825
			}
826
827
			if ( in_array( 'debug_details', $fields_params, true ) ) {
828
				$debug = self::get_debug_details();
829
			}
830
		}
831
832
		$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
833
834
		$full_queue = self::$sender->get_full_sync_queue();
835
836
		$result = array_merge(
837
			$full_sync_status,
838
			$checksums,
839
			$debug,
840
			array(
841
				'cron_size'            => count( $cron_timestamps ),
842
				'next_cron'            => $next_cron,
843
				'queue_size'           => $queue->size(),
844
				'queue_lag'            => $queue->lag(),
845
				'queue_next_sync'      => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
846
				'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
847
			)
848
		);
849
850
		// Verify $sync_module is not false.
851
		if ( ( $sync_module ) && false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
852
			$result['full_queue_size'] = $full_queue->size();
853
			$result['full_queue_lag']  = $full_queue->lag();
854
		}
855
		return $result;
856
	}
857
}
858