Completed
Push — update/opentable-embed-url ( 211c30 )
by
unknown
21:15 queued 14:06
created

Listener::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
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
			return;
303
		}
304
305
		/*
306
		 * If we add any items to the queue, we should try to ensure that our script
307
		 * can't be killed before they are sent.
308
		 */
309
		if ( function_exists( 'ignore_user_abort' ) ) {
310
			ignore_user_abort( true );
311
		}
312
313
		if (
314
			'sync' === $queue->id ||
315
			in_array(
316
				$current_filter,
317
				array(
318
					'jetpack_full_sync_start',
319
					'jetpack_full_sync_end',
320
					'jetpack_full_sync_cancel',
321
				),
322
				true
323
			)
324
		) {
325
			$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...
326
				array(
327
					$current_filter,
328
					$args,
329
					get_current_user_id(),
330
					microtime( true ),
331
					Settings::is_importing(),
332
					$this->get_actor( $current_filter, $args ),
333
				)
334
			);
335
		} else {
336
			$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...
337
				array(
338
					$current_filter,
339
					$args,
340
					get_current_user_id(),
341
					microtime( true ),
342
					Settings::is_importing(),
343
				)
344
			);
345
		}
346
347
		// since we've added some items, let's try to load the sender so we can send them as quickly as possible.
348
		if ( ! Actions::$sender ) {
349
			add_filter( 'jetpack_sync_sender_should_load', '__return_true' );
350
			if ( did_action( 'init' ) ) {
351
				Actions::add_sender_shutdown();
352
			}
353
		}
354
	}
355
356
	/**
357
	 * Get the event's actor.
358
	 *
359
	 * @param string $current_filter Current wp-admin page.
360
	 * @param object $args Sync event.
361
	 * @return array Actor information.
362
	 */
363
	public function get_actor( $current_filter, $args ) {
364
		if ( 'wp_login' === $current_filter ) {
365
			$user = get_user_by( 'ID', $args[1]->data->ID );
366
		} else {
367
			$user = wp_get_current_user();
368
		}
369
370
		$roles           = new Roles();
371
		$translated_role = $roles->translate_user_to_role( $user );
372
373
		$actor = array(
374
			'wpcom_user_id'    => null,
375
			'external_user_id' => isset( $user->ID ) ? $user->ID : null,
376
			'display_name'     => isset( $user->display_name ) ? $user->display_name : null,
377
			'user_email'       => isset( $user->user_email ) ? $user->user_email : null,
378
			'user_roles'       => isset( $user->roles ) ? $user->roles : null,
379
			'translated_role'  => $translated_role ? $translated_role : null,
380
			'is_cron'          => defined( 'DOING_CRON' ) ? DOING_CRON : false,
381
			'is_rest'          => defined( 'REST_API_REQUEST' ) ? REST_API_REQUEST : false,
382
			'is_xmlrpc'        => defined( 'XMLRPC_REQUEST' ) ? XMLRPC_REQUEST : false,
383
			'is_wp_rest'       => defined( 'REST_REQUEST' ) ? REST_REQUEST : false,
384
			'is_ajax'          => defined( 'DOING_AJAX' ) ? DOING_AJAX : false,
385
			'is_wp_admin'      => is_admin(),
386
			'is_cli'           => defined( 'WP_CLI' ) ? WP_CLI : false,
387
			'from_url'         => $this->get_request_url(),
388
		);
389
390
		if ( $this->should_send_user_data_with_actor( $current_filter ) ) {
391
			require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
392
			$actor['ip']         = jetpack_protect_get_ip();
393
			$actor['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown';
394
		}
395
396
		return $actor;
397
	}
398
399
	/**
400
	 * Should user data be sent as the actor?
401
	 *
402
	 * @param string $current_filter The current WordPress filter being executed.
403
	 * @return bool
404
	 */
405
	public function should_send_user_data_with_actor( $current_filter ) {
406
		$should_send = in_array( $current_filter, array( 'jetpack_wp_login', 'wp_logout', 'jetpack_valid_failed_login_attempt' ), true );
407
		/**
408
		 * Allow or deny sending actor's user data ( IP and UA ) during a sync event
409
		 *
410
		 * @since 5.8.0
411
		 *
412
		 * @module sync
413
		 *
414
		 * @param bool True if we should send user data
415
		 * @param string The current filter that is performing the sync action
416
		 */
417
		return apply_filters( 'jetpack_sync_actor_user_data', $should_send, $current_filter );
418
	}
419
420
	/**
421
	 * Sets Listener defaults.
422
	 */
423
	public function set_defaults() {
424
		$this->sync_queue      = new Queue( 'sync' );
425
		$this->full_sync_queue = new Queue( 'full_sync' );
426
		$this->set_queue_size_limit( Settings::get_setting( 'max_queue_size' ) );
427
		$this->set_queue_lag_limit( Settings::get_setting( 'max_queue_lag' ) );
428
	}
429
430
	/**
431
	 * Get the request URL.
432
	 *
433
	 * @return string Request URL, if known. Otherwise, wp-admin or home_url.
434
	 */
435
	public function get_request_url() {
436
		if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
437
			return 'http' . ( isset( $_SERVER['HTTPS'] ) ? 's' : '' ) . '://' . "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
438
		}
439
		return is_admin() ? get_admin_url( get_current_blog_id() ) : home_url();
440
	}
441
}
442