Completed
Push — renovate/copy-webpack-plugin-5... ( 1645c2...f892fa )
by
unknown
233:24 queued 224:35
created

Users::get_reassigned_network_user_id()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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