Completed
Push — sync/user-meta ( 2eca1f )
by
unknown
07:56
created

Jetpack_Sync_Module_Users::expand_user()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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