Completed
Push — instant-search-master ( e67c60...e24956 )
by
unknown
11:28 queued 04:53
created

Listener   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 0
Metric Value
dl 0
loc 456
rs 4.08
c 0
b 0
f 0
wmc 59
lcom 2
cbo 8

20 Methods

Rating   Name   Duplication   Size   Complexity  
A get_instance() 0 7 2
A __construct() 0 4 1
A init() 0 19 2
A get_sync_queue() 0 3 1
A get_full_sync_queue() 0 3 1
A set_queue_size_limit() 0 3 1
A get_queue_size_limit() 0 3 1
A set_queue_lag_limit() 0 3 1
A get_queue_lag_limit() 0 3 1
A force_recheck_queue_limit() 0 4 1
A can_add_to_queue() 0 20 4
A full_sync_action_handler() 0 3 1
A action_handler() 0 3 1
B bulk_enqueue_full_sync_actions() 0 50 7
C enqueue_action() 0 90 10
A sync_data_loss() 0 19 3
F get_actor() 0 35 15
A should_send_user_data_with_actor() 0 14 1
A set_defaults() 0 6 1
A get_request_url() 0 6 4

How to fix   Complexity   

Complex Class

Complex classes like Listener 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 Listener, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Jetpack's Sync Listener
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync;
9
10
use Automattic\Jetpack\Roles;
11
12
/**
13
 * This class monitors actions and logs them to the queue to be sent.
14
 */
15
class Listener {
16
	const QUEUE_STATE_CHECK_TRANSIENT = 'jetpack_sync_last_checked_queue_state';
17
	const QUEUE_STATE_CHECK_TIMEOUT   = 300; // 5 minutes.
18
19
	/**
20
	 * Sync queue.
21
	 *
22
	 * @var object
23
	 */
24
	private $sync_queue;
25
26
	/**
27
	 * Full sync queue.
28
	 *
29
	 * @var object
30
	 */
31
	private $full_sync_queue;
32
33
	/**
34
	 * Sync queue size limit.
35
	 *
36
	 * @var int size limit.
37
	 */
38
	private $sync_queue_size_limit;
39
40
	/**
41
	 * Sync queue lag limit.
42
	 *
43
	 * @var int Lag limit.
44
	 */
45
	private $sync_queue_lag_limit;
46
47
	/**
48
	 * Singleton implementation.
49
	 *
50
	 * @var Listener
51
	 */
52
	private static $instance;
53
54
	/**
55
	 * Get the Listener instance.
56
	 *
57
	 * @return Listener
58
	 */
59
	public static function get_instance() {
60
		if ( null === self::$instance ) {
61
			self::$instance = new self();
62
		}
63
64
		return self::$instance;
65
	}
66
67
	/**
68
	 * Listener constructor.
69
	 *
70
	 * This is necessary because you can't use "new" when you declare instance properties >:(
71
	 */
72
	protected function __construct() {
73
		$this->set_defaults();
74
		$this->init();
75
	}
76
77
	/**
78
	 * Sync Listener init.
79
	 */
80
	private function init() {
81
		$handler           = array( $this, 'action_handler' );
82
		$full_sync_handler = array( $this, 'full_sync_action_handler' );
83
84
		foreach ( Modules::get_modules() as $module ) {
85
			$module->init_listeners( $handler );
86
			$module->init_full_sync_listeners( $full_sync_handler );
87
		}
88
89
		// Module Activation.
90
		add_action( 'jetpack_activate_module', $handler );
91
		add_action( 'jetpack_deactivate_module', $handler );
92
93
		// Jetpack Upgrade.
94
		add_action( 'updating_jetpack_version', $handler, 10, 2 );
95
96
		// Send periodic checksum.
97
		add_action( 'jetpack_sync_checksum', $handler );
98
	}
99
100
	/**
101
	 * Get incremental sync queue.
102
	 */
103
	public function get_sync_queue() {
104
		return $this->sync_queue;
105
	}
106
107
	/**
108
	 * Gets the full sync queue.
109
	 */
110
	public function get_full_sync_queue() {
111
		return $this->full_sync_queue;
112
	}
113
114
	/**
115
	 * Sets queue size limit.
116
	 *
117
	 * @param int $limit Queue size limit.
118
	 */
119
	public function set_queue_size_limit( $limit ) {
120
		$this->sync_queue_size_limit = $limit;
121
	}
122
123
	/**
124
	 * Get queue size limit.
125
	 */
126
	public function get_queue_size_limit() {
127
		return $this->sync_queue_size_limit;
128
	}
129
130
	/**
131
	 * Sets the queue lag limit.
132
	 *
133
	 * @param int $age Queue lag limit.
134
	 */
135
	public function set_queue_lag_limit( $age ) {
136
		$this->sync_queue_lag_limit = $age;
137
	}
138
139
	/**
140
	 * Return value of queue lag limit.
141
	 */
142
	public function get_queue_lag_limit() {
143
		return $this->sync_queue_lag_limit;
144
	}
145
146
	/**
147
	 * Force a recheck of the queue limit.
148
	 */
149
	public function force_recheck_queue_limit() {
150
		delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->sync_queue->id );
151
		delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->full_sync_queue->id );
152
	}
153
154
	/**
155
	 * Determine if an item can be added to the queue.
156
	 *
157
	 * Prevent adding items to the queue if it hasn't sent an item for 15 mins
158
	 * AND the queue is over 1000 items long (by default).
159
	 *
160
	 * @param object $queue Sync queue.
161
	 * @return bool
162
	 */
163
	public function can_add_to_queue( $queue ) {
164
		if ( ! Settings::is_sync_enabled() ) {
165
			return false;
166
		}
167
168
		$state_transient_name = self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $queue->id;
169
170
		$queue_state = get_transient( $state_transient_name );
171
172
		if ( false === $queue_state ) {
173
			$queue_state = array( $queue->size(), $queue->lag() );
174
			set_transient( $state_transient_name, $queue_state, self::QUEUE_STATE_CHECK_TIMEOUT );
175
		}
176
177
		list( $queue_size, $queue_age ) = $queue_state;
178
179
		return ( $queue_age < $this->sync_queue_lag_limit )
180
			||
181
			( ( $queue_size + 1 ) < $this->sync_queue_size_limit );
182
	}
183
184
	/**
185
	 * Full sync action handler.
186
	 *
187
	 * @param mixed ...$args Args passed to the action.
188
	 */
189
	public function full_sync_action_handler( ...$args ) {
190
		$this->enqueue_action( current_filter(), $args, $this->full_sync_queue );
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,*>, but the function expects a object.

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...
Documentation introduced by
$this->full_sync_queue is of type object, 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...
191
	}
192
193
	/**
194
	 * Action handler.
195
	 *
196
	 * @param mixed ...$args Args passed to the action.
197
	 */
198
	public function action_handler( ...$args ) {
199
		$this->enqueue_action( current_filter(), $args, $this->sync_queue );
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,*>, but the function expects a object.

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...
Documentation introduced by
$this->sync_queue is of type object, 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...
200
	}
201
202
	// add many actions to the queue directly, without invoking them.
203
204
	/**
205
	 * Bulk add action to the queue.
206
	 *
207
	 * @param string $action_name The name the full sync action.
208
	 * @param array  $args_array Array of chunked arguments.
209
	 */
210
	public function bulk_enqueue_full_sync_actions( $action_name, $args_array ) {
211
		$queue = $this->get_full_sync_queue();
212
213
		/*
214
		 * If we add any items to the queue, we should try to ensure that our script
215
		 * can't be killed before they are sent.
216
		 */
217
		if ( function_exists( 'ignore_user_abort' ) ) {
218
			ignore_user_abort( true );
219
		}
220
221
		$data_to_enqueue = array();
222
		$user_id         = get_current_user_id();
223
		$currtime        = microtime( true );
224
		$is_importing    = Settings::is_importing();
225
226
		foreach ( $args_array as $args ) {
227
			$previous_end = isset( $args['previous_end'] ) ? $args['previous_end'] : null;
228
			$args         = isset( $args['ids'] ) ? $args['ids'] : $args;
229
230
			/**
231
			 * Modify or reject the data within an action before it is enqueued locally.
232
			 *
233
			 * @since 4.2.0
234
			 *
235
			 * @module sync
236
			 *
237
			 * @param array The action parameters
238
			 */
239
			$args        = apply_filters( "jetpack_sync_before_enqueue_$action_name", $args );
240
			$action_data = array( $args );
241
			if ( ! is_null( $previous_end ) ) {
242
				$action_data[] = $previous_end;
243
			}
244
			// allow listeners to abort.
245
			if ( false === $args ) {
246
				continue;
247
			}
248
249
			$data_to_enqueue[] = array(
250
				$action_name,
251
				$action_data,
252
				$user_id,
253
				$currtime,
254
				$is_importing,
255
			);
256
		}
257
258
		$queue->add_all( $data_to_enqueue );
259
	}
260
261
	/**
262
	 * Enqueue the action.
263
	 *
264
	 * @param string $current_filter Current WordPress filter.
265
	 * @param object $args Sync args.
266
	 * @param string $queue Sync queue.
267
	 */
268
	public function enqueue_action( $current_filter, $args, $queue ) {
269
		// don't enqueue an action during the outbound http request - this prevents recursion.
270
		if ( Settings::is_sending() ) {
271
			return;
272
		}
273
274
		/**
275
		 * Add an action hook to execute when anything on the whitelist gets sent to the queue to sync.
276
		 *
277
		 * @module sync
278
		 *
279
		 * @since 5.9.0
280
		 */
281
		do_action( 'jetpack_sync_action_before_enqueue' );
282
283
		/**
284
		 * Modify or reject the data within an action before it is enqueued locally.
285
		 *
286
		 * @since 4.2.0
287
		 *
288
		 * @param array The action parameters
289
		 */
290
		$args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args );
291
292
		// allow listeners to abort.
293
		if ( false === $args ) {
294
			return;
295
		}
296
297
		/*
298
		 * Periodically check the size of the queue, and disable adding to it if
299
		 * it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped).
300
		 */
301
		if ( ! $this->can_add_to_queue( $queue ) ) {
0 ignored issues
show
Documentation introduced by
$queue is of type string, but the function expects a object.

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...
302
			if ( 'sync' === $queue->id ) {
303
				$this->sync_data_loss( $queue );
0 ignored issues
show
Documentation introduced by
$queue is of type string, but the function expects a object<Automattic\Jetpack\Sync\Queue>.

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...
304
			}
305
			return;
306
		}
307
308
		/*
309
		 * If we add any items to the queue, we should try to ensure that our script
310
		 * can't be killed before they are sent.
311
		 */
312
		if ( function_exists( 'ignore_user_abort' ) ) {
313
			ignore_user_abort( true );
314
		}
315
316
		if (
317
			'sync' === $queue->id ||
318
			in_array(
319
				$current_filter,
320
				array(
321
					'jetpack_full_sync_start',
322
					'jetpack_full_sync_end',
323
					'jetpack_full_sync_cancel',
324
				),
325
				true
326
			)
327
		) {
328
			$queue->add(
0 ignored issues
show
Bug introduced by
The method add cannot be called on $queue (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
329
				array(
330
					$current_filter,
331
					$args,
332
					get_current_user_id(),
333
					microtime( true ),
334
					Settings::is_importing(),
335
					$this->get_actor( $current_filter, $args ),
336
				)
337
			);
338
		} else {
339
			$queue->add(
0 ignored issues
show
Bug introduced by
The method add cannot be called on $queue (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
340
				array(
341
					$current_filter,
342
					$args,
343
					get_current_user_id(),
344
					microtime( true ),
345
					Settings::is_importing(),
346
				)
347
			);
348
		}
349
350
		// since we've added some items, let's try to load the sender so we can send them as quickly as possible.
351
		if ( ! Actions::$sender ) {
352
			add_filter( 'jetpack_sync_sender_should_load', '__return_true' );
353
			if ( did_action( 'init' ) ) {
354
				Actions::add_sender_shutdown();
355
			}
356
		}
357
	}
358
359
	/**
360
	 * Sync Data Loss Handler
361
	 *
362
	 * @param Queue $queue Sync queue.
363
	 * @return boolean was send successful
364
	 */
365
	public function sync_data_loss( $queue ) {
366
		if ( ! Settings::is_sync_enabled() ) {
367
			return;
368
		}
369
		$updated = Health::update_status( Health::STATUS_OUT_OF_SYNC );
370
371
		if ( ! $updated ) {
372
			return;
373
		}
374
375
		$data = array(
376
			'timestamp'  => microtime( true ),
377
			'queue_size' => $queue->size(),
378
			'queue_lag'  => $queue->lag(),
379
		);
380
381
		$sender = Sender::get_instance();
382
		return $sender->send_action( 'jetpack_sync_data_loss', $data );
383
	}
384
385
	/**
386
	 * Get the event's actor.
387
	 *
388
	 * @param string $current_filter Current wp-admin page.
389
	 * @param object $args Sync event.
390
	 * @return array Actor information.
391
	 */
392
	public function get_actor( $current_filter, $args ) {
393
		if ( 'wp_login' === $current_filter ) {
394
			$user = get_user_by( 'ID', $args[1]->data->ID );
395
		} else {
396
			$user = wp_get_current_user();
397
		}
398
399
		$roles           = new Roles();
400
		$translated_role = $roles->translate_user_to_role( $user );
401
402
		$actor = array(
403
			'wpcom_user_id'    => null,
404
			'external_user_id' => isset( $user->ID ) ? $user->ID : null,
405
			'display_name'     => isset( $user->display_name ) ? $user->display_name : null,
406
			'user_email'       => isset( $user->user_email ) ? $user->user_email : null,
407
			'user_roles'       => isset( $user->roles ) ? $user->roles : null,
408
			'translated_role'  => $translated_role ? $translated_role : null,
409
			'is_cron'          => defined( 'DOING_CRON' ) ? DOING_CRON : false,
410
			'is_rest'          => defined( 'REST_API_REQUEST' ) ? REST_API_REQUEST : false,
411
			'is_xmlrpc'        => defined( 'XMLRPC_REQUEST' ) ? XMLRPC_REQUEST : false,
412
			'is_wp_rest'       => defined( 'REST_REQUEST' ) ? REST_REQUEST : false,
413
			'is_ajax'          => defined( 'DOING_AJAX' ) ? DOING_AJAX : false,
414
			'is_wp_admin'      => is_admin(),
415
			'is_cli'           => defined( 'WP_CLI' ) ? WP_CLI : false,
416
			'from_url'         => $this->get_request_url(),
417
		);
418
419
		if ( $this->should_send_user_data_with_actor( $current_filter ) ) {
420
			require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
421
			$actor['ip']         = jetpack_protect_get_ip();
422
			$actor['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown';
423
		}
424
425
		return $actor;
426
	}
427
428
	/**
429
	 * Should user data be sent as the actor?
430
	 *
431
	 * @param string $current_filter The current WordPress filter being executed.
432
	 * @return bool
433
	 */
434
	public function should_send_user_data_with_actor( $current_filter ) {
435
		$should_send = in_array( $current_filter, array( 'jetpack_wp_login', 'wp_logout', 'jetpack_valid_failed_login_attempt' ), true );
436
		/**
437
		 * Allow or deny sending actor's user data ( IP and UA ) during a sync event
438
		 *
439
		 * @since 5.8.0
440
		 *
441
		 * @module sync
442
		 *
443
		 * @param bool True if we should send user data
444
		 * @param string The current filter that is performing the sync action
445
		 */
446
		return apply_filters( 'jetpack_sync_actor_user_data', $should_send, $current_filter );
447
	}
448
449
	/**
450
	 * Sets Listener defaults.
451
	 */
452
	public function set_defaults() {
453
		$this->sync_queue      = new Queue( 'sync' );
454
		$this->full_sync_queue = new Queue( 'full_sync' );
455
		$this->set_queue_size_limit( Settings::get_setting( 'max_queue_size' ) );
456
		$this->set_queue_lag_limit( Settings::get_setting( 'max_queue_lag' ) );
457
	}
458
459
	/**
460
	 * Get the request URL.
461
	 *
462
	 * @return string Request URL, if known. Otherwise, wp-admin or home_url.
463
	 */
464
	public function get_request_url() {
465
		if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
466
			return 'http' . ( isset( $_SERVER['HTTPS'] ) ? 's' : '' ) . '://' . "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
467
		}
468
		return is_admin() ? get_admin_url( get_current_blog_id() ) : home_url();
469
	}
470
}
471