Completed
Push — renovate/copy-webpack-plugin-5... ( 1645c2...f892fa )
by
unknown
233:24 queued 224:35
created

Actions   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 13

Importance

Changes 0
Metric Value
wmc 84
lcom 2
cbo 13
dl 0
loc 490
rs 2
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 38 5
A add_sender_shutdown() 0 22 2
B should_initialize_sender() 0 23 7
A sync_via_cron_allowed() 0 3 1
A prevent_publicize_blacklisted_posts() 0 7 2
A set_is_importing_true() 0 3 1
B send_data() 0 76 8
A do_initial_sync() 0 19 3
A do_full_sync() 0 17 3
A jetpack_cron_schedule() 0 12 2
A do_cron_sync() 0 3 1
A do_cron_full_sync() 0 3 1
B do_cron_sync_by_type() 0 25 11
A initialize_listener() 0 3 1
A initialize_sender() 0 6 1
A initialize_woocommerce() 0 6 2
A add_woocommerce_sync_module() 0 4 1
A initialize_wp_super_cache() 0 6 2
A add_wp_super_cache_sync_module() 0 4 1
A sanitize_filtered_sync_cron_schedule() 0 11 2
A get_start_time_offset() 0 24 2
A maybe_schedule_sync_cron() 0 16 4
A clear_sync_cron_jobs() 0 4 1
A init_sync_cron_jobs() 0 26 1
A cleanup_on_upgrade() 0 15 5
B get_sync_status() 0 46 7
B sync_allowed() 0 22 7

How to fix   Complexity   

Complex Class

Complex classes like Actions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Actions, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Automattic\Jetpack\Sync;
4
5
use Automattic\Jetpack\Constants;
6
7
/**
8
 * The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
9
 * when to send, when to perform a full sync, etc.
10
 *
11
 * It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
12
 */
13
class Actions {
14
	static $sender                         = null;
15
	static $listener                       = null;
16
	const DEFAULT_SYNC_CRON_INTERVAL_NAME  = 'jetpack_sync_interval';
17
	const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
18
19
	static function init() {
20
		// everything below this point should only happen if we're a valid sync site
21
		if ( ! self::sync_allowed() ) {
22
			return;
23
		}
24
25
		if ( self::sync_via_cron_allowed() ) {
26
			self::init_sync_cron_jobs();
27
		} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
28
			self::clear_sync_cron_jobs();
29
		}
30
		// When importing via cron, do not sync
31
		add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
32
33
		// Sync connected user role changes to .com
34
		Users::init();
35
36
		// publicize filter to prevent publicizing blacklisted post types
37
		add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
38
39
		/**
40
		 * Fires on every request before default loading sync listener code.
41
		 * Return false to not load sync listener code that monitors common
42
		 * WP actions to be serialized.
43
		 *
44
		 * By default this returns true for cron jobs, non-GET-requests, or requests where the
45
		 * user is logged-in.
46
		 *
47
		 * @since 4.2.0
48
		 *
49
		 * @param bool should we load sync listener code for this request
50
		 */
51
		if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
52
			self::initialize_listener();
53
		}
54
55
		add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
56
	}
57
58
	static function add_sender_shutdown() {
59
		/**
60
		 * Fires on every request before default loading sync sender code.
61
		 * Return false to not load sync sender code that serializes pending
62
		 * data and sends it to WPCOM for processing.
63
		 *
64
		 * By default this returns true for cron jobs, POST requests, admin requests, or requests
65
		 * by users who can manage_options.
66
		 *
67
		 * @since 4.2.0
68
		 *
69
		 * @param bool should we load sync sender code for this request
70
		 */
71
		if ( apply_filters(
72
			'jetpack_sync_sender_should_load',
73
			self::should_initialize_sender()
74
		) ) {
75
			self::initialize_sender();
76
			add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
77
			add_action( 'shutdown', array( self::$sender, 'do_full_sync' ) );
78
		}
79
	}
80
81
	static function should_initialize_sender() {
82
		if ( Constants::is_true( 'DOING_CRON' ) ) {
83
			return self::sync_via_cron_allowed();
84
		}
85
86
		if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
87
			return true;
88
		}
89
90
		if ( current_user_can( 'manage_options' ) ) {
91
			return true;
92
		}
93
94
		if ( is_admin() ) {
95
			return true;
96
		}
97
98
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
99
			return true;
100
		}
101
102
		return false;
103
	}
104
105
	static function sync_allowed() {
106
		if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
107
			return true;
108
		}
109
110
		if ( ! Settings::is_sync_enabled() ) {
111
			return false;
112
		}
113
		if ( \Jetpack::is_development_mode() ) {
114
			return false;
115
		}
116
		if ( \Jetpack::is_staging_site() ) {
117
			return false;
118
		}
119
		if ( ! \Jetpack::is_active() ) {
120
			if ( ! doing_action( 'jetpack_user_authorized' ) ) {
121
				return false;
122
			}
123
		}
124
125
		return true;
126
	}
127
128
	static function sync_via_cron_allowed() {
129
		return ( Settings::get_setting( 'sync_via_cron' ) );
130
	}
131
132
	static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
133
		if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ) ) ) {
134
			return false;
135
		}
136
137
		return $should_publicize;
138
	}
139
140
	static function set_is_importing_true() {
141
		Settings::set_importing( true );
142
	}
143
144
	static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
145
		\Jetpack::load_xml_rpc_client();
146
147
		$query_args = array(
148
			'sync'      => '1',             // add an extra parameter to the URL so we can tell it's a sync action
149
			'codec'     => $codec_name,     // send the name of the codec used to encode the data
150
			'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences
151
			'queue'     => $queue_id,       // sync or full_sync
152
			'home'      => Functions::home_url(),  // Send home url option to check for Identity Crisis server-side
153
			'siteurl'   => Functions::site_url(),  // Send siteurl option to check for Identity Crisis server-side
154
			'cd'        => sprintf( '%.4f', $checkout_duration ),   // Time spent retrieving queue items from the DB
155
			'pd'        => sprintf( '%.4f', $preprocess_duration ), // Time spent converting queue items into data to send
156
		);
157
158
		// Has the site opted in to IDC mitigation?
159
		if ( \Jetpack::sync_idc_optin() ) {
160
			$query_args['idc'] = true;
161
		}
162
163
		if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
164
			$query_args['migrate_for_idc'] = true;
165
		}
166
167
		$query_args['timeout'] = Settings::is_doing_cron() ? 30 : 15;
168
169
		/**
170
		 * Filters query parameters appended to the Sync request URL sent to WordPress.com.
171
		 *
172
		 * @since 4.7.0
173
		 *
174
		 * @param array $query_args associative array of query parameters.
175
		 */
176
		$query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
177
178
		$url = add_query_arg( $query_args, \Jetpack::xmlrpc_api_url() );
179
180
		$rpc = new \Jetpack_IXR_Client(
181
			array(
182
				'url'     => $url,
183
				'user_id' => JETPACK_MASTER_USER,
184
				'timeout' => $query_args['timeout'],
185
			)
186
		);
187
188
		$result = $rpc->query( 'jetpack.syncActions', $data );
189
190
		if ( ! $result ) {
191
			return $rpc->get_jetpack_error();
192
		}
193
194
		$response = $rpc->getResponse();
195
196
		// Check if WordPress.com IDC mitigation blocked the sync request
197
		if ( is_array( $response ) && isset( $response['error_code'] ) ) {
198
			$error_code              = $response['error_code'];
199
			$allowed_idc_error_codes = array(
200
				'jetpack_url_mismatch',
201
				'jetpack_home_url_mismatch',
202
				'jetpack_site_url_mismatch',
203
			);
204
205
			if ( in_array( $error_code, $allowed_idc_error_codes ) ) {
206
				\Jetpack_Options::update_option(
207
					'sync_error_idc',
208
					\Jetpack::get_sync_error_idc_option( $response )
209
				);
210
			}
211
212
			return new \WP_Error(
213
				'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...
214
				esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
215
			);
216
		}
217
218
		return $response;
219
	}
220
221
	static function do_initial_sync() {
222
		// Lets not sync if we are not suppose to.
223
		if ( ! self::sync_allowed() ) {
224
			return false;
225
		}
226
227
		$initial_sync_config = array(
228
			'options'   => true,
229
			'functions' => true,
230
			'constants' => true,
231
			'users'     => array( get_current_user_id() ),
232
		);
233
234
		if ( is_multisite() ) {
235
			$initial_sync_config['network_options'] = true;
236
		}
237
238
		self::do_full_sync( $initial_sync_config );
239
	}
240
241
	static function do_full_sync( $modules = null ) {
242
		if ( ! self::sync_allowed() ) {
243
			return false;
244
		}
245
246
		$full_sync_module = Modules::get_module( 'full-sync' );
247
248
		if ( ! $full_sync_module ) {
249
			return false;
250
		}
251
252
		self::initialize_listener();
253
254
		$full_sync_module->start( $modules );
255
256
		return true;
257
	}
258
259
	static function jetpack_cron_schedule( $schedules ) {
260
		if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
261
			$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
262
				'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
263
				'display'  => sprintf(
264
					esc_html( _n( 'Every minute', 'Every %d minutes', intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 ), 'jetpack' ) ),
265
					intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 )
266
				),
267
			);
268
		}
269
		return $schedules;
270
	}
271
272
	static function do_cron_sync() {
273
		self::do_cron_sync_by_type( 'sync' );
274
	}
275
276
	static function do_cron_full_sync() {
277
		self::do_cron_sync_by_type( 'full_sync' );
278
	}
279
280
	/**
281
	 * Try to send actions until we run out of things to send,
282
	 * or have to wait more than 15s before sending again,
283
	 * or we hit a lock or some other sending issue
284
	 *
285
	 * @param string $type Sync type. Can be `sync` or `full_sync`.
286
	 */
287
	static function do_cron_sync_by_type( $type ) {
288
		if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
289
			return;
290
		}
291
292
		self::initialize_sender();
293
294
		$time_limit = Settings::get_setting( 'cron_sync_time_limit' );
295
		$start_time = time();
296
297
		do {
298
			$next_sync_time = self::$sender->get_next_sync_time( $type );
299
300
			if ( $next_sync_time ) {
301
				$delay = $next_sync_time - time() + 1;
302
				if ( $delay > 15 ) {
303
					break;
304
				} elseif ( $delay > 0 ) {
305
					sleep( $delay );
306
				}
307
			}
308
309
			$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
310
		} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
311
	}
312
313
	static function initialize_listener() {
314
		self::$listener = Listener::get_instance();
315
	}
316
317
	static function initialize_sender() {
318
		self::$sender = Sender::get_instance();
319
320
		// bind the sending process
321
		add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 6 );
322
	}
323
324
	static function initialize_woocommerce() {
325
		if ( false === class_exists( 'WooCommerce' ) ) {
326
			return;
327
		}
328
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
329
	}
330
331
	static function add_woocommerce_sync_module( $sync_modules ) {
332
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
333
		return $sync_modules;
334
	}
335
336
	static function initialize_wp_super_cache() {
337
		if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
338
			return;
339
		}
340
		add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
341
	}
342
343
	static function add_wp_super_cache_sync_module( $sync_modules ) {
344
		$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
345
		return $sync_modules;
346
	}
347
348
	static function sanitize_filtered_sync_cron_schedule( $schedule ) {
349
		$schedule  = sanitize_key( $schedule );
350
		$schedules = wp_get_schedules();
351
352
		// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
353
		if ( isset( $schedules[ $schedule ] ) ) {
354
			return $schedule;
355
		}
356
357
		return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
358
	}
359
360
	static function get_start_time_offset( $schedule = '', $hook = '' ) {
361
		$start_time_offset = is_multisite()
362
			? mt_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
363
			: 0;
364
365
		/**
366
		 * Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
367
		 * cron jobs across multiple sites in a network.
368
		 *
369
		 * @since 4.5.0
370
		 *
371
		 * @param int    $start_time_offset
372
		 * @param string $hook
373
		 * @param string $schedule
374
		 */
375
		return intval(
376
			apply_filters(
377
				'jetpack_sync_cron_start_time_offset',
378
				$start_time_offset,
379
				$hook,
380
				$schedule
381
			)
382
		);
383
	}
384
385
	static function maybe_schedule_sync_cron( $schedule, $hook ) {
386
		if ( ! $hook ) {
387
			return;
388
		}
389
		$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
390
391
		$start_time = time() + self::get_start_time_offset( $schedule, $hook );
392
		if ( ! wp_next_scheduled( $hook ) ) {
393
			// Schedule a job to send pending queue items once a minute
394
			wp_schedule_event( $start_time, $schedule, $hook );
395
		} elseif ( $schedule != wp_get_schedule( $hook ) ) {
396
			// If the schedule has changed, update the schedule
397
			wp_clear_scheduled_hook( $hook );
398
			wp_schedule_event( $start_time, $schedule, $hook );
399
		}
400
	}
401
402
	static function clear_sync_cron_jobs() {
403
		wp_clear_scheduled_hook( 'jetpack_sync_cron' );
404
		wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
405
	}
406
407
	static function init_sync_cron_jobs() {
408
		add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) );
409
410
		add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
411
		add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
412
413
		/**
414
		 * Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
415
		 *
416
		 * @since 4.3.2
417
		 *
418
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
419
		 */
420
		$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
421
		self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
422
423
		/**
424
		 * Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
425
		 *
426
		 * @since 4.3.2
427
		 *
428
		 * @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
429
		 */
430
		$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
431
		self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
432
	}
433
434
	static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
435
		if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
436
			wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
437
		}
438
439
		$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
440
		if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
441
			self::clear_sync_cron_jobs();
442
			Settings::update_settings(
443
				array(
444
					'render_filtered_content' => Defaults::$default_render_filtered_content,
0 ignored issues
show
Bug introduced by
The property default_render_filtered_content cannot be accessed from this context as it is declared private in class Automattic\Jetpack\Sync\Defaults.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
445
				)
446
			);
447
		}
448
	}
449
450
	/**
451
	 * Get the sync status
452
	 *
453
	 * @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
454
	 * @return array
455
	 */
456
	static function get_sync_status( $fields = null ) {
457
		self::initialize_sender();
458
459
		$sync_module     = Modules::get_module( 'full-sync' );
460
		$queue           = self::$sender->get_sync_queue();
461
		$full_queue      = self::$sender->get_full_sync_queue();
462
		$cron_timestamps = array_keys( _get_cron_array() );
463
		$next_cron       = $cron_timestamps[0] - time();
464
465
		$checksums = array();
466
467
		if ( ! empty( $fields ) ) {
468
			$store         = new Replicastore();
469
			$fields_params = array_map( 'trim', explode( ',', $fields ) );
470
471
			if ( in_array( 'posts_checksum', $fields_params, true ) ) {
472
				$checksums['posts_checksum'] = $store->posts_checksum();
473
			}
474
			if ( in_array( 'comments_checksum', $fields_params, true ) ) {
475
				$checksums['comments_checksum'] = $store->comments_checksum();
476
			}
477
			if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
478
				$checksums['post_meta_checksum'] = $store->post_meta_checksum();
479
			}
480
			if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
481
				$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
482
			}
483
		}
484
485
		$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
486
487
		return array_merge(
488
			$full_sync_status,
489
			$checksums,
490
			array(
491
				'cron_size'            => count( $cron_timestamps ),
492
				'next_cron'            => $next_cron,
493
				'queue_size'           => $queue->size(),
494
				'queue_lag'            => $queue->lag(),
495
				'queue_next_sync'      => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
496
				'full_queue_size'      => $full_queue->size(),
497
				'full_queue_lag'       => $full_queue->lag(),
498
				'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
499
			)
500
		);
501
	}
502
}
503