Completed
Push — add/module-registration-refact... ( af2fe6...74b9d5 )
by
unknown
295:53 queued 288:51
created

Jetpack_Sync_Module_Users   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 490
Duplicated Lines 10.41 %

Coupling/Cohesion

Components 3
Dependencies 5

Importance

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

36 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A get_object_by_id() 0 7 3
A init_listeners() 0 44 1
A init_full_sync_listeners() 0 3 1
A init_before_send() 0 8 1
A get_user() 0 9 3
A sanitize_user() 0 10 3
A expand_user() 0 14 3
A get_real_user_capabilities() 0 12 4
A sanitize_user_and_expand() 0 5 1
A expand_action() 0 10 2
A expand_login_username() 0 6 1
A expand_logout_username() 0 15 4
A get_flags() 0 6 2
A clear_flags() 0 5 2
A add_flags() 0 3 1
B maybe_save_user_meta() 0 21 8
A enqueue_full_sync_actions() 0 5 1
A estimate_full_sync_actions() 0 13 2
A get_where_sql() 12 12 2
A get_full_sync_actions() 0 3 1
A get_initial_sync_user_config() 0 11 2
A expand_users() 0 5 1
A remove_user_from_blog_handler() 0 19 2
A is_add_new_user_to_blog() 0 3 1
A is_add_user_to_blog() 0 3 1
A is_delete_user() 0 3 1
A is_create_user() 0 9 1
A get_reassigned_network_user_id() 0 13 4
A wp_login_handler() 0 13 1
A authenticate_handler() 0 31 5
A deleted_user_handler() 0 13 1
A user_register_handler() 20 20 3
A add_user_to_blog_handler() 19 19 3
B save_user_handler() 0 38 8
A save_user_role_handler() 0 19 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

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
30
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
31
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
32
33
		add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
34
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
35
36
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
37
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
38
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
39
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
40
41
		// user roles
42
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
43
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
44
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
45
46
		// user capabilities
47
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
48
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
49
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
50
51
		// user authentication
52
		add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 );
53
		add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 );
54
55
		add_action( 'jetpack_wp_login', $callable, 10, 3 );
56
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
		add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
73
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
74
75
		// full sync
76
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
77
	}
78
79
	private function get_user( $user ) {
80
		if ( is_numeric( $user ) ) {
81
			$user = get_user_by( 'id', $user );
82
		}
83
		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...
84
			return $user;
85
		}
86
		return null;
87
	}
88
89
	public function sanitize_user( $user ) {
90
		$user = $this->get_user( $user );
91
		// this create a new user object and stops the passing of the object by reference.
92
		$user = unserialize( serialize( $user ) );
93
94
		if ( is_object( $user ) && is_object( $user->data ) ) {
95
			unset( $user->data->user_pass );
96
		}
97
		return $user;
98
	}
99
100
	public function expand_user( $user ) {
101
		if ( ! is_object( $user ) ) {
102
			return null;
103
		}
104
		$user->allowed_mime_types = get_allowed_mime_types( $user );
105
		$user->allcaps            = $this->get_real_user_capabilities( $user );
106
107
		// Only set the user locale if it is different from the site local
108
		if ( get_locale() !== get_user_locale( $user->ID ) ) {
109
			$user->locale = get_user_locale( $user->ID );
110
		}
111
112
		return $user;
113
	}
114
115
	public function get_real_user_capabilities( $user ) {
116
		$user_capabilities = array();
117
		if ( is_wp_error( $user ) ) {
118
			return $user_capabilities;
119
		}
120
		foreach ( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
121
			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...
122
				$user_capabilities[ $capability ] = true;
123
			}
124
		}
125
		return $user_capabilities;
126
	}
127
128
	public function sanitize_user_and_expand( $user ) {
129
		$user = $this->get_user( $user );
130
		$user = $this->expand_user( $user );
131
		return $this->sanitize_user( $user );
132
	}
133
134
	public function expand_action( $args ) {
135
		// the first argument is always the user
136
		list( $user ) = $args;
137
		if ( $user ) {
138
			$args[0] = $this->sanitize_user_and_expand( $user );
139
			return $args;
140
		}
141
142
		return false;
143
	}
144
145
	public function expand_login_username( $args ) {
146
		list( $login, $user, $flags ) = $args;
147
		$user                         = $this->sanitize_user( $user );
148
149
		return array( $login, $user, $flags );
150
	}
151
152
	public function expand_logout_username( $args, $user_id ) {
153
		$user = get_userdata( $user_id );
154
		$user = $this->sanitize_user( $user );
155
156
		$login = '';
157
		if ( is_object( $user ) && is_object( $user->data ) ) {
158
			$login = $user->data->user_login;
159
		}
160
		// if we don't have a user here lets not send anything.
161
		if ( empty( $login ) ) {
162
			return false;
163
		}
164
165
		return array( $login, $user );
166
	}
167
168
	/**
169
	 * Additional processing is needed for wp_login so we introduce this wrapper
170
	 * handler.
171
	 *
172
	 * @param String  $user_login the user login.
173
	 * @param WP_User $user       the user object.
174
	 */
175
	 function wp_login_handler( $user_login, $user ) {
176
		/**
177
		 * Fires when a user is logged into a site.
178
		 *
179
		 * @since 7.2.0
180
		 *
181
		 * @param Numeric $user_id The user ID.
182
		 * @param WP_User $user  The User Object  of the user that currently logged in
183
		 * @param Array   $params Any Flags that have been added during login
184
		 */
185
		do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) );
186
		$this->clear_flags( $user->ID );
187
	}
188
189
	/**
190
	 * A hook for the authenticate event that checks the password strength.
191
	 *
192
	 * @param WP_Error|WP_User $user     the user object, or an error.
193
	 * @param String           $username the username.
194
	 * @param Sting            $password the password used to authenticate.
195
	 * @return WP_Error|WP_User the same object that was passed into the function.
196
	 */
197
	public function authenticate_handler( $user, $username, $password ) {
198
		// In case of cookie authentication we don't do anything here.
199
		if ( empty( $password ) ) {
200
			return $user;
201
		}
202
203
		// We are only interested in successful authentication events.
204
		if ( is_wp_error( $user ) || ! ( $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...
205
			return $user;
206
		}
207
208
		jetpack_require_lib( 'class.jetpack-password-checker' );
209
		$password_checker = new Jetpack_Password_Checker( $user->ID );
210
211
		$test_results = $password_checker->test( $password, true );
212
213
		// If the password passes tests, we don't do anything.
214
		if ( empty( $test_results['test_results']['failed'] ) ) {
215
			return $user;
216
		}
217
218
		$this->add_flags(
219
			$user->ID,
220
			array(
221
				'warning'          => 'The password failed at least one strength test.',
222
				'failures'         => $test_results['test_results']['failed'],
223
			)
224
		);
225
226
		return $user;
227
	}
228
229
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
230
		$is_multisite = is_multisite();
231
		/**
232
		 * Fires when a user is deleted on a site
233
		 *
234
		 * @since 5.4.0
235
		 *
236
		 * @param int $deleted_user_id - ID of the deleted user
237
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
238
		 * @param bool $is_multisite - Whether this site is a multisite installation
239
		 */
240
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
241
	}
242
243 View Code Duplication
	function user_register_handler( $user_id, $old_user_data = null ) {
244
		// ensure we only sync users who are members of the current blog
245
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
246
			return;
247
		}
248
249
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
250
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
251
		}
252
		/**
253
		 * Fires when a new user is registered on a site
254
		 *
255
		 * @since 4.9.0
256
		 *
257
		 * @param object The WP_User object
258
		 */
259
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
260
		$this->clear_flags( $user_id );
261
262
	}
263
264 View Code Duplication
	function add_user_to_blog_handler( $user_id, $old_user_data = null ) {
265
		// ensure we only sync users who are members of the current blog
266
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
267
			return;
268
		}
269
270
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
271
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
272
		}
273
		/**
274
		 * Fires when a user is added on a site
275
		 *
276
		 * @since 4.9.0
277
		 *
278
		 * @param object The WP_User object
279
		 */
280
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
281
		$this->clear_flags( $user_id );
282
	}
283
284
	function save_user_handler( $user_id, $old_user_data = null ) {
285
		// ensure we only sync users who are members of the current blog
286
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
287
			return;
288
		}
289
290
		$user = get_user_by( 'id', $user_id );
291
292
		// Older versions of WP don't pass the old_user_data in ->data
293
		if ( isset( $old_user_data->data ) ) {
294
			$old_user = $old_user_data->data;
295
		} else {
296
			$old_user = $old_user_data;
297
		}
298
299
		if ( $old_user !== null && $user->user_pass !== $old_user->user_pass ) {
300
			$this->flags[ $user_id ]['password_changed'] = true;
301
		}
302
		if ( $old_user !== null && $user->data->user_email !== $old_user->user_email ) {
303
			// The '_new_email' user meta is deleted right after the call to wp_update_user
304
			// that got us to this point so if it's still set then this was a user confirming
305
			// their new email address
306
			if ( 1 === intval( get_user_meta( $user->ID, '_new_email', true ) ) ) {
307
				$this->flags[ $user_id ]['email_changed'] = true;
308
			}
309
		}
310
311
		/**
312
		 * Fires when the client needs to sync an updated user
313
		 *
314
		 * @since 4.2.0
315
		 *
316
		 * @param object The WP_User object
317
		 * @param array state - New since 5.8.0
318
		 */
319
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
320
		$this->clear_flags( $user_id );
321
	}
322
323
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
324
		$this->add_flags(
325
			$user_id,
326
			array(
327
				'role_changed'  => true,
328
				'previous_role' => $old_roles,
329
			)
330
		);
331
332
		// The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
333
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
334
			return;
335
		}
336
		/**
337
		 * This action is documented already in this file
338
		 */
339
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
340
		$this->clear_flags( $user_id );
341
	}
342
343
	function get_flags( $user_id ) {
344
		if ( isset( $this->flags[ $user_id ] ) ) {
345
			return $this->flags[ $user_id ];
346
		}
347
		return array();
348
	}
349
350
	function clear_flags( $user_id ) {
351
		if ( isset( $this->flags[ $user_id ] ) ) {
352
			unset( $this->flags[ $user_id ] );
353
		}
354
	}
355
356
	function add_flags( $user_id, $flags ) {
357
		$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
358
	}
359
360
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
361
		if ( $meta_key === 'locale' ) {
362
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
363
		}
364
365
		$user = get_user_by( 'id', $user_id );
366
		if ( isset( $user->cap_key ) && $meta_key === $user->cap_key ) {
367
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
368
		}
369
370
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
371
			return;
372
		}
373
374
		if ( isset( $this->flags[ $user_id ] ) ) {
375
			/**
376
			 * This action is documented already in this file
377
			 */
378
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
379
		}
380
	}
381
382
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
383
		global $wpdb;
384
385
		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 );
386
	}
387
388
	public function estimate_full_sync_actions( $config ) {
389
		global $wpdb;
390
391
		$query = "SELECT count(*) FROM $wpdb->usermeta";
392
393
		if ( $where_sql = $this->get_where_sql( $config ) ) {
394
			$query .= ' WHERE ' . $where_sql;
395
		}
396
397
		$count = $wpdb->get_var( $query );
398
399
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
400
	}
401
402 View Code Duplication
	private function get_where_sql( $config ) {
403
		global $wpdb;
404
405
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
406
407
		// config is a list of user IDs to sync
408
		if ( is_array( $config ) ) {
409
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
410
		}
411
412
		return $query;
413
	}
414
415
	function get_full_sync_actions() {
416
		return array( 'jetpack_full_sync_users' );
417
	}
418
419
	function get_initial_sync_user_config() {
420
		global $wpdb;
421
422
		$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 ) );
423
424
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
425
			return $user_ids;
426
		} else {
427
			return false;
428
		}
429
	}
430
431
	public function expand_users( $args ) {
432
		$user_ids = $args[0];
433
434
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
435
	}
436
437
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
438
		// User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
439
		if ( $this->is_add_new_user_to_blog() ) {
440
			return;
441
		}
442
443
		$reassigned_user_id = $this->get_reassigned_network_user_id();
444
445
		// 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
446
		/**
447
		 * Fires when a user is removed from a blog on a multisite installation
448
		 *
449
		 * @since 5.4.0
450
		 *
451
		 * @param int $user_id - ID of the removed user
452
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
453
		 */
454
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
455
	}
456
457
	protected function is_add_new_user_to_blog() {
458
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
459
	}
460
461
	protected function is_add_user_to_blog() {
462
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
463
	}
464
465
	protected function is_delete_user() {
466
		return Jetpack::is_function_in_backtrace( array( 'wp_delete_user', 'remove_user_from_blog' ) );
467
	}
468
469
	protected function is_create_user() {
470
		$functions = array(
471
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
472
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
473
			'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
474
		);
475
476
		return Jetpack::is_function_in_backtrace( $functions );
477
	}
478
479
	protected function get_reassigned_network_user_id() {
480
		$backtrace = debug_backtrace( false ); // phpcs:ignore PHPCompatibility.PHP.NewFunctionParameters.debug_backtrace_optionsFound
481
		foreach ( $backtrace as $call ) {
482
			if (
483
				'remove_user_from_blog' === $call['function'] &&
484
				3 === count( $call['args'] )
485
			) {
486
				return $call['args'][2];
487
			}
488
		}
489
490
		return false;
491
	}
492
}
493