Completed
Push — fix/widget-extend-and-fix-on-u... ( c91e85...434ae4 )
by
unknown
10:16
created

Jetpack_Sync_Module_Users   C

Complexity

Total Complexity 72

Size/Duplication

Total Lines 424
Duplicated Lines 2.83 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 72
lcom 2
cbo 3
dl 12
loc 424
rs 5.5667
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A get_object_by_id() 0 7 3
A init_full_sync_listeners() 0 3 1
A init_before_send() 0 10 1
B init_listeners() 0 37 1
A sanitize_user_and_expand() 0 5 1
B get_user() 0 9 5
A sanitize_user() 0 13 4
A add_to_user() 0 16 4
A get_real_user_capabilities() 0 12 4
A expand_user() 0 9 2
A expand_login_username() 0 6 1
A expand_logout_username() 0 15 4
A deleted_user_handler() 0 13 1
A edited_user_handler() 0 10 1
B save_user_handler() 0 57 7
A save_user_role_handler() 0 16 3
B maybe_save_user_meta() 0 25 3
C save_user_cap_handler() 0 28 7
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_create_user() 0 9 1
A get_reassigned_network_user_id() 0 13 4

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
	function name() {
7
		return 'users';
8
	}
9
10
	// this is here to support the backfill API
11
	public function get_object_by_id( $object_type, $id ) {
12
		if ( $object_type === 'user' && $user = get_user_by( 'id', intval( $id ) ) ) {
13
			return $this->sanitize_user_and_expand( $user );
14
		}
15
16
		return false;
17
	}
18
19
	public function init_listeners( $callable ) {
20
		// users
21
		add_action( 'user_register', array( $this, 'save_user_handler' ) );
22
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
23
		add_action( 'add_user_to_blog', array( $this, 'save_user_handler' ) );
24
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
25
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
26
		add_action( 'jetpack_sync_save_user', $callable );
27
28
		//Edit user info, see https://github.com/WordPress/WordPress/blob/c05f1dc805bddcc0e76fd90c4aaf2d9ea76dc0fb/wp-admin/user-edit.php#L126
29
		add_action( 'personal_options_update', array( $this, 'edited_user_handler' ) );
30
		add_action( 'edit_user_profile_update', array( $this, 'edited_user_handler' ) );
31
		add_action( 'jetpack_user_edited', $callable );
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_action( 'wp_login', $callable, 10, 2 );
53
		add_action( 'wp_logout', $callable, 10, 0 );
54
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
55
	}
56
57
	public function init_full_sync_listeners( $callable ) {
58
		add_action( 'jetpack_full_sync_users', $callable );
59
	}
60
61
	public function init_before_send() {
62
		add_filter( 'jetpack_sync_before_send_jetpack_sync_add_user', array( $this, 'expand_user' ) );
63
		add_filter( 'jetpack_sync_before_send_jetpack_sync_register_user', array( $this, 'expand_user' ) );
64
		add_filter( 'jetpack_sync_before_send_jetpack_sync_save_user', array( $this, 'expand_user' ), 10, 2 );
65
		add_filter( 'jetpack_sync_before_send_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
66
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
67
68
		// full sync
69
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
70
	}
71
72
	public function sanitize_user_and_expand( $user ) {
73
		$user = $this->get_user( $user );
74
		$user = $this->add_to_user( $user );
75
		return $this->sanitize_user( $user );
76
	}
77
78
	private function get_user( $user ) {
79
		if ( $user && ! is_object( $user ) && is_numeric( $user ) ) {
80
			$user = get_user_by( 'id', $user );
81
		}
82
		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...
83
			return $user;
84
		}
85
		return null;
86
	}
87
88
	public function sanitize_user( $user ) {
89
		$user = $this->get_user( $user );
90
		// this create a new user object and stops the passing of the object by reference.
91
		$user = unserialize( serialize( $user ) );
92
93
		if ( is_object( $user ) && is_object( $user->data ) ) {
94
			unset( $user->data->user_pass );
95
		}
96
		if ( $user ) {
97
			$user->allcaps = $this->get_real_user_capabilities( $user );
98
		}
99
		return $user;
100
	}
101
102
	public function add_to_user( $user ) {
103
		if ( ! is_object( $user ) ) {
104
			return null;
105
		}
106
		$user->allowed_mime_types = get_allowed_mime_types( $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
116
		return $user;
117
	}
118
119
	public function get_real_user_capabilities( $user ) {
120
		$user_capabilities = array();
121
		if ( is_wp_error( $user ) ) {
122
			return $user_capabilities;
123
		}
124
		foreach( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
125
			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...
126
				$user_capabilities[ $capability ] = true;
127
			}
128
		}
129
		return $user_capabilities;
130
	}
131
132
	public function expand_user( $args ) {
133
		list( $user ) = $args;
134
135
		if ( $user ) {
136
			return array( $this->add_to_user( $user ) );
137
		}
138
139
		return false;
140
	}
141
142
	public function expand_login_username( $args ) {
143
		list( $login, $user ) = $args;
144
		$user = $this->sanitize_user( $user );
145
146
		return array( $login, $user );
147
	}
148
149
	public function expand_logout_username( $args, $user_id ) {
150
		$user  = get_userdata( $user_id );
151
		$user  = $this->sanitize_user( $user );
152
		
153
		$login = '';
154
		if ( is_object( $user ) && is_object( $user->data ) ) {
155
			$login = $user->data->user_login;
156
		}
157
		// if we don't have a user here lets not send anything.
158
		if ( empty( $login ) ) {
159
			return false;
160
		}
161
162
		return array( $login, $user );
163
	}
164
165
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
166
		$is_multisite = is_multisite();
167
		/**
168
		 * Fires when a user is deleted on a site
169
		 *
170
		 * @since 5.4.0
171
		 *
172
		 * @param int $deleted_user_id - ID of the deleted user
173
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
174
		 * @param bool $is_multisite - Whether this site is a multisite installation
175
		 */
176
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
177
	}
178
179
	public function edited_user_handler( $user_id ) {
180
		/**
181
		 * Fires when a user is edited on a site
182
		 *
183
		 * @since 5.4.0
184
		 *
185
		 * @param int $user_id - ID of the edited user
186
		 */
187
		do_action( 'jetpack_user_edited', $user_id );
188
	}
189
	
190
	function save_user_handler( $user_id, $old_user_data = null ) {
191
		// ensure we only sync users who are members of the current blog
192
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
193
			return;
194
		}
195
196
		$user = $this->sanitize_user( get_user_by( 'id', $user_id ) );
197
198
		// Older versions of WP don't pass the old_user_data in ->data
199
		if ( isset( $old_user_data->data ) ) {
200
			$old_user = $old_user_data->data;
201
		} else {
202
			$old_user = $old_user_data;
203
		}
204
205
		if ( $old_user !== null ) {
206
			unset( $old_user->user_pass );
207
			if ( serialize( $old_user ) === serialize( $user->data ) ) {
208
				return;
209
			}
210
		}
211
212
		if ( 'user_register' === current_filter() ) {
213
			/**
214
			 * Fires when a new user is registered on a site
215
			 *
216
			 * @since 4.9.0
217
			 *
218
			 * @param object The WP_User object
219
			 */
220
			do_action( 'jetpack_sync_register_user', $user );
221
222
			return;
223
		}
224
		/* MU Sites add users instead of register them to sites */
225
		if ( 'add_user_to_blog' === current_filter() ) {
226
			/**
227
			 * Fires when a new user is added to a site. (WordPress Multisite)
228
			 *
229
			 * @since 4.9.0
230
			 *
231
			 * @param object The WP_User object
232
			 */
233
			do_action( 'jetpack_sync_add_user', $user );
234
235
			return;
236
		}
237
238
		/**
239
		 * Fires when the client needs to sync an updated user
240
		 *
241
		 * @since 4.2.0
242
		 *
243
		 * @param object The WP_User object
244
		 */
245
		do_action( 'jetpack_sync_save_user', $user );
246
	}
247
248
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
249
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
250
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
251
			return;
252
		}
253
254
		$user = $this->sanitize_user( get_user_by( 'id', $user_id ) );
255
		/**
256
		 * Fires when the client needs to sync an updated user
257
		 *
258
		 * @since 4.2.0
259
		 *
260
		 * @param object The WP_User object
261
		 */
262
		do_action( 'jetpack_sync_save_user', $user );
263
	}
264
265
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
266
		if ( $meta_key === 'locale' ) {
267
			if ( current_filter() === 'deleted_user_meta' ) {
268
				/**
269
				 * Allow listeners to listen for user local delete changes
270
				 *
271
				 * @since 4.8.0
272
				 *
273
				 * @param int $user_id - The ID of the user whos locale is being deleted
274
				 */
275
				do_action( 'jetpack_sync_user_locale_delete', $user_id );
276
			} else {
277
				/**
278
				 * Allow listeners to listen for user local changes
279
				 *
280
				 * @since 4.8.0
281
				 *
282
				 * @param int $user_id - The ID of the user whos locale is being changed
283
				 * @param int $value - The value of the new locale
284
				 */
285
				do_action( 'jetpack_sync_user_locale', $user_id, $value );
286
			}
287
		}
288
		$this->save_user_cap_handler( $meta_id, $user_id, $meta_key, $value );
289
	}
290
291
	function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) {
292
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
293
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
294
			return;
295
		}
296
297
		// if a user is currently being removed as a member of this blog, we don't fire the event
298
		if ( current_filter() === 'deleted_user_meta'
299
		     &&
300
		     preg_match( '/capabilities|user_level/', $meta_key )
301
		     &&
302
		     ! is_user_member_of_blog( $user_id, get_current_blog_id() )
303
		) {
304
			return;
305
		}
306
307
		$user = get_user_by( 'id', $user_id );
308
		if ( $meta_key === $user->cap_key ) {
309
			/**
310
			 * Fires when the client needs to sync an updated user
311
			 *
312
			 * @since 4.2.0
313
			 *
314
			 * @param object The Sanitized WP_User object
315
			 */
316
			do_action( 'jetpack_sync_save_user', $this->sanitize_user( $user ) );
317
		}
318
	}
319
320
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
321
		global $wpdb;
322
323
		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 );
324
	}
325
326
	public function estimate_full_sync_actions( $config ) {
327
		global $wpdb;
328
329
		$query = "SELECT count(*) FROM $wpdb->usermeta";
330
331
		if ( $where_sql = $this->get_where_sql( $config ) ) {
332
			$query .= ' WHERE ' . $where_sql;
333
		}
334
335
		$count = $wpdb->get_var( $query );
336
337
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
338
	}
339
340 View Code Duplication
	private function get_where_sql( $config ) {
341
		global $wpdb;
342
343
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
344
345
		// config is a list of user IDs to sync
346
		if ( is_array( $config ) ) {
347
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
348
		}
349
350
		return $query;
351
	}
352
353
	function get_full_sync_actions() {
354
		return array( 'jetpack_full_sync_users' );
355
	}
356
357
	function get_initial_sync_user_config() {
358
		global $wpdb;
359
360
		$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 ) );
361
362
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
363
			return $user_ids;
364
		} else {
365
			return false;
366
		}
367
	}
368
369
	public function expand_users( $args ) {
370
		$user_ids = $args[0];
371
372
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
373
	}
374
375
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
376
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
377
		if ( $this->is_add_new_user_to_blog() ) {
378
			return;
379
		}
380
381
		$reassigned_user_id = $this->get_reassigned_network_user_id();
382
383
		//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
384
		/**
385
		 * Fires when a user is removed from a blog on a multisite installation
386
		 *
387
		 * @since 5.4.0
388
		 *
389
		 * @param int $user_id - ID of the removed user
390
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
391
		 */
392
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
393
	}
394
395
	private function is_add_new_user_to_blog() {
396
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
397
	}
398
399
	private function is_add_user_to_blog() {
400
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
401
	}
402
403
	private function is_create_user() {
404
		$functions = array(
405
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
406
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
407
			'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
408
		);
409
410
		return Jetpack::is_function_in_backtrace( $functions );
411
	}
412
413
	private function get_reassigned_network_user_id() {
414
		$backtrace = debug_backtrace( false );
415
		foreach ( $backtrace as $call ) {
416
			if (
417
				'remove_user_from_blog' === $call['function'] &&
418
				3 === count( $call['args'] )
419
			) {
420
				return $call['args'][2];
421
			}
422
		}
423
424
		return false;
425
	}
426
}
427