Completed
Push — add/user-tracking-option-api ( 55d15a...445566 )
by
unknown
47:20 queued 38:47
created

Jetpack_Sync_Module_Users::expand_user()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 7
nop 1
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
class Jetpack_Sync_Module_Users extends Jetpack_Sync_Module {
4
	const MAX_INITIAL_SYNC_USERS = 100;
5
6
	protected $flags = array();
7
8
	function name() {
9
		return 'users';
10
	}
11
12
	// this is here to support the backfill API
13
	public function get_object_by_id( $object_type, $id ) {
14
		if ( $object_type === 'user' && $user = get_user_by( 'id', intval( $id ) ) ) {
15
			return $this->sanitize_user_and_expand( $user );
16
		}
17
18
		return false;
19
	}
20
21
	public function init_listeners( $callable ) {
22
23
		// users
24
		add_action( 'user_register', array( $this, 'user_register_handler' ) );
25
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
26
27
		add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
28
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
29
		add_action( 'jetpack_sync_add_user', array( $this, 'clear_flags' ), 11 );
30
31
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
32
		add_action( 'jetpack_sync_register_user', array( $this, 'clear_flags' ), 11 );
33
34
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
35
		add_action( 'jetpack_sync_save_user', array( $this, 'clear_flags' ), 11 );
36
37
		add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
38
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
39
40
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
41
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
42
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
43
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
44
45
		// user roles
46
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
47
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
48
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
49
50
		// user capabilities
51
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
52
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
53
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
54
55
		// user authentication
56
		add_action( 'wp_login', $callable, 10, 2 );
57
		add_action( 'wp_logout', $callable, 10, 0 );
58
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
59
60
		// Add on init
61
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
62
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
63
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
64
	}
65
66
	public function init_full_sync_listeners( $callable ) {
67
		add_action( 'jetpack_full_sync_users', $callable );
68
	}
69
70
	public function init_before_send() {
71
72
73
		add_filter( 'jetpack_sync_before_send_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
74
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
75
76
		// full sync
77
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
78
	}
79
80
	private function get_user( $user ) {
81
		if ( is_numeric( $user ) ) {
82
			$user = get_user_by( 'id', $user );
83
		}
84
		if ( $user instanceof WP_User ) {
0 ignored issues
show
Bug introduced by
The class WP_User does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
85
			return $user;
86
		}
87
		return null;
88
	}
89
90
	public function sanitize_user( $user ) {
91
		$user = $this->get_user( $user );
92
		// this create a new user object and stops the passing of the object by reference.
93
		$user = unserialize( serialize( $user ) );
94
95
		if ( is_object( $user ) && is_object( $user->data ) ) {
96
			unset( $user->data->user_pass );
97
		}
98
		return $user;
99
	}
100
101
	public function expand_user( $user ) {
102
		if ( ! is_object( $user ) ) {
103
			return null;
104
		}
105
		$user->allowed_mime_types = get_allowed_mime_types( $user );
106
		$user->allcaps = $this->get_real_user_capabilities( $user );
107
108
		if ( function_exists( 'get_user_locale' ) ) {
109
110
			// Only set the user locale if it is different from the site local
111
			if ( get_locale() !== get_user_locale( $user->ID ) ) {
112
				$user->locale = get_user_locale( $user->ID );
113
			}
114
		}
115
		jetpack_require_lib(  'class.jetpack-user-event-tracking' );
116
		if ( Jetpack_User_Event_Tracking::has_value( $user->ID ) ) {
117
			$user->jetpack_tracking_enabled = Jetpack_User_Event_Tracking::is_enabled( $user->ID );
118
		}
119
		return $user;
120
	}
121
122
	public function get_real_user_capabilities( $user ) {
123
		$user_capabilities = array();
124
		if ( is_wp_error( $user ) ) {
125
			return $user_capabilities;
126
		}
127
		foreach( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
128
			if ( $user_has_capabilities = user_can( $user , $capability ) ) {
0 ignored issues
show
Unused Code introduced by
$user_has_capabilities is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
129
				$user_capabilities[ $capability ] = true;
130
			}
131
		}
132
		return $user_capabilities;
133
	}
134
135
	public function sanitize_user_and_expand( $user ) {
136
		$user = $this->get_user( $user );
137
		$user = $this->expand_user( $user );
138
		return $this->sanitize_user( $user );
139
	}
140
141
	public function expand_action( $args ) {
142
		// the first argument is always the user
143
		list( $user ) = $args;
144
		if ( $user ) {
145
			$args[0] = $this->sanitize_user_and_expand( $user );
146
			return $args;
147
		}
148
149
		return false;
150
	}
151
152
	public function expand_login_username( $args ) {
153
		list( $login, $user ) = $args;
154
		$user = $this->sanitize_user( $user );
155
156
		return array( $login, $user );
157
	}
158
159
	public function expand_logout_username( $args, $user_id ) {
160
		$user  = get_userdata( $user_id );
161
		$user  = $this->sanitize_user( $user );
162
163
		$login = '';
164
		if ( is_object( $user ) && is_object( $user->data ) ) {
165
			$login = $user->data->user_login;
166
		}
167
		// if we don't have a user here lets not send anything.
168
		if ( empty( $login ) ) {
169
			return false;
170
		}
171
172
		return array( $login, $user );
173
	}
174
175
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
176
		$is_multisite = is_multisite();
177
		/**
178
		 * Fires when a user is deleted on a site
179
		 *
180
		 * @since 5.4.0
181
		 *
182
		 * @param int $deleted_user_id - ID of the deleted user
183
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
184
		 * @param bool $is_multisite - Whether this site is a multisite installation
185
		 */
186
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
187
	}
188
189 View Code Duplication
	function user_register_handler( $user_id, $old_user_data = null ) {
190
		// ensure we only sync users who are members of the current blog
191
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
192
			return;
193
		}
194
195
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
196
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
197
		}
198
		/**
199
		 * Fires when a new user is registered on a site
200
		 *
201
		 * @since 4.9.0
202
		 *
203
		 * @param object The WP_User object
204
		 */
205
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
206
207
	}
208
209 View Code Duplication
	function add_user_to_blog_handler( $user_id, $old_user_data = null ) {
210
		// ensure we only sync users who are members of the current blog
211
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
212
			return;
213
		}
214
215
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
216
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
217
		}
218
		/**
219
		 * Fires when a user is added on a site
220
		 *
221
		 * @since 4.9.0
222
		 *
223
		 * @param object The WP_User object
224
		 */
225
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
226
	}
227
228
	function save_user_handler( $user_id, $old_user_data = null ) {
229
		// ensure we only sync users who are members of the current blog
230
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
231
			return;
232
		}
233
234
		$user = get_user_by( 'id', $user_id );
235
236
		// Older versions of WP don't pass the old_user_data in ->data
237
		if ( isset( $old_user_data->data ) ) {
238
			$old_user = $old_user_data->data;
239
		} else {
240
			$old_user = $old_user_data;
241
		}
242
		if ( $old_user !== null && $user->user_pass !== $old_user->user_pass ) {
243
			$this->flags[ $user_id ]['password_changed'] = true;
244
		}
245
246
		/**
247
		 * Fires when the client needs to sync an updated user
248
		 *
249
		 * @since 4.2.0
250
		 *
251
		 * @param object The WP_User object
252
		 * @param array state - New since 5.8.0
253
		 */
254
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
255
	}
256
257
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
258
		$this->add_flags( $user_id, array( 'role_changed' => true, 'previous_role' => $old_roles ) );
259
260
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
261
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
262
			return;
263
		}
264
		/**
265
		 * This action is documented already in this file
266
		 */
267
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
268
	}
269
270
	function get_flags( $user_id ) {
271
		if ( isset( $this->flags[ $user_id ] ) ) {
272
			return $this->flags[ $user_id ];
273
		}
274
		return array();
275
	}
276
277
	function clear_flags( $user_id ) {
278
		if ( isset( $this->flags[ $user_id ] ) ) {
279
			unset( $this->flags[ $user_id ] );
280
		}
281
	}
282
283
	function add_flags( $user_id, $flags ) {
284
		$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
285
	}
286
287
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
288
		if ( $meta_key === 'locale' ) {
289
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
290
		}
291
292
		jetpack_require_lib( 'class.jetpack-user-event-tracking' );
293
		if ( $meta_key === Jetpack_User_Event_Tracking::$KEY ) {
294
			$this->add_flags( $user_id, array( 'tracking_opt_out_changed' => true ) );
295
		}
296
297
		$user = get_user_by( 'id', $user_id );
298
		if ( $meta_key === $user->cap_key  ) {
299
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
300
		}
301
302
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
303
			return;
304
		}
305
306
		if ( isset( $this->flags[ $user_id ] ) ) {
307
			/**
308
			 * This action is documented already in this file
309
			 */
310
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
311
		}
312
	}
313
314
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
315
		global $wpdb;
316
317
		return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->usermeta, 'user_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
318
	}
319
320
	public function estimate_full_sync_actions( $config ) {
321
		global $wpdb;
322
323
		$query = "SELECT count(*) FROM $wpdb->usermeta";
324
325
		if ( $where_sql = $this->get_where_sql( $config ) ) {
326
			$query .= ' WHERE ' . $where_sql;
327
		}
328
329
		$count = $wpdb->get_var( $query );
330
331
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
332
	}
333
334 View Code Duplication
	private function get_where_sql( $config ) {
335
		global $wpdb;
336
337
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
338
339
		// config is a list of user IDs to sync
340
		if ( is_array( $config ) ) {
341
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
342
		}
343
344
		return $query;
345
	}
346
347
	function get_full_sync_actions() {
348
		return array( 'jetpack_full_sync_users' );
349
	}
350
351
	function get_initial_sync_user_config() {
352
		global $wpdb;
353
354
		$user_ids = $wpdb->get_col( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0 LIMIT " . ( self::MAX_INITIAL_SYNC_USERS + 1 ) );
355
356
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
357
			return $user_ids;
358
		} else {
359
			return false;
360
		}
361
	}
362
363
	public function expand_users( $args ) {
364
		$user_ids = $args[0];
365
366
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
367
	}
368
369
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
370
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
371
		if ( $this->is_add_new_user_to_blog() ) {
372
			return;
373
		}
374
375
		$reassigned_user_id = $this->get_reassigned_network_user_id();
376
377
		//Note that we are in the context of the blog the user is removed from, see https://github.com/WordPress/WordPress/blob/473e1ba73bc5c18c72d7f288447503713d518790/wp-includes/ms-functions.php#L233
378
		/**
379
		 * Fires when a user is removed from a blog on a multisite installation
380
		 *
381
		 * @since 5.4.0
382
		 *
383
		 * @param int $user_id - ID of the removed user
384
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
385
		 */
386
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
387
	}
388
389
	private function is_add_new_user_to_blog() {
390
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
391
	}
392
393
	private function is_add_user_to_blog() {
394
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
395
	}
396
397
	private function is_delete_user() {
398
		return Jetpack::is_function_in_backtrace( array( 'wp_delete_user' , 'remove_user_from_blog' ) );
399
	}
400
401
	private function is_create_user() {
402
		$functions = array(
403
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
404
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
405
			'wp_insert_user', // Used to suppress jetpack_sync_save_user in save_user_cap_handler and save_user_role_handler when user registered on single site
406
		);
407
408
		return Jetpack::is_function_in_backtrace( $functions );
409
	}
410
411
	private function get_reassigned_network_user_id() {
412
		$backtrace = debug_backtrace( false );
413
		foreach ( $backtrace as $call ) {
414
			if (
415
				'remove_user_from_blog' === $call['function'] &&
416
				3 === count( $call['args'] )
417
			) {
418
				return $call['args'][2];
419
			}
420
		}
421
422
		return false;
423
	}
424
}
425