Completed
Push — update/sync-psr4-listener ( afc98b )
by Marin
190:34 queued 181:43
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
namespace Automattic\Jetpack\Sync;
4
5
/**
6
 * This class monitors actions and logs them to the queue to be sent
7
 */
8
class Listener {
9
	const QUEUE_STATE_CHECK_TRANSIENT = 'jetpack_sync_last_checked_queue_state';
10
	const QUEUE_STATE_CHECK_TIMEOUT   = 300; // 5 minutes
11
12
	private $sync_queue;
13
	private $full_sync_queue;
14
	private $sync_queue_size_limit;
15
	private $sync_queue_lag_limit;
16
17
	// singleton functions
18
	private static $instance;
19
20
	public static function get_instance() {
21
		if ( null === self::$instance ) {
22
			self::$instance = new self();
23
		}
24
25
		return self::$instance;
26
	}
27
28
	// this is necessary because you can't use "new" when you declare instance properties >:(
29
	protected function __construct() {
30
		\Jetpack_Sync_Main::init();
31
		$this->set_defaults();
32
		$this->init();
33
	}
34
35
	private function init() {
36
		$handler           = array( $this, 'action_handler' );
37
		$full_sync_handler = array( $this, 'full_sync_action_handler' );
38
39
		foreach ( \Jetpack_Sync_Modules::get_modules() as $module ) {
40
			$module->init_listeners( $handler );
41
			$module->init_full_sync_listeners( $full_sync_handler );
42
		}
43
44
		// Module Activation
45
		add_action( 'jetpack_activate_module', $handler );
46
		add_action( 'jetpack_deactivate_module', $handler );
47
48
		// Jetpack Upgrade
49
		add_action( 'updating_jetpack_version', $handler, 10, 2 );
50
51
		// Send periodic checksum
52
		add_action( 'jetpack_sync_checksum', $handler );
53
	}
54
55
	function get_sync_queue() {
56
		return $this->sync_queue;
57
	}
58
59
	function get_full_sync_queue() {
60
		return $this->full_sync_queue;
61
	}
62
63
	function set_queue_size_limit( $limit ) {
64
		$this->sync_queue_size_limit = $limit;
65
	}
66
67
	function get_queue_size_limit() {
68
		return $this->sync_queue_size_limit;
69
	}
70
71
	function set_queue_lag_limit( $age ) {
72
		$this->sync_queue_lag_limit = $age;
73
	}
74
75
	function get_queue_lag_limit() {
76
		return $this->sync_queue_lag_limit;
77
	}
78
79
	function force_recheck_queue_limit() {
80
		delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->sync_queue->id );
81
		delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->full_sync_queue->id );
82
	}
83
84
	// prevent adding items to the queue if it hasn't sent an item for 15 mins
85
	// AND the queue is over 1000 items long (by default)
86
	function can_add_to_queue( $queue ) {
87
		if ( ! \Jetpack_Sync_Settings::is_sync_enabled() ) {
88
			return false;
89
		}
90
91
		$state_transient_name = self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $queue->id;
92
93
		$queue_state = get_transient( $state_transient_name );
94
95
		if ( false === $queue_state ) {
96
			$queue_state = array( $queue->size(), $queue->lag() );
97
			set_transient( $state_transient_name, $queue_state, self::QUEUE_STATE_CHECK_TIMEOUT );
98
		}
99
100
		list( $queue_size, $queue_age ) = $queue_state;
101
102
		return ( $queue_age < $this->sync_queue_lag_limit )
103
			   ||
104
			   ( ( $queue_size + 1 ) < $this->sync_queue_size_limit );
105
	}
106
107
	function full_sync_action_handler() {
108
		$args = func_get_args();
109
		$this->enqueue_action( current_filter(), $args, $this->full_sync_queue );
110
	}
111
112
	function action_handler() {
113
		$args = func_get_args();
114
		$this->enqueue_action( current_filter(), $args, $this->sync_queue );
115
	}
116
117
	// add many actions to the queue directly, without invoking them
118
119
	/**
120
	 * Bulk add action to the queue.
121
	 *
122
	 * @param $action_name String the name the full sync action.
123
	 * @param $args_array Array of chunked arguments
124
	 */
125
	function bulk_enqueue_full_sync_actions( $action_name, $args_array ) {
126
		$queue = $this->get_full_sync_queue();
127
128
		// periodically check the size of the queue, and disable adding to it if
129
		// it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped)
130
		if ( ! $this->can_add_to_queue( $queue ) ) {
131
			return;
132
		}
133
134
		// if we add any items to the queue, we should try to ensure that our script
135
		// can't be killed before they are sent
136
		if ( function_exists( 'ignore_user_abort' ) ) {
137
			ignore_user_abort( true );
138
		}
139
140
		$data_to_enqueue = array();
141
		$user_id         = get_current_user_id();
142
		$currtime        = microtime( true );
143
		$is_importing    = \Jetpack_Sync_Settings::is_importing();
144
145
		foreach ( $args_array as $args ) {
146
			$previous_end = isset( $args['previous_end'] ) ? $args['previous_end'] : null;
147
			$args         = isset( $args['ids'] ) ? $args['ids'] : $args;
148
149
			/**
150
			 * Modify or reject the data within an action before it is enqueued locally.
151
			 *
152
			 * @since 4.2.0
153
			 *
154
			 * @module sync
155
			 *
156
			 * @param array The action parameters
157
			 */
158
			$args        = apply_filters( "jetpack_sync_before_enqueue_$action_name", $args );
159
			$action_data = array( $args );
160
			if ( ! is_null( $previous_end ) ) {
161
				$action_data[] = $previous_end;
162
			}
163
			// allow listeners to abort
164
			if ( $args === false ) {
165
				continue;
166
			}
167
168
			$data_to_enqueue[] = array(
169
				$action_name,
170
				$action_data,
171
				$user_id,
172
				$currtime,
173
				$is_importing,
174
			);
175
		}
176
177
		$queue->add_all( $data_to_enqueue );
178
	}
179
180
	function enqueue_action( $current_filter, $args, $queue ) {
181
		// don't enqueue an action during the outbound http request - this prevents recursion
182
		if ( \Jetpack_Sync_Settings::is_sending() ) {
183
			return;
184
		}
185
186
		/**
187
		 * Add an action hook to execute when anything on the whitelist gets sent to the queue to sync.
188
		 *
189
		 * @module sync
190
		 *
191
		 * @since 5.9.0
192
		 */
193
		do_action( 'jetpack_sync_action_before_enqueue' );
194
195
		/**
196
		 * Modify or reject the data within an action before it is enqueued locally.
197
		 *
198
		 * @since 4.2.0
199
		 *
200
		 * @param array The action parameters
201
		 */
202
		$args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args );
203
204
		// allow listeners to abort
205
		if ( $args === false ) {
206
			return;
207
		}
208
209
		// periodically check the size of the queue, and disable adding to it if
210
		// it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped)
211
		if ( ! $this->can_add_to_queue( $queue ) ) {
212
			return;
213
		}
214
215
		// if we add any items to the queue, we should try to ensure that our script
216
		// can't be killed before they are sent
217
		if ( function_exists( 'ignore_user_abort' ) ) {
218
			ignore_user_abort( true );
219
		}
220
221
		if (
222
			'sync' === $queue->id ||
223
			in_array(
224
				$current_filter,
225
				array(
226
					'jetpack_full_sync_start',
227
					'jetpack_full_sync_end',
228
					'jetpack_full_sync_cancel',
229
				)
230
			)
231
		) {
232
			$queue->add(
233
				array(
234
					$current_filter,
235
					$args,
236
					get_current_user_id(),
237
					microtime( true ),
238
					\Jetpack_Sync_Settings::is_importing(),
239
					$this->get_actor( $current_filter, $args ),
240
				)
241
			);
242
		} else {
243
			$queue->add(
244
				array(
245
					$current_filter,
246
					$args,
247
					get_current_user_id(),
248
					microtime( true ),
249
					\Jetpack_Sync_Settings::is_importing(),
250
				)
251
			);
252
		}
253
254
		// since we've added some items, let's try to load the sender so we can send them as quickly as possible
255
		if ( ! \Jetpack_Sync_Actions::$sender ) {
0 ignored issues
show
Bug introduced by
The property sender cannot be accessed from this context as it is declared private in class Jetpack_Sync_Actions.

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...
256
			add_filter( 'jetpack_sync_sender_should_load', '__return_true' );
257
			if ( did_action( 'init' ) ) {
258
				\Jetpack_Sync_Actions::add_sender_shutdown();
259
			}
260
		}
261
	}
262
263
	function get_actor( $current_filter, $args ) {
264
		if ( 'wp_login' === $current_filter ) {
265
			$user = get_user_by( 'ID', $args[1]->data->ID );
266
		} else {
267
			$user = wp_get_current_user();
268
		}
269
270
		$translated_role = \Jetpack::translate_user_to_role( $user );
271
272
		$actor = array(
273
			'wpcom_user_id'    => null,
274
			'external_user_id' => isset( $user->ID ) ? $user->ID : null,
275
			'display_name'     => isset( $user->display_name ) ? $user->display_name : null,
276
			'user_email'       => isset( $user->user_email ) ? $user->user_email : null,
277
			'user_roles'       => isset( $user->roles ) ? $user->roles : null,
278
			'translated_role'  => $translated_role ? $translated_role : null,
279
			'is_cron'          => defined( 'DOING_CRON' ) ? DOING_CRON : false,
280
			'is_rest'          => defined( 'REST_API_REQUEST' ) ? REST_API_REQUEST : false,
281
			'is_xmlrpc'        => defined( 'XMLRPC_REQUEST' ) ? XMLRPC_REQUEST : false,
282
			'is_wp_rest'       => defined( 'REST_REQUEST' ) ? REST_REQUEST : false,
283
			'is_ajax'          => defined( 'DOING_AJAX' ) ? DOING_AJAX : false,
284
			'is_wp_admin'      => is_admin(),
285
			'is_cli'           => defined( 'WP_CLI' ) ? WP_CLI : false,
286
			'from_url'         => $this->get_request_url(),
287
		);
288
289
		if ( $this->should_send_user_data_with_actor( $current_filter ) ) {
290
			require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
291
			$actor['ip']         = jetpack_protect_get_ip();
292
			$actor['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown';
293
		}
294
295
		return $actor;
296
	}
297
298
	function should_send_user_data_with_actor( $current_filter ) {
299
		$should_send = in_array( $current_filter, array( 'jetpack_wp_login', 'wp_logout', 'jetpack_valid_failed_login_attempt' ) );
300
		/**
301
		 * Allow or deny sending actor's user data ( IP and UA ) during a sync event
302
		 *
303
		 * @since 5.8.0
304
		 *
305
		 * @module sync
306
		 *
307
		 * @param bool True if we should send user data
308
		 * @param string The current filter that is performing the sync action
309
		 */
310
		return apply_filters( 'jetpack_sync_actor_user_data', $should_send, $current_filter );
311
	}
312
313
	function set_defaults() {
314
		$this->sync_queue      = new \Jetpack_Sync_Queue( 'sync' );
315
		$this->full_sync_queue = new \Jetpack_Sync_Queue( 'full_sync' );
316
		$this->set_queue_size_limit( \Jetpack_Sync_Settings::get_setting( 'max_queue_size' ) );
317
		$this->set_queue_lag_limit( \Jetpack_Sync_Settings::get_setting( 'max_queue_lag' ) );
318
	}
319
320
	function get_request_url() {
321
		if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
322
			return 'http' . ( isset( $_SERVER['HTTPS'] ) ? 's' : '' ) . '://' . "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
323
		}
324
		return is_admin() ? get_admin_url( get_current_blog_id() ) : home_url();
325
	}
326
}
327