Completed
Push — add/changelog-891 ( 3d3241...e512a9 )
by Jeremy
25:30 queued 17:32
created

Actions::jetpack_cron_schedule()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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