Completed
Push — update/use-password-checker-pa... ( 715ed1 )
by
unknown
38:40 queued 28:29
created

Users   F

Complexity

Total Complexity 91

Size/Duplication

Total Lines 849
Duplicated Lines 7.89 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
dl 67
loc 849
rs 1.751
c 0
b 0
f 0
wmc 91
lcom 2
cbo 1

39 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A table_name() 0 3 1
A id_field() 0 3 1
A get_object_by_id() 0 10 3
A init_listeners() 0 43 1
A init_full_sync_listeners() 0 3 1
A init_before_send() 0 7 1
A get_user() 0 9 3
A sanitize_user() 0 11 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 16 4
A wp_login_handler() 0 13 1
A authenticate_handler() 0 30 5
A deleted_user_handler() 0 13 1
A user_register_handler() 20 20 3
A add_user_to_blog_handler() 20 20 3
B save_user_handler() 0 40 8
A save_user_role_handler() 0 19 3
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() 15 15 2
A get_where_sql() 12 12 2
A get_full_sync_actions() 0 3 1
A get_initial_sync_user_config() 0 12 2
A expand_users() 0 17 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 is_function_in_backtrace() 0 23 5

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 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 Users, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Users sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Constants as Jetpack_Constants;
11
use Automattic\Jetpack\Password_Checker;
12
use Automattic\Jetpack\Sync\Defaults;
13
namespace Automattic\Jetpack;
14
15
/**
16
 * Class to handle sync for users.
17
 */
18
class Users extends Module {
19
	/**
20
	 * Maximum number of users to sync initially.
21
	 *
22
	 * @var int
23
	 */
24
	const MAX_INITIAL_SYNC_USERS = 100;
25
26
	/**
27
	 * User flags we care about.
28
	 *
29
	 * @access protected
30
	 *
31
	 * @var array
32
	 */
33
	protected $flags = array();
34
35
	/**
36
	 * Sync module name.
37
	 *
38
	 * @access public
39
	 *
40
	 * @return string
41
	 */
42
	public function name() {
43
		return 'users';
44
	}
45
46
	/**
47
	 * The table in the database.
48
	 *
49
	 * @access public
50
	 *
51
	 * @return string
52
	 */
53
	public function table_name() {
54
		return 'usermeta';
55
	}
56
57
	/**
58
	 * The id field in the database.
59
	 *
60
	 * @access public
61
	 *
62
	 * @return string
63
	 */
64
	public function id_field() {
65
		return 'user_id';
66
	}
67
68
	/**
69
	 * Retrieve a user by its ID.
70
	 * This is here to support the backfill API.
71
	 *
72
	 * @access public
73
	 *
74
	 * @param string $object_type Type of the sync object.
75
	 * @param int    $id          ID of the sync object.
76
	 * @return \WP_User|bool Filtered \WP_User object, or false if the object is not a user.
77
	 */
78
	public function get_object_by_id( $object_type, $id ) {
79
		if ( 'user' === $object_type ) {
80
			$user = get_user_by( 'id', (int) $id );
81
			if ( $user ) {
82
				return $this->sanitize_user_and_expand( $user );
83
			}
84
		}
85
86
		return false;
87
	}
88
89
	/**
90
	 * Initialize users action listeners.
91
	 *
92
	 * @access public
93
	 *
94
	 * @param callable $callable Action handler callable.
95
	 */
96
	public function init_listeners( $callable ) {
97
		// Users.
98
		add_action( 'user_register', array( $this, 'user_register_handler' ) );
99
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
100
101
		add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
102
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
103
104
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
105
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
106
107
		add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
108
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
109
110
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
111
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
112
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
113
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
114
115
		// User roles.
116
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
117
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
118
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
119
120
		// User capabilities.
121
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
122
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
123
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
124
125
		// User authentication.
126
		add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 );
127
		add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 );
128
129
		add_action( 'jetpack_wp_login', $callable, 10, 3 );
130
131
		add_action( 'wp_logout', $callable, 10, 0 );
132
		add_action( 'wp_masterbar_logout', $callable, 10, 1 );
133
134
		// Add on init.
135
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
136
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
137
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
138
	}
139
140
	/**
141
	 * Initialize users action listeners for full sync.
142
	 *
143
	 * @access public
144
	 *
145
	 * @param callable $callable Action handler callable.
146
	 */
147
	public function init_full_sync_listeners( $callable ) {
148
		add_action( 'jetpack_full_sync_users', $callable );
149
	}
150
151
	/**
152
	 * Initialize the module in the sender.
153
	 *
154
	 * @access public
155
	 */
156
	public function init_before_send() {
157
		add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
158
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
159
160
		// Full sync.
161
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
162
	}
163
164
	/**
165
	 * Retrieve a user by a user ID or object.
166
	 *
167
	 * @access private
168
	 *
169
	 * @param mixed $user User object or ID.
170
	 * @return \WP_User User object, or `null` if user invalid/not found.
171
	 */
172
	private function get_user( $user ) {
173
		if ( is_numeric( $user ) ) {
174
			$user = get_user_by( 'id', $user );
175
		}
176
		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...
177
			return $user;
178
		}
179
		return null;
180
	}
181
182
	/**
183
	 * Sanitize a user object.
184
	 * Removes the password from the user object because we don't want to sync it.
185
	 *
186
	 * @access public
187
	 *
188
	 * @todo Refactor `serialize`/`unserialize` to `wp_json_encode`/`wp_json_decode`.
189
	 *
190
	 * @param \WP_User $user User object.
191
	 * @return \WP_User Sanitized user object.
192
	 */
193
	public function sanitize_user( $user ) {
194
		$user = $this->get_user( $user );
195
		// This creates a new user object and stops the passing of the object by reference.
196
		// // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
197
		$user = unserialize( serialize( $user ) );
198
199
		if ( is_object( $user ) && is_object( $user->data ) ) {
200
			unset( $user->data->user_pass );
201
		}
202
		return $user;
203
	}
204
205
	/**
206
	 * Expand a particular user.
207
	 *
208
	 * @access public
209
	 *
210
	 * @param \WP_User $user User object.
211
	 * @return \WP_User Expanded user object.
212
	 */
213
	public function expand_user( $user ) {
214
		if ( ! is_object( $user ) ) {
215
			return null;
216
		}
217
		$user->allowed_mime_types = get_allowed_mime_types( $user );
218
		$user->allcaps            = $this->get_real_user_capabilities( $user );
219
220
		// Only set the user locale if it is different from the site locale.
221
		if ( get_locale() !== get_user_locale( $user->ID ) ) {
222
			$user->locale = get_user_locale( $user->ID );
223
		}
224
225
		return $user;
226
	}
227
228
	/**
229
	 * Retrieve capabilities we care about for a particular user.
230
	 *
231
	 * @access public
232
	 *
233
	 * @param \WP_User $user User object.
234
	 * @return array User capabilities.
235
	 */
236
	public function get_real_user_capabilities( $user ) {
237
		$user_capabilities = array();
238
		if ( is_wp_error( $user ) ) {
239
			return $user_capabilities;
240
		}
241
		foreach ( Defaults::get_capabilities_whitelist() as $capability ) {
242
			if ( user_can( $user, $capability ) ) {
243
				$user_capabilities[ $capability ] = true;
244
			}
245
		}
246
		return $user_capabilities;
247
	}
248
249
	/**
250
	 * Retrieve, expand and sanitize a user.
251
	 * Can be directly used in the sync user action handlers.
252
	 *
253
	 * @access public
254
	 *
255
	 * @param mixed $user User ID or user object.
256
	 * @return \WP_User Expanded and sanitized user object.
257
	 */
258
	public function sanitize_user_and_expand( $user ) {
259
		$user = $this->get_user( $user );
260
		$user = $this->expand_user( $user );
261
		return $this->sanitize_user( $user );
262
	}
263
264
	/**
265
	 * Expand the user within a hook before it is serialized and sent to the server.
266
	 *
267
	 * @access public
268
	 *
269
	 * @param array $args The hook arguments.
270
	 * @return array $args The hook arguments.
271
	 */
272
	public function expand_action( $args ) {
273
		// The first argument is always the user.
274
		list( $user ) = $args;
275
		if ( $user ) {
276
			$args[0] = $this->sanitize_user_and_expand( $user );
277
			return $args;
278
		}
279
280
		return false;
281
	}
282
283
	/**
284
	 * Expand the user username at login before being sent to the server.
285
	 *
286
	 * @access public
287
	 *
288
	 * @param array $args The hook arguments.
289
	 * @return array $args Expanded hook arguments.
290
	 */
291
	public function expand_login_username( $args ) {
292
		list( $login, $user, $flags ) = $args;
293
		$user                         = $this->sanitize_user( $user );
294
295
		return array( $login, $user, $flags );
296
	}
297
298
	/**
299
	 * Expand the user username at logout before being sent to the server.
300
	 *
301
	 * @access public
302
	 *
303
	 * @param  array $args The hook arguments.
304
	 * @param  int   $user_id ID of the user.
305
	 * @return array $args Expanded hook arguments.
306
	 */
307
	public function expand_logout_username( $args, $user_id ) {
308
		$user = get_userdata( $user_id );
309
		$user = $this->sanitize_user( $user );
310
311
		$login = '';
312
		if ( is_object( $user ) && is_object( $user->data ) ) {
313
			$login = $user->data->user_login;
314
		}
315
316
		// If we don't have a user here lets not send anything.
317
		if ( empty( $login ) ) {
318
			return false;
319
		}
320
321
		return array( $login, $user );
322
	}
323
324
	/**
325
	 * Additional processing is needed for wp_login so we introduce this wrapper handler.
326
	 *
327
	 * @access public
328
	 *
329
	 * @param string   $user_login The user login.
330
	 * @param \WP_User $user       The user object.
331
	 */
332
	public function wp_login_handler( $user_login, $user ) {
333
		/**
334
		 * Fires when a user is logged into a site.
335
		 *
336
		 * @since 7.2.0
337
		 *
338
		 * @param int      $user_id The user ID.
339
		 * @param \WP_User $user    The User Object  of the user that currently logged in.
340
		 * @param array    $params  Any Flags that have been added during login.
341
		 */
342
		do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) );
343
		$this->clear_flags( $user->ID );
344
	}
345
346
	/**
347
	 * A hook for the authenticate event that checks the password strength.
348
	 *
349
	 * @access public
350
	 *
351
	 * @param \WP_Error|\WP_User $user     The user object, or an error.
352
	 * @param string             $username The username.
353
	 * @param string             $password The password used to authenticate.
354
	 * @return \WP_Error|\WP_User the same object that was passed into the function.
355
	 */
356
	public function authenticate_handler( $user, $username, $password ) {
357
		// In case of cookie authentication we don't do anything here.
358
		if ( empty( $password ) ) {
359
			return $user;
360
		}
361
362
		// We are only interested in successful authentication events.
363
		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...
364
			return $user;
365
		}
366
367
		$password_checker = new Password_Checker( $user->ID );
368
369
		$test_results = $password_checker->test( $password, true );
370
371
		// If the password passes tests, we don't do anything.
372
		if ( empty( $test_results['test_results']['failed'] ) ) {
373
			return $user;
374
		}
375
376
		$this->add_flags(
377
			$user->ID,
378
			array(
379
				'warning'  => 'The password failed at least one strength test.',
380
				'failures' => $test_results['test_results']['failed'],
381
			)
382
		);
383
384
		return $user;
385
	}
386
387
	/**
388
	 * Handler for after the user is deleted.
389
	 *
390
	 * @access public
391
	 *
392
	 * @param int $deleted_user_id    ID of the deleted user.
393
	 * @param int $reassigned_user_id ID of the user the deleted user's posts are reassigned to (if any).
0 ignored issues
show
Documentation introduced by
Should the type for parameter $reassigned_user_id not be string|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
394
	 */
395
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
396
		$is_multisite = is_multisite();
397
		/**
398
		 * Fires when a user is deleted on a site
399
		 *
400
		 * @since 5.4.0
401
		 *
402
		 * @param int $deleted_user_id - ID of the deleted user.
403
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts are reassigned to (if any).
404
		 * @param bool $is_multisite - Whether this site is a multisite installation.
405
		 */
406
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
407
	}
408
409
	/**
410
	 * Handler for user registration.
411
	 *
412
	 * @access public
413
	 *
414
	 * @param int $user_id ID of the deleted user.
415
	 */
416 View Code Duplication
	public function user_register_handler( $user_id ) {
417
		// Ensure we only sync users who are members of the current blog.
418
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
419
			return;
420
		}
421
422
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
423
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
424
		}
425
		/**
426
		 * Fires when a new user is registered on a site
427
		 *
428
		 * @since 4.9.0
429
		 *
430
		 * @param object The WP_User object
431
		 */
432
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
433
		$this->clear_flags( $user_id );
434
435
	}
436
437
	/**
438
	 * Handler for user addition to the current blog.
439
	 *
440
	 * @access public
441
	 *
442
	 * @param int $user_id ID of the user.
443
	 */
444 View Code Duplication
	public function add_user_to_blog_handler( $user_id ) {
445
		// Ensure we only sync users who are members of the current blog.
446
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
447
			return;
448
		}
449
450
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
451
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
452
		}
453
454
		/**
455
		 * Fires when a user is added on a site
456
		 *
457
		 * @since 4.9.0
458
		 *
459
		 * @param object The WP_User object
460
		 */
461
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
462
		$this->clear_flags( $user_id );
463
	}
464
465
	/**
466
	 * Handler for user save.
467
	 *
468
	 * @access public
469
	 *
470
	 * @param int      $user_id ID of the user.
471
	 * @param \WP_User $old_user_data User object before the changes.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $old_user_data not be \WP_User|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
472
	 */
473
	public function save_user_handler( $user_id, $old_user_data = null ) {
474
		// Ensure we only sync users who are members of the current blog.
475
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
476
			return;
477
		}
478
479
		$user = get_user_by( 'id', $user_id );
480
481
		// Older versions of WP don't pass the old_user_data in ->data.
482
		if ( isset( $old_user_data->data ) ) {
483
			$old_user = $old_user_data->data;
484
		} else {
485
			$old_user = $old_user_data;
486
		}
487
488
		if ( null !== $old_user && $user->user_pass !== $old_user->user_pass ) {
489
			$this->flags[ $user_id ]['password_changed'] = true;
490
		}
491
		if ( null !== $old_user && $user->data->user_email !== $old_user->user_email ) {
492
			/**
493
			 * The '_new_email' user meta is deleted right after the call to wp_update_user
494
			 * that got us to this point so if it's still set then this was a user confirming
495
			 * their new email address.
496
			 */
497
			if ( 1 === (int) get_user_meta( $user->ID, '_new_email', true ) ) {
498
				$this->flags[ $user_id ]['email_changed'] = true;
499
			}
500
		}
501
502
		/**
503
		 * Fires when the client needs to sync an updated user.
504
		 *
505
		 * @since 4.2.0
506
		 *
507
		 * @param \WP_User The WP_User object
508
		 * @param array    State - New since 5.8.0
509
		 */
510
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
511
		$this->clear_flags( $user_id );
512
	}
513
514
	/**
515
	 * Handler for user role change.
516
	 *
517
	 * @access public
518
	 *
519
	 * @param int    $user_id   ID of the user.
520
	 * @param string $role      New user role.
521
	 * @param array  $old_roles Previous user roles.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $old_roles not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
522
	 */
523
	public function save_user_role_handler( $user_id, $role, $old_roles = null ) {
524
		$this->add_flags(
525
			$user_id,
526
			array(
527
				'role_changed'  => true,
528
				'previous_role' => $old_roles,
529
			)
530
		);
531
532
		// The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both.
533
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
534
			return;
535
		}
536
		/**
537
		 * This action is documented already in this file
538
		 */
539
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
540
		$this->clear_flags( $user_id );
541
	}
542
543
	/**
544
	 * Retrieve current flags for a particular user.
545
	 *
546
	 * @access public
547
	 *
548
	 * @param int $user_id ID of the user.
549
	 * @return array Current flags of the user.
550
	 */
551
	public function get_flags( $user_id ) {
552
		if ( isset( $this->flags[ $user_id ] ) ) {
553
			return $this->flags[ $user_id ];
554
		}
555
		return array();
556
	}
557
558
	/**
559
	 * Clear the flags of a particular user.
560
	 *
561
	 * @access public
562
	 *
563
	 * @param int $user_id ID of the user.
564
	 */
565
	public function clear_flags( $user_id ) {
566
		if ( isset( $this->flags[ $user_id ] ) ) {
567
			unset( $this->flags[ $user_id ] );
568
		}
569
	}
570
571
	/**
572
	 * Add flags to a particular user.
573
	 *
574
	 * @access public
575
	 *
576
	 * @param int   $user_id ID of the user.
577
	 * @param array $flags   New flags to add for the user.
578
	 */
579
	public function add_flags( $user_id, $flags ) {
580
		$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
0 ignored issues
show
Documentation introduced by
$this->get_flags($user_id) is of type array, 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...
581
	}
582
583
	/**
584
	 * Save the user meta, if we're interested in it.
585
	 * Also uses the time to add flags for the user.
586
	 *
587
	 * @access public
588
	 *
589
	 * @param int    $meta_id  ID of the meta object.
590
	 * @param int    $user_id  ID of the user.
591
	 * @param string $meta_key Meta key.
592
	 * @param mixed  $value    Meta value.
593
	 */
594
	public function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
595
		if ( 'locale' === $meta_key ) {
596
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
597
		}
598
599
		$user = get_user_by( 'id', $user_id );
600
		if ( isset( $user->cap_key ) && $meta_key === $user->cap_key ) {
601
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
602
		}
603
604
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
605
			return;
606
		}
607
608
		if ( isset( $this->flags[ $user_id ] ) ) {
609
			/**
610
			 * This action is documented already in this file
611
			 */
612
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
613
		}
614
	}
615
616
	/**
617
	 * Enqueue the users actions for full sync.
618
	 *
619
	 * @access public
620
	 *
621
	 * @param array   $config               Full sync configuration for this sync module.
622
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
623
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
624
	 * @return array Number of actions enqueued, and next module state.
625
	 */
626
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
627
		global $wpdb;
628
629
		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 );
630
	}
631
632
	/**
633
	 * Retrieve an estimated number of actions that will be enqueued.
634
	 *
635
	 * @access public
636
	 *
637
	 * @todo Refactor to prepare the SQL query before executing it.
638
	 *
639
	 * @param array $config Full sync configuration for this sync module.
640
	 * @return array Number of items yet to be enqueued.
641
	 */
642 View Code Duplication
	public function estimate_full_sync_actions( $config ) {
643
		global $wpdb;
644
645
		$query = "SELECT count(*) FROM $wpdb->usermeta";
646
647
		$where_sql = $this->get_where_sql( $config );
648
		if ( $where_sql ) {
649
			$query .= ' WHERE ' . $where_sql;
650
		}
651
652
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
653
		$count = $wpdb->get_var( $query );
654
655
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
656
	}
657
658
	/**
659
	 * Retrieve the WHERE SQL clause based on the module config.
660
	 *
661
	 * @access public
662
	 *
663
	 * @param array $config Full sync configuration for this sync module.
664
	 * @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
665
	 */
666 View Code Duplication
	public function get_where_sql( $config ) {
667
		global $wpdb;
668
669
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
670
671
		// The $config variable is a list of user IDs to sync.
672
		if ( is_array( $config ) ) {
673
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
674
		}
675
676
		return $query;
677
	}
678
679
	/**
680
	 * Retrieve the actions that will be sent for this module during a full sync.
681
	 *
682
	 * @access public
683
	 *
684
	 * @return array Full sync actions of this module.
685
	 */
686
	public function get_full_sync_actions() {
687
		return array( 'jetpack_full_sync_users' );
688
	}
689
690
	/**
691
	 * Retrieve initial sync user config.
692
	 *
693
	 * @access public
694
	 *
695
	 * @todo Refactor the SQL query to call $wpdb->prepare() before execution.
696
	 *
697
	 * @return array|boolean IDs of users to initially sync, or false if tbe number of users exceed the maximum.
698
	 */
699
	public function get_initial_sync_user_config() {
700
		global $wpdb;
701
702
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
703
		$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 ) );
704
705
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
706
			return $user_ids;
707
		} else {
708
			return false;
709
		}
710
	}
711
712
	/**
713
	 * Expand the users within a hook before they are serialized and sent to the server.
714
	 *
715
	 * @access public
716
	 *
717
	 * @param array $args The hook arguments.
718
	 * @return array $args The hook arguments.
719
	 */
720
	public function expand_users( $args ) {
721
		list( $user_ids, $previous_end ) = $args;
722
723
		return array(
724
			'users'        => array_map(
725
				array( $this, 'sanitize_user_and_expand' ),
726
				get_users(
727
					array(
728
						'include' => $user_ids,
729
						'orderby' => 'ID',
730
						'order'   => 'DESC',
731
					)
732
				)
733
			),
734
			'previous_end' => $previous_end,
735
		);
736
	}
737
738
	/**
739
	 * Handler for user removal from a particular blog.
740
	 *
741
	 * @access public
742
	 *
743
	 * @param int $user_id ID of the user.
744
	 * @param int $blog_id ID of the blog.
745
	 */
746
	public function remove_user_from_blog_handler( $user_id, $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
747
		// User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114.
748
		if ( $this->is_add_new_user_to_blog() ) {
749
			return;
750
		}
751
752
		$reassigned_user_id = $this->get_reassigned_network_user_id();
753
754
		// 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.
755
		/**
756
		 * Fires when a user is removed from a blog on a multisite installation
757
		 *
758
		 * @since 5.4.0
759
		 *
760
		 * @param int $user_id - ID of the removed user
761
		 * @param int $reassigned_user_id - ID of the user the removed user's posts are reassigned to (if any).
762
		 */
763
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
764
	}
765
766
	/**
767
	 * Whether we're adding a new user to a blog in this request.
768
	 *
769
	 * @access protected
770
	 *
771
	 * @return boolean
772
	 */
773
	protected function is_add_new_user_to_blog() {
774
		return $this->is_function_in_backtrace( 'add_new_user_to_blog' );
775
	}
776
777
	/**
778
	 * Whether we're adding an existing user to a blog in this request.
779
	 *
780
	 * @access protected
781
	 *
782
	 * @return boolean
783
	 */
784
	protected function is_add_user_to_blog() {
785
		return $this->is_function_in_backtrace( 'add_user_to_blog' );
786
	}
787
788
	/**
789
	 * Whether we're removing a user from a blog in this request.
790
	 *
791
	 * @access protected
792
	 *
793
	 * @return boolean
794
	 */
795
	protected function is_delete_user() {
796
		return $this->is_function_in_backtrace( array( 'wp_delete_user', 'remove_user_from_blog' ) );
797
	}
798
799
	/**
800
	 * Whether we're creating a user or adding a new user to a blog.
801
	 *
802
	 * @access protected
803
	 *
804
	 * @return boolean
805
	 */
806
	protected function is_create_user() {
807
		$functions = array(
808
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site.
809
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site.
810
			'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.
811
		);
812
813
		return $this->is_function_in_backtrace( $functions );
814
	}
815
816
	/**
817
	 * Retrieve the ID of the user the removed user's posts are reassigned to (if any).
818
	 *
819
	 * @return int ID of the user that got reassigned as the author of the posts.
820
	 */
821
	protected function get_reassigned_network_user_id() {
822
		$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
823
		foreach ( $backtrace as $call ) {
824
			if (
825
				'remove_user_from_blog' === $call['function'] &&
826
				3 === count( $call['args'] )
827
			) {
828
				return $call['args'][2];
829
			}
830
		}
831
832
		return false;
833
	}
834
835
	/**
836
	 * Checks if one or more function names is in debug_backtrace.
837
	 *
838
	 * @access protected
839
	 *
840
	 * @param array|string $names Mixed string name of function or array of string names of functions.
841
	 * @return bool
842
	 */
843
	protected function is_function_in_backtrace( $names ) {
844
		$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
845
		if ( ! is_array( $names ) ) {
846
			$names = array( $names );
847
		}
848
		$names_as_keys = array_flip( $names );
849
850
		// Do check in constant O(1) time for PHP5.5+.
851
		if ( function_exists( 'array_column' ) ) {
852
			$backtrace_functions         = array_column( $backtrace, 'function' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_columnFound
853
			$backtrace_functions_as_keys = array_flip( $backtrace_functions );
854
			$intersection                = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
855
			return ! empty( $intersection );
856
		}
857
858
		// Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) ).
859
		foreach ( $backtrace as $call ) {
860
			if ( isset( $names_as_keys[ $call['function'] ] ) ) {
861
				return true;
862
			}
863
		}
864
		return false;
865
	}
866
}
867