balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

user.php (155195B)


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