· 6 years ago · Nov 02, 2019, 07:06 PM
1<?php
2/**
3 * Core User API
4 *
5 * @package WordPress
6 * @subpackage Users
7 */
8
9/**
10 * Authenticates and logs a user in with 'remember' capability.
11 *
12 * The credentials is an array that has 'user_login', 'user_password', and
13 * 'remember' indices. If the credentials is not given, then the log in form
14 * will be assumed and used if set.
15 *
16 * The various authentication cookies will be set by this function and will be
17 * set for a longer period depending on if the 'remember' credential is set to
18 * true.
19 *
20 * Note: wp_signon() doesn't handle setting the current user. This means that if the
21 * function is called before the {@see 'init'} hook is fired, is_user_logged_in() will
22 * evaluate as false until that point. If is_user_logged_in() is needed in conjunction
23 * with wp_signon(), wp_set_current_user() should be called explicitly.
24 *
25 * @since 2.5.0
26 *
27 * @global string $auth_secure_cookie
28 *
29 * @param array $credentials Optional. User info in order to sign on.
30 * @param string|bool $secure_cookie Optional. Whether to use secure cookie.
31 * @return WP_User|WP_Error WP_User on success, WP_Error on failure.
32 */
33function wp_signon( $credentials = array(), $secure_cookie = '' ) {
34 if ( empty( $credentials ) ) {
35 $credentials = array(); // Back-compat for plugins passing an empty string.
36
37 if ( ! empty( $_POST['log'] ) ) {
38 $credentials['user_login'] = $_POST['log'];
39 }
40 if ( ! empty( $_POST['pwd'] ) ) {
41 $credentials['user_password'] = $_POST['pwd'];
42 }
43 if ( ! empty( $_POST['rememberme'] ) ) {
44 $credentials['remember'] = $_POST['rememberme'];
45 }
46 }
47
48 if ( ! empty( $credentials['remember'] ) ) {
49 $credentials['remember'] = true;
50 } else {
51 $credentials['remember'] = false;
52 }
53
54 /**
55 * Fires before the user is authenticated.
56 *
57 * The variables passed to the callbacks are passed by reference,
58 * and can be modified by callback functions.
59 *
60 * @since 1.5.1
61 *
62 * @todo Decide whether to deprecate the wp_authenticate action.
63 *
64 * @param string $user_login Username (passed by reference).
65 * @param string $user_password User password (passed by reference).
66 */
67 do_action_ref_array( 'wp_authenticate', array( &$credentials['user_login'], &$credentials['user_password'] ) );
68
69 if ( '' === $secure_cookie ) {
70 $secure_cookie = is_ssl();
71 }
72
73 /**
74 * Filters whether to use a secure sign-on cookie.
75 *
76 * @since 3.1.0
77 *
78 * @param bool $secure_cookie Whether to use a secure sign-on cookie.
79 * @param array $credentials {
80 * Array of entered sign-on data.
81 *
82 * @type string $user_login Username.
83 * @type string $user_password Password entered.
84 * @type bool $remember Whether to 'remember' the user. Increases the time
85 * that the cookie will be kept. Default false.
86 * }
87 */
88 $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials );
89
90 global $auth_secure_cookie; // XXX ugly hack to pass this to wp_authenticate_cookie
91 $auth_secure_cookie = $secure_cookie;
92
93 add_filter( 'authenticate', 'wp_authenticate_cookie', 30, 3 );
94
95 $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] );
96
97 if ( is_wp_error( $user ) ) {
98 return $user;
99 }
100
101 wp_set_auth_cookie( $user->ID, $credentials['remember'], $secure_cookie );
102 /**
103 * Fires after the user has successfully logged in.
104 *
105 * @since 1.5.0
106 *
107 * @param string $user_login Username.
108 * @param WP_User $user WP_User object of the logged-in user.
109 */
110 do_action( 'wp_login', $user->user_login, $user );
111 return $user;
112}
113
114/**
115 * Authenticate a user, confirming the username and password are valid.
116 *
117 * @since 2.8.0
118 *
119 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null.
120 * @param string $username Username for authentication.
121 * @param string $password Password for authentication.
122 * @return WP_User|WP_Error WP_User on success, WP_Error on failure.
123 */
124function wp_authenticate_username_password( $user, $username, $password ) {
125 if ( $user instanceof WP_User ) {
126 return $user;
127 }
128
129 if ( empty( $username ) || empty( $password ) ) {
130 if ( is_wp_error( $user ) ) {
131 return $user;
132 }
133
134 $error = new WP_Error();
135
136 if ( empty( $username ) ) {
137 $error->add( 'empty_username', __( '<strong>ERROR</strong>: The username field is empty.' ) );
138 }
139
140 if ( empty( $password ) ) {
141 $error->add( 'empty_password', __( '<strong>ERROR</strong>: The password field is empty.' ) );
142 }
143
144 return $error;
145 }
146
147 $user = get_user_by( 'login', $username );
148
149 if ( ! $user ) {
150 return new WP_Error(
151 'invalid_username',
152 __( '<strong>ERROR</strong>: Invalid username.' ) .
153 ' <a href="' . wp_lostpassword_url() . '">' .
154 __( 'Lost your password?' ) .
155 '</a>'
156 );
157 }
158
159 /**
160 * Filters whether the given user can be authenticated with the provided $password.
161 *
162 * @since 2.5.0
163 *
164 * @param WP_User|WP_Error $user WP_User or WP_Error object if a previous
165 * callback failed authentication.
166 * @param string $password Password to check against the user.
167 */
168 $user = apply_filters( 'wp_authenticate_user', $user, $password );
169 if ( is_wp_error( $user ) ) {
170 return $user;
171 }
172
173 if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
174 return new WP_Error(
175 'incorrect_password',
176 sprintf(
177 /* translators: %s: user name */
178 __( '<strong>ERROR</strong>: The password you entered for the username %s is incorrect.' ),
179 '<strong>' . $username . '</strong>'
180 ) .
181 ' <a href="' . wp_lostpassword_url() . '">' .
182 __( 'Lost your password?' ) .
183 '</a>'
184 );
185 }
186
187 return $user;
188}
189
190/**
191 * Authenticates a user using the email and password.
192 *
193 * @since 4.5.0
194 *
195 * @param WP_User|WP_Error|null $user WP_User or WP_Error object if a previous
196 * callback failed authentication.
197 * @param string $email Email address for authentication.
198 * @param string $password Password for authentication.
199 * @return WP_User|WP_Error WP_User on success, WP_Error on failure.
200 */
201function wp_authenticate_email_password( $user, $email, $password ) {
202 if ( $user instanceof WP_User ) {
203 return $user;
204 }
205
206 if ( empty( $email ) || empty( $password ) ) {
207 if ( is_wp_error( $user ) ) {
208 return $user;
209 }
210
211 $error = new WP_Error();
212
213 if ( empty( $email ) ) {
214 $error->add( 'empty_username', __( '<strong>ERROR</strong>: The email field is empty.' ) ); // Uses 'empty_username' for back-compat with wp_signon()
215 }
216
217 if ( empty( $password ) ) {
218 $error->add( 'empty_password', __( '<strong>ERROR</strong>: The password field is empty.' ) );
219 }
220
221 return $error;
222 }
223
224 if ( ! is_email( $email ) ) {
225 return $user;
226 }
227
228 $user = get_user_by( 'email', $email );
229
230 if ( ! $user ) {
231 return new WP_Error(
232 'invalid_email',
233 __( '<strong>ERROR</strong>: Invalid email address.' ) .
234 ' <a href="' . wp_lostpassword_url() . '">' .
235 __( 'Lost your password?' ) .
236 '</a>'
237 );
238 }
239
240 /** This filter is documented in wp-includes/user.php */
241 $user = apply_filters( 'wp_authenticate_user', $user, $password );
242
243 if ( is_wp_error( $user ) ) {
244 return $user;
245 }
246
247 if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
248 return new WP_Error(
249 'incorrect_password',
250 sprintf(
251 /* translators: %s: email address */
252 __( '<strong>ERROR</strong>: The password you entered for the email address %s is incorrect.' ),
253 '<strong>' . $email . '</strong>'
254 ) .
255 ' <a href="' . wp_lostpassword_url() . '">' .
256 __( 'Lost your password?' ) .
257 '</a>'
258 );
259 }
260
261 return $user;
262}
263
264/**
265 * Authenticate the user using the WordPress auth cookie.
266 *
267 * @since 2.8.0
268 *
269 * @global string $auth_secure_cookie
270 *
271 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null.
272 * @param string $username Username. If not empty, cancels the cookie authentication.
273 * @param string $password Password. If not empty, cancels the cookie authentication.
274 * @return WP_User|WP_Error WP_User on success, WP_Error on failure.
275 */
276function wp_authenticate_cookie( $user, $username, $password ) {
277 if ( $user instanceof WP_User ) {
278 return $user;
279 }
280
281 if ( empty( $username ) && empty( $password ) ) {
282 $user_id = wp_validate_auth_cookie();
283 if ( $user_id ) {
284 return new WP_User( $user_id );
285 }
286
287 global $auth_secure_cookie;
288
289 if ( $auth_secure_cookie ) {
290 $auth_cookie = SECURE_AUTH_COOKIE;
291 } else {
292 $auth_cookie = AUTH_COOKIE;
293 }
294
295 if ( ! empty( $_COOKIE[ $auth_cookie ] ) ) {
296 return new WP_Error( 'expired_session', __( 'Please log in again.' ) );
297 }
298
299 // If the cookie is not set, be silent.
300 }
301
302 return $user;
303}
304
305/**
306 * For Multisite blogs, check if the authenticated user has been marked as a
307 * spammer, or if the user's primary blog has been marked as spam.
308 *
309 * @since 3.7.0
310 *
311 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null.
312 * @return WP_User|WP_Error WP_User on success, WP_Error if the user is considered a spammer.
313 */
314function wp_authenticate_spam_check( $user ) {
315 if ( $user instanceof WP_User && is_multisite() ) {
316 /**
317 * Filters whether the user has been marked as a spammer.
318 *
319 * @since 3.7.0
320 *
321 * @param bool $spammed Whether the user is considered a spammer.
322 * @param WP_User $user User to check against.
323 */
324 $spammed = apply_filters( 'check_is_user_spammed', is_user_spammy( $user ), $user );
325
326 if ( $spammed ) {
327 return new WP_Error( 'spammer_account', __( '<strong>ERROR</strong>: Your account has been marked as a spammer.' ) );
328 }
329 }
330 return $user;
331}
332
333/**
334 * Validates the logged-in cookie.
335 *
336 * Checks the logged-in cookie if the previous auth cookie could not be
337 * validated and parsed.
338 *
339 * This is a callback for the {@see 'determine_current_user'} filter, rather than API.
340 *
341 * @since 3.9.0
342 *
343 * @param int|bool $user_id The user ID (or false) as received from the
344 * determine_current_user filter.
345 * @return int|false User ID if validated, false otherwise. If a user ID from
346 * an earlier filter callback is received, that value is returned.
347 */
348function wp_validate_logged_in_cookie( $user_id ) {
349 if ( $user_id ) {
350 return $user_id;
351 }
352
353 if ( is_blog_admin() || is_network_admin() || empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
354 return false;
355 }
356
357 return wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' );
358}
359
360/**
361 * Number of posts user has written.
362 *
363 * @since 3.0.0
364 * @since 4.1.0 Added `$post_type` argument.
365 * @since 4.3.0 Added `$public_only` argument. Added the ability to pass an array
366 * of post types to `$post_type`.
367 *
368 * @global wpdb $wpdb WordPress database abstraction object.
369 *
370 * @param int $userid User ID.
371 * @param array|string $post_type Optional. Single post type or array of post types to count the number of posts for. Default 'post'.
372 * @param bool $public_only Optional. Whether to only return counts for public posts. Default false.
373 * @return string Number of posts the user has written in this post type.
374 */
375function count_user_posts( $userid, $post_type = 'post', $public_only = false ) {
376 global $wpdb;
377
378 $where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
379
380 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
381
382 /**
383 * Filters the number of posts a user has written.
384 *
385 * @since 2.7.0
386 * @since 4.1.0 Added `$post_type` argument.
387 * @since 4.3.1 Added `$public_only` argument.
388 *
389 * @param int $count The user's post count.
390 * @param int $userid User ID.
391 * @param string|array $post_type Single post type or array of post types to count the number of posts for.
392 * @param bool $public_only Whether to limit counted posts to public posts.
393 */
394 return apply_filters( 'get_usernumposts', $count, $userid, $post_type, $public_only );
395}
396
397/**
398 * Number of posts written by a list of users.
399 *
400 * @since 3.0.0
401 *
402 * @global wpdb $wpdb WordPress database abstraction object.
403 *
404 * @param array $users Array of user IDs.
405 * @param string|array $post_type Optional. Single post type or array of post types to check. Defaults to 'post'.
406 * @param bool $public_only Optional. Only return counts for public posts. Defaults to false.
407 * @return array Amount of posts each user has written.
408 */
409function count_many_users_posts( $users, $post_type = 'post', $public_only = false ) {
410 global $wpdb;
411
412 $count = array();
413 if ( empty( $users ) || ! is_array( $users ) ) {
414 return $count;
415 }
416
417 $userlist = implode( ',', array_map( 'absint', $users ) );
418 $where = get_posts_by_author_sql( $post_type, true, null, $public_only );
419
420 $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N );
421 foreach ( $result as $row ) {
422 $count[ $row[0] ] = $row[1];
423 }
424
425 foreach ( $users as $id ) {
426 if ( ! isset( $count[ $id ] ) ) {
427 $count[ $id ] = 0;
428 }
429 }
430
431 return $count;
432}
433
434//
435// User option functions
436//
437
438/**
439 * Get the current user's ID
440 *
441 * @since MU (3.0.0)
442 *
443 * @return int The current user's ID, or 0 if no user is logged in.
444 */
445function get_current_user_id() {
446 if ( ! function_exists( 'wp_get_current_user' ) ) {
447 return 0;
448 }
449 $user = wp_get_current_user();
450 return ( isset( $user->ID ) ? (int) $user->ID : 0 );
451}
452
453/**
454 * Retrieve user option that can be either per Site or per Network.
455 *
456 * If the user ID is not given, then the current user will be used instead. If
457 * the user ID is given, then the user data will be retrieved. The filter for
458 * the result, will also pass the original option name and finally the user data
459 * object as the third parameter.
460 *
461 * The option will first check for the per site name and then the per Network name.
462 *
463 * @since 2.0.0
464 *
465 * @global wpdb $wpdb WordPress database abstraction object.
466 *
467 * @param string $option User option name.
468 * @param int $user Optional. User ID.
469 * @param string $deprecated Use get_option() to check for an option in the options table.
470 * @return mixed User option value on success, false on failure.
471 */
472function get_user_option( $option, $user = 0, $deprecated = '' ) {
473 global $wpdb;
474
475 if ( ! empty( $deprecated ) ) {
476 _deprecated_argument( __FUNCTION__, '3.0.0' );
477 }
478
479 if ( empty( $user ) ) {
480 $user = get_current_user_id();
481 }
482
483 if ( ! $user = get_userdata( $user ) ) {
484 return false;
485 }
486
487 $prefix = $wpdb->get_blog_prefix();
488 if ( $user->has_prop( $prefix . $option ) ) { // Blog specific
489 $result = $user->get( $prefix . $option );
490 } elseif ( $user->has_prop( $option ) ) { // User specific and cross-blog
491 $result = $user->get( $option );
492 } else {
493 $result = false;
494 }
495
496 /**
497 * Filters a specific user option value.
498 *
499 * The dynamic portion of the hook name, `$option`, refers to the user option name.
500 *
501 * @since 2.5.0
502 *
503 * @param mixed $result Value for the user's option.
504 * @param string $option Name of the option being retrieved.
505 * @param WP_User $user WP_User object of the user whose option is being retrieved.
506 */
507 return apply_filters( "get_user_option_{$option}", $result, $option, $user );
508}
509
510/**
511 * Update user option with global blog capability.
512 *
513 * User options are just like user metadata except that they have support for
514 * global blog options. If the 'global' parameter is false, which it is by default
515 * it will prepend the WordPress table prefix to the option name.
516 *
517 * Deletes the user option if $newvalue is empty.
518 *
519 * @since 2.0.0
520 *
521 * @global wpdb $wpdb WordPress database abstraction object.
522 *
523 * @param int $user_id User ID.
524 * @param string $option_name User option name.
525 * @param mixed $newvalue User option value.
526 * @param bool $global Optional. Whether option name is global or blog specific.
527 * Default false (blog specific).
528 * @return int|bool User meta ID if the option didn't exist, true on successful update,
529 * false on failure.
530 */
531function update_user_option( $user_id, $option_name, $newvalue, $global = false ) {
532 global $wpdb;
533
534 if ( ! $global ) {
535 $option_name = $wpdb->get_blog_prefix() . $option_name;
536 }
537
538 return update_user_meta( $user_id, $option_name, $newvalue );
539}
540
541/**
542 * Delete user option with global blog capability.
543 *
544 * User options are just like user metadata except that they have support for
545 * global blog options. If the 'global' parameter is false, which it is by default
546 * it will prepend the WordPress table prefix to the option name.
547 *
548 * @since 3.0.0
549 *
550 * @global wpdb $wpdb WordPress database abstraction object.
551 *
552 * @param int $user_id User ID
553 * @param string $option_name User option name.
554 * @param bool $global Optional. Whether option name is global or blog specific.
555 * Default false (blog specific).
556 * @return bool True on success, false on failure.
557 */
558function delete_user_option( $user_id, $option_name, $global = false ) {
559 global $wpdb;
560
561 if ( ! $global ) {
562 $option_name = $wpdb->get_blog_prefix() . $option_name;
563 }
564 return delete_user_meta( $user_id, $option_name );
565}
566
567/**
568 * Retrieve list of users matching criteria.
569 *
570 * @since 3.1.0
571 *
572 * @see WP_User_Query
573 *
574 * @param array $args Optional. Arguments to retrieve users. See WP_User_Query::prepare_query().
575 * for more information on accepted arguments.
576 * @return array List of users.
577 */
578function get_users( $args = array() ) {
579
580 $args = wp_parse_args( $args );
581 $args['count_total'] = false;
582
583 $user_search = new WP_User_Query( $args );
584
585 return (array) $user_search->get_results();
586}
587
588/**
589 * Get the sites a user belongs to.
590 *
591 * @since 3.0.0
592 * @since 4.7.0 Converted to use `get_sites()`.
593 *
594 * @global wpdb $wpdb WordPress database abstraction object.
595 *
596 * @param int $user_id User ID
597 * @param bool $all Whether to retrieve all sites, or only sites that are not
598 * marked as deleted, archived, or spam.
599 * @return array A list of the user's sites. An empty array if the user doesn't exist
600 * or belongs to no sites.
601 */
602function get_blogs_of_user( $user_id, $all = false ) {
603 global $wpdb;
604
605 $user_id = (int) $user_id;
606
607 // Logged out users can't have sites
608 if ( empty( $user_id ) ) {
609 return array();
610 }
611
612 /**
613 * Filters the list of a user's sites before it is populated.
614 *
615 * Passing a non-null value to the filter will effectively short circuit
616 * get_blogs_of_user(), returning that value instead.
617 *
618 * @since 4.6.0
619 *
620 * @param null|array $sites An array of site objects of which the user is a member.
621 * @param int $user_id User ID.
622 * @param bool $all Whether the returned array should contain all sites, including
623 * those marked 'deleted', 'archived', or 'spam'. Default false.
624 */
625 $sites = apply_filters( 'pre_get_blogs_of_user', null, $user_id, $all );
626
627 if ( null !== $sites ) {
628 return $sites;
629 }
630
631 $keys = get_user_meta( $user_id );
632 if ( empty( $keys ) ) {
633 return array();
634 }
635
636 if ( ! is_multisite() ) {
637 $site_id = get_current_blog_id();
638 $sites = array( $site_id => new stdClass );
639 $sites[ $site_id ]->userblog_id = $site_id;
640 $sites[ $site_id ]->blogname = get_option( 'blogname' );
641 $sites[ $site_id ]->domain = '';
642 $sites[ $site_id ]->path = '';
643 $sites[ $site_id ]->site_id = 1;
644 $sites[ $site_id ]->siteurl = get_option( 'siteurl' );
645 $sites[ $site_id ]->archived = 0;
646 $sites[ $site_id ]->spam = 0;
647 $sites[ $site_id ]->deleted = 0;
648 return $sites;
649 }
650
651 $site_ids = array();
652
653 if ( isset( $keys[ $wpdb->base_prefix . 'capabilities' ] ) && defined( 'MULTISITE' ) ) {
654 $site_ids[] = 1;
655 unset( $keys[ $wpdb->base_prefix . 'capabilities' ] );
656 }
657
658 $keys = array_keys( $keys );
659
660 foreach ( $keys as $key ) {
661 if ( 'capabilities' !== substr( $key, -12 ) ) {
662 continue;
663 }
664 if ( $wpdb->base_prefix && 0 !== strpos( $key, $wpdb->base_prefix ) ) {
665 continue;
666 }
667 $site_id = str_replace( array( $wpdb->base_prefix, '_capabilities' ), '', $key );
668 if ( ! is_numeric( $site_id ) ) {
669 continue;
670 }
671
672 $site_ids[] = (int) $site_id;
673 }
674
675 $sites = array();
676
677 if ( ! empty( $site_ids ) ) {
678 $args = array(
679 'number' => '',
680 'site__in' => $site_ids,
681 'update_site_meta_cache' => false,
682 );
683 if ( ! $all ) {
684 $args['archived'] = 0;
685 $args['spam'] = 0;
686 $args['deleted'] = 0;
687 }
688
689 $_sites = get_sites( $args );
690
691 foreach ( $_sites as $site ) {
692 $sites[ $site->id ] = (object) array(
693 'userblog_id' => $site->id,
694 'blogname' => $site->blogname,
695 'domain' => $site->domain,
696 'path' => $site->path,
697 'site_id' => $site->network_id,
698 'siteurl' => $site->siteurl,
699 'archived' => $site->archived,
700 'mature' => $site->mature,
701 'spam' => $site->spam,
702 'deleted' => $site->deleted,
703 );
704 }
705 }
706
707 /**
708 * Filters the list of sites a user belongs to.
709 *
710 * @since MU (3.0.0)
711 *
712 * @param array $sites An array of site objects belonging to the user.
713 * @param int $user_id User ID.
714 * @param bool $all Whether the returned sites array should contain all sites, including
715 * those marked 'deleted', 'archived', or 'spam'. Default false.
716 */
717 return apply_filters( 'get_blogs_of_user', $sites, $user_id, $all );
718}
719
720/**
721 * Find out whether a user is a member of a given blog.
722 *
723 * @since MU (3.0.0)
724 *
725 * @global wpdb $wpdb WordPress database abstraction object.
726 *
727 * @param int $user_id Optional. The unique ID of the user. Defaults to the current user.
728 * @param int $blog_id Optional. ID of the blog to check. Defaults to the current site.
729 * @return bool
730 */
731function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) {
732 global $wpdb;
733
734 $user_id = (int) $user_id;
735 $blog_id = (int) $blog_id;
736
737 if ( empty( $user_id ) ) {
738 $user_id = get_current_user_id();
739 }
740
741 // Technically not needed, but does save calls to get_site and get_user_meta
742 // in the event that the function is called when a user isn't logged in
743 if ( empty( $user_id ) ) {
744 return false;
745 } else {
746 $user = get_userdata( $user_id );
747 if ( ! $user instanceof WP_User ) {
748 return false;
749 }
750 }
751
752 if ( ! is_multisite() ) {
753 return true;
754 }
755
756 if ( empty( $blog_id ) ) {
757 $blog_id = get_current_blog_id();
758 }
759
760 $blog = get_site( $blog_id );
761
762 if ( ! $blog || ! isset( $blog->domain ) || $blog->archived || $blog->spam || $blog->deleted ) {
763 return false;
764 }
765
766 $keys = get_user_meta( $user_id );
767 if ( empty( $keys ) ) {
768 return false;
769 }
770
771 // no underscore before capabilities in $base_capabilities_key
772 $base_capabilities_key = $wpdb->base_prefix . 'capabilities';
773 $site_capabilities_key = $wpdb->base_prefix . $blog_id . '_capabilities';
774
775 if ( isset( $keys[ $base_capabilities_key ] ) && $blog_id == 1 ) {
776 return true;
777 }
778
779 if ( isset( $keys[ $site_capabilities_key ] ) ) {
780 return true;
781 }
782
783 return false;
784}
785
786/**
787 * Adds meta data to a user.
788 *
789 * @since 3.0.0
790 *
791 * @param int $user_id User ID.
792 * @param string $meta_key Metadata name.
793 * @param mixed $meta_value Metadata value.
794 * @param bool $unique Optional. Whether the same key should not be added. Default false.
795 * @return int|false Meta ID on success, false on failure.
796 */
797function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) {
798 return add_metadata( 'user', $user_id, $meta_key, $meta_value, $unique );
799}
800
801/**
802 * Remove metadata matching criteria from a user.
803 *
804 * You can match based on the key, or key and value. Removing based on key and
805 * value, will keep from removing duplicate metadata with the same key. It also
806 * allows removing all metadata matching key, if needed.
807 *
808 * @since 3.0.0
809 * @link https://codex.wordpress.org/Function_Reference/delete_user_meta
810 *
811 * @param int $user_id User ID
812 * @param string $meta_key Metadata name.
813 * @param mixed $meta_value Optional. Metadata value.
814 * @return bool True on success, false on failure.
815 */
816function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
817 return delete_metadata( 'user', $user_id, $meta_key, $meta_value );
818}
819
820/**
821 * Retrieve user meta field for a user.
822 *
823 * @since 3.0.0
824 * @link https://codex.wordpress.org/Function_Reference/get_user_meta
825 *
826 * @param int $user_id User ID.
827 * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys.
828 * @param bool $single Whether to return a single value.
829 * @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true.
830 */
831function get_user_meta( $user_id, $key = '', $single = false ) {
832 return get_metadata( 'user', $user_id, $key, $single );
833}
834
835/**
836 * Update user meta field based on user ID.
837 *
838 * Use the $prev_value parameter to differentiate between meta fields with the
839 * same key and user ID.
840 *
841 * If the meta field for the user does not exist, it will be added.
842 *
843 * @since 3.0.0
844 * @link https://codex.wordpress.org/Function_Reference/update_user_meta
845 *
846 * @param int $user_id User ID.
847 * @param string $meta_key Metadata key.
848 * @param mixed $meta_value Metadata value.
849 * @param mixed $prev_value Optional. Previous value to check before removing.
850 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
851 */
852function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
853 return update_metadata( 'user', $user_id, $meta_key, $meta_value, $prev_value );
854}
855
856/**
857 * Count number of users who have each of the user roles.
858 *
859 * Assumes there are neither duplicated nor orphaned capabilities meta_values.
860 * Assumes role names are unique phrases. Same assumption made by WP_User_Query::prepare_query()
861 * Using $strategy = 'time' this is CPU-intensive and should handle around 10^7 users.
862 * Using $strategy = 'memory' this is memory-intensive and should handle around 10^5 users, but see WP Bug #12257.
863 *
864 * @since 3.0.0
865 * @since 4.4.0 The number of users with no role is now included in the `none` element.
866 * @since 4.9.0 The `$site_id` parameter was added to support multisite.
867 *
868 * @global wpdb $wpdb WordPress database abstraction object.
869 *
870 * @param string $strategy Optional. The computational strategy to use when counting the users.
871 * Accepts either 'time' or 'memory'. Default 'time'.
872 * @param int|null $site_id Optional. The site ID to count users for. Defaults to the current site.
873 * @return array Includes a grand total and an array of counts indexed by role strings.
874 */
875function count_users( $strategy = 'time', $site_id = null ) {
876 global $wpdb;
877
878 // Initialize
879 if ( ! $site_id ) {
880 $site_id = get_current_blog_id();
881 }
882
883 /**
884 * Filter the user count before queries are run. Return a non-null value to cause count_users()
885 * to return early.
886 *
887 * @since 5.1.0
888 *
889 * @param null|string $result Default null.
890 * @param string $strategy Optional. The computational strategy to use when counting the users.
891 * Accepts either 'time' or 'memory'. Default 'time'.
892 * @param int|null $site_id Optional. The site ID to count users for. Defaults to the current site.
893 */
894 $pre = apply_filters( 'pre_count_users', null, $strategy, $site_id );
895
896 if ( null !== $pre ) {
897 return $pre;
898 }
899
900 $blog_prefix = $wpdb->get_blog_prefix( $site_id );
901 $result = array();
902
903 if ( 'time' == $strategy ) {
904 if ( is_multisite() && $site_id != get_current_blog_id() ) {
905 switch_to_blog( $site_id );
906 $avail_roles = wp_roles()->get_names();
907 restore_current_blog();
908 } else {
909 $avail_roles = wp_roles()->get_names();
910 }
911
912 // Build a CPU-intensive query that will return concise information.
913 $select_count = array();
914 foreach ( $avail_roles as $this_role => $name ) {
915 $select_count[] = $wpdb->prepare( 'COUNT(NULLIF(`meta_value` LIKE %s, false))', '%' . $wpdb->esc_like( '"' . $this_role . '"' ) . '%' );
916 }
917 $select_count[] = "COUNT(NULLIF(`meta_value` = 'a:0:{}', false))";
918 $select_count = implode( ', ', $select_count );
919
920 // Add the meta_value index to the selection list, then run the query.
921 $row = $wpdb->get_row(
922 "
923 SELECT {$select_count}, COUNT(*)
924 FROM {$wpdb->usermeta}
925 INNER JOIN {$wpdb->users} ON user_id = ID
926 WHERE meta_key = '{$blog_prefix}capabilities'
927 ",
928 ARRAY_N
929 );
930
931 // Run the previous loop again to associate results with role names.
932 $col = 0;
933 $role_counts = array();
934 foreach ( $avail_roles as $this_role => $name ) {
935 $count = (int) $row[ $col++ ];
936 if ( $count > 0 ) {
937 $role_counts[ $this_role ] = $count;
938 }
939 }
940
941 $role_counts['none'] = (int) $row[ $col++ ];
942
943 // Get the meta_value index from the end of the result set.
944 $total_users = (int) $row[ $col ];
945
946 $result['total_users'] = $total_users;
947 $result['avail_roles'] =& $role_counts;
948 } else {
949 $avail_roles = array(
950 'none' => 0,
951 );
952
953 $users_of_blog = $wpdb->get_col(
954 "
955 SELECT meta_value
956 FROM {$wpdb->usermeta}
957 INNER JOIN {$wpdb->users} ON user_id = ID
958 WHERE meta_key = '{$blog_prefix}capabilities'
959 "
960 );
961
962 foreach ( $users_of_blog as $caps_meta ) {
963 $b_roles = maybe_unserialize( $caps_meta );
964 if ( ! is_array( $b_roles ) ) {
965 continue;
966 }
967 if ( empty( $b_roles ) ) {
968 $avail_roles['none']++;
969 }
970 foreach ( $b_roles as $b_role => $val ) {
971 if ( isset( $avail_roles[ $b_role ] ) ) {
972 $avail_roles[ $b_role ]++;
973 } else {
974 $avail_roles[ $b_role ] = 1;
975 }
976 }
977 }
978
979 $result['total_users'] = count( $users_of_blog );
980 $result['avail_roles'] =& $avail_roles;
981 }
982
983 return $result;
984}
985
986//
987// Private helper functions
988//
989
990/**
991 * Set up global user vars.
992 *
993 * Used by wp_set_current_user() for back compat. Might be deprecated in the future.
994 *
995 * @since 2.0.4
996 *
997 * @global string $user_login The user username for logging in
998 * @global WP_User $userdata User data.
999 * @global int $user_level The level of the user
1000 * @global int $user_ID The ID of the user
1001 * @global string $user_email The email address of the user
1002 * @global string $user_url The url in the user's profile
1003 * @global string $user_identity The display name of the user
1004 *
1005 * @param int $for_user_id Optional. User ID to set up global data. Default 0.
1006 */
1007function setup_userdata( $for_user_id = 0 ) {
1008 global $user_login, $userdata, $user_level, $user_ID, $user_email, $user_url, $user_identity;
1009
1010 if ( ! $for_user_id ) {
1011 $for_user_id = get_current_user_id();
1012 }
1013 $user = get_userdata( $for_user_id );
1014
1015 if ( ! $user ) {
1016 $user_ID = 0;
1017 $user_level = 0;
1018 $userdata = null;
1019 $user_login = $user_email = $user_url = $user_identity = '';
1020 return;
1021 }
1022
1023 $user_ID = (int) $user->ID;
1024 $user_level = (int) $user->user_level;
1025 $userdata = $user;
1026 $user_login = $user->user_login;
1027 $user_email = $user->user_email;
1028 $user_url = $user->user_url;
1029 $user_identity = $user->display_name;
1030}
1031
1032/**
1033 * Create dropdown HTML content of users.
1034 *
1035 * The content can either be displayed, which it is by default or retrieved by
1036 * setting the 'echo' argument. The 'include' and 'exclude' arguments do not
1037 * need to be used; all users will be displayed in that case. Only one can be
1038 * used, either 'include' or 'exclude', but not both.
1039 *
1040 * The available arguments are as follows:
1041 *
1042 * @since 2.3.0
1043 * @since 4.5.0 Added the 'display_name_with_login' value for 'show'.
1044 * @since 4.7.0 Added the `$role`, `$role__in`, and `$role__not_in` parameters.
1045 *
1046 * @param array|string $args {
1047 * Optional. Array or string of arguments to generate a drop-down of users.
1048 * See WP_User_Query::prepare_query() for additional available arguments.
1049 *
1050 * @type string $show_option_all Text to show as the drop-down default (all).
1051 * Default empty.
1052 * @type string $show_option_none Text to show as the drop-down default when no
1053 * users were found. Default empty.
1054 * @type int|string $option_none_value Value to use for $show_option_non when no users
1055 * were found. Default -1.
1056 * @type string $hide_if_only_one_author Whether to skip generating the drop-down
1057 * if only one user was found. Default empty.
1058 * @type string $orderby Field to order found users by. Accepts user fields.
1059 * Default 'display_name'.
1060 * @type string $order Whether to order users in ascending or descending
1061 * order. Accepts 'ASC' (ascending) or 'DESC' (descending).
1062 * Default 'ASC'.
1063 * @type array|string $include Array or comma-separated list of user IDs to include.
1064 * Default empty.
1065 * @type array|string $exclude Array or comma-separated list of user IDs to exclude.
1066 * Default empty.
1067 * @type bool|int $multi Whether to skip the ID attribute on the 'select' element.
1068 * Accepts 1|true or 0|false. Default 0|false.
1069 * @type string $show User data to display. If the selected item is empty
1070 * then the 'user_login' will be displayed in parentheses.
1071 * Accepts any user field, or 'display_name_with_login' to show
1072 * the display name with user_login in parentheses.
1073 * Default 'display_name'.
1074 * @type int|bool $echo Whether to echo or return the drop-down. Accepts 1|true (echo)
1075 * or 0|false (return). Default 1|true.
1076 * @type int $selected Which user ID should be selected. Default 0.
1077 * @type bool $include_selected Whether to always include the selected user ID in the drop-
1078 * down. Default false.
1079 * @type string $name Name attribute of select element. Default 'user'.
1080 * @type string $id ID attribute of the select element. Default is the value of $name.
1081 * @type string $class Class attribute of the select element. Default empty.
1082 * @type int $blog_id ID of blog (Multisite only). Default is ID of the current blog.
1083 * @type string $who Which type of users to query. Accepts only an empty string or
1084 * 'authors'. Default empty.
1085 * @type string|array $role An array or a comma-separated list of role names that users must
1086 * match to be included in results. Note that this is an inclusive
1087 * list: users must match *each* role. Default empty.
1088 * @type array $role__in An array of role names. Matched users must have at least one of
1089 * these roles. Default empty array.
1090 * @type array $role__not_in An array of role names to exclude. Users matching one or more of
1091 * these roles will not be included in results. Default empty array.
1092 * }
1093 * @return string String of HTML content.
1094 */
1095function wp_dropdown_users( $args = '' ) {
1096 $defaults = array(
1097 'show_option_all' => '',
1098 'show_option_none' => '',
1099 'hide_if_only_one_author' => '',
1100 'orderby' => 'display_name',
1101 'order' => 'ASC',
1102 'include' => '',
1103 'exclude' => '',
1104 'multi' => 0,
1105 'show' => 'display_name',
1106 'echo' => 1,
1107 'selected' => 0,
1108 'name' => 'user',
1109 'class' => '',
1110 'id' => '',
1111 'blog_id' => get_current_blog_id(),
1112 'who' => '',
1113 'include_selected' => false,
1114 'option_none_value' => -1,
1115 'role' => '',
1116 'role__in' => array(),
1117 'role__not_in' => array(),
1118 );
1119
1120 $defaults['selected'] = is_author() ? get_query_var( 'author' ) : 0;
1121
1122 $r = wp_parse_args( $args, $defaults );
1123
1124 $query_args = wp_array_slice_assoc( $r, array( 'blog_id', 'include', 'exclude', 'orderby', 'order', 'who', 'role', 'role__in', 'role__not_in' ) );
1125
1126 $fields = array( 'ID', 'user_login' );
1127
1128 $show = ! empty( $r['show'] ) ? $r['show'] : 'display_name';
1129 if ( 'display_name_with_login' === $show ) {
1130 $fields[] = 'display_name';
1131 } else {
1132 $fields[] = $show;
1133 }
1134
1135 $query_args['fields'] = $fields;
1136
1137 $show_option_all = $r['show_option_all'];
1138 $show_option_none = $r['show_option_none'];
1139 $option_none_value = $r['option_none_value'];
1140
1141 /**
1142 * Filters the query arguments for the list of users in the dropdown.
1143 *
1144 * @since 4.4.0
1145 *
1146 * @param array $query_args The query arguments for get_users().
1147 * @param array $r The arguments passed to wp_dropdown_users() combined with the defaults.
1148 */
1149 $query_args = apply_filters( 'wp_dropdown_users_args', $query_args, $r );
1150
1151 $users = get_users( $query_args );
1152
1153 $output = '';
1154 if ( ! empty( $users ) && ( empty( $r['hide_if_only_one_author'] ) || count( $users ) > 1 ) ) {
1155 $name = esc_attr( $r['name'] );
1156 if ( $r['multi'] && ! $r['id'] ) {
1157 $id = '';
1158 } else {
1159 $id = $r['id'] ? " id='" . esc_attr( $r['id'] ) . "'" : " id='$name'";
1160 }
1161 $output = "<select name='{$name}'{$id} class='" . $r['class'] . "'>\n";
1162
1163 if ( $show_option_all ) {
1164 $output .= "\t<option value='0'>$show_option_all</option>\n";
1165 }
1166
1167 if ( $show_option_none ) {
1168 $_selected = selected( $option_none_value, $r['selected'], false );
1169 $output .= "\t<option value='" . esc_attr( $option_none_value ) . "'$_selected>$show_option_none</option>\n";
1170 }
1171
1172 if ( $r['include_selected'] && ( $r['selected'] > 0 ) ) {
1173 $found_selected = false;
1174 $r['selected'] = (int) $r['selected'];
1175 foreach ( (array) $users as $user ) {
1176 $user->ID = (int) $user->ID;
1177 if ( $user->ID === $r['selected'] ) {
1178 $found_selected = true;
1179 }
1180 }
1181
1182 if ( ! $found_selected ) {
1183 $users[] = get_userdata( $r['selected'] );
1184 }
1185 }
1186
1187 foreach ( (array) $users as $user ) {
1188 if ( 'display_name_with_login' === $show ) {
1189 /* translators: 1: display name, 2: user_login */
1190 $display = sprintf( _x( '%1$s (%2$s)', 'user dropdown' ), $user->display_name, $user->user_login );
1191 } elseif ( ! empty( $user->$show ) ) {
1192 $display = $user->$show;
1193 } else {
1194 $display = '(' . $user->user_login . ')';
1195 }
1196
1197 $_selected = selected( $user->ID, $r['selected'], false );
1198 $output .= "\t<option value='$user->ID'$_selected>" . esc_html( $display ) . "</option>\n";
1199 }
1200
1201 $output .= '</select>';
1202 }
1203
1204 /**
1205 * Filters the wp_dropdown_users() HTML output.
1206 *
1207 * @since 2.3.0
1208 *
1209 * @param string $output HTML output generated by wp_dropdown_users().
1210 */
1211 $html = apply_filters( 'wp_dropdown_users', $output );
1212
1213 if ( $r['echo'] ) {
1214 echo $html;
1215 }
1216 return $html;
1217}
1218
1219/**
1220 * Sanitize user field based on context.
1221 *
1222 * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and 'js'. The
1223 * 'display' context is used by default. 'attribute' and 'js' contexts are treated like 'display'
1224 * when calling filters.
1225 *
1226 * @since 2.3.0
1227 *
1228 * @param string $field The user Object field name.
1229 * @param mixed $value The user Object value.
1230 * @param int $user_id User ID.
1231 * @param string $context How to sanitize user fields. Looks for 'raw', 'edit', 'db', 'display',
1232 * 'attribute' and 'js'.
1233 * @return mixed Sanitized value.
1234 */
1235function sanitize_user_field( $field, $value, $user_id, $context ) {
1236 $int_fields = array( 'ID' );
1237 if ( in_array( $field, $int_fields ) ) {
1238 $value = (int) $value;
1239 }
1240
1241 if ( 'raw' == $context ) {
1242 return $value;
1243 }
1244
1245 if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
1246 return $value;
1247 }
1248
1249 $prefixed = false !== strpos( $field, 'user_' );
1250
1251 if ( 'edit' == $context ) {
1252 if ( $prefixed ) {
1253
1254 /** This filter is documented in wp-includes/post.php */
1255 $value = apply_filters( "edit_{$field}", $value, $user_id );
1256 } else {
1257
1258 /**
1259 * Filters a user field value in the 'edit' context.
1260 *
1261 * The dynamic portion of the hook name, `$field`, refers to the prefixed user
1262 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc.
1263 *
1264 * @since 2.9.0
1265 *
1266 * @param mixed $value Value of the prefixed user field.
1267 * @param int $user_id User ID.
1268 */
1269 $value = apply_filters( "edit_user_{$field}", $value, $user_id );
1270 }
1271
1272 if ( 'description' == $field ) {
1273 $value = esc_html( $value ); // textarea_escaped?
1274 } else {
1275 $value = esc_attr( $value );
1276 }
1277 } elseif ( 'db' == $context ) {
1278 if ( $prefixed ) {
1279 /** This filter is documented in wp-includes/post.php */
1280 $value = apply_filters( "pre_{$field}", $value );
1281 } else {
1282
1283 /**
1284 * Filters the value of a user field in the 'db' context.
1285 *
1286 * The dynamic portion of the hook name, `$field`, refers to the prefixed user
1287 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc.
1288 *
1289 * @since 2.9.0
1290 *
1291 * @param mixed $value Value of the prefixed user field.
1292 */
1293 $value = apply_filters( "pre_user_{$field}", $value );
1294 }
1295 } else {
1296 // Use display filters by default.
1297 if ( $prefixed ) {
1298
1299 /** This filter is documented in wp-includes/post.php */
1300 $value = apply_filters( "{$field}", $value, $user_id, $context );
1301 } else {
1302
1303 /**
1304 * Filters the value of a user field in a standard context.
1305 *
1306 * The dynamic portion of the hook name, `$field`, refers to the prefixed user
1307 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc.
1308 *
1309 * @since 2.9.0
1310 *
1311 * @param mixed $value The user object value to sanitize.
1312 * @param int $user_id User ID.
1313 * @param string $context The context to filter within.
1314 */
1315 $value = apply_filters( "user_{$field}", $value, $user_id, $context );
1316 }
1317 }
1318
1319 if ( 'user_url' == $field ) {
1320 $value = esc_url( $value );
1321 }
1322
1323 if ( 'attribute' == $context ) {
1324 $value = esc_attr( $value );
1325 } elseif ( 'js' == $context ) {
1326 $value = esc_js( $value );
1327 }
1328 return $value;
1329}
1330
1331/**
1332 * Update all user caches
1333 *
1334 * @since 3.0.0
1335 *
1336 * @param WP_User $user User object to be cached
1337 * @return bool|null Returns false on failure.
1338 */
1339function update_user_caches( $user ) {
1340 if ( $user instanceof WP_User ) {
1341 if ( ! $user->exists() ) {
1342 return false;
1343 }
1344
1345 $user = $user->data;
1346 }
1347
1348 wp_cache_add( $user->ID, $user, 'users' );
1349 wp_cache_add( $user->user_login, $user->ID, 'userlogins' );
1350 wp_cache_add( $user->user_email, $user->ID, 'useremail' );
1351 wp_cache_add( $user->user_nicename, $user->ID, 'userslugs' );
1352}
1353
1354/**
1355 * Clean all user caches
1356 *
1357 * @since 3.0.0
1358 * @since 4.4.0 'clean_user_cache' action was added.
1359 *
1360 * @param WP_User|int $user User object or ID to be cleaned from the cache
1361 */
1362function clean_user_cache( $user ) {
1363 if ( is_numeric( $user ) ) {
1364 $user = new WP_User( $user );
1365 }
1366
1367 if ( ! $user->exists() ) {
1368 return;
1369 }
1370
1371 wp_cache_delete( $user->ID, 'users' );
1372 wp_cache_delete( $user->user_login, 'userlogins' );
1373 wp_cache_delete( $user->user_email, 'useremail' );
1374 wp_cache_delete( $user->user_nicename, 'userslugs' );
1375
1376 /**
1377 * Fires immediately after the given user's cache is cleaned.
1378 *
1379 * @since 4.4.0
1380 *
1381 * @param int $user_id User ID.
1382 * @param WP_User $user User object.
1383 */
1384 do_action( 'clean_user_cache', $user->ID, $user );
1385}
1386
1387/**
1388 * Determines whether the given username exists.
1389 *
1390 * For more information on this and similar theme functions, check out
1391 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1392 * Conditional Tags} article in the Theme Developer Handbook.
1393 *
1394 * @since 2.0.0
1395 *
1396 * @param string $username Username.
1397 * @return int|false The user's ID on success, and false on failure.
1398 */
1399function username_exists( $username ) {
1400 $user = get_user_by( 'login', $username );
1401 if ( $user ) {
1402 $user_id = $user->ID;
1403 } else {
1404 $user_id = false;
1405 }
1406
1407 /**
1408 * Filters whether the given username exists or not.
1409 *
1410 * @since 4.9.0
1411 *
1412 * @param int|false $user_id The user's ID on success, and false on failure.
1413 * @param string $username Username to check.
1414 */
1415 return apply_filters( 'username_exists', $user_id, $username );
1416}
1417
1418/**
1419 * Determines whether the given email exists.
1420 *
1421 * For more information on this and similar theme functions, check out
1422 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1423 * Conditional Tags} article in the Theme Developer Handbook.
1424 *
1425 * @since 2.1.0
1426 *
1427 * @param string $email Email.
1428 * @return int|false The user's ID on success, and false on failure.
1429 */
1430function email_exists( $email ) {
1431 $user = get_user_by( 'email', $email );
1432 if ( $user ) {
1433 return $user->ID;
1434 }
1435 return false;
1436}
1437
1438/**
1439 * Checks whether a username is valid.
1440 *
1441 * @since 2.0.1
1442 * @since 4.4.0 Empty sanitized usernames are now considered invalid
1443 *
1444 * @param string $username Username.
1445 * @return bool Whether username given is valid
1446 */
1447function validate_username( $username ) {
1448 $sanitized = sanitize_user( $username, true );
1449 $valid = ( $sanitized == $username && ! empty( $sanitized ) );
1450
1451 /**
1452 * Filters whether the provided username is valid or not.
1453 *
1454 * @since 2.0.1
1455 *
1456 * @param bool $valid Whether given username is valid.
1457 * @param string $username Username to check.
1458 */
1459 return apply_filters( 'validate_username', $valid, $username );
1460}
1461
1462/**
1463 * Insert a user into the database.
1464 *
1465 * Most of the `$userdata` array fields have filters associated with the values. Exceptions are
1466 * 'ID', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl',
1467 * 'user_registered', and 'role'. The filters have the prefix 'pre_user_' followed by the field
1468 * name. An example using 'description' would have the filter called, 'pre_user_description' that
1469 * can be hooked into.
1470 *
1471 * @since 2.0.0
1472 * @since 3.6.0 The `aim`, `jabber`, and `yim` fields were removed as default user contact
1473 * methods for new installations. See wp_get_user_contact_methods().
1474 * @since 4.7.0 The user's locale can be passed to `$userdata`.
1475 *
1476 * @global wpdb $wpdb WordPress database abstraction object.
1477 *
1478 * @param array|object|WP_User $userdata {
1479 * An array, object, or WP_User object of user data arguments.
1480 *
1481 * @type int $ID User ID. If supplied, the user will be updated.
1482 * @type string $user_pass The plain-text user password.
1483 * @type string $user_login The user's login username.
1484 * @type string $user_nicename The URL-friendly user name.
1485 * @type string $user_url The user URL.
1486 * @type string $user_email The user email address.
1487 * @type string $display_name The user's display name.
1488 * Default is the user's username.
1489 * @type string $nickname The user's nickname.
1490 * Default is the user's username.
1491 * @type string $first_name The user's first name. For new users, will be used
1492 * to build the first part of the user's display name
1493 * if `$display_name` is not specified.
1494 * @type string $last_name The user's last name. For new users, will be used
1495 * to build the second part of the user's display name
1496 * if `$display_name` is not specified.
1497 * @type string $description The user's biographical description.
1498 * @type string|bool $rich_editing Whether to enable the rich-editor for the user.
1499 * False if not empty.
1500 * @type string|bool $syntax_highlighting Whether to enable the rich code editor for the user.
1501 * False if not empty.
1502 * @type string|bool $comment_shortcuts Whether to enable comment moderation keyboard
1503 * shortcuts for the user. Default false.
1504 * @type string $admin_color Admin color scheme for the user. Default 'fresh'.
1505 * @type bool $use_ssl Whether the user should always access the admin over
1506 * https. Default false.
1507 * @type string $user_registered Date the user registered. Format is 'Y-m-d H:i:s'.
1508 * @type string|bool $show_admin_bar_front Whether to display the Admin Bar for the user on the
1509 * site's front end. Default true.
1510 * @type string $role User's role.
1511 * @type string $locale User's locale. Default empty.
1512 * }
1513 * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not
1514 * be created.
1515 */
1516function wp_insert_user( $userdata ) {
1517 global $wpdb;
1518
1519 if ( $userdata instanceof stdClass ) {
1520 $userdata = get_object_vars( $userdata );
1521 } elseif ( $userdata instanceof WP_User ) {
1522 $userdata = $userdata->to_array();
1523 }
1524
1525 // Are we updating or creating?
1526 if ( ! empty( $userdata['ID'] ) ) {
1527 $ID = (int) $userdata['ID'];
1528 $update = true;
1529 $old_user_data = get_userdata( $ID );
1530
1531 if ( ! $old_user_data ) {
1532 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) );
1533 }
1534
1535 // hashed in wp_update_user(), plaintext if called directly
1536 $user_pass = ! empty( $userdata['user_pass'] ) ? $userdata['user_pass'] : $old_user_data->user_pass;
1537 } else {
1538 $update = false;
1539 // Hash the password
1540 $user_pass = wp_hash_password( $userdata['user_pass'] );
1541 }
1542
1543 $sanitized_user_login = sanitize_user( $userdata['user_login'], true );
1544
1545 /**
1546 * Filters a username after it has been sanitized.
1547 *
1548 * This filter is called before the user is created or updated.
1549 *
1550 * @since 2.0.3
1551 *
1552 * @param string $sanitized_user_login Username after it has been sanitized.
1553 */
1554 $pre_user_login = apply_filters( 'pre_user_login', $sanitized_user_login );
1555
1556 //Remove any non-printable chars from the login string to see if we have ended up with an empty username
1557 $user_login = trim( $pre_user_login );
1558
1559 // user_login must be between 0 and 60 characters.
1560 if ( empty( $user_login ) ) {
1561 return new WP_Error( 'empty_user_login', __( 'Cannot create a user with an empty login name.' ) );
1562 } elseif ( mb_strlen( $user_login ) > 60 ) {
1563 return new WP_Error( 'user_login_too_long', __( 'Username may not be longer than 60 characters.' ) );
1564 }
1565
1566 if ( ! $update && username_exists( $user_login ) ) {
1567 return new WP_Error( 'existing_user_login', __( 'Sorry, that username already exists!' ) );
1568 }
1569
1570 /**
1571 * Filters the list of blacklisted usernames.
1572 *
1573 * @since 4.4.0
1574 *
1575 * @param array $usernames Array of blacklisted usernames.
1576 */
1577 $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
1578
1579 if ( in_array( strtolower( $user_login ), array_map( 'strtolower', $illegal_logins ) ) ) {
1580 return new WP_Error( 'invalid_username', __( 'Sorry, that username is not allowed.' ) );
1581 }
1582
1583 /*
1584 * If a nicename is provided, remove unsafe user characters before using it.
1585 * Otherwise build a nicename from the user_login.
1586 */
1587 if ( ! empty( $userdata['user_nicename'] ) ) {
1588 $user_nicename = sanitize_user( $userdata['user_nicename'], true );
1589 if ( mb_strlen( $user_nicename ) > 50 ) {
1590 return new WP_Error( 'user_nicename_too_long', __( 'Nicename may not be longer than 50 characters.' ) );
1591 }
1592 } else {
1593 $user_nicename = mb_substr( $user_login, 0, 50 );
1594 }
1595
1596 $user_nicename = sanitize_title( $user_nicename );
1597
1598 // Store values to save in user meta.
1599 $meta = array();
1600
1601 /**
1602 * Filters a user's nicename before the user is created or updated.
1603 *
1604 * @since 2.0.3
1605 *
1606 * @param string $user_nicename The user's nicename.
1607 */
1608 $user_nicename = apply_filters( 'pre_user_nicename', $user_nicename );
1609
1610 $raw_user_url = empty( $userdata['user_url'] ) ? '' : $userdata['user_url'];
1611
1612 /**
1613 * Filters a user's URL before the user is created or updated.
1614 *
1615 * @since 2.0.3
1616 *
1617 * @param string $raw_user_url The user's URL.
1618 */
1619 $user_url = apply_filters( 'pre_user_url', $raw_user_url );
1620
1621 $raw_user_email = empty( $userdata['user_email'] ) ? '' : $userdata['user_email'];
1622
1623 /**
1624 * Filters a user's email before the user is created or updated.
1625 *
1626 * @since 2.0.3
1627 *
1628 * @param string $raw_user_email The user's email.
1629 */
1630 $user_email = apply_filters( 'pre_user_email', $raw_user_email );
1631
1632 /*
1633 * If there is no update, just check for `email_exists`. If there is an update,
1634 * check if current email and new email are the same, or not, and check `email_exists`
1635 * accordingly.
1636 */
1637 if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) )
1638 && ! defined( 'WP_IMPORTING' )
1639 && email_exists( $user_email )
1640 ) {
1641 return new WP_Error( 'existing_user_email', __( 'Sorry, that email address is already used!' ) );
1642 }
1643 $raw_user_email = empty( $userdata['user_email'] ) ? '' : $userdata['user_email'];
1644 $user_email = apply_filters( 'pre_user_email', $raw_user_email );
1645
1646 $nickname = empty( $userdata['nickname'] ) ? $user_login : $userdata['nickname'];
1647
1648 /**
1649 * Filters a user's nickname before the user is created or updated.
1650 *
1651 * @since 2.0.3
1652 *
1653 * @param string $nickname The user's nickname.
1654 */
1655 $meta['nickname'] = apply_filters( 'pre_user_nickname', $nickname );
1656
1657 $first_name = empty( $userdata['first_name'] ) ? '' : $userdata['first_name'];
1658
1659 /**
1660 * Filters a user's first name before the user is created or updated.
1661 *
1662 * @since 2.0.3
1663 *
1664 * @param string $first_name The user's first name.
1665 */
1666 $meta['first_name'] = apply_filters( 'pre_user_first_name', $first_name );
1667
1668 $last_name = empty( $userdata['last_name'] ) ? '' : $userdata['last_name'];
1669
1670 /**
1671 * Filters a user's last name before the user is created or updated.
1672 *
1673 * @since 2.0.3
1674 *
1675 * @param string $last_name The user's last name.
1676 */
1677 $meta['last_name'] = apply_filters( 'pre_user_last_name', $last_name );
1678
1679 if ( empty( $userdata['display_name'] ) ) {
1680 if ( $update ) {
1681 $display_name = $user_login;
1682 } elseif ( $meta['first_name'] && $meta['last_name'] ) {
1683 /* translators: 1: first name, 2: last name */
1684 $display_name = sprintf( _x( '%1$s %2$s', 'Display name based on first name and last name' ), $meta['first_name'], $meta['last_name'] );
1685 } elseif ( $meta['first_name'] ) {
1686 $display_name = $meta['first_name'];
1687 } elseif ( $meta['last_name'] ) {
1688 $display_name = $meta['last_name'];
1689 } else {
1690 $display_name = $user_login;
1691 }
1692 } else {
1693 $display_name = $userdata['display_name'];
1694 }
1695
1696 /**
1697 * Filters a user's display name before the user is created or updated.
1698 *
1699 * @since 2.0.3
1700 *
1701 * @param string $display_name The user's display name.
1702 */
1703 $display_name = apply_filters( 'pre_user_display_name', $display_name );
1704
1705 $description = empty( $userdata['description'] ) ? '' : $userdata['description'];
1706
1707 /**
1708 * Filters a user's description before the user is created or updated.
1709 *
1710 * @since 2.0.3
1711 *
1712 * @param string $description The user's description.
1713 */
1714 $meta['description'] = apply_filters( 'pre_user_description', $description );
1715
1716 $meta['rich_editing'] = empty( $userdata['rich_editing'] ) ? 'true' : $userdata['rich_editing'];
1717
1718 $meta['syntax_highlighting'] = empty( $userdata['syntax_highlighting'] ) ? 'true' : $userdata['syntax_highlighting'];
1719
1720 $meta['comment_shortcuts'] = empty( $userdata['comment_shortcuts'] ) || 'false' === $userdata['comment_shortcuts'] ? 'false' : 'true';
1721
1722 $admin_color = empty( $userdata['admin_color'] ) ? 'fresh' : $userdata['admin_color'];
1723 $meta['admin_color'] = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $admin_color );
1724
1725 $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? 0 : $userdata['use_ssl'];
1726
1727 $user_registered = empty( $userdata['user_registered'] ) ? gmdate( 'Y-m-d H:i:s' ) : $userdata['user_registered'];
1728
1729 $meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front'];
1730
1731 $meta['locale'] = isset( $userdata['locale'] ) ? $userdata['locale'] : '';
1732
1733 $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $user_nicename, $user_login ) );
1734
1735 if ( $user_nicename_check ) {
1736 $suffix = 2;
1737 while ( $user_nicename_check ) {
1738 // user_nicename allows 50 chars. Subtract one for a hyphen, plus the length of the suffix.
1739 $base_length = 49 - mb_strlen( $suffix );
1740 $alt_user_nicename = mb_substr( $user_nicename, 0, $base_length ) . "-$suffix";
1741 $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $alt_user_nicename, $user_login ) );
1742 $suffix++;
1743 }
1744 $user_nicename = $alt_user_nicename;
1745 }
1746
1747 $compacted = compact( 'user_pass', 'user_email', 'user_url', 'user_nicename', 'display_name', 'user_registered' );
1748 $data = wp_unslash( $compacted );
1749
1750 if ( ! $update ) {
1751 $data = $data + compact( 'user_login' );
1752 }
1753
1754 /**
1755 * Filters user data before the record is created or updated.
1756 *
1757 * It only includes data in the wp_users table wp_user, not any user metadata.
1758 *
1759 * @since 4.9.0
1760 *
1761 * @param array $data {
1762 * Values and keys for the user.
1763 *
1764 * @type string $user_login The user's login. Only included if $update == false
1765 * @type string $user_pass The user's password.
1766 * @type string $user_email The user's email.
1767 * @type string $user_url The user's url.
1768 * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login
1769 * @type string $display_name The user's display name.
1770 * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to
1771 * the current UTC timestamp.
1772 * }
1773 * @param bool $update Whether the user is being updated rather than created.
1774 * @param int|null $id ID of the user to be updated, or NULL if the user is being created.
1775 */
1776 $data = apply_filters( 'wp_pre_insert_user_data', $data, $update, $update ? (int) $ID : null );
1777
1778 if ( $update ) {
1779 if ( $user_email !== $old_user_data->user_email ) {
1780 $data['user_activation_key'] = '';
1781 }
1782 $wpdb->update( $wpdb->users, $data, compact( 'ID' ) );
1783 $user_id = (int) $ID;
1784 } else {
1785 $wpdb->insert( $wpdb->users, $data );
1786 $user_id = (int) $wpdb->insert_id;
1787 }
1788
1789 $user = new WP_User( $user_id );
1790
1791 /**
1792 * Filters a user's meta values and keys immediately after the user is created or updated
1793 * and before any user meta is inserted or updated.
1794 *
1795 * Does not include contact methods. These are added using `wp_get_user_contact_methods( $user )`.
1796 *
1797 * @since 4.4.0
1798 *
1799 * @param array $meta {
1800 * Default meta values and keys for the user.
1801 *
1802 * @type string $nickname The user's nickname. Default is the user's username.
1803 * @type string $first_name The user's first name.
1804 * @type string $last_name The user's last name.
1805 * @type string $description The user's description.
1806 * @type bool $rich_editing Whether to enable the rich-editor for the user. False if not empty.
1807 * @type bool $syntax_highlighting Whether to enable the rich code editor for the user. False if not empty.
1808 * @type bool $comment_shortcuts Whether to enable keyboard shortcuts for the user. Default false.
1809 * @type string $admin_color The color scheme for a user's admin screen. Default 'fresh'.
1810 * @type int|bool $use_ssl Whether to force SSL on the user's admin area. 0|false if SSL is
1811 * not forced.
1812 * @type bool $show_admin_bar_front Whether to show the admin bar on the front end for the user.
1813 * Default true.
1814 * }
1815 * @param WP_User $user User object.
1816 * @param bool $update Whether the user is being updated rather than created.
1817 */
1818 $meta = apply_filters( 'insert_user_meta', $meta, $user, $update );
1819
1820 // Update user meta.
1821 foreach ( $meta as $key => $value ) {
1822 update_user_meta( $user_id, $key, $value );
1823 }
1824
1825 foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) {
1826 if ( isset( $userdata[ $key ] ) ) {
1827 update_user_meta( $user_id, $key, $userdata[ $key ] );
1828 }
1829 }
1830
1831 if ( isset( $userdata['role'] ) ) {
1832 $user->set_role( $userdata['role'] );
1833 } elseif ( ! $update ) {
1834 $user->set_role( get_option( 'default_role' ) );
1835 }
1836 wp_cache_delete( $user_id, 'users' );
1837 wp_cache_delete( $user_login, 'userlogins' );
1838
1839 if ( $update ) {
1840 /**
1841 * Fires immediately after an existing user is updated.
1842 *
1843 * @since 2.0.0
1844 *
1845 * @param int $user_id User ID.
1846 * @param WP_User $old_user_data Object containing user's data prior to update.
1847 */
1848 do_action( 'profile_update', $user_id, $old_user_data );
1849 } else {
1850 /**
1851 * Fires immediately after a new user is registered.
1852 *
1853 * @since 1.5.0
1854 *
1855 * @param int $user_id User ID.
1856 */
1857 do_action( 'user_register', $user_id );
1858 }
1859
1860 return $user_id;
1861}
1862
1863/**
1864 * Update a user in the database.
1865 *
1866 * It is possible to update a user's password by specifying the 'user_pass'
1867 * value in the $userdata parameter array.
1868 *
1869 * If current user's password is being updated, then the cookies will be
1870 * cleared.
1871 *
1872 * @since 2.0.0
1873 *
1874 * @see wp_insert_user() For what fields can be set in $userdata.
1875 *
1876 * @param array|object|WP_User $userdata An array of user data or a user object of type stdClass or WP_User.
1877 * @return int|WP_Error The updated user's ID or a WP_Error object if the user could not be updated.
1878 */
1879function wp_update_user( $userdata ) {
1880 if ( $userdata instanceof stdClass ) {
1881 $userdata = get_object_vars( $userdata );
1882 } elseif ( $userdata instanceof WP_User ) {
1883 $userdata = $userdata->to_array();
1884 }
1885
1886 $ID = isset( $userdata['ID'] ) ? (int) $userdata['ID'] : 0;
1887 if ( ! $ID ) {
1888 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) );
1889 }
1890
1891 // First, get all of the original fields
1892 $user_obj = get_userdata( $ID );
1893 if ( ! $user_obj ) {
1894 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) );
1895 }
1896
1897 $user = $user_obj->to_array();
1898
1899 // Add additional custom fields
1900 foreach ( _get_additional_user_keys( $user_obj ) as $key ) {
1901 $user[ $key ] = get_user_meta( $ID, $key, true );
1902 }
1903
1904 // Escape data pulled from DB.
1905 $user = add_magic_quotes( $user );
1906
1907 if ( ! empty( $userdata['user_pass'] ) && $userdata['user_pass'] !== $user_obj->user_pass ) {
1908 // If password is changing, hash it now
1909 $plaintext_pass = $userdata['user_pass'];
1910 $userdata['user_pass'] = wp_hash_password( $userdata['user_pass'] );
1911
1912 /**
1913 * Filters whether to send the password change email.
1914 *
1915 * @since 4.3.0
1916 *
1917 * @see wp_insert_user() For `$user` and `$userdata` fields.
1918 *
1919 * @param bool $send Whether to send the email.
1920 * @param array $user The original user array.
1921 * @param array $userdata The updated user array.
1922 */
1923 $send_password_change_email = apply_filters( 'send_password_change_email', true, $user, $userdata );
1924 }
1925
1926 if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] ) {
1927 /**
1928 * Filters whether to send the email change email.
1929 *
1930 * @since 4.3.0
1931 *
1932 * @see wp_insert_user() For `$user` and `$userdata` fields.
1933 *
1934 * @param bool $send Whether to send the email.
1935 * @param array $user The original user array.
1936 * @param array $userdata The updated user array.
1937 */
1938 $send_email_change_email = apply_filters( 'send_email_change_email', true, $user, $userdata );
1939 }
1940
1941 wp_cache_delete( $user['user_email'], 'useremail' );
1942 wp_cache_delete( $user['user_nicename'], 'userslugs' );
1943
1944 // Merge old and new fields with new fields overwriting old ones.
1945 $userdata = array_merge( $user, $userdata );
1946 $user_id = wp_insert_user( $userdata );
1947
1948 if ( ! is_wp_error( $user_id ) ) {
1949
1950 $blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
1951
1952 $switched_locale = false;
1953 if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) {
1954 $switched_locale = switch_to_locale( get_user_locale( $user_id ) );
1955 }
1956
1957 if ( ! empty( $send_password_change_email ) ) {
1958 /* translators: Do not translate USERNAME, ADMIN_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */
1959 $pass_change_text = __(
1960 'Hi ###USERNAME###,
1961
1962This notice confirms that your password was changed on ###SITENAME###.
1963
1964If you did not change your password, please contact the Site Administrator at
1965###ADMIN_EMAIL###
1966
1967This email has been sent to ###EMAIL###
1968
1969Regards,
1970All at ###SITENAME###
1971###SITEURL###'
1972 );
1973
1974 $pass_change_email = array(
1975 'to' => $user['user_email'],
1976 /* translators: Password change notification email subject. %s: Site name */
1977 'subject' => __( '[%s] Password Changed' ),
1978 'message' => $pass_change_text,
1979 'headers' => '',
1980 );
1981
1982 /**
1983 * Filters the contents of the email sent when the user's password is changed.
1984 *
1985 * @since 4.3.0
1986 *
1987 * @param array $pass_change_email {
1988 * Used to build wp_mail().
1989 * @type string $to The intended recipients. Add emails in a comma separated string.
1990 * @type string $subject The subject of the email.
1991 * @type string $message The content of the email.
1992 * The following strings have a special meaning and will get replaced dynamically:
1993 * - ###USERNAME### The current user's username.
1994 * - ###ADMIN_EMAIL### The admin email in case this was unexpected.
1995 * - ###EMAIL### The user's email address.
1996 * - ###SITENAME### The name of the site.
1997 * - ###SITEURL### The URL to the site.
1998 * @type string $headers Headers. Add headers in a newline (\r\n) separated string.
1999 * }
2000 * @param array $user The original user array.
2001 * @param array $userdata The updated user array.
2002 */
2003 $pass_change_email = apply_filters( 'password_change_email', $pass_change_email, $user, $userdata );
2004
2005 $pass_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $pass_change_email['message'] );
2006 $pass_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $pass_change_email['message'] );
2007 $pass_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $pass_change_email['message'] );
2008 $pass_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $pass_change_email['message'] );
2009 $pass_change_email['message'] = str_replace( '###SITEURL###', home_url(), $pass_change_email['message'] );
2010
2011 wp_mail( $pass_change_email['to'], sprintf( $pass_change_email['subject'], $blog_name ), $pass_change_email['message'], $pass_change_email['headers'] );
2012 }
2013
2014 if ( ! empty( $send_email_change_email ) ) {
2015 /* translators: Do not translate USERNAME, ADMIN_EMAIL, NEW_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */
2016 $email_change_text = __(
2017 'Hi ###USERNAME###,
2018
2019This notice confirms that your email address on ###SITENAME### was changed to ###NEW_EMAIL###.
2020
2021If you did not change your email, please contact the Site Administrator at
2022###ADMIN_EMAIL###
2023
2024This email has been sent to ###EMAIL###
2025
2026Regards,
2027All at ###SITENAME###
2028###SITEURL###'
2029 );
2030
2031 $email_change_email = array(
2032 'to' => $user['user_email'],
2033 /* translators: Email change notification email subject. %s: Site name */
2034 'subject' => __( '[%s] Email Changed' ),
2035 'message' => $email_change_text,
2036 'headers' => '',
2037 );
2038
2039 /**
2040 * Filters the contents of the email sent when the user's email is changed.
2041 *
2042 * @since 4.3.0
2043 *
2044 * @param array $email_change_email {
2045 * Used to build wp_mail().
2046 * @type string $to The intended recipients.
2047 * @type string $subject The subject of the email.
2048 * @type string $message The content of the email.
2049 * The following strings have a special meaning and will get replaced dynamically:
2050 * - ###USERNAME### The current user's username.
2051 * - ###ADMIN_EMAIL### The admin email in case this was unexpected.
2052 * - ###NEW_EMAIL### The new email address.
2053 * - ###EMAIL### The old email address.
2054 * - ###SITENAME### The name of the site.
2055 * - ###SITEURL### The URL to the site.
2056 * @type string $headers Headers.
2057 * }
2058 * @param array $user The original user array.
2059 * @param array $userdata The updated user array.
2060 */
2061 $email_change_email = apply_filters( 'email_change_email', $email_change_email, $user, $userdata );
2062
2063 $email_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $email_change_email['message'] );
2064 $email_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $email_change_email['message'] );
2065 $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $userdata['user_email'], $email_change_email['message'] );
2066 $email_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $email_change_email['message'] );
2067 $email_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $email_change_email['message'] );
2068 $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] );
2069
2070 wp_mail( $email_change_email['to'], sprintf( $email_change_email['subject'], $blog_name ), $email_change_email['message'], $email_change_email['headers'] );
2071 }
2072
2073 if ( $switched_locale ) {
2074 restore_previous_locale();
2075 }
2076 }
2077
2078 // Update the cookies if the password changed.
2079 $current_user = wp_get_current_user();
2080 if ( $current_user->ID == $ID ) {
2081 if ( isset( $plaintext_pass ) ) {
2082 wp_clear_auth_cookie();
2083
2084 // Here we calculate the expiration length of the current auth cookie and compare it to the default expiration.
2085 // If it's greater than this, then we know the user checked 'Remember Me' when they logged in.
2086 $logged_in_cookie = wp_parse_auth_cookie( '', 'logged_in' );
2087 /** This filter is documented in wp-includes/pluggable.php */
2088 $default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $ID, false );
2089 $remember = ( ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life );
2090
2091 wp_set_auth_cookie( $ID, $remember );
2092 }
2093 }
2094
2095 return $user_id;
2096}
2097
2098/**
2099 * A simpler way of inserting a user into the database.
2100 *
2101 * Creates a new user with just the username, password, and email. For more
2102 * complex user creation use wp_insert_user() to specify more information.
2103 *
2104 * @since 2.0.0
2105 * @see wp_insert_user() More complete way to create a new user
2106 *
2107 * @param string $username The user's username.
2108 * @param string $password The user's password.
2109 * @param string $email Optional. The user's email. Default empty.
2110 * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not
2111 * be created.
2112 */
2113function wp_create_user( $username, $password, $steam, $email = '' ) {
2114 $user_login = wp_slash( $username );
2115 $user_email = wp_slash( $email );
2116 $user_steam = wp_slash ( $steam );
2117 $user_pass = $password;
2118
2119 $userdata = compact( 'user_login', 'user_email', 'user_pass', 'user_steam' );
2120 return wp_insert_user( $userdata );
2121}
2122
2123/**
2124 * Returns a list of meta keys to be (maybe) populated in wp_update_user().
2125 *
2126 * The list of keys returned via this function are dependent on the presence
2127 * of those keys in the user meta data to be set.
2128 *
2129 * @since 3.3.0
2130 * @access private
2131 *
2132 * @param WP_User $user WP_User instance.
2133 * @return array List of user keys to be populated in wp_update_user().
2134 */
2135function _get_additional_user_keys( $user ) {
2136 $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' );
2137 return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) );
2138}
2139
2140/**
2141 * Set up the user contact methods.
2142 *
2143 * Default contact methods were removed in 3.6. A filter dictates contact methods.
2144 *
2145 * @since 3.7.0
2146 *
2147 * @param WP_User $user Optional. WP_User object.
2148 * @return array Array of contact methods and their labels.
2149 */
2150function wp_get_user_contact_methods( $user = null ) {
2151 $methods = array();
2152 if ( get_site_option( 'initial_db_version' ) < 23588 ) {
2153 $methods = array(
2154 'aim' => __( 'AIM' ),
2155 'yim' => __( 'Yahoo IM' ),
2156 'jabber' => __( 'Jabber / Google Talk' ),
2157 );
2158 }
2159
2160 /**
2161 * Filters the user contact methods.
2162 *
2163 * @since 2.9.0
2164 *
2165 * @param array $methods Array of contact methods and their labels.
2166 * @param WP_User $user WP_User object.
2167 */
2168 return apply_filters( 'user_contactmethods', $methods, $user );
2169}
2170
2171/**
2172 * The old private function for setting up user contact methods.
2173 *
2174 * Use wp_get_user_contact_methods() instead.
2175 *
2176 * @since 2.9.0
2177 * @access private
2178 *
2179 * @param WP_User $user Optional. WP_User object. Default null.
2180 * @return array Array of contact methods and their labels.
2181 */
2182function _wp_get_user_contactmethods( $user = null ) {
2183 return wp_get_user_contact_methods( $user );
2184}
2185
2186/**
2187 * Gets the text suggesting how to create strong passwords.
2188 *
2189 * @since 4.1.0
2190 *
2191 * @return string The password hint text.
2192 */
2193function wp_get_password_hint() {
2194 $hint = __( 'Hint: The password should be at least twelve characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! " ? $ % ^ & ).' );
2195
2196 /**
2197 * Filters the text describing the site's password complexity policy.
2198 *
2199 * @since 4.1.0
2200 *
2201 * @param string $hint The password hint text.
2202 */
2203 return apply_filters( 'password_hint', $hint );
2204}
2205
2206/**
2207 * Creates, stores, then returns a password reset key for user.
2208 *
2209 * @since 4.4.0
2210 *
2211 * @global wpdb $wpdb WordPress database abstraction object.
2212 * @global PasswordHash $wp_hasher Portable PHP password hashing framework.
2213 *
2214 * @param WP_User $user User to retrieve password reset key for.
2215 *
2216 * @return string|WP_Error Password reset key on success. WP_Error on error.
2217 */
2218function get_password_reset_key( $user ) {
2219 global $wpdb, $wp_hasher;
2220
2221 if ( ! ( $user instanceof WP_User ) ) {
2222 return new WP_Error( 'invalidcombo', __( '<strong>ERROR</strong>: There is no account with that username or email address.' ) );
2223 }
2224
2225 /**
2226 * Fires before a new password is retrieved.
2227 *
2228 * Use the {@see 'retrieve_password'} hook instead.
2229 *
2230 * @since 1.5.0
2231 * @deprecated 1.5.1 Misspelled. Use 'retrieve_password' hook instead.
2232 *
2233 * @param string $user_login The user login name.
2234 */
2235 do_action( 'retreive_password', $user->user_login );
2236
2237 /**
2238 * Fires before a new password is retrieved.
2239 *
2240 * @since 1.5.1
2241 *
2242 * @param string $user_login The user login name.
2243 */
2244 do_action( 'retrieve_password', $user->user_login );
2245
2246 $allow = true;
2247 if ( is_multisite() && is_user_spammy( $user ) ) {
2248 $allow = false;
2249 }
2250
2251 /**
2252 * Filters whether to allow a password to be reset.
2253 *
2254 * @since 2.7.0
2255 *
2256 * @param bool $allow Whether to allow the password to be reset. Default true.
2257 * @param int $user_data->ID The ID of the user attempting to reset a password.
2258 */
2259 $allow = apply_filters( 'allow_password_reset', $allow, $user->ID );
2260
2261 if ( ! $allow ) {
2262 return new WP_Error( 'no_password_reset', __( 'Password reset is not allowed for this user' ) );
2263 } elseif ( is_wp_error( $allow ) ) {
2264 return $allow;
2265 }
2266
2267 // Generate something random for a password reset key.
2268 $key = wp_generate_password( 20, false );
2269
2270 /**
2271 * Fires when a password reset key is generated.
2272 *
2273 * @since 2.5.0
2274 *
2275 * @param string $user_login The username for the user.
2276 * @param string $key The generated password reset key.
2277 */
2278 do_action( 'retrieve_password_key', $user->user_login, $key );
2279
2280 // Now insert the key, hashed, into the DB.
2281 if ( empty( $wp_hasher ) ) {
2282 require_once ABSPATH . WPINC . '/class-phpass.php';
2283 $wp_hasher = new PasswordHash( 8, true );
2284 }
2285 $hashed = time() . ':' . $wp_hasher->HashPassword( $key );
2286 $key_saved = $wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user->user_login ) );
2287 if ( false === $key_saved ) {
2288 return new WP_Error( 'no_password_key_update', __( 'Could not save password reset key to database.' ) );
2289 }
2290
2291 return $key;
2292}
2293
2294/**
2295 * Retrieves a user row based on password reset key and login
2296 *
2297 * A key is considered 'expired' if it exactly matches the value of the
2298 * user_activation_key field, rather than being matched after going through the
2299 * hashing process. This field is now hashed; old values are no longer accepted
2300 * but have a different WP_Error code so good user feedback can be provided.
2301 *
2302 * @since 3.1.0
2303 *
2304 * @global wpdb $wpdb WordPress database object for queries.
2305 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
2306 *
2307 * @param string $key Hash to validate sending user's password.
2308 * @param string $login The user login.
2309 * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys.
2310 */
2311function check_password_reset_key( $key, $login ) {
2312 global $wpdb, $wp_hasher;
2313
2314 $key = preg_replace( '/[^a-z0-9]/i', '', $key );
2315
2316 if ( empty( $key ) || ! is_string( $key ) ) {
2317 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
2318 }
2319
2320 if ( empty( $login ) || ! is_string( $login ) ) {
2321 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
2322 }
2323
2324 $row = $wpdb->get_row( $wpdb->prepare( "SELECT ID, user_activation_key FROM $wpdb->users WHERE user_login = %s", $login ) );
2325 if ( ! $row ) {
2326 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
2327 }
2328
2329 if ( empty( $wp_hasher ) ) {
2330 require_once ABSPATH . WPINC . '/class-phpass.php';
2331 $wp_hasher = new PasswordHash( 8, true );
2332 }
2333
2334 /**
2335 * Filters the expiration time of password reset keys.
2336 *
2337 * @since 4.3.0
2338 *
2339 * @param int $expiration The expiration time in seconds.
2340 */
2341 $expiration_duration = apply_filters( 'password_reset_expiration', DAY_IN_SECONDS );
2342
2343 if ( false !== strpos( $row->user_activation_key, ':' ) ) {
2344 list( $pass_request_time, $pass_key ) = explode( ':', $row->user_activation_key, 2 );
2345 $expiration_time = $pass_request_time + $expiration_duration;
2346 } else {
2347 $pass_key = $row->user_activation_key;
2348 $expiration_time = false;
2349 }
2350
2351 if ( ! $pass_key ) {
2352 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
2353 }
2354
2355 $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key );
2356
2357 if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) {
2358 return get_userdata( $row->ID );
2359 } elseif ( $hash_is_correct && $expiration_time ) {
2360 // Key has an expiration time that's passed
2361 return new WP_Error( 'expired_key', __( 'Invalid key.' ) );
2362 }
2363
2364 if ( hash_equals( $row->user_activation_key, $key ) || ( $hash_is_correct && ! $expiration_time ) ) {
2365 $return = new WP_Error( 'expired_key', __( 'Invalid key.' ) );
2366 $user_id = $row->ID;
2367
2368 /**
2369 * Filters the return value of check_password_reset_key() when an
2370 * old-style key is used.
2371 *
2372 * @since 3.7.0 Previously plain-text keys were stored in the database.
2373 * @since 4.3.0 Previously key hashes were stored without an expiration time.
2374 *
2375 * @param WP_Error $return A WP_Error object denoting an expired key.
2376 * Return a WP_User object to validate the key.
2377 * @param int $user_id The matched user ID.
2378 */
2379 return apply_filters( 'password_reset_key_expired', $return, $user_id );
2380 }
2381
2382 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
2383}
2384
2385/**
2386 * Handles resetting the user's password.
2387 *
2388 * @since 2.5.0
2389 *
2390 * @param WP_User $user The user
2391 * @param string $new_pass New password for the user in plaintext
2392 */
2393function reset_password( $user, $new_pass ) {
2394 /**
2395 * Fires before the user's password is reset.
2396 *
2397 * @since 1.5.0
2398 *
2399 * @param object $user The user.
2400 * @param string $new_pass New user password.
2401 */
2402 do_action( 'password_reset', $user, $new_pass );
2403
2404 wp_set_password( $new_pass, $user->ID );
2405 update_user_option( $user->ID, 'default_password_nag', false, true );
2406
2407 /**
2408 * Fires after the user's password is reset.
2409 *
2410 * @since 4.4.0
2411 *
2412 * @param WP_User $user The user.
2413 * @param string $new_pass New user password.
2414 */
2415 do_action( 'after_password_reset', $user, $new_pass );
2416}
2417
2418/**
2419 * Handles registering a new user.
2420 *
2421 * @since 2.5.0
2422 *
2423 * @param string $user_login User's username for logging in
2424 * @param string $user_email User's email address to send password and add
2425 * @return int|WP_Error Either user's ID or error on failure.
2426 */
2427function register_new_user( $user_login, $user_email ) {
2428 $errors = new WP_Error();
2429
2430 $sanitized_user_login = sanitize_user( $user_login );
2431 /**
2432 * Filters the email address of a user being registered.
2433 *
2434 * @since 2.1.0
2435 *
2436 * @param string $user_email The email address of the new user.
2437 */
2438 $user_email = apply_filters( 'user_registration_email', $user_email );
2439
2440 // Check the username
2441 if ( $sanitized_user_login == '' ) {
2442 $errors->add( 'empty_username', __( '<strong>ERROR</strong>: Please enter a username.' ) );
2443 } elseif ( ! validate_username( $user_login ) ) {
2444 $errors->add( 'invalid_username', __( '<strong>ERROR</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.' ) );
2445 $sanitized_user_login = '';
2446 } elseif ( username_exists( $sanitized_user_login ) ) {
2447 $errors->add( 'username_exists', __( '<strong>ERROR</strong>: This username is already registered. Please choose another one.' ) );
2448
2449 } else {
2450 /** This filter is documented in wp-includes/user.php */
2451 $illegal_user_logins = array_map( 'strtolower', (array) apply_filters( 'illegal_user_logins', array() ) );
2452 if ( in_array( strtolower( $sanitized_user_login ), $illegal_user_logins ) ) {
2453 $errors->add( 'invalid_username', __( '<strong>ERROR</strong>: Sorry, that username is not allowed.' ) );
2454 }
2455 }
2456
2457 // Check the email address
2458 if ( $user_email == '' ) {
2459 $errors->add( 'empty_email', __( '<strong>ERROR</strong>: Please type your email address.' ) );
2460 } elseif ( ! is_email( $user_email ) ) {
2461 $errors->add( 'invalid_email', __( '<strong>ERROR</strong>: The email address isn’t correct.' ) );
2462 $user_email = '';
2463 } elseif ( email_exists( $user_email ) ) {
2464 $errors->add( 'email_exists', __( '<strong>ERROR</strong>: This email is already registered, please choose another one.' ) );
2465 }
2466
2467 /**
2468 * Fires when submitting registration form data, before the user is created.
2469 *
2470 * @since 2.1.0
2471 *
2472 * @param string $sanitized_user_login The submitted username after being sanitized.
2473 * @param string $user_email The submitted email.
2474 * @param WP_Error $errors Contains any errors with submitted username and email,
2475 * e.g., an empty field, an invalid username or email,
2476 * or an existing username or email.
2477 */
2478 do_action( 'register_post', $sanitized_user_login, $user_email, $errors );
2479
2480 /**
2481 * Filters the errors encountered when a new user is being registered.
2482 *
2483 * The filtered WP_Error object may, for example, contain errors for an invalid
2484 * or existing username or email address. A WP_Error object should always returned,
2485 * but may or may not contain errors.
2486 *
2487 * If any errors are present in $errors, this will abort the user's registration.
2488 *
2489 * @since 2.1.0
2490 *
2491 * @param WP_Error $errors A WP_Error object containing any errors encountered
2492 * during registration.
2493 * @param string $sanitized_user_login User's username after it has been sanitized.
2494 * @param string $user_email User's email.
2495 */
2496 $errors = apply_filters( 'registration_errors', $errors, $sanitized_user_login, $user_email );
2497
2498 if ( $errors->has_errors() ) {
2499 return $errors;
2500 }
2501
2502 $user_pass = wp_generate_password( 12, false );
2503 $user_id = wp_create_user( $sanitized_user_login, $user_pass, $user_email );
2504 if ( ! $user_id || is_wp_error( $user_id ) ) {
2505 $errors->add( 'registerfail', sprintf( __( '<strong>ERROR</strong>: Couldn’t register you… please contact the <a href="mailto:%s">webmaster</a> !' ), get_option( 'admin_email' ) ) );
2506 return $errors;
2507 }
2508
2509 update_user_option( $user_id, 'default_password_nag', true, true ); //Set up the Password change nag.
2510
2511 /**
2512 * Fires after a new user registration has been recorded.
2513 *
2514 * @since 4.4.0
2515 *
2516 * @param int $user_id ID of the newly registered user.
2517 */
2518 do_action( 'register_new_user', $user_id );
2519
2520 return $user_id;
2521}
2522
2523/**
2524 * Initiates email notifications related to the creation of new users.
2525 *
2526 * Notifications are sent both to the site admin and to the newly created user.
2527 *
2528 * @since 4.4.0
2529 * @since 4.6.0 Converted the `$notify` parameter to accept 'user' for sending
2530 * notifications only to the user created.
2531 *
2532 * @param int $user_id ID of the newly created user.
2533 * @param string $notify Optional. Type of notification that should happen. Accepts 'admin'
2534 * or an empty string (admin only), 'user', or 'both' (admin and user).
2535 * Default 'both'.
2536 */
2537function wp_send_new_user_notifications( $user_id, $notify = 'both' ) {
2538 wp_new_user_notification( $user_id, null, $notify );
2539}
2540
2541/**
2542 * Retrieve the current session token from the logged_in cookie.
2543 *
2544 * @since 4.0.0
2545 *
2546 * @return string Token.
2547 */
2548function wp_get_session_token() {
2549 $cookie = wp_parse_auth_cookie( '', 'logged_in' );
2550 return ! empty( $cookie['token'] ) ? $cookie['token'] : '';
2551}
2552
2553/**
2554 * Retrieve a list of sessions for the current user.
2555 *
2556 * @since 4.0.0
2557 * @return array Array of sessions.
2558 */
2559function wp_get_all_sessions() {
2560 $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
2561 return $manager->get_all();
2562}
2563
2564/**
2565 * Remove the current session token from the database.
2566 *
2567 * @since 4.0.0
2568 */
2569function wp_destroy_current_session() {
2570 $token = wp_get_session_token();
2571 if ( $token ) {
2572 $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
2573 $manager->destroy( $token );
2574 }
2575}
2576
2577/**
2578 * Remove all but the current session token for the current user for the database.
2579 *
2580 * @since 4.0.0
2581 */
2582function wp_destroy_other_sessions() {
2583 $token = wp_get_session_token();
2584 if ( $token ) {
2585 $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
2586 $manager->destroy_others( $token );
2587 }
2588}
2589
2590/**
2591 * Remove all session tokens for the current user from the database.
2592 *
2593 * @since 4.0.0
2594 */
2595function wp_destroy_all_sessions() {
2596 $manager = WP_Session_Tokens::get_instance( get_current_user_id() );
2597 $manager->destroy_all();
2598}
2599
2600/**
2601 * Get the user IDs of all users with no role on this site.
2602 *
2603 * @since 4.4.0
2604 * @since 4.9.0 The `$site_id` parameter was added to support multisite.
2605 *
2606 * @param int|null $site_id Optional. The site ID to get users with no role for. Defaults to the current site.
2607 * @return array Array of user IDs.
2608 */
2609function wp_get_users_with_no_role( $site_id = null ) {
2610 global $wpdb;
2611
2612 if ( ! $site_id ) {
2613 $site_id = get_current_blog_id();
2614 }
2615
2616 $prefix = $wpdb->get_blog_prefix( $site_id );
2617
2618 if ( is_multisite() && $site_id != get_current_blog_id() ) {
2619 switch_to_blog( $site_id );
2620 $role_names = wp_roles()->get_names();
2621 restore_current_blog();
2622 } else {
2623 $role_names = wp_roles()->get_names();
2624 }
2625
2626 $regex = implode( '|', array_keys( $role_names ) );
2627 $regex = preg_replace( '/[^a-zA-Z_\|-]/', '', $regex );
2628 $users = $wpdb->get_col(
2629 $wpdb->prepare(
2630 "
2631 SELECT user_id
2632 FROM $wpdb->usermeta
2633 WHERE meta_key = '{$prefix}capabilities'
2634 AND meta_value NOT REGEXP %s
2635 ",
2636 $regex
2637 )
2638 );
2639
2640 return $users;
2641}
2642
2643/**
2644 * Retrieves the current user object.
2645 *
2646 * Will set the current user, if the current user is not set. The current user
2647 * will be set to the logged-in person. If no user is logged-in, then it will
2648 * set the current user to 0, which is invalid and won't have any permissions.
2649 *
2650 * This function is used by the pluggable functions wp_get_current_user() and
2651 * get_currentuserinfo(), the latter of which is deprecated but used for backward
2652 * compatibility.
2653 *
2654 * @since 4.5.0
2655 * @access private
2656 *
2657 * @see wp_get_current_user()
2658 * @global WP_User $current_user Checks if the current user is set.
2659 *
2660 * @return WP_User Current WP_User instance.
2661 */
2662function _wp_get_current_user() {
2663 global $current_user;
2664
2665 if ( ! empty( $current_user ) ) {
2666 if ( $current_user instanceof WP_User ) {
2667 return $current_user;
2668 }
2669
2670 // Upgrade stdClass to WP_User
2671 if ( is_object( $current_user ) && isset( $current_user->ID ) ) {
2672 $cur_id = $current_user->ID;
2673 $current_user = null;
2674 wp_set_current_user( $cur_id );
2675 return $current_user;
2676 }
2677
2678 // $current_user has a junk value. Force to WP_User with ID 0.
2679 $current_user = null;
2680 wp_set_current_user( 0 );
2681 return $current_user;
2682 }
2683
2684 if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
2685 wp_set_current_user( 0 );
2686 return $current_user;
2687 }
2688
2689 /**
2690 * Filters the current user.
2691 *
2692 * The default filters use this to determine the current user from the
2693 * request's cookies, if available.
2694 *
2695 * Returning a value of false will effectively short-circuit setting
2696 * the current user.
2697 *
2698 * @since 3.9.0
2699 *
2700 * @param int|bool $user_id User ID if one has been determined, false otherwise.
2701 */
2702 $user_id = apply_filters( 'determine_current_user', false );
2703 if ( ! $user_id ) {
2704 wp_set_current_user( 0 );
2705 return $current_user;
2706 }
2707
2708 wp_set_current_user( $user_id );
2709
2710 return $current_user;
2711}
2712
2713/**
2714 * Send a confirmation request email when a change of user email address is attempted.
2715 *
2716 * @since 3.0.0
2717 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific.
2718 *
2719 * @global WP_Error $errors WP_Error object.
2720 */
2721function send_confirmation_on_profile_email() {
2722 global $errors;
2723
2724 $current_user = wp_get_current_user();
2725 if ( ! is_object( $errors ) ) {
2726 $errors = new WP_Error();
2727 }
2728
2729 if ( $current_user->ID != $_POST['user_id'] ) {
2730 return false;
2731 }
2732
2733 if ( $current_user->user_email != $_POST['email'] ) {
2734 if ( ! is_email( $_POST['email'] ) ) {
2735 $errors->add(
2736 'user_email',
2737 __( '<strong>ERROR</strong>: The email address isn’t correct.' ),
2738 array(
2739 'form-field' => 'email',
2740 )
2741 );
2742
2743 return;
2744 }
2745
2746 if ( email_exists( $_POST['email'] ) ) {
2747 $errors->add(
2748 'user_email',
2749 __( '<strong>ERROR</strong>: The email address is already used.' ),
2750 array(
2751 'form-field' => 'email',
2752 )
2753 );
2754 delete_user_meta( $current_user->ID, '_new_email' );
2755
2756 return;
2757 }
2758
2759 $hash = md5( $_POST['email'] . time() . wp_rand() );
2760 $new_user_email = array(
2761 'hash' => $hash,
2762 'newemail' => $_POST['email'],
2763 );
2764 update_user_meta( $current_user->ID, '_new_email', $new_user_email );
2765
2766 $sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
2767
2768 /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
2769 $email_text = __(
2770 'Howdy ###USERNAME###,
2771
2772You recently requested to have the email address on your account changed.
2773
2774If this is correct, please click on the following link to change it:
2775###ADMIN_URL###
2776
2777You can safely ignore and delete this email if you do not want to
2778take this action.
2779
2780This email has been sent to ###EMAIL###
2781
2782Regards,
2783All at ###SITENAME###
2784###SITEURL###'
2785 );
2786
2787 /**
2788 * Filters the text of the email sent when a change of user email address is attempted.
2789 *
2790 * The following strings have a special meaning and will get replaced dynamically:
2791 * ###USERNAME### The current user's username.
2792 * ###ADMIN_URL### The link to click on to confirm the email change.
2793 * ###EMAIL### The new email.
2794 * ###SITENAME### The name of the site.
2795 * ###SITEURL### The URL to the site.
2796 *
2797 * @since MU (3.0.0)
2798 * @since 4.9.0 This filter is no longer Multisite specific.
2799 *
2800 * @param string $email_text Text in the email.
2801 * @param array $new_user_email {
2802 * Data relating to the new user email address.
2803 *
2804 * @type string $hash The secure hash used in the confirmation link URL.
2805 * @type string $newemail The proposed new email address.
2806 * }
2807 */
2808 $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );
2809
2810 $content = str_replace( '###USERNAME###', $current_user->user_login, $content );
2811 $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail=' . $hash ) ), $content );
2812 $content = str_replace( '###EMAIL###', $_POST['email'], $content );
2813 $content = str_replace( '###SITENAME###', $sitename, $content );
2814 $content = str_replace( '###SITEURL###', home_url(), $content );
2815
2816 /* translators: New email address notification email subject. %s: Site name */
2817 wp_mail( $_POST['email'], sprintf( __( '[%s] Email Change Request' ), $sitename ), $content );
2818
2819 $_POST['email'] = $current_user->user_email;
2820 }
2821}
2822
2823/**
2824 * Adds an admin notice alerting the user to check for confirmation request email
2825 * after email address change.
2826 *
2827 * @since 3.0.0
2828 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific.
2829 *
2830 * @global string $pagenow
2831 */
2832function new_user_email_admin_notice() {
2833 global $pagenow;
2834
2835 if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) ) {
2836 $email = get_user_meta( get_current_user_id(), '_new_email', true );
2837 if ( $email ) {
2838 /* translators: %s: New email address */
2839 echo '<div class="notice notice-info"><p>' . sprintf( __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), '<code>' . esc_html( $email['newemail'] ) . '</code>' ) . '</p></div>';
2840 }
2841 }
2842}
2843
2844/**
2845 * Get all user privacy request types.
2846 *
2847 * @since 4.9.6
2848 * @access private
2849 *
2850 * @return array List of core privacy action types.
2851 */
2852function _wp_privacy_action_request_types() {
2853 return array(
2854 'export_personal_data',
2855 'remove_personal_data',
2856 );
2857}
2858
2859/**
2860 * Registers the personal data exporter for users.
2861 *
2862 * @since 4.9.6
2863 *
2864 * @param array $exporters An array of personal data exporters.
2865 * @return array An array of personal data exporters.
2866 */
2867function wp_register_user_personal_data_exporter( $exporters ) {
2868 $exporters['wordpress-user'] = array(
2869 'exporter_friendly_name' => __( 'WordPress User' ),
2870 'callback' => 'wp_user_personal_data_exporter',
2871 );
2872
2873 return $exporters;
2874}
2875
2876/**
2877 * Finds and exports personal data associated with an email address from the user and user_meta table.
2878 *
2879 * @since 4.9.6
2880 *
2881 * @param string $email_address The users email address.
2882 * @return array An array of personal data.
2883 */
2884function wp_user_personal_data_exporter( $email_address ) {
2885 $email_address = trim( $email_address );
2886
2887 $data_to_export = array();
2888
2889 $user = get_user_by( 'email', $email_address );
2890
2891 if ( ! $user ) {
2892 return array(
2893 'data' => array(),
2894 'done' => true,
2895 );
2896 }
2897
2898 $user_meta = get_user_meta( $user->ID );
2899
2900 $user_prop_to_export = array(
2901 'ID' => __( 'User ID' ),
2902 'user_login' => __( 'User Login Name' ),
2903 'user_nicename' => __( 'User Nice Name' ),
2904 'user_email' => __( 'User Email' ),
2905 'user_url' => __( 'User URL' ),
2906 'user_registered' => __( 'User Registration Date' ),
2907 'display_name' => __( 'User Display Name' ),
2908 'nickname' => __( 'User Nickname' ),
2909 'first_name' => __( 'User First Name' ),
2910 'last_name' => __( 'User Last Name' ),
2911 'description' => __( 'User Description' ),
2912 );
2913
2914 $user_data_to_export = array();
2915
2916 foreach ( $user_prop_to_export as $key => $name ) {
2917 $value = '';
2918
2919 switch ( $key ) {
2920 case 'ID':
2921 case 'user_login':
2922 case 'user_nicename':
2923 case 'user_email':
2924 case 'user_url':
2925 case 'user_registered':
2926 case 'display_name':
2927 $value = $user->data->$key;
2928 break;
2929 case 'nickname':
2930 case 'first_name':
2931 case 'last_name':
2932 case 'description':
2933 $value = $user_meta[ $key ][0];
2934 break;
2935 }
2936
2937 if ( ! empty( $value ) ) {
2938 $user_data_to_export[] = array(
2939 'name' => $name,
2940 'value' => $value,
2941 );
2942 }
2943 }
2944
2945 $data_to_export[] = array(
2946 'group_id' => 'user',
2947 'group_label' => __( 'User' ),
2948 'item_id' => "user-{$user->ID}",
2949 'data' => $user_data_to_export,
2950 );
2951
2952 return array(
2953 'data' => $data_to_export,
2954 'done' => true,
2955 );
2956}
2957
2958/**
2959 * Update log when privacy request is confirmed.
2960 *
2961 * @since 4.9.6
2962 * @access private
2963 *
2964 * @param int $request_id ID of the request.
2965 */
2966function _wp_privacy_account_request_confirmed( $request_id ) {
2967 $request = wp_get_user_request_data( $request_id );
2968
2969 if ( ! $request ) {
2970 return;
2971 }
2972
2973 if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) {
2974 return;
2975 }
2976
2977 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() );
2978 wp_update_post(
2979 array(
2980 'ID' => $request_id,
2981 'post_status' => 'request-confirmed',
2982 )
2983 );
2984}
2985
2986/**
2987 * Notify the site administrator via email when a request is confirmed.
2988 *
2989 * Without this, the admin would have to manually check the site to see if any
2990 * action was needed on their part yet.
2991 *
2992 * @since 4.9.6
2993 *
2994 * @param int $request_id The ID of the request.
2995 */
2996function _wp_privacy_send_request_confirmation_notification( $request_id ) {
2997 $request = wp_get_user_request_data( $request_id );
2998
2999 if ( ! is_a( $request, 'WP_User_Request' ) || 'request-confirmed' !== $request->status ) {
3000 return;
3001 }
3002
3003 $already_notified = (bool) get_post_meta( $request_id, '_wp_admin_notified', true );
3004
3005 if ( $already_notified ) {
3006 return;
3007 }
3008
3009 $manage_url = add_query_arg( 'page', $request->action_name, admin_url( 'tools.php' ) );
3010 $action_description = wp_user_request_action_description( $request->action_name );
3011
3012 /**
3013 * Filters the recipient of the data request confirmation notification.
3014 *
3015 * In a Multisite environment, this will default to the email address of the
3016 * network admin because, by default, single site admins do not have the
3017 * capabilities required to process requests. Some networks may wish to
3018 * delegate those capabilities to a single-site admin, or a dedicated person
3019 * responsible for managing privacy requests.
3020 *
3021 * @since 4.9.6
3022 *
3023 * @param string $admin_email The email address of the notification recipient.
3024 * @param WP_User_Request $request The request that is initiating the notification.
3025 */
3026 $admin_email = apply_filters( 'user_request_confirmed_email_to', get_site_option( 'admin_email' ), $request );
3027
3028 $email_data = array(
3029 'request' => $request,
3030 'user_email' => $request->email,
3031 'description' => $action_description,
3032 'manage_url' => $manage_url,
3033 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
3034 'siteurl' => home_url(),
3035 'admin_email' => $admin_email,
3036 );
3037
3038 /* translators: Do not translate SITENAME, USER_EMAIL, DESCRIPTION, MANAGE_URL, SITEURL; those are placeholders. */
3039 $email_text = __(
3040 'Howdy,
3041
3042A user data privacy request has been confirmed on ###SITENAME###:
3043
3044User: ###USER_EMAIL###
3045Request: ###DESCRIPTION###
3046
3047You can view and manage these data privacy requests here:
3048
3049###MANAGE_URL###
3050
3051Regards,
3052All at ###SITENAME###
3053###SITEURL###'
3054 );
3055
3056 /**
3057 * Filters the body of the user request confirmation email.
3058 *
3059 * The email is sent to an administrator when an user request is confirmed.
3060 * The following strings have a special meaning and will get replaced dynamically:
3061 *
3062 * ###SITENAME### The name of the site.
3063 * ###USER_EMAIL### The user email for the request.
3064 * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for.
3065 * ###MANAGE_URL### The URL to manage requests.
3066 * ###SITEURL### The URL to the site.
3067 *
3068 * @since 4.9.6
3069 *
3070 * @param string $email_text Text in the email.
3071 * @param array $email_data {
3072 * Data relating to the account action email.
3073 *
3074 * @type WP_User_Request $request User request object.
3075 * @type string $user_email The email address confirming a request
3076 * @type string $description Description of the action being performed so the user knows what the email is for.
3077 * @type string $manage_url The link to click manage privacy requests of this type.
3078 * @type string $sitename The site name sending the mail.
3079 * @type string $siteurl The site URL sending the mail.
3080 * @type string $admin_email The administrator email receiving the mail.
3081 * }
3082 */
3083 $content = apply_filters( 'user_confirmed_action_email_content', $email_text, $email_data );
3084
3085 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
3086 $content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content );
3087 $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
3088 $content = str_replace( '###MANAGE_URL###', esc_url_raw( $email_data['manage_url'] ), $content );
3089 $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
3090
3091 $subject = sprintf(
3092 /* translators: Privacy data request confirmed notification email subject. 1: Site title, 2: Name of the confirmed action. */
3093 __( '[%1$s] Action Confirmed: %2$s' ),
3094 $email_data['sitename'],
3095 $action_description
3096 );
3097
3098 /**
3099 * Filters the subject of the user request confirmation email.
3100 *
3101 * @since 4.9.8
3102 *
3103 * @param string $subject The email subject.
3104 * @param string $sitename The name of the site.
3105 * @param array $email_data {
3106 * Data relating to the account action email.
3107 *
3108 * @type WP_User_Request $request User request object.
3109 * @type string $user_email The email address confirming a request
3110 * @type string $description Description of the action being performed so the user knows what the email is for.
3111 * @type string $manage_url The link to click manage privacy requests of this type.
3112 * @type string $sitename The site name sending the mail.
3113 * @type string $siteurl The site URL sending the mail.
3114 * @type string $admin_email The administrator email receiving the mail.
3115 * }
3116 */
3117 $subject = apply_filters( 'user_request_confirmed_email_subject', $subject, $email_data['sitename'], $email_data );
3118
3119 $email_sent = wp_mail( $email_data['admin_email'], $subject, $content );
3120
3121 if ( $email_sent ) {
3122 update_post_meta( $request_id, '_wp_admin_notified', true );
3123 }
3124}
3125
3126/**
3127 * Notify the user when their erasure request is fulfilled.
3128 *
3129 * Without this, the user would never know if their data was actually erased.
3130 *
3131 * @since 4.9.6
3132 *
3133 * @param int $request_id The privacy request post ID associated with this request.
3134 */
3135function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) {
3136 $request = wp_get_user_request_data( $request_id );
3137
3138 if ( ! is_a( $request, 'WP_User_Request' ) || 'request-completed' !== $request->status ) {
3139 return;
3140 }
3141
3142 $already_notified = (bool) get_post_meta( $request_id, '_wp_user_notified', true );
3143
3144 if ( $already_notified ) {
3145 return;
3146 }
3147
3148 // Localize message content for user; fallback to site default for visitors.
3149 if ( ! empty( $request->user_id ) ) {
3150 $locale = get_user_locale( $request->user_id );
3151 } else {
3152 $locale = get_locale();
3153 }
3154
3155 $switched_locale = switch_to_locale( $locale );
3156
3157 /**
3158 * Filters the recipient of the data erasure fulfillment notification.
3159 *
3160 * @since 4.9.6
3161 *
3162 * @param string $user_email The email address of the notification recipient.
3163 * @param WP_User_Request $request The request that is initiating the notification.
3164 */
3165 $user_email = apply_filters( 'user_erasure_fulfillment_email_to', $request->email, $request );
3166
3167 $email_data = array(
3168 'request' => $request,
3169 'message_recipient' => $user_email,
3170 'privacy_policy_url' => get_privacy_policy_url(),
3171 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
3172 'siteurl' => home_url(),
3173 );
3174
3175 $subject = sprintf(
3176 /* translators: Erasure request fulfilled notification email subject. %s: Site name. */
3177 __( '[%s] Erasure Request Fulfilled' ),
3178 $email_data['sitename']
3179 );
3180
3181 /**
3182 * Filters the subject of the email sent when an erasure request is completed.
3183 *
3184 * @since 4.9.8
3185 *
3186 * @param string $subject The email subject.
3187 * @param string $sitename The name of the site.
3188 * @param array $email_data {
3189 * Data relating to the account action email.
3190 *
3191 * @type WP_User_Request $request User request object.
3192 * @type string $message_recipient The address that the email will be sent to. Defaults
3193 * to the value of `$request->email`, but can be changed
3194 * by the `user_erasure_fulfillment_email_to` filter.
3195 * @type string $privacy_policy_url Privacy policy URL.
3196 * @type string $sitename The site name sending the mail.
3197 * @type string $siteurl The site URL sending the mail.
3198 * }
3199 */
3200 $subject = apply_filters( 'user_erasure_complete_email_subject', $subject, $email_data['sitename'], $email_data );
3201
3202 if ( empty( $email_data['privacy_policy_url'] ) ) {
3203 /* translators: Do not translate SITENAME, SITEURL; those are placeholders. */
3204 $email_text = __(
3205 'Howdy,
3206
3207Your request to erase your personal data on ###SITENAME### has been completed.
3208
3209If you have any follow-up questions or concerns, please contact the site administrator.
3210
3211Regards,
3212All at ###SITENAME###
3213###SITEURL###'
3214 );
3215 } else {
3216 /* translators: Do not translate SITENAME, SITEURL, PRIVACY_POLICY_URL; those are placeholders. */
3217 $email_text = __(
3218 'Howdy,
3219
3220Your request to erase your personal data on ###SITENAME### has been completed.
3221
3222If you have any follow-up questions or concerns, please contact the site administrator.
3223
3224For more information, you can also read our privacy policy: ###PRIVACY_POLICY_URL###
3225
3226Regards,
3227All at ###SITENAME###
3228###SITEURL###'
3229 );
3230 }
3231
3232 /**
3233 * Filters the body of the data erasure fulfillment notification.
3234 *
3235 * The email is sent to a user when a their data erasure request is fulfilled
3236 * by an administrator.
3237 *
3238 * The following strings have a special meaning and will get replaced dynamically:
3239 *
3240 * ###SITENAME### The name of the site.
3241 * ###PRIVACY_POLICY_URL### Privacy policy page URL.
3242 * ###SITEURL### The URL to the site.
3243 *
3244 * @since 4.9.6
3245 *
3246 * @param string $email_text Text in the email.
3247 * @param array $email_data {
3248 * Data relating to the account action email.
3249 *
3250 * @type WP_User_Request $request User request object.
3251 * @type string $message_recipient The address that the email will be sent to. Defaults
3252 * to the value of `$request->email`, but can be changed
3253 * by the `user_erasure_fulfillment_email_to` filter.
3254 * @type string $privacy_policy_url Privacy policy URL.
3255 * @type string $sitename The site name sending the mail.
3256 * @type string $siteurl The site URL sending the mail.
3257 * }
3258 */
3259 $content = apply_filters( 'user_confirmed_action_email_content', $email_text, $email_data );
3260
3261 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
3262 $content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content );
3263 $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
3264
3265 $email_sent = wp_mail( $user_email, $subject, $content );
3266
3267 if ( $switched_locale ) {
3268 restore_previous_locale();
3269 }
3270
3271 if ( $email_sent ) {
3272 update_post_meta( $request_id, '_wp_user_notified', true );
3273 }
3274}
3275
3276/**
3277 * Return request confirmation message HTML.
3278 *
3279 * @since 4.9.6
3280 * @access private
3281 *
3282 * @param int $request_id The request ID being confirmed.
3283 * @return string $message The confirmation message.
3284 */
3285function _wp_privacy_account_request_confirmed_message( $request_id ) {
3286 $request = wp_get_user_request_data( $request_id );
3287
3288 $message = '<p class="success">' . __( 'Action has been confirmed.' ) . '</p>';
3289 $message .= '<p>' . __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ) . '</p>';
3290
3291 if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) {
3292 if ( 'export_personal_data' === $request->action_name ) {
3293 $message = '<p class="success">' . __( 'Thanks for confirming your export request.' ) . '</p>';
3294 $message .= '<p>' . __( 'The site administrator has been notified. You will receive a link to download your export via email when they fulfill your request.' ) . '</p>';
3295 } elseif ( 'remove_personal_data' === $request->action_name ) {
3296 $message = '<p class="success">' . __( 'Thanks for confirming your erasure request.' ) . '</p>';
3297 $message .= '<p>' . __( 'The site administrator has been notified. You will receive an email confirmation when they erase your data.' ) . '</p>';
3298 }
3299 }
3300
3301 /**
3302 * Filters the message displayed to a user when they confirm a data request.
3303 *
3304 * @since 4.9.6
3305 *
3306 * @param string $message The message to the user.
3307 * @param int $request_id The ID of the request being confirmed.
3308 */
3309 $message = apply_filters( 'user_request_action_confirmed_message', $message, $request_id );
3310
3311 return $message;
3312}
3313
3314/**
3315 * Create and log a user request to perform a specific action.
3316 *
3317 * Requests are stored inside a post type named `user_request` since they can apply to both
3318 * users on the site, or guests without a user account.
3319 *
3320 * @since 4.9.6
3321 *
3322 * @param string $email_address User email address. This can be the address of a registered or non-registered user.
3323 * @param string $action_name Name of the action that is being confirmed. Required.
3324 * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed.
3325 * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure.
3326 */
3327function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array() ) {
3328 $email_address = sanitize_email( $email_address );
3329 $action_name = sanitize_key( $action_name );
3330
3331 if ( ! is_email( $email_address ) ) {
3332 return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) );
3333 }
3334
3335 if ( ! $action_name ) {
3336 return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) );
3337 }
3338
3339 $user = get_user_by( 'email', $email_address );
3340 $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0;
3341
3342 // Check for duplicates.
3343 $requests_query = new WP_Query(
3344 array(
3345 'post_type' => 'user_request',
3346 'post_name__in' => array( $action_name ), // Action name stored in post_name column.
3347 'title' => $email_address, // Email address stored in post_title column.
3348 'post_status' => array(
3349 'request-pending',
3350 'request-confirmed',
3351 ),
3352 'fields' => 'ids',
3353 )
3354 );
3355
3356 if ( $requests_query->found_posts ) {
3357 return new WP_Error( 'duplicate_request', __( 'An incomplete request for this email address already exists.' ) );
3358 }
3359
3360 $request_id = wp_insert_post(
3361 array(
3362 'post_author' => $user_id,
3363 'post_name' => $action_name,
3364 'post_title' => $email_address,
3365 'post_content' => wp_json_encode( $request_data ),
3366 'post_status' => 'request-pending',
3367 'post_type' => 'user_request',
3368 'post_date' => current_time( 'mysql', false ),
3369 'post_date_gmt' => current_time( 'mysql', true ),
3370 ),
3371 true
3372 );
3373
3374 return $request_id;
3375}
3376
3377/**
3378 * Get action description from the name and return a string.
3379 *
3380 * @since 4.9.6
3381 *
3382 * @param string $action_name Action name of the request.
3383 * @return string Human readable action name.
3384 */
3385function wp_user_request_action_description( $action_name ) {
3386 switch ( $action_name ) {
3387 case 'export_personal_data':
3388 $description = __( 'Export Personal Data' );
3389 break;
3390 case 'remove_personal_data':
3391 $description = __( 'Erase Personal Data' );
3392 break;
3393 default:
3394 /* translators: %s: action name */
3395 $description = sprintf( __( 'Confirm the "%s" action' ), $action_name );
3396 break;
3397 }
3398
3399 /**
3400 * Filters the user action description.
3401 *
3402 * @since 4.9.6
3403 *
3404 * @param string $description The default description.
3405 * @param string $action_name The name of the request.
3406 */
3407 return apply_filters( 'user_request_action_description', $description, $action_name );
3408}
3409
3410/**
3411 * Send a confirmation request email to confirm an action.
3412 *
3413 * If the request is not already pending, it will be updated.
3414 *
3415 * @since 4.9.6
3416 *
3417 * @param string $request_id ID of the request created via wp_create_user_request().
3418 * @return bool|WP_Error True on success, `WP_Error` on failure.
3419 */
3420function wp_send_user_request( $request_id ) {
3421 $request_id = absint( $request_id );
3422 $request = wp_get_user_request_data( $request_id );
3423
3424 if ( ! $request ) {
3425 return new WP_Error( 'invalid_request', __( 'Invalid user request.' ) );
3426 }
3427
3428 // Localize message content for user; fallback to site default for visitors.
3429 if ( ! empty( $request->user_id ) ) {
3430 $locale = get_user_locale( $request->user_id );
3431 } else {
3432 $locale = get_locale();
3433 }
3434
3435 $switched_locale = switch_to_locale( $locale );
3436
3437 $email_data = array(
3438 'request' => $request,
3439 'email' => $request->email,
3440 'description' => wp_user_request_action_description( $request->action_name ),
3441 'confirm_url' => add_query_arg(
3442 array(
3443 'action' => 'confirmaction',
3444 'request_id' => $request_id,
3445 'confirm_key' => wp_generate_user_request_key( $request_id ),
3446 ),
3447 wp_login_url()
3448 ),
3449 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
3450 'siteurl' => home_url(),
3451 );
3452
3453 /* translators: Do not translate DESCRIPTION, CONFIRM_URL, SITENAME, SITEURL: those are placeholders. */
3454 $email_text = __(
3455 'Howdy,
3456
3457A request has been made to perform the following action on your account:
3458
3459 ###DESCRIPTION###
3460
3461To confirm this, please click on the following link:
3462###CONFIRM_URL###
3463
3464You can safely ignore and delete this email if you do not want to
3465take this action.
3466
3467Regards,
3468All at ###SITENAME###
3469###SITEURL###'
3470 );
3471
3472 /**
3473 * Filters the text of the email sent when an account action is attempted.
3474 *
3475 * The following strings have a special meaning and will get replaced dynamically:
3476 *
3477 * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for.
3478 * ###CONFIRM_URL### The link to click on to confirm the account action.
3479 * ###SITENAME### The name of the site.
3480 * ###SITEURL### The URL to the site.
3481 *
3482 * @since 4.9.6
3483 *
3484 * @param string $email_text Text in the email.
3485 * @param array $email_data {
3486 * Data relating to the account action email.
3487 *
3488 * @type WP_User_Request $request User request object.
3489 * @type string $email The email address this is being sent to.
3490 * @type string $description Description of the action being performed so the user knows what the email is for.
3491 * @type string $confirm_url The link to click on to confirm the account action.
3492 * @type string $sitename The site name sending the mail.
3493 * @type string $siteurl The site URL sending the mail.
3494 * }
3495 */
3496 $content = apply_filters( 'user_request_action_email_content', $email_text, $email_data );
3497
3498 $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
3499 $content = str_replace( '###CONFIRM_URL###', esc_url_raw( $email_data['confirm_url'] ), $content );
3500 $content = str_replace( '###EMAIL###', $email_data['email'], $content );
3501 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
3502 $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
3503
3504 /* translators: Confirm privacy data request notification email subject. 1: Site title, 2: Name of the action */
3505 $subject = sprintf( __( '[%1$s] Confirm Action: %2$s' ), $email_data['sitename'], $email_data['description'] );
3506
3507 /**
3508 * Filters the subject of the email sent when an account action is attempted.
3509 *
3510 * @since 4.9.6
3511 *
3512 * @param string $subject The email subject.
3513 * @param string $sitename The name of the site.
3514 * @param array $email_data {
3515 * Data relating to the account action email.
3516 *
3517 * @type WP_User_Request $request User request object.
3518 * @type string $email The email address this is being sent to.
3519 * @type string $description Description of the action being performed so the user knows what the email is for.
3520 * @type string $confirm_url The link to click on to confirm the account action.
3521 * @type string $sitename The site name sending the mail.
3522 * @type string $siteurl The site URL sending the mail.
3523 * }
3524 */
3525 $subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data );
3526
3527 $email_sent = wp_mail( $email_data['email'], $subject, $content );
3528
3529 if ( $switched_locale ) {
3530 restore_previous_locale();
3531 }
3532
3533 if ( ! $email_sent ) {
3534 return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) );
3535 }
3536
3537 return true;
3538}
3539
3540/**
3541 * Returns a confirmation key for a user action and stores the hashed version for future comparison.
3542 *
3543 * @since 4.9.6
3544 *
3545 * @param int $request_id Request ID.
3546 * @return string Confirmation key.
3547 */
3548function wp_generate_user_request_key( $request_id ) {
3549 global $wp_hasher;
3550
3551 // Generate something random for a confirmation key.
3552 $key = wp_generate_password( 20, false );
3553
3554 // Return the key, hashed.
3555 if ( empty( $wp_hasher ) ) {
3556 require_once ABSPATH . WPINC . '/class-phpass.php';
3557 $wp_hasher = new PasswordHash( 8, true );
3558 }
3559
3560 wp_update_post(
3561 array(
3562 'ID' => $request_id,
3563 'post_status' => 'request-pending',
3564 'post_password' => $wp_hasher->HashPassword( $key ),
3565 )
3566 );
3567
3568 return $key;
3569}
3570
3571/**
3572 * Validate a user request by comparing the key with the request's key.
3573 *
3574 * @since 4.9.6
3575 *
3576 * @param string $request_id ID of the request being confirmed.
3577 * @param string $key Provided key to validate.
3578 * @return bool|WP_Error WP_Error on failure, true on success.
3579 */
3580function wp_validate_user_request_key( $request_id, $key ) {
3581 global $wp_hasher;
3582
3583 $request_id = absint( $request_id );
3584 $request = wp_get_user_request_data( $request_id );
3585
3586 if ( ! $request ) {
3587 return new WP_Error( 'invalid_request', __( 'Invalid request.' ) );
3588 }
3589
3590 if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) {
3591 return new WP_Error( 'expired_link', __( 'This link has expired.' ) );
3592 }
3593
3594 if ( empty( $key ) ) {
3595 return new WP_Error( 'missing_key', __( 'Missing confirm key.' ) );
3596 }
3597
3598 if ( empty( $wp_hasher ) ) {
3599 require_once ABSPATH . WPINC . '/class-phpass.php';
3600 $wp_hasher = new PasswordHash( 8, true );
3601 }
3602
3603 $key_request_time = $request->modified_timestamp;
3604 $saved_key = $request->confirm_key;
3605
3606 if ( ! $saved_key ) {
3607 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
3608 }
3609
3610 if ( ! $key_request_time ) {
3611 return new WP_Error( 'invalid_key', __( 'Invalid action.' ) );
3612 }
3613
3614 /**
3615 * Filters the expiration time of confirm keys.
3616 *
3617 * @since 4.9.6
3618 *
3619 * @param int $expiration The expiration time in seconds.
3620 */
3621 $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
3622 $expiration_time = $key_request_time + $expiration_duration;
3623
3624 if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
3625 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
3626 }
3627
3628 if ( ! $expiration_time || time() > $expiration_time ) {
3629 return new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) );
3630 }
3631
3632 return true;
3633}
3634
3635/**
3636 * Return data about a user request.
3637 *
3638 * @since 4.9.6
3639 *
3640 * @param int $request_id Request ID to get data about.
3641 * @return WP_User_Request|false
3642 */
3643function wp_get_user_request_data( $request_id ) {
3644 $request_id = absint( $request_id );
3645 $post = get_post( $request_id );
3646
3647 if ( ! $post || 'user_request' !== $post->post_type ) {
3648 return false;
3649 }
3650
3651 return new WP_User_Request( $post );
3652}
3653
3654/**
3655 * WP_User_Request class.
3656 *
3657 * Represents user request data loaded from a WP_Post object.
3658 *
3659 * @since 4.9.6
3660 */
3661final class WP_User_Request {
3662 /**
3663 * Request ID.
3664 *
3665 * @var int
3666 */
3667 public $ID = 0;
3668
3669 /**
3670 * User ID.
3671 *
3672 * @var int
3673 */
3674 public $user_id = 0;
3675
3676 /**
3677 * User email.
3678 *
3679 * @var int
3680 */
3681 public $email = '';
3682
3683 /**
3684 * Action name.
3685 *
3686 * @var string
3687 */
3688 public $action_name = '';
3689
3690 /**
3691 * Current status.
3692 *
3693 * @var string
3694 */
3695 public $status = '';
3696
3697 /**
3698 * Timestamp this request was created.
3699 *
3700 * @var int|null
3701 */
3702 public $created_timestamp = null;
3703
3704 /**
3705 * Timestamp this request was last modified.
3706 *
3707 * @var int|null
3708 */
3709 public $modified_timestamp = null;
3710
3711 /**
3712 * Timestamp this request was confirmed.
3713 *
3714 * @var int
3715 */
3716 public $confirmed_timestamp = null;
3717
3718 /**
3719 * Timestamp this request was completed.
3720 *
3721 * @var int
3722 */
3723 public $completed_timestamp = null;
3724
3725 /**
3726 * Misc data assigned to this request.
3727 *
3728 * @var array
3729 */
3730 public $request_data = array();
3731
3732 /**
3733 * Key used to confirm this request.
3734 *
3735 * @var string
3736 */
3737 public $confirm_key = '';
3738
3739 /**
3740 * Constructor.
3741 *
3742 * @since 4.9.6
3743 *
3744 * @param WP_Post|object $post Post object.
3745 */
3746 public function __construct( $post ) {
3747 $this->ID = $post->ID;
3748 $this->user_id = $post->post_author;
3749 $this->email = $post->post_title;
3750 $this->action_name = $post->post_name;
3751 $this->status = $post->post_status;
3752 $this->created_timestamp = strtotime( $post->post_date_gmt );
3753 $this->modified_timestamp = strtotime( $post->post_modified_gmt );
3754 $this->confirmed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_confirmed_timestamp', true );
3755 $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true );
3756 $this->request_data = json_decode( $post->post_content, true );
3757 $this->confirm_key = $post->post_password;
3758 }
3759}