Completed
Push — add/changelog-64 ( 49ba93...06347a )
by
unknown
169:34 queued 150:02
created

Jetpack_Sync_Module_Users::save_user_handler()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 13
nop 2
dl 0
loc 37
rs 8.0835
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 $flags = 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
23
		// users
24
		add_action( 'user_register', array( $this, 'user_register_handler' ) );
25
		add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
26
27
		add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
28
		add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
29
		add_action( 'jetpack_sync_add_user', array( $this, 'clear_flags' ), 11 );
30
31
		add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
32
		add_action( 'jetpack_sync_register_user', array( $this, 'clear_flags' ), 11 );
33
34
		add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
35
		add_action( 'jetpack_sync_save_user', array( $this, 'clear_flags' ), 11 );
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_action( 'wp_login', $callable, 10, 2 );
57
		add_action( 'wp_logout', $callable, 10, 0 );
58
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
59
60
		// Add on init
61
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
62
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
63
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
64
	}
65
66
	public function init_full_sync_listeners( $callable ) {
67
		add_action( 'jetpack_full_sync_users', $callable );
68
	}
69
70
	public function init_before_send() {
71
72
73
		add_filter( 'jetpack_sync_before_send_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
74
		add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
75
76
		// full sync
77
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
78
	}
79
80
	private function get_user( $user ) {
81
		if ( 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
		return $user;
99
	}
100
101
	public function expand_user( $user ) {
102
		if ( ! is_object( $user ) ) {
103
			return null;
104
		}
105
		$user->allowed_mime_types = get_allowed_mime_types( $user );
106
		$user->allcaps = $this->get_real_user_capabilities( $user );
107
108
		if ( function_exists( 'get_user_locale' ) ) {
109
110
			// Only set the user locale if it is different from the site local
111
			if ( get_locale() !== get_user_locale( $user->ID ) ) {
112
				$user->locale = get_user_locale( $user->ID );
113
			}
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 ) = $args;
151
		$user = $this->sanitize_user( $user );
152
153
		return array( $login, $user );
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
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
173
		$is_multisite = is_multisite();
174
		/**
175
		 * Fires when a user is deleted on a site
176
		 *
177
		 * @since 5.4.0
178
		 *
179
		 * @param int $deleted_user_id - ID of the deleted user
180
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
181
		 * @param bool $is_multisite - Whether this site is a multisite installation
182
		 */
183
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
184
	}
185
186 View Code Duplication
	function user_register_handler( $user_id, $old_user_data = null ) {
187
		// ensure we only sync users who are members of the current blog
188
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
189
			return;
190
		}
191
192
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
193
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
194
		}
195
		/**
196
		 * Fires when a new user is registered on a site
197
		 *
198
		 * @since 4.9.0
199
		 *
200
		 * @param object The WP_User object
201
		 */
202
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
203
204
	}
205
206 View Code Duplication
	function add_user_to_blog_handler( $user_id, $old_user_data = null ) {
207
		// ensure we only sync users who are members of the current blog
208
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
209
			return;
210
		}
211
212
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
213
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
214
		}
215
		/**
216
		 * Fires when a user is added on a site
217
		 *
218
		 * @since 4.9.0
219
		 *
220
		 * @param object The WP_User object
221
		 */
222
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
223
	}
224
225
	function save_user_handler( $user_id, $old_user_data = null ) {
226
		// ensure we only sync users who are members of the current blog
227
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
228
			return;
229
		}
230
231
		$user = get_user_by( 'id', $user_id );
232
233
		// Older versions of WP don't pass the old_user_data in ->data
234
		if ( isset( $old_user_data->data ) ) {
235
			$old_user = $old_user_data->data;
236
		} else {
237
			$old_user = $old_user_data;
238
		}
239
240
		if ( $old_user !== null && $user->user_pass !== $old_user->user_pass ) {
241
			$this->flags[ $user_id ]['password_changed'] = true;
242
		}
243
		if ( $old_user !== null && $user->data->user_email !== $old_user->user_email ) {
244
			// The '_new_email' user meta is deleted right after the call to wp_update_user
245
			// that got us to this point so if it's still set then this was a user confirming
246
			// their new email address
247
			if ( 1 === intval( get_user_meta( $user->ID, '_new_email', true ) ) ) {
248
				$this->flags[ $user_id ]['email_changed'] = true;
249
			}
250
		}
251
252
		/**
253
		 * Fires when the client needs to sync an updated user
254
		 *
255
		 * @since 4.2.0
256
		 *
257
		 * @param object The WP_User object
258
		 * @param array state - New since 5.8.0
259
		 */
260
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
261
	}
262
263
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
264
		$this->add_flags( $user_id, array( 'role_changed' => true, 'previous_role' => $old_roles ) );
265
266
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
267
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
268
			return;
269
		}
270
		/**
271
		 * This action is documented already in this file
272
		 */
273
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
274
	}
275
276
	function get_flags( $user_id ) {
277
		if ( isset( $this->flags[ $user_id ] ) ) {
278
			return $this->flags[ $user_id ];
279
		}
280
		return array();
281
	}
282
283
	function clear_flags( $user_id ) {
284
		if ( isset( $this->flags[ $user_id ] ) ) {
285
			unset( $this->flags[ $user_id ] );
286
		}
287
	}
288
289
	function add_flags( $user_id, $flags ) {
290
		$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
291
	}
292
293
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
294
		if ( $meta_key === 'locale' ) {
295
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
296
		}
297
298
		$user = get_user_by( 'id', $user_id );
299
		if ( $meta_key === $user->cap_key  ) {
300
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
301
		}
302
303
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
304
			return;
305
		}
306
307
		if ( isset( $this->flags[ $user_id ] ) ) {
308
			/**
309
			 * This action is documented already in this file
310
			 */
311
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
312
		}
313
	}
314
315
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
316
		global $wpdb;
317
318
		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 );
319
	}
320
321
	public function estimate_full_sync_actions( $config ) {
322
		global $wpdb;
323
324
		$query = "SELECT count(*) FROM $wpdb->usermeta";
325
326
		if ( $where_sql = $this->get_where_sql( $config ) ) {
327
			$query .= ' WHERE ' . $where_sql;
328
		}
329
330
		$count = $wpdb->get_var( $query );
331
332
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
333
	}
334
335 View Code Duplication
	private function get_where_sql( $config ) {
336
		global $wpdb;
337
338
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
339
340
		// config is a list of user IDs to sync
341
		if ( is_array( $config ) ) {
342
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
343
		}
344
345
		return $query;
346
	}
347
348
	function get_full_sync_actions() {
349
		return array( 'jetpack_full_sync_users' );
350
	}
351
352
	function get_initial_sync_user_config() {
353
		global $wpdb;
354
355
		$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 ) );
356
357
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
358
			return $user_ids;
359
		} else {
360
			return false;
361
		}
362
	}
363
364
	public function expand_users( $args ) {
365
		$user_ids = $args[0];
366
367
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
368
	}
369
370
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
371
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
372
		if ( $this->is_add_new_user_to_blog() ) {
373
			return;
374
		}
375
376
		$reassigned_user_id = $this->get_reassigned_network_user_id();
377
378
		//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
379
		/**
380
		 * Fires when a user is removed from a blog on a multisite installation
381
		 *
382
		 * @since 5.4.0
383
		 *
384
		 * @param int $user_id - ID of the removed user
385
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
386
		 */
387
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
388
	}
389
390
	private function is_add_new_user_to_blog() {
391
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
392
	}
393
394
	private function is_add_user_to_blog() {
395
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
396
	}
397
398
	private function is_delete_user() {
399
		return Jetpack::is_function_in_backtrace( array( 'wp_delete_user' , 'remove_user_from_blog' ) );
400
	}
401
402
	private function is_create_user() {
403
		$functions = array(
404
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
405
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
406
			'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
407
		);
408
409
		return Jetpack::is_function_in_backtrace( $functions );
410
	}
411
412
	private function get_reassigned_network_user_id() {
413
		$backtrace = debug_backtrace( false ); // phpcs:ignore PHPCompatibility
414
		foreach ( $backtrace as $call ) {
415
			if (
416
				'remove_user_from_blog' === $call['function'] &&
417
				3 === count( $call['args'] )
418
			) {
419
				return $call['args'][2];
420
			}
421
		}
422
423
		return false;
424
	}
425
}
426