Completed
Push — add/sync-user-password-changes ( 6548b6...963622 )
by
unknown
34:29 queued 22:24
created

Jetpack_Sync_Module_Users::expand_save_user()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
class Jetpack_Sync_Module_Users extends Jetpack_Sync_Module {
4
	const MAX_INITIAL_SYNC_USERS = 100;
5
6
	protected $previous_role = array();
7
8
	function name() {
9
		return 'users';
10
	}
11
12
	// this is here to support the backfill API
13
	public function get_object_by_id( $object_type, $id ) {
14
		if ( $object_type === 'user' && $user = get_user_by( 'id', intval( $id ) ) ) {
15
			return $this->sanitize_user_and_expand( $user );
16
		}
17
18
		return false;
19
	}
20
21
	public function init_listeners( $callable ) {
22
		// users
23
		add_action( 'user_register', array( $this, 'save_user_handler' ) );
24
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
25
		add_action( 'add_user_to_blog', array( $this, 'save_user_handler' ) );
26
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
27
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
28
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
29
30
		//Edit user info, see https://github.com/WordPress/WordPress/blob/c05f1dc805bddcc0e76fd90c4aaf2d9ea76dc0fb/wp-admin/user-edit.php#L126
31
		add_action( 'personal_options_update', array( $this, 'edited_user_handler' ) );
32
		add_action( 'edit_user_profile_update', array( $this, 'edited_user_handler' ) );
33
		add_action( 'jetpack_user_edited', $callable );
34
35
		add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
36
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
37
38
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
39
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
40
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
41
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
42
43
		// user roles
44
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
45
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
46
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
47
48
		// user capabilities
49
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
50
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
51
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
52
53
		// user authentication
54
		add_action( 'wp_login', $callable, 10, 2 );
55
		add_action( 'wp_logout', $callable, 10, 0 );
56
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
57
	}
58
59
	public function init_full_sync_listeners( $callable ) {
60
		add_action( 'jetpack_full_sync_users', $callable );
61
	}
62
63
	public function init_before_send() {
64
		add_filter( 'jetpack_sync_before_send_jetpack_sync_add_user', array( $this, 'expand_user' ) );
65
		add_filter( 'jetpack_sync_before_send_jetpack_sync_register_user', array( $this, 'expand_user' ) );
66
		add_filter( 'jetpack_sync_before_send_jetpack_sync_save_user', array( $this, 'expand_save_user' ) );
67
		add_filter( 'jetpack_sync_before_send_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
68
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
69
70
		// full sync
71
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
72
	}
73
74
	public function sanitize_user_and_expand( $user ) {
75
		$user = $this->get_user( $user );
76
		$user = $this->add_to_user( $user );
77
		return $this->sanitize_user( $user );
78
	}
79
80
	private function get_user( $user ) {
81
		if ( $user && ! is_object( $user ) && is_numeric( $user ) ) {
82
			$user = get_user_by( 'id', $user );
83
		}
84
		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...
85
			return $user;
86
		}
87
		return null;
88
	}
89
90
	public function sanitize_user( $user ) {
91
		$user = $this->get_user( $user );
92
		// this create a new user object and stops the passing of the object by reference.
93
		$user = unserialize( serialize( $user ) );
94
95
		if ( is_object( $user ) && is_object( $user->data ) ) {
96
			unset( $user->data->user_pass );
97
		}
98
		if ( $user ) {
99
			$user->allcaps = $this->get_real_user_capabilities( $user );
100
		}
101
		return $user;
102
	}
103
104
	public function add_to_user( $user ) {
105
		if ( ! is_object( $user ) ) {
106
			return null;
107
		}
108
		$user->allowed_mime_types = get_allowed_mime_types( $user );
109
110
		if ( function_exists( 'get_user_locale' ) ) {
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
118
		return $user;
119
	}
120
121
	public function get_real_user_capabilities( $user ) {
122
		$user_capabilities = array();
123
		if ( is_wp_error( $user ) ) {
124
			return $user_capabilities;
125
		}
126
		foreach( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
127
			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...
128
				$user_capabilities[ $capability ] = true;
129
			}
130
		}
131
		return $user_capabilities;
132
	}
133
134
	public function expand_user( $args ) {
135
		list( $user ) = $args;
136
		if ( $user ) {
137
			return array( $this->add_to_user( $user ) );
138
		}
139
140
		return false;
141
	}
142
143
	public function expand_save_user( $args ) {
144
		list( $user, $flags ) = $args;
0 ignored issues
show
Unused Code introduced by
The assignment to $user is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
145
146
		$expanded_user = $this->expand_user( $args );
147
148
		if ( $expanded_user ) {
149
			$expanded_user[] = $flags;
150
		}
151
152
		return $expanded_user;
153
	}
154
155
	public function expand_login_username( $args ) {
156
		list( $login, $user ) = $args;
157
		$user = $this->sanitize_user( $user );
158
159
		return array( $login, $user );
160
	}
161
162
	public function expand_logout_username( $args, $user_id ) {
163
		$user  = get_userdata( $user_id );
164
		$user  = $this->sanitize_user( $user );
165
166
		$login = '';
167
		if ( is_object( $user ) && is_object( $user->data ) ) {
168
			$login = $user->data->user_login;
169
		}
170
		// if we don't have a user here lets not send anything.
171
		if ( empty( $login ) ) {
172
			return false;
173
		}
174
175
		return array( $login, $user );
176
	}
177
178
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
179
		$is_multisite = is_multisite();
180
		/**
181
		 * Fires when a user is deleted on a site
182
		 *
183
		 * @since 5.4.0
184
		 *
185
		 * @param int $deleted_user_id - ID of the deleted user
186
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
187
		 * @param bool $is_multisite - Whether this site is a multisite installation
188
		 */
189
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
190
	}
191
192
	public function edited_user_handler( $user_id ) {
193
		/**
194
		 * Fires when a user is edited on a site
195
		 *
196
		 * @since 5.4.0
197
		 *
198
		 * @param int $user_id - ID of the edited user
199
		 */
200
		do_action( 'jetpack_user_edited', $user_id );
201
	}
202
203
	function save_user_handler( $user_id, $old_user_data = null ) {
204
		// ensure we only sync users who are members of the current blog
205
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
206
			return;
207
		}
208
		$raw_user = get_user_by( 'id', $user_id );
209
		$user = $this->sanitize_user( $raw_user );
210
		$user_password_changed = false;
211
212
		// Older versions of WP don't pass the old_user_data in ->data
213
		if ( isset( $old_user_data->data ) ) {
214
			$old_user = $old_user_data->data;
215
		} else {
216
			$old_user = $old_user_data;
217
		}
218
219
		$role_changed = isset( $this->previous_role[ $user_id ] ) ? $this->previous_role[ $user_id ] : false;
220
221
		if ( $old_user !== null && $raw_user->user_pass !== $old_user->user_pass ) {
222
			$user_password_changed = true;
223
		}
224
225
		if ( 'user_register' === current_filter() ) {
226
			/**
227
			 * Fires when a new user is registered on a site
228
			 *
229
			 * @since 4.9.0
230
			 *
231
			 * @param object The WP_User object
232
			 */
233
			do_action( 'jetpack_sync_register_user', $user );
234
235
			return;
236
		}
237
		/* MU Sites add users instead of register them to sites */
238
		if ( 'add_user_to_blog' === current_filter() ) {
239
			/**
240
			 * Fires when a new user is added to a site. (WordPress Multisite)
241
			 *
242
			 * @since 4.9.0
243
			 *
244
			 * @param object The WP_User object
245
			 */
246
			do_action( 'jetpack_sync_add_user', $user );
247
248
			return;
249
		}
250
251
		/**
252
		 * Fires when the client needs to sync an updated user
253
		 *
254
		 * @since 4.2.0
255
		 *
256
		 * @param object The WP_User object
257
		 * @param array state - New since 5.8.0
258
		 */
259
		do_action( 'jetpack_sync_save_user', $user, array(
260
			'password_changed' => $user_password_changed,
261
			'role_changed' => (bool) $role_changed,
262
			'previous_role' => $role_changed,
263
			) );
264
	}
265
266
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
267
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
268
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
269
			$this->previous_role[ $user_id ] = $old_roles;
270
			return;
271
		}
272
273
		$user = $this->sanitize_user( get_user_by( 'id', $user_id ) );
274
		/**
275
		 * Fires when the client needs to sync an updated user
276
		 *
277
		 * @since 4.2.0
278
		 *
279
		 * @param object The WP_User object
280
	 	 * @param array state
281
		 */
282
		do_action( 'jetpack_sync_save_user', $user, array(
283
			'role_changed' => true,
284
			'previous_role' => $old_roles ) );
285
	}
286
287
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
288
		if ( $meta_key === 'locale' ) {
289
			if ( current_filter() === 'deleted_user_meta' ) {
290
				/**
291
				 * Allow listeners to listen for user local delete changes
292
				 *
293
				 * @since 4.8.0
294
				 *
295
				 * @param int $user_id - The ID of the user whos locale is being deleted
296
				 */
297
				do_action( 'jetpack_sync_user_locale_delete', $user_id );
298
			} else {
299
				/**
300
				 * Allow listeners to listen for user local changes
301
				 *
302
				 * @since 4.8.0
303
				 *
304
				 * @param int $user_id - The ID of the user whos locale is being changed
305
				 * @param int $value - The value of the new locale
306
				 */
307
				do_action( 'jetpack_sync_user_locale', $user_id, $value );
308
			}
309
		}
310
		$this->save_user_cap_handler( $meta_id, $user_id, $meta_key, $value );
311
	}
312
313
	function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) {
314
		// if a user is currently being removed as a member of this blog, we don't fire the event
315
		if ( current_filter() === 'deleted_user_meta' ) {
316
			return;
317
		}
318
319
		// Since we are currently only caring about capabilities at this point don't need to save the user info at this save the user info at this point.
320
		if ( current_filter() === 'added_user_meta' ) {
321
			return;
322
		}
323
324
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
325
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
326
			return;
327
		}
328
		$user = get_user_by( 'id', $user_id );
329
		if ( $meta_key === $user->cap_key  ) {
330
331
			/**
332
			 * Fires when the client needs to sync an updated user
333
			 *
334
			 * @since 4.2.0
335
			 *
336
			 * @param object The Sanitized WP_User object
337
		     * @param array state Since 5.8
338
			 */
339
			do_action( 'jetpack_sync_save_user', $this->sanitize_user( $user ),
340
				array( 'capabilities_action' => current_filter(), 'capabilities' => $capabilities )
341
			);
342
		}
343
	}
344
345
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
346
		global $wpdb;
347
348
		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 );
349
	}
350
351
	public function estimate_full_sync_actions( $config ) {
352
		global $wpdb;
353
354
		$query = "SELECT count(*) FROM $wpdb->usermeta";
355
356
		if ( $where_sql = $this->get_where_sql( $config ) ) {
357
			$query .= ' WHERE ' . $where_sql;
358
		}
359
360
		$count = $wpdb->get_var( $query );
361
362
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
363
	}
364
365 View Code Duplication
	private function get_where_sql( $config ) {
366
		global $wpdb;
367
368
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
369
370
		// config is a list of user IDs to sync
371
		if ( is_array( $config ) ) {
372
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
373
		}
374
375
		return $query;
376
	}
377
378
	function get_full_sync_actions() {
379
		return array( 'jetpack_full_sync_users' );
380
	}
381
382
	function get_initial_sync_user_config() {
383
		global $wpdb;
384
385
		$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 ) );
386
387
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
388
			return $user_ids;
389
		} else {
390
			return false;
391
		}
392
	}
393
394
	public function expand_users( $args ) {
395
		$user_ids = $args[0];
396
397
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
398
	}
399
400
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
401
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
402
		if ( $this->is_add_new_user_to_blog() ) {
403
			return;
404
		}
405
406
		$reassigned_user_id = $this->get_reassigned_network_user_id();
407
408
		//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
409
		/**
410
		 * Fires when a user is removed from a blog on a multisite installation
411
		 *
412
		 * @since 5.4.0
413
		 *
414
		 * @param int $user_id - ID of the removed user
415
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
416
		 */
417
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
418
	}
419
420
	private function is_add_new_user_to_blog() {
421
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
422
	}
423
424
	private function is_add_user_to_blog() {
425
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
426
	}
427
428
	private function is_create_user() {
429
		$functions = array(
430
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
431
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
432
			'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
433
		);
434
435
		return Jetpack::is_function_in_backtrace( $functions );
436
	}
437
438
	private function get_reassigned_network_user_id() {
439
		$backtrace = debug_backtrace( false );
440
		foreach ( $backtrace as $call ) {
441
			if (
442
				'remove_user_from_blog' === $call['function'] &&
443
				3 === count( $call['args'] )
444
			) {
445
				return $call['args'][2];
446
			}
447
		}
448
449
		return false;
450
	}
451
}
452