Make WordPress Core

Ticket #44510: user.diff

File user.diff, 120.2 KB (added by krynes, 7 years ago)

I think this is solution

  • wp-includes/user.php

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