Completed
Push — add/sync-health ( 928c9c...fae778 )
by
unknown
21:39 queued 12:28
created

Actions::cleanup_on_upgrade()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 2
dl 0
loc 19
rs 9.0111
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Automattic\Jetpack\Sync\Status.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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