Completed
Push — add/cli ( 74cf7f...2842da )
by
unknown
10:13
created

Actions::send_data()   C

Complexity

Conditions 9
Paths 40

Size

Total Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 40
nop 8
dl 0
loc 86
rs 6.7498
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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