Completed
Push — feature/determine_email_change ( 336b63...969d0a )
by
unknown
09:47
created

Jetpack_Sync_Module_Users::save_user_handler()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 9
nop 2
dl 0
loc 31
rs 8.4906
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
		//Confirmed new email address
46
		add_action( 'deleted_user_meta', array( $this, 'deleted_user_meta_handler' ), 10, 4 );
47
48
		// user roles
49
		add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
50
		add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
51
		add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
52
53
		// user capabilities
54
		add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
55
		add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
56
		add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
57
58
		// user authentication
59
		add_action( 'wp_login', $callable, 10, 2 );
60
		add_action( 'wp_logout', $callable, 10, 0 );
61
		add_action( 'wp_masterbar_logout', $callable, 10, 0 );
62
63
		// Add on init
64
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
65
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
66
		add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
67
	}
68
69
	public function init_full_sync_listeners( $callable ) {
70
		add_action( 'jetpack_full_sync_users', $callable );
71
	}
72
73
	public function init_before_send() {
74
75
76
		add_filter( 'jetpack_sync_before_send_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
	public function deleted_user_meta_handler( $meta_ids, $object_id, $meta_key, $_meta_value ) {
84
		error_log(print_r( $meta_ids, true ));
85
		error_log($object_id);
86
		error_log( $meta_key);
87
		error_log( $_meta_value );
88
	}
89
90
	private function get_user( $user ) {
91
		if ( is_numeric( $user ) ) {
92
			$user = get_user_by( 'id', $user );
93
		}
94
		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...
95
			return $user;
96
		}
97
		return null;
98
	}
99
100
	public function sanitize_user( $user ) {
101
		$user = $this->get_user( $user );
102
		// this create a new user object and stops the passing of the object by reference.
103
		$user = unserialize( serialize( $user ) );
104
105
		if ( is_object( $user ) && is_object( $user->data ) ) {
106
			unset( $user->data->user_pass );
107
		}
108
		return $user;
109
	}
110
111
	public function expand_user( $user ) {
112
		if ( ! is_object( $user ) ) {
113
			return null;
114
		}
115
		$user->allowed_mime_types = get_allowed_mime_types( $user );
116
		$user->allcaps = $this->get_real_user_capabilities( $user );
117
118
		if ( function_exists( 'get_user_locale' ) ) {
119
120
			// Only set the user locale if it is different from the site local
121
			if ( get_locale() !== get_user_locale( $user->ID ) ) {
122
				$user->locale = get_user_locale( $user->ID );
123
			}
124
		}
125
126
		return $user;
127
	}
128
129
	public function get_real_user_capabilities( $user ) {
130
		$user_capabilities = array();
131
		if ( is_wp_error( $user ) ) {
132
			return $user_capabilities;
133
		}
134
		foreach( Jetpack_Sync_Defaults::get_capabilities_whitelist() as $capability ) {
135
			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...
136
				$user_capabilities[ $capability ] = true;
137
			}
138
		}
139
		return $user_capabilities;
140
	}
141
142
	public function sanitize_user_and_expand( $user ) {
143
		$user = $this->get_user( $user );
144
		$user = $this->expand_user( $user );
145
		return $this->sanitize_user( $user );
146
	}
147
148
	public function expand_action( $args ) {
149
		// the first argument is always the user
150
		list( $user ) = $args;
151
		if ( $user ) {
152
			$args[0] = $this->sanitize_user_and_expand( $user );
153
			return $args;
154
		}
155
156
		return false;
157
	}
158
159
	public function expand_login_username( $args ) {
160
		list( $login, $user ) = $args;
161
		$user = $this->sanitize_user( $user );
162
163
		return array( $login, $user );
164
	}
165
166
	public function expand_logout_username( $args, $user_id ) {
167
		$user  = get_userdata( $user_id );
168
		$user  = $this->sanitize_user( $user );
169
170
		$login = '';
171
		if ( is_object( $user ) && is_object( $user->data ) ) {
172
			$login = $user->data->user_login;
173
		}
174
		// if we don't have a user here lets not send anything.
175
		if ( empty( $login ) ) {
176
			return false;
177
		}
178
179
		return array( $login, $user );
180
	}
181
182
	public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
183
		$is_multisite = is_multisite();
184
		/**
185
		 * Fires when a user is deleted on a site
186
		 *
187
		 * @since 5.4.0
188
		 *
189
		 * @param int $deleted_user_id - ID of the deleted user
190
		 * @param int $reassigned_user_id - ID of the user the deleted user's posts is reassigned to (if any)
191
		 * @param bool $is_multisite - Whether this site is a multisite installation
192
		 */
193
		do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
194
	}
195
196 View Code Duplication
	function user_register_handler( $user_id, $old_user_data = null ) {
197
		// ensure we only sync users who are members of the current blog
198
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
199
			return;
200
		}
201
202
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
203
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
204
		}
205
		/**
206
		 * Fires when a new user is registered on a site
207
		 *
208
		 * @since 4.9.0
209
		 *
210
		 * @param object The WP_User object
211
		 */
212
		do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
213
214
	}
215
216 View Code Duplication
	function add_user_to_blog_handler( $user_id, $old_user_data = null ) {
217
		// ensure we only sync users who are members of the current blog
218
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
219
			return;
220
		}
221
222
		if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
223
			$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
224
		}
225
		/**
226
		 * Fires when a user is added on a site
227
		 *
228
		 * @since 4.9.0
229
		 *
230
		 * @param object The WP_User object
231
		 */
232
		do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
233
	}
234
235
	function save_user_handler( $user_id, $old_user_data = null ) {
236
		// ensure we only sync users who are members of the current blog
237
		if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
238
			return;
239
		}
240
241
		$user = get_user_by( 'id', $user_id );
242
243
		// Older versions of WP don't pass the old_user_data in ->data
244
		if ( isset( $old_user_data->data ) ) {
245
			$old_user = $old_user_data->data;
246
		} else {
247
			$old_user = $old_user_data;
248
		}
249
		if ( $old_user !== null && $user->user_pass !== $old_user->user_pass ) {
250
			$this->flags[ $user_id ]['password_changed'] = true;
251
		}
252
		if ( $old_user !== null && $user->user_email !== $old_user->user_email ) {
253
			$this->flags[ $user_id ]['email_changed'] = true;
254
		}
255
256
		/**
257
		 * Fires when the client needs to sync an updated user
258
		 *
259
		 * @since 4.2.0
260
		 *
261
		 * @param object The WP_User object
262
		 * @param array state - New since 5.8.0
263
		 */
264
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
265
	}
266
267
	function save_user_role_handler( $user_id, $role, $old_roles = null ) {
268
		$this->add_flags( $user_id, array( 'role_changed' => true, 'previous_role' => $old_roles ) );
269
270
		//The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both
271
		if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
272
			return;
273
		}
274
		/**
275
		 * This action is documented already in this file
276
		 */
277
		do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
278
	}
279
280
	function get_flags( $user_id ) {
281
		if ( isset( $this->flags[ $user_id ] ) ) {
282
			return $this->flags[ $user_id ];
283
		}
284
		return array();
285
	}
286
287
	function clear_flags( $user_id ) {
288
		if ( isset( $this->flags[ $user_id ] ) ) {
289
			unset( $this->flags[ $user_id ] );
290
		}
291
	}
292
293
	function add_flags( $user_id, $flags ) {
294
		$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
295
	}
296
297
	function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
298
		if ( $meta_key === 'locale' ) {
299
			$this->add_flags( $user_id, array( 'locale_changed' => true ) );
300
		}
301
302
		$user = get_user_by( 'id', $user_id );
303
		if ( $meta_key === $user->cap_key  ) {
304
			$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
305
		}
306
307
		if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
308
			return;
309
		}
310
311
		if ( isset( $this->flags[ $user_id ] ) ) {
312
			/**
313
			 * This action is documented already in this file
314
			 */
315
			do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
316
		}
317
	}
318
319
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
320
		global $wpdb;
321
322
		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 );
323
	}
324
325
	public function estimate_full_sync_actions( $config ) {
326
		global $wpdb;
327
328
		$query = "SELECT count(*) FROM $wpdb->usermeta";
329
330
		if ( $where_sql = $this->get_where_sql( $config ) ) {
331
			$query .= ' WHERE ' . $where_sql;
332
		}
333
334
		$count = $wpdb->get_var( $query );
335
336
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
337
	}
338
339 View Code Duplication
	private function get_where_sql( $config ) {
340
		global $wpdb;
341
342
		$query = "meta_key = '{$wpdb->prefix}capabilities'";
343
344
		// config is a list of user IDs to sync
345
		if ( is_array( $config ) ) {
346
			$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
347
		}
348
349
		return $query;
350
	}
351
352
	function get_full_sync_actions() {
353
		return array( 'jetpack_full_sync_users' );
354
	}
355
356
	function get_initial_sync_user_config() {
357
		global $wpdb;
358
359
		$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 ) );
360
361
		if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
362
			return $user_ids;
363
		} else {
364
			return false;
365
		}
366
	}
367
368
	public function expand_users( $args ) {
369
		$user_ids = $args[0];
370
371
		return array_map( array( $this, 'sanitize_user_and_expand' ), get_users( array( 'include' => $user_ids ) ) );
372
	}
373
374
	public function remove_user_from_blog_handler( $user_id, $blog_id ) {
375
		//User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114
376
		if ( $this->is_add_new_user_to_blog() ) {
377
			return;
378
		}
379
380
		$reassigned_user_id = $this->get_reassigned_network_user_id();
381
382
		//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
383
		/**
384
		 * Fires when a user is removed from a blog on a multisite installation
385
		 *
386
		 * @since 5.4.0
387
		 *
388
		 * @param int $user_id - ID of the removed user
389
		 * @param int $reassigned_user_id - ID of the user the removed user's posts is reassigned to (if any)
390
		 */
391
		do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
392
	}
393
394
	private function is_add_new_user_to_blog() {
395
		return Jetpack::is_function_in_backtrace( 'add_new_user_to_blog' );
396
	}
397
398
	private function is_add_user_to_blog() {
399
		return Jetpack::is_function_in_backtrace( 'add_user_to_blog' );
400
	}
401
402
	private function is_delete_user() {
403
		return Jetpack::is_function_in_backtrace( array( 'wp_delete_user' , 'remove_user_from_blog' ) );
404
	}
405
406
	private function is_create_user() {
407
		$functions = array(
408
			'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site
409
			'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site
410
			'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
411
		);
412
413
		return Jetpack::is_function_in_backtrace( $functions );
414
	}
415
416
	private function get_reassigned_network_user_id() {
417
		$backtrace = debug_backtrace( false );
418
		foreach ( $backtrace as $call ) {
419
			if (
420
				'remove_user_from_blog' === $call['function'] &&
421
				3 === count( $call['args'] )
422
			) {
423
				return $call['args'][2];
424
			}
425
		}
426
427
		return false;
428
	}
429
}
430