Completed
Push — update/sync-users-enqueue-orde... ( 9915a7...2ea5db )
by
unknown
19:55
created

expand_logout_username()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 15
rs 9.2
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
	// Stores things that happen inside wp_insert_user so we sync them after the wp_insert_user call
7
	static $calls_within_wp_insert_user = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $calls_within_wp_insert_user.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
8
9
	function name() {
10
		return 'users';
11
	}
12
13
	// this is here to support the backfill API
14
	public function get_object_by_id( $object_type, $id ) {
15
		if ( $object_type === 'user' && $user = get_user_by( 'id', intval( $id ) ) ) {
16
			return $this->sanitize_user_and_expand( $user );
17
		}
18
19
		return false;
20
	}
21
22
	var $callable;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $callable.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
23
24
	public function init_listeners( $callable ) {
25
		$this->callable = $callable;
26
		add_action( 'user_register', array( $this, 'enqueue_within_wp_insert_user_calls' ), 11 );
27
		add_action( 'profile_update', array( $this, 'enqueue_within_wp_insert_user_calls' ), 11 );
28
29
		// users
30
		add_action( 'user_register', array( $this, 'save_user_handler' ) );
31
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
32
		add_action( 'add_user_to_blog', array( $this, 'save_user_handler' ) );
33
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
34
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
35
		add_action( 'jetpack_sync_save_user', array( $this, 'maybe_within_wp_insert_user' ) );
36
37
		add_action( 'jetpack_sync_change_role_user', array( $this, 'maybe_within_wp_insert_user' ), 10, 3 );
38
39
		//Edit user info, see https://github.com/WordPress/WordPress/blob/c05f1dc805bddcc0e76fd90c4aaf2d9ea76dc0fb/wp-admin/user-edit.php#L126
40
		add_action( 'personal_options_update', array( $this, 'edited_user_handler' ) );
41
		add_action( 'edit_user_profile_update', array( $this, 'edited_user_handler' ) );
42
		add_action( 'jetpack_user_edited', $callable );
43
44
		add_action( 'jetpack_sync_user_locale', array( $this, 'maybe_within_wp_insert_user' ), 10, 2 );
45
46
		add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
47
48
		add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
49
		add_action( 'jetpack_deleted_user', $callable, 10, 3 );
50
		add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
51
		add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
52
53
		// user roles
54
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
55
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
56
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
57
58
		// user capabilities
59
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
60
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
61
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
62
63
		// user authentication
64
		add_action( 'wp_login', $callable, 10, 2 );
65
		add_action( 'wp_logout', $callable, 10, 0 );
66
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
67
	}
68
69
	public function maybe_within_wp_insert_user() {
70
		error_log('jetpack_sync_user_locale_arg');
71
		if ( ! Jetpack::is_function_in_backtrace('enqueue_within_wp_insert_user_calls') ) {
72
			if ( Jetpack::is_function_in_backtrace('wp_insert_user' )  ) {
73
				self::$calls_within_wp_insert_user[ current_action() ][] = func_get_args();
74
				return;
75
			}
76
		}
77
		call_user_func( $this->callable, func_get_args() );
78
	}
79
80
	public function enqueue_within_wp_insert_user_calls() {
81
		foreach ( self::$calls_within_wp_insert_user as $action => $calls ) {
82
			foreach ( $calls as $call ) {
83
				if ( 'user_register' === current_filter() ) {
84
					if ( 'jetpack_sync_change_role_user' === $action ) {
85
						break;
86
					}
87
				}
88
				if ( 'profile_update' === current_filter() ) {
89
					if ( 'jetpack_sync_save_user' === $action ) {
90
						break;
91
					}
92
				}
93
				do_action( $action, $call );
94
			}
95
		}
96
	}
97
98
	public function init_full_sync_listeners( $callable ) {
99
		add_action( 'jetpack_full_sync_users', $callable );
100
	}
101
102
	public function init_before_send() {
103
		add_filter( 'jetpack_sync_before_send_jetpack_sync_add_user', array( $this, 'expand_user' ) );
104
		add_filter( 'jetpack_sync_before_send_jetpack_sync_register_user', array( $this, 'expand_user' ) );
105
		add_filter( 'jetpack_sync_before_send_jetpack_sync_save_user', array( $this, 'expand_user' ), 10, 2 );
106
		add_filter( 'jetpack_sync_before_send_jetpack_user_edited', array( $this, 'expand_user' ) );
107
108
		add_filter( 'jetpack_sync_before_send_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
109
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
110
111
		// full sync
112
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
113
	}
114
115
	public function sanitize_user_and_expand( $user ) {
116
		$user = $this->get_user( $user );
117
		$user = $this->add_to_user( $user );
118
		return $this->sanitize_user( $user );
119
	}
120
121
	private function get_user( $user ) {
122
		if ( $user && ! is_object( $user ) && is_numeric( $user ) ) {
123
			$user = get_user_by( 'id', $user );
124
		}
125
		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...
126
			return $user;
127
		}
128
		return null;
129
	}
130
131
	public function sanitize_user( $user ) {
132
		$user = $this->get_user( $user );
133
		// this create a new user object and stops the passing of the object by reference.
134
		$user = unserialize( serialize( $user ) );
135
136
		if ( is_object( $user ) && is_object( $user->data ) ) {
137
			unset( $user->data->user_pass );
138
			unset( $user->data->user_activation_key );
139
		}
140
		if ( $user ) {
141
			$user->allcaps = $this->get_real_user_capabilities( $user );
142
		}
143
		return $user;
144
	}
145
146
	public function add_to_user( $user ) {
147
		$user = $this->get_user( $user );
148
		if ( ! is_object( $user ) ) {
149
			return null;
150
		}
151
		$user->allowed_mime_types = get_allowed_mime_types( $user );
152
153
		if ( function_exists( 'get_user_locale' ) ) {
154
155
			// Only set the user locale if it is different from the site local
156
			if ( get_locale() !== get_user_locale( $user->ID ) ) {
157
				$user->locale = get_user_locale( $user->ID );
158
			}
159
		}
160
161
		return $user;
162
	}
163
164
	public function get_real_user_capabilities( $user ) {
165
		$user_capabilities = array();
166
		if ( is_wp_error( $user ) ) {
167
			return $user_capabilities;
168
		}
169
		foreach( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
170
			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...
171
				$user_capabilities[ $capability ] = true;
172
			}
173
		}
174
		return $user_capabilities;
175
	}
176
177
	public function expand_user( $args ) {
178
		list( $user ) = $args;
179
180
		if ( $user ) {
181
			return array( $this->sanitize_user( $this->add_to_user( $user ) ) );
182
		}
183
184
		return false;
185
	}
186
187
	public function expand_login_username( $args ) {
188
		list( $login, $user ) = $args;
189
		$user = $this->sanitize_user( $user );
190
191
		return array( $login, $user );
192
	}
193
194
	public function expand_logout_username( $args, $user_id ) {
195
		$user  = get_userdata( $user_id );
196
		$user  = $this->sanitize_user( $user );
197
		
198
		$login = '';
199
		if ( is_object( $user ) && is_object( $user->data ) ) {
200
			$login = $user->data->user_login;
201
		}
202
		// if we don't have a user here lets not send anything.
203
		if ( empty( $login ) ) {
204
			return false;
205
		}
206
207
		return array( $login, $user );
208
	}
209
210
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
211
		$is_multisite = is_multisite();
212
		/**
213
		 * Fires when a user is deleted on a site
214
		 *
215
		 * @since 5.4.0
216
		 *
217
		 * @param int $deleted_user_id - ID of the deleted user
218
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
219
		 * @param bool $is_multisite - Whether this site is a multisite installation
220
		 */
221
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
222
	}
223
224
	public function edited_user_handler( $user_id ) {
225
		error_log('edited_user_handler');
226
		/**
227
		 * Fires when a user is edited on a site
228
		 *
229
		 * @since 5.4.0
230
		 *
231
		 * @param int $user_id - ID of the edited user
232
		 */
233
		do_action( 'jetpack_user_edited', $user_id );
234
	}
235
	
236
	function save_user_handler( $user_id, $old_user_data = null ) {
237
		error_log('save_user_handler');
238
		// ensure we only sync users who are members of the current blog
239
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
240
			return;
241
		}
242
243
		$user = $this->sanitize_user( get_user_by( 'id', $user_id ) );
244
245
		// Older versions of WP don't pass the old_user_data in ->data
246
		if ( isset( $old_user_data->data ) ) {
247
			$old_user = $old_user_data->data;
248
		} else {
249
			$old_user = $old_user_data;
250
		}
251
252
		if ( $old_user !== null ) {
253
			unset( $old_user->user_pass );
254
			if ( serialize( $old_user ) === serialize( $user->data ) ) {
255
				return;
256
			}
257
		}
258
259
		if ( 'user_register' === current_filter() ) {
260
			/**
261
			 * Fires when a new user is registered on a site
262
			 *
263
			 * @since 4.9.0
264
			 *
265
			 * @param object The WP_User object
266
			 */
267
			do_action( 'jetpack_sync_register_user', $user );
268
269
			return;
270
		}
271
		/* MU Sites add users instead of register them to sites */
272
		if ( 'add_user_to_blog' === current_filter() ) {
273
			/**
274
			 * Fires when a new user is added to a site. (WordPress Multisite)
275
			 *
276
			 * @since 4.9.0
277
			 *
278
			 * @param object The WP_User object
279
			 */
280
			do_action( 'jetpack_sync_add_user', $user );
281
282
			return;
283
		}
284
285
		/**
286
		 * Fires when the client needs to sync an updated user
287
		 *
288
		 * @since 4.2.0
289
		 *
290
		 * @param object The WP_User object
291
		 */
292
		do_action( 'jetpack_sync_save_user', $user );
293
	}
294
295
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
296
		/**
297
		 * Fires when the client needs to sync an updated user
298
		 *
299
		 * @since 4.2.0
300
		 *
301
		 * @param object The WP_User object
302
		 */
303
		do_action( 'jetpack_sync_change_role_user', $user_id, $role, $old_roles );
304
	}
305
306
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
307
		if ( $meta_key === 'locale' ) {
308
			if ( current_filter() === 'deleted_user_meta' ) {
309
				/**
310
				 * Allow listeners to listen for user local delete changes
311
				 *
312
				 * @since 4.8.0
313
				 *
314
				 * @param int $user_id - The ID of the user whos locale is being deleted
315
				 */
316
				do_action( 'jetpack_sync_user_locale_delete', $user_id );
317
			} else {
318
				/**
319
				 * Allow listeners to listen for user local changes
320
				 *
321
				 * @since 4.8.0
322
				 *
323
				 * @param int $user_id - The ID of the user whos locale is being changed
324
				 * @param int $value - The value of the new locale
325
				 */
326
				do_action( 'jetpack_sync_user_locale', $user_id, $value );
327
			}
328
		}
329
		$this->save_user_cap_handler( $meta_id, $user_id, $meta_key, $value );
330
	}
331
332
	function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) {
333
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
334
		error_log('save_user_cap_handler_1');
335
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
336
			return;
337
		}
338
		error_log('save_user_cap_handler_2');
339
		// if a user is currently being removed as a member of this blog, we don't fire the event
340
		if ( current_filter() === 'deleted_user_meta'
341
		     &&
342
		     preg_match( '/capabilities|user_level/', $meta_key )
343
		     &&
344
		     ! is_user_member_of_blog( $user_id, get_current_blog_id() )
345
		) {
346
			return;
347
		}
348
349
		$user = get_user_by( 'id', $user_id );
350
		if ( $meta_key === $user->cap_key ) {
351
			/**
352
			 * Fires when the client needs to sync an updated user
353
			 *
354
			 * @since 4.2.0
355
			 *
356
			 * @param object The Sanitized WP_User object
357
			 */
358
			do_action( 'jetpack_sync_save_user', $this->sanitize_user( $user ) );
359
		}
360
	}
361
362
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
363
		global $wpdb;
364
365
		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 );
366
	}
367
368
	public function estimate_full_sync_actions( $config ) {
369
		global $wpdb;
370
371
		$query = "SELECT count(*) FROM $wpdb->usermeta";
372
373
		if ( $where_sql = $this->get_where_sql( $config ) ) {
374
			$query .= ' WHERE ' . $where_sql;
375
		}
376
377
		$count = $wpdb->get_var( $query );
378
379
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
380
	}
381
382 View Code Duplication
	private function get_where_sql( $config ) {
383
		global $wpdb;
384
385
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
386
387
		// config is a list of user IDs to sync
388
		if ( is_array( $config ) ) {
389
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
390
		}
391
392
		return $query;
393
	}
394
395
	function get_full_sync_actions() {
396
		return array( 'jetpack_full_sync_users' );
397
	}
398
399
	function get_initial_sync_user_config() {
400
		global $wpdb;
401
402
		$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 ) );
403
404
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
405
			return $user_ids;
406
		} else {
407
			return false;
408
		}
409
	}
410
411
	public function expand_users( $args ) {
412
		$user_ids = $args[0];
413
414
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
415
	}
416
417
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
418
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
419
		if ( $this->is_add_new_user_to_blog() ) {
420
			return;
421
		}
422
423
		$reassigned_user_id = $this->get_reassigned_network_user_id();
424
425
		//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
426
		/**
427
		 * Fires when a user is removed from a blog on a multisite installation
428
		 *
429
		 * @since 5.4.0
430
		 *
431
		 * @param int $user_id - ID of the removed user
432
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
433
		 */
434
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
435
	}
436
437
	private function is_add_new_user_to_blog() {
438
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
439
	}
440
441
	private function is_add_user_to_blog() {
442
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
443
	}
444
445
	private function is_create_user() {
446
		$functions = array(
447
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
448
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
449
			'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
450
		);
451
452
		return Jetpack::is_function_in_backtrace( $functions );
453
	}
454
455
	private function get_reassigned_network_user_id() {
456
		$backtrace = debug_backtrace( false );
457
		foreach ( $backtrace as $call ) {
458
			if (
459
				'remove_user_from_blog' === $call['function'] &&
460
				3 === count( $call['args'] )
461
			) {
462
				return $call['args'][2];
463
			}
464
		}
465
466
		return false;
467
	}
468
}
469