Completed
Push — sync/user-meta ( 973db3...e1df47 )
by
unknown
07:28
created

Jetpack_Sync_Module_Users::map_user_meta_key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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