Completed
Push — fix/concurrency-race ( 35614e...7b1800 )
by
unknown
90:55 queued 81:30
created

Actions::send_data()   D

Complexity

Conditions 12
Paths 208

Size

Total Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nc 208
nop 8
dl 0
loc 100
rs 4.8266
c 0
b 0
f 0

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