Completed
Push — update/use-password-checker-pa... ( 715ed1...fa7c54 )
by
unknown
76:33 queued 66:52
created

Users::add_user_to_blog_handler()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 20
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 20
loc 20
rs 9.6
c 0
b 0
f 0
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
14
/**
15
 * Class to handle sync for users.
16
 */
17
class Users extends Module {
18
	/**
19
	 * Maximum number of users to sync initially.
20
	 *
21
	 * @var int
22
	 */
23
	const MAX_INITIAL_SYNC_USERS = 100;
24
25
	/**
26
	 * User flags we care about.
27
	 *
28
	 * @access protected
29
	 *
30
	 * @var array
31
	 */
32
	protected $flags = array();
33
34
	/**
35
	 * Sync module name.
36
	 *
37
	 * @access public
38
	 *
39
	 * @return string
40
	 */
41
	public function name() {
42
		return 'users';
43
	}
44
45
	/**
46
	 * The table in the database.
47
	 *
48
	 * @access public
49
	 *
50
	 * @return string
51
	 */
52
	public function table_name() {
53
		return 'usermeta';
54
	}
55
56
	/**
57
	 * The id field in the database.
58
	 *
59
	 * @access public
60
	 *
61
	 * @return string
62
	 */
63
	public function id_field() {
64
		return 'user_id';
65
	}
66
67
	/**
68
	 * Retrieve a user by its ID.
69
	 * This is here to support the backfill API.
70
	 *
71
	 * @access public
72
	 *
73
	 * @param string $object_type Type of the sync object.
74
	 * @param int    $id          ID of the sync object.
75
	 * @return \WP_User|bool Filtered \WP_User object, or false if the object is not a user.
76
	 */
77
	public function get_object_by_id( $object_type, $id ) {
78
		if ( 'user' === $object_type ) {
79
			$user = get_user_by( 'id', (int) $id );
80
			if ( $user ) {
81
				return $this->sanitize_user_and_expand( $user );
82
			}
83
		}
84
85
		return false;
86
	}
87
88
	/**
89
	 * Initialize users action listeners.
90
	 *
91
	 * @access public
92
	 *
93
	 * @param callable $callable Action handler callable.
94
	 */
95
	public function init_listeners( $callable ) {
96
		// Users.
97
		add_action( 'user_register', array( $this, 'user_register_handler' ) );
98
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
99
100
		add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
101
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
102
103
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
104
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
105
106
		add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
107
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
108
109
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
110
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
111
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
112
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
113
114
		// User roles.
115
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
116
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
117
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
118
119
		// User capabilities.
120
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
121
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
122
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
123
124
		// User authentication.
125
		add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 );
126
		add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 );
127
128
		add_action( 'jetpack_wp_login', $callable, 10, 3 );
129
130
		add_action( 'wp_logout', $callable, 10, 0 );
131
		add_action( 'wp_masterbar_logout', $callable, 10, 1 );
132
133
		// Add on init.
134
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
135
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
136
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
137
	}
138
139
	/**
140
	 * Initialize users action listeners for full sync.
141
	 *
142
	 * @access public
143
	 *
144
	 * @param callable $callable Action handler callable.
145
	 */
146
	public function init_full_sync_listeners( $callable ) {
147
		add_action( 'jetpack_full_sync_users', $callable );
148
	}
149
150
	/**
151
	 * Initialize the module in the sender.
152
	 *
153
	 * @access public
154
	 */
155
	public function init_before_send() {
156
		add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
157
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
158
159
		// Full sync.
160
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
161
	}
162
163
	/**
164
	 * Retrieve a user by a user ID or object.
165
	 *
166
	 * @access private
167
	 *
168
	 * @param mixed $user User object or ID.
169
	 * @return \WP_User User object, or `null` if user invalid/not found.
170
	 */
171
	private function get_user( $user ) {
172
		if ( is_numeric( $user ) ) {
173
			$user = get_user_by( 'id', $user );
174
		}
175
		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...
176
			return $user;
177
		}
178
		return null;
179
	}
180
181
	/**
182
	 * Sanitize a user object.
183
	 * Removes the password from the user object because we don't want to sync it.
184
	 *
185
	 * @access public
186
	 *
187
	 * @todo Refactor `serialize`/`unserialize` to `wp_json_encode`/`wp_json_decode`.
188
	 *
189
	 * @param \WP_User $user User object.
190
	 * @return \WP_User Sanitized user object.
191
	 */
192
	public function sanitize_user( $user ) {
193
		$user = $this->get_user( $user );
194
		// This creates a new user object and stops the passing of the object by reference.
195
		// // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
196
		$user = unserialize( serialize( $user ) );
197
198
		if ( is_object( $user ) && is_object( $user->data ) ) {
199
			unset( $user->data->user_pass );
200
		}
201
		return $user;
202
	}
203
204
	/**
205
	 * Expand a particular user.
206
	 *
207
	 * @access public
208
	 *
209
	 * @param \WP_User $user User object.
210
	 * @return \WP_User Expanded user object.
211
	 */
212
	public function expand_user( $user ) {
213
		if ( ! is_object( $user ) ) {
214
			return null;
215
		}
216
		$user->allowed_mime_types = get_allowed_mime_types( $user );
217
		$user->allcaps            = $this->get_real_user_capabilities( $user );
218
219
		// Only set the user locale if it is different from the site locale.
220
		if ( get_locale() !== get_user_locale( $user->ID ) ) {
221
			$user->locale = get_user_locale( $user->ID );
222
		}
223
224
		return $user;
225
	}
226
227
	/**
228
	 * Retrieve capabilities we care about for a particular user.
229
	 *
230
	 * @access public
231
	 *
232
	 * @param \WP_User $user User object.
233
	 * @return array User capabilities.
234
	 */
235
	public function get_real_user_capabilities( $user ) {
236
		$user_capabilities = array();
237
		if ( is_wp_error( $user ) ) {
238
			return $user_capabilities;
239
		}
240
		foreach ( Defaults::get_capabilities_whitelist() as $capability ) {
241
			if ( user_can( $user, $capability ) ) {
242
				$user_capabilities[ $capability ] = true;
243
			}
244
		}
245
		return $user_capabilities;
246
	}
247
248
	/**
249
	 * Retrieve, expand and sanitize a user.
250
	 * Can be directly used in the sync user action handlers.
251
	 *
252
	 * @access public
253
	 *
254
	 * @param mixed $user User ID or user object.
255
	 * @return \WP_User Expanded and sanitized user object.
256
	 */
257
	public function sanitize_user_and_expand( $user ) {
258
		$user = $this->get_user( $user );
259
		$user = $this->expand_user( $user );
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->expand_user($user) on line 259 can be null; however, Automattic\Jetpack\Sync\...es\Users::expand_user() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
260
		return $this->sanitize_user( $user );
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->expand_user($user) on line 259 can be null; however, Automattic\Jetpack\Sync\...\Users::sanitize_user() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
261
	}
262
263
	/**
264
	 * Expand the user within a hook before it is serialized and sent to the server.
265
	 *
266
	 * @access public
267
	 *
268
	 * @param array $args The hook arguments.
269
	 * @return array $args The hook arguments.
270
	 */
271
	public function expand_action( $args ) {
272
		// The first argument is always the user.
273
		list( $user ) = $args;
274
		if ( $user ) {
275
			$args[0] = $this->sanitize_user_and_expand( $user );
276
			return $args;
277
		}
278
279
		return false;
280
	}
281
282
	/**
283
	 * Expand the user username at login before being sent to the server.
284
	 *
285
	 * @access public
286
	 *
287
	 * @param array $args The hook arguments.
288
	 * @return array $args Expanded hook arguments.
289
	 */
290
	public function expand_login_username( $args ) {
291
		list( $login, $user, $flags ) = $args;
292
		$user                         = $this->sanitize_user( $user );
293
294
		return array( $login, $user, $flags );
295
	}
296
297
	/**
298
	 * Expand the user username at logout before being sent to the server.
299
	 *
300
	 * @access public
301
	 *
302
	 * @param  array $args The hook arguments.
303
	 * @param  int   $user_id ID of the user.
304
	 * @return array $args Expanded hook arguments.
305
	 */
306
	public function expand_logout_username( $args, $user_id ) {
307
		$user = get_userdata( $user_id );
308
		$user = $this->sanitize_user( $user );
309
310
		$login = '';
311
		if ( is_object( $user ) && is_object( $user->data ) ) {
312
			$login = $user->data->user_login;
313
		}
314
315
		// If we don't have a user here lets not send anything.
316
		if ( empty( $login ) ) {
317
			return false;
318
		}
319
320
		return array( $login, $user );
321
	}
322
323
	/**
324
	 * Additional processing is needed for wp_login so we introduce this wrapper handler.
325
	 *
326
	 * @access public
327
	 *
328
	 * @param string   $user_login The user login.
329
	 * @param \WP_User $user       The user object.
330
	 */
331
	public function wp_login_handler( $user_login, $user ) {
332
		/**
333
		 * Fires when a user is logged into a site.
334
		 *
335
		 * @since 7.2.0
336
		 *
337
		 * @param int      $user_id The user ID.
338
		 * @param \WP_User $user    The User Object  of the user that currently logged in.
339
		 * @param array    $params  Any Flags that have been added during login.
340
		 */
341
		do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) );
342
		$this->clear_flags( $user->ID );
343
	}
344
345
	/**
346
	 * A hook for the authenticate event that checks the password strength.
347
	 *
348
	 * @access public
349
	 *
350
	 * @param \WP_Error|\WP_User $user     The user object, or an error.
351
	 * @param string             $username The username.
352
	 * @param string             $password The password used to authenticate.
353
	 * @return \WP_Error|\WP_User the same object that was passed into the function.
354
	 */
355
	public function authenticate_handler( $user, $username, $password ) {
356
		// In case of cookie authentication we don't do anything here.
357
		if ( empty( $password ) ) {
358
			return $user;
359
		}
360
361
		// We are only interested in successful authentication events.
362
		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...
363
			return $user;
364
		}
365
366
		$password_checker = new Password_Checker( $user->ID );
367
368
		$test_results = $password_checker->test( $password, true );
369
370
		// If the password passes tests, we don't do anything.
371
		if ( empty( $test_results['test_results']['failed'] ) ) {
372
			return $user;
373
		}
374
375
		$this->add_flags(
376
			$user->ID,
377
			array(
378
				'warning'  => 'The password failed at least one strength test.',
379
				'failures' => $test_results['test_results']['failed'],
380
			)
381
		);
382
383
		return $user;
384
	}
385
386
	/**
387
	 * Handler for after the user is deleted.
388
	 *
389
	 * @access public
390
	 *
391
	 * @param int $deleted_user_id    ID of the deleted user.
392
	 * @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...
393
	 */
394
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
395
		$is_multisite = is_multisite();
396
		/**
397
		 * Fires when a user is deleted on a site
398
		 *
399
		 * @since 5.4.0
400
		 *
401
		 * @param int $deleted_user_id - ID of the deleted user.
402
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts are reassigned to (if any).
403
		 * @param bool $is_multisite - Whether this site is a multisite installation.
404
		 */
405
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
406
	}
407
408
	/**
409
	 * Handler for user registration.
410
	 *
411
	 * @access public
412
	 *
413
	 * @param int $user_id ID of the deleted user.
414
	 */
415 View Code Duplication
	public function user_register_handler( $user_id ) {
416
		// Ensure we only sync users who are members of the current blog.
417
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
418
			return;
419
		}
420
421
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
422
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
423
		}
424
		/**
425
		 * Fires when a new user is registered on a site
426
		 *
427
		 * @since 4.9.0
428
		 *
429
		 * @param object The WP_User object
430
		 */
431
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
432
		$this->clear_flags( $user_id );
433
434
	}
435
436
	/**
437
	 * Handler for user addition to the current blog.
438
	 *
439
	 * @access public
440
	 *
441
	 * @param int $user_id ID of the user.
442
	 */
443 View Code Duplication
	public function add_user_to_blog_handler( $user_id ) {
444
		// Ensure we only sync users who are members of the current blog.
445
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
446
			return;
447
		}
448
449
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
450
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
451
		}
452
453
		/**
454
		 * Fires when a user is added on a site
455
		 *
456
		 * @since 4.9.0
457
		 *
458
		 * @param object The WP_User object
459
		 */
460
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
461
		$this->clear_flags( $user_id );
462
	}
463
464
	/**
465
	 * Handler for user save.
466
	 *
467
	 * @access public
468
	 *
469
	 * @param int      $user_id ID of the user.
470
	 * @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...
471
	 */
472
	public function save_user_handler( $user_id, $old_user_data = null ) {
473
		// Ensure we only sync users who are members of the current blog.
474
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
475
			return;
476
		}
477
478
		$user = get_user_by( 'id', $user_id );
479
480
		// Older versions of WP don't pass the old_user_data in ->data.
481
		if ( isset( $old_user_data->data ) ) {
482
			$old_user = $old_user_data->data;
483
		} else {
484
			$old_user = $old_user_data;
485
		}
486
487
		if ( null !== $old_user && $user->user_pass !== $old_user->user_pass ) {
488
			$this->flags[ $user_id ]['password_changed'] = true;
489
		}
490
		if ( null !== $old_user && $user->data->user_email !== $old_user->user_email ) {
491
			/**
492
			 * The '_new_email' user meta is deleted right after the call to wp_update_user
493
			 * that got us to this point so if it's still set then this was a user confirming
494
			 * their new email address.
495
			 */
496
			if ( 1 === (int) get_user_meta( $user->ID, '_new_email', true ) ) {
497
				$this->flags[ $user_id ]['email_changed'] = true;
498
			}
499
		}
500
501
		/**
502
		 * Fires when the client needs to sync an updated user.
503
		 *
504
		 * @since 4.2.0
505
		 *
506
		 * @param \WP_User The WP_User object
507
		 * @param array    State - New since 5.8.0
508
		 */
509
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
510
		$this->clear_flags( $user_id );
511
	}
512
513
	/**
514
	 * Handler for user role change.
515
	 *
516
	 * @access public
517
	 *
518
	 * @param int    $user_id   ID of the user.
519
	 * @param string $role      New user role.
520
	 * @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...
521
	 */
522
	public function save_user_role_handler( $user_id, $role, $old_roles = null ) {
523
		$this->add_flags(
524
			$user_id,
525
			array(
526
				'role_changed'  => true,
527
				'previous_role' => $old_roles,
528
			)
529
		);
530
531
		// The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both.
532
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
533
			return;
534
		}
535
		/**
536
		 * This action is documented already in this file
537
		 */
538
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
539
		$this->clear_flags( $user_id );
540
	}
541
542
	/**
543
	 * Retrieve current flags for a particular user.
544
	 *
545
	 * @access public
546
	 *
547
	 * @param int $user_id ID of the user.
548
	 * @return array Current flags of the user.
549
	 */
550
	public function get_flags( $user_id ) {
551
		if ( isset( $this->flags[ $user_id ] ) ) {
552
			return $this->flags[ $user_id ];
553
		}
554
		return array();
555
	}
556
557
	/**
558
	 * Clear the flags of a particular user.
559
	 *
560
	 * @access public
561
	 *
562
	 * @param int $user_id ID of the user.
563
	 */
564
	public function clear_flags( $user_id ) {
565
		if ( isset( $this->flags[ $user_id ] ) ) {
566
			unset( $this->flags[ $user_id ] );
567
		}
568
	}
569
570
	/**
571
	 * Add flags to a particular user.
572
	 *
573
	 * @access public
574
	 *
575
	 * @param int   $user_id ID of the user.
576
	 * @param array $flags   New flags to add for the user.
577
	 */
578
	public function add_flags( $user_id, $flags ) {
579
		$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...
580
	}
581
582
	/**
583
	 * Save the user meta, if we're interested in it.
584
	 * Also uses the time to add flags for the user.
585
	 *
586
	 * @access public
587
	 *
588
	 * @param int    $meta_id  ID of the meta object.
589
	 * @param int    $user_id  ID of the user.
590
	 * @param string $meta_key Meta key.
591
	 * @param mixed  $value    Meta value.
592
	 */
593
	public function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
594
		if ( 'locale' === $meta_key ) {
595
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
596
		}
597
598
		$user = get_user_by( 'id', $user_id );
599
		if ( isset( $user->cap_key ) && $meta_key === $user->cap_key ) {
600
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
601
		}
602
603
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
604
			return;
605
		}
606
607
		if ( isset( $this->flags[ $user_id ] ) ) {
608
			/**
609
			 * This action is documented already in this file
610
			 */
611
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
612
		}
613
	}
614
615
	/**
616
	 * Enqueue the users actions for full sync.
617
	 *
618
	 * @access public
619
	 *
620
	 * @param array   $config               Full sync configuration for this sync module.
621
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
622
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
623
	 * @return array Number of actions enqueued, and next module state.
624
	 */
625
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
626
		global $wpdb;
627
628
		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 );
629
	}
630
631
	/**
632
	 * Retrieve an estimated number of actions that will be enqueued.
633
	 *
634
	 * @access public
635
	 *
636
	 * @todo Refactor to prepare the SQL query before executing it.
637
	 *
638
	 * @param array $config Full sync configuration for this sync module.
639
	 * @return array Number of items yet to be enqueued.
640
	 */
641 View Code Duplication
	public function estimate_full_sync_actions( $config ) {
642
		global $wpdb;
643
644
		$query = "SELECT count(*) FROM $wpdb->usermeta";
645
646
		$where_sql = $this->get_where_sql( $config );
647
		if ( $where_sql ) {
648
			$query .= ' WHERE ' . $where_sql;
649
		}
650
651
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
652
		$count = $wpdb->get_var( $query );
653
654
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
655
	}
656
657
	/**
658
	 * Retrieve the WHERE SQL clause based on the module config.
659
	 *
660
	 * @access public
661
	 *
662
	 * @param array $config Full sync configuration for this sync module.
663
	 * @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
664
	 */
665 View Code Duplication
	public function get_where_sql( $config ) {
666
		global $wpdb;
667
668
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
669
670
		// The $config variable is a list of user IDs to sync.
671
		if ( is_array( $config ) ) {
672
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
673
		}
674
675
		return $query;
676
	}
677
678
	/**
679
	 * Retrieve the actions that will be sent for this module during a full sync.
680
	 *
681
	 * @access public
682
	 *
683
	 * @return array Full sync actions of this module.
684
	 */
685
	public function get_full_sync_actions() {
686
		return array( 'jetpack_full_sync_users' );
687
	}
688
689
	/**
690
	 * Retrieve initial sync user config.
691
	 *
692
	 * @access public
693
	 *
694
	 * @todo Refactor the SQL query to call $wpdb->prepare() before execution.
695
	 *
696
	 * @return array|boolean IDs of users to initially sync, or false if tbe number of users exceed the maximum.
697
	 */
698
	public function get_initial_sync_user_config() {
699
		global $wpdb;
700
701
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
702
		$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 ) );
703
704
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
705
			return $user_ids;
706
		} else {
707
			return false;
708
		}
709
	}
710
711
	/**
712
	 * Expand the users within a hook before they are serialized and sent to the server.
713
	 *
714
	 * @access public
715
	 *
716
	 * @param array $args The hook arguments.
717
	 * @return array $args The hook arguments.
718
	 */
719
	public function expand_users( $args ) {
720
		list( $user_ids, $previous_end ) = $args;
721
722
		return array(
723
			'users'        => array_map(
724
				array( $this, 'sanitize_user_and_expand' ),
725
				get_users(
726
					array(
727
						'include' => $user_ids,
728
						'orderby' => 'ID',
729
						'order'   => 'DESC',
730
					)
731
				)
732
			),
733
			'previous_end' => $previous_end,
734
		);
735
	}
736
737
	/**
738
	 * Handler for user removal from a particular blog.
739
	 *
740
	 * @access public
741
	 *
742
	 * @param int $user_id ID of the user.
743
	 * @param int $blog_id ID of the blog.
744
	 */
745
	public function remove_user_from_blog_handler( $user_id, $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
746
		// User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114.
747
		if ( $this->is_add_new_user_to_blog() ) {
748
			return;
749
		}
750
751
		$reassigned_user_id = $this->get_reassigned_network_user_id();
752
753
		// 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.
754
		/**
755
		 * Fires when a user is removed from a blog on a multisite installation
756
		 *
757
		 * @since 5.4.0
758
		 *
759
		 * @param int $user_id - ID of the removed user
760
		 * @param int $reassigned_user_id - ID of the user the removed user's posts are reassigned to (if any).
761
		 */
762
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
763
	}
764
765
	/**
766
	 * Whether we're adding a new user to a blog in this request.
767
	 *
768
	 * @access protected
769
	 *
770
	 * @return boolean
771
	 */
772
	protected function is_add_new_user_to_blog() {
773
		return $this->is_function_in_backtrace( 'add_new_user_to_blog' );
774
	}
775
776
	/**
777
	 * Whether we're adding an existing user to a blog in this request.
778
	 *
779
	 * @access protected
780
	 *
781
	 * @return boolean
782
	 */
783
	protected function is_add_user_to_blog() {
784
		return $this->is_function_in_backtrace( 'add_user_to_blog' );
785
	}
786
787
	/**
788
	 * Whether we're removing a user from a blog in this request.
789
	 *
790
	 * @access protected
791
	 *
792
	 * @return boolean
793
	 */
794
	protected function is_delete_user() {
795
		return $this->is_function_in_backtrace( array( 'wp_delete_user', 'remove_user_from_blog' ) );
796
	}
797
798
	/**
799
	 * Whether we're creating a user or adding a new user to a blog.
800
	 *
801
	 * @access protected
802
	 *
803
	 * @return boolean
804
	 */
805
	protected function is_create_user() {
806
		$functions = array(
807
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site.
808
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site.
809
			'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.
810
		);
811
812
		return $this->is_function_in_backtrace( $functions );
813
	}
814
815
	/**
816
	 * Retrieve the ID of the user the removed user's posts are reassigned to (if any).
817
	 *
818
	 * @return int ID of the user that got reassigned as the author of the posts.
819
	 */
820
	protected function get_reassigned_network_user_id() {
821
		$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
822
		foreach ( $backtrace as $call ) {
823
			if (
824
				'remove_user_from_blog' === $call['function'] &&
825
				3 === count( $call['args'] )
826
			) {
827
				return $call['args'][2];
828
			}
829
		}
830
831
		return false;
832
	}
833
834
	/**
835
	 * Checks if one or more function names is in debug_backtrace.
836
	 *
837
	 * @access protected
838
	 *
839
	 * @param array|string $names Mixed string name of function or array of string names of functions.
840
	 * @return bool
841
	 */
842
	protected function is_function_in_backtrace( $names ) {
843
		$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
844
		if ( ! is_array( $names ) ) {
845
			$names = array( $names );
846
		}
847
		$names_as_keys = array_flip( $names );
848
849
		// Do check in constant O(1) time for PHP5.5+.
850
		if ( function_exists( 'array_column' ) ) {
851
			$backtrace_functions         = array_column( $backtrace, 'function' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_columnFound
852
			$backtrace_functions_as_keys = array_flip( $backtrace_functions );
853
			$intersection                = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
854
			return ! empty( $intersection );
855
		}
856
857
		// Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) ).
858
		foreach ( $backtrace as $call ) {
859
			if ( isset( $names_as_keys[ $call['function'] ] ) ) {
860
				return true;
861
			}
862
		}
863
		return false;
864
	}
865
}
866