Completed
Push — add/jetpack-75-release-changel... ( be7ef8...25f28e )
by Jeremy
367:57 queued 358:58
created

Users::add_user_to_blog_handler()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 19
Ratio 100 %

Importance

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