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