Ticket #34281: 34281.9.diff
File 34281.9.diff, 18.4 KB (added by , 5 years ago) |
---|
-
src/js/_enqueues/admin/user-profile.js
diff --git src/js/_enqueues/admin/user-profile.js src/js/_enqueues/admin/user-profile.js index ff4830242c..327ab2e4f7 100644
93 93 }); 94 94 } 95 95 96 /** 97 * Handle the password reset button. Sets up an ajax callback to trigger sending 98 * a password reset email. 99 */ 100 function bindPasswordRestLink() { 101 $( '#generate-reset-link' ).on( 'click', function() { 102 var $this = $(this); 103 var data = { 104 'user_id': userProfileL10n.user_id, // The user to send a reset to. 105 'nonce': userProfileL10n.nonce // Nonce to validate the action. 106 }; 107 108 // Remove any previous error messages. 109 $this.parent().find( '.notice-error' ).remove(); 110 111 // Send the reset request. 112 var resetAction = wp.ajax.post( 'send-password-reset', data ); 113 114 // Handle reset success. 115 resetAction.done( function( response ) { 116 addInlineNotice( $this, true, response ); 117 } ); 118 119 // Handle reset failure. 120 resetAction.fail( function( response ) { 121 addInlineNotice( $this, false, response ); 122 } ); 123 124 } ); 125 126 } 127 128 /** 129 * Helper function to insert an inline notice of success or failure. 130 * 131 * @param {jQuery Object} $this The button element: the message will be inserted 132 * above this button 133 * @param {bool} success Whether the message is a success message. 134 * @param {string} message The message to insert. 135 */ 136 function addInlineNotice( $this, success, message ) { 137 var resultDiv = $( '<div />' ); 138 139 // Set up the notice div. 140 resultDiv.addClass( 'notice inline' ); 141 142 // Add a class indicating success or failure. 143 resultDiv.addClass( 'notice-' + ( success ? 'success' : 'error' ) ); 144 145 // Add the message, wrapping in a p tag, with a fadein to highlight each message. 146 resultDiv.text( $( $.parseHTML( message ) ).text() ).wrapInner( '<p />' ); 147 148 // Disable the button when the callback has succeeded. 149 $this.prop( 'disabled', success ); 150 151 // Remove any previous notices. 152 $this.siblings( '.notice' ).remove(); 153 154 // Insert the notice. 155 $this.before( resultDiv ); 156 } 157 96 158 function bindPasswordForm() { 97 159 var $passwordWrapper, 98 160 $generateButton, … … 377 439 }); 378 440 379 441 bindPasswordForm(); 442 bindPasswordRestLink(); 380 443 }); 381 444 382 445 $( '#destroy-sessions' ).on( 'click', function( e ) { -
src/wp-admin/admin-ajax.php
diff --git src/wp-admin/admin-ajax.php src/wp-admin/admin-ajax.php index d3dc4da2a5..ca25cc7a13 100644
$core_actions_post = array( 139 139 'health-check-background-updates', 140 140 'health-check-loopback-requests', 141 141 'health-check-get-sizes', 142 'send-password-reset', 142 143 ); 143 144 144 145 // Deprecated. -
src/wp-admin/includes/ajax-actions.php
diff --git src/wp-admin/includes/ajax-actions.php src/wp-admin/includes/ajax-actions.php index ea33d9d792..3872665305 100644
function wp_ajax_health_check_get_sizes() { 5265 5265 function wp_ajax_rest_nonce() { 5266 5266 exit( wp_create_nonce( 'wp_rest' ) ); 5267 5267 } 5268 5269 /** 5270 * Ajax handler sends a password reset link. 5271 * 5272 * @since 5.4.0 5273 */ 5274 function wp_ajax_send_password_reset() { 5275 5276 // Validate the nonce for this action. 5277 $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0; 5278 check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' ); 5279 5280 // Verify user capabilities. 5281 if ( ! current_user_can( 'edit_user', $user_id ) ) { 5282 wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) ); 5283 } 5284 5285 // Send the password reset link. 5286 $user = get_userdata( $user_id ); 5287 $results = retrieve_password( $user->user_login ); 5288 5289 if ( true === $results ) { 5290 wp_send_json_success( 5291 /* translators: 1: User's display name. */ 5292 sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name ) 5293 ); 5294 } else { 5295 wp_send_json_error( $results ); 5296 } 5297 } -
src/wp-admin/includes/class-wp-users-list-table.php
diff --git src/wp-admin/includes/class-wp-users-list-table.php src/wp-admin/includes/class-wp-users-list-table.php index 505aa9a435..d875f993b0 100644
class WP_Users_List_Table extends WP_List_Table { 274 274 } 275 275 } 276 276 277 // Add a password reset link to the bulk actions dropdown. 278 if ( current_user_can( 'edit_users' ) ) { 279 $actions['resetpassword'] = __( 'Send password reset' ); 280 } 281 282 277 283 return $actions; 278 284 } 279 285 … … class WP_Users_List_Table extends WP_List_Table { 470 476 ); 471 477 } 472 478 479 // Add a link to send the user a reset password link by email. 480 if ( get_current_user_id() != $user_object->ID && current_user_can( 'edit_user', $user_object->ID ) ) { 481 $actions['resetpassword'] = "<a class='resetpassword' href='" . wp_nonce_url( "users.php?action=resetpassword&users=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Send password reset' ) . "</a>"; 482 } 483 484 473 485 /** 474 486 * Filters the action links displayed under each user in the Users list table. 475 487 * -
src/wp-admin/user-edit.php
diff --git src/wp-admin/user-edit.php src/wp-admin/user-edit.php index de5131af24..f8e425a900 100644
endif; 604 604 </p> 605 605 </td> 606 606 </tr> 607 <?php endif; ?> 608 <?php 609 // Allow admins to send reset password link 610 if ( ! IS_PROFILE_PAGE ) : 611 ?> 612 <tr class="user-sessions-wrap hide-if-no-js"> 613 <th><?php _e( 'Password Reset' ); ?></th> 614 <td> 615 <div class="generate-reset-link"> 616 <button type="button" class="button button-secondary" id="generate-reset-link"> 617 <?php _e( 'Send Reset Link' ); ?> 618 </button> 619 </div> 620 <p class="description"> 621 <?php 622 /* translators: 1: User's display name. */ 623 printf( __( 'Send %s a link to reset their password. This will not change their password, nor will it force a change.' ), esc_html( $profileuser->display_name ) ); 624 ?> 625 </p> 626 </td> 627 </tr> 607 628 <?php endif; ?> 608 629 609 630 <?php -
src/wp-admin/users.php
diff --git src/wp-admin/users.php src/wp-admin/users.php index b8eb3a45c0..a83507aadc 100644
switch ( $wp_list_table->current_action() ) { 213 213 wp_redirect( $redirect ); 214 214 exit(); 215 215 216 case 'resetpassword': 217 check_admin_referer('bulk-users'); 218 if ( ! current_user_can( 'edit_users' ) ) { 219 $errors = new WP_Error( 'edit_users', __( 'You can’t edit users.' ) ); 220 } 221 222 if ( empty( $_REQUEST['users'] ) ) { 223 wp_redirect( $redirect ); 224 exit(); 225 } 226 227 $userids = array_map( 'intval', (array) $_REQUEST['users'] ); 228 229 $reset_count = 0; 230 231 foreach ( $userids as $id ) { 232 if ( ! current_user_can( 'edit_user', $id ) ) { 233 wp_die(__( 'You can’t edit that user.' ) ); 234 } 235 236 if ( $id == $current_user->ID ) { 237 $update = 'err_admin_reset'; 238 continue; 239 } 240 241 // Send the password reset link. 242 $user = get_userdata( $id ); 243 if ( retrieve_password( $user->user_login ) ) { 244 ++$reset_count; 245 } 246 247 } 248 249 $redirect = add_query_arg( 250 array( 251 'reset_count' => $reset_count, 252 'update' => 'resetpassword', 253 ), $redirect 254 ); 255 wp_redirect( $redirect ); 256 exit(); 257 216 258 case 'delete': 217 259 if ( is_multisite() ) { 218 260 wp_die( __( 'User deletion is not allowed from this screen.' ), 400 ); … … switch ( $wp_list_table->current_action() ) { 509 551 ); 510 552 } 511 553 554 $messages[] = '<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>'; 555 break; 556 case 'resetpassword': 557 $reset_count = isset( $_GET['reset_count'] ) ? (int) $_GET['reset_count'] : 0; 558 if ( 1 === $reset_count ) { 559 $message = __( 'Password reset link sent.' ); 560 } else { 561 $message = sprintf( __( 'Password reset links sent to %s users.' ), $reset_count ); 562 } 512 563 $messages[] = '<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>'; 513 564 break; 514 565 case 'promote': -
src/wp-includes/functions.php
diff --git src/wp-includes/functions.php src/wp-includes/functions.php index 10e8ad49fe..4d88f54743 100644
function is_php_version_compatible( $required ) { 7593 7593 function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { 7594 7594 return abs( (float) $expected - (float) $actual ) <= $precision; 7595 7595 } 7596 7597 /** 7598 * Handles sending password retrieval email to user. 7599 * 7600 * @global wpdb $wpdb WordPress database abstraction object. 7601 * @global PasswordHash $wp_hasher Portable PHP password hashing framework. 7602 * @param string $user_login Optional user_login, default null. 7603 * Uses $_POST['user_login'] if $user_login not set. 7604 * 7605 * @return bool|WP_Error True: when finish. WP_Error on error 7606 */ 7607 function retrieve_password( $user_login = null ) { 7608 global $wpdb, $wp_hasher; 7609 7610 $errors = new WP_Error(); 7611 7612 // Use the passed $user_login if available, otherwise use $_POST['user_login']. 7613 if ( !$user_login && !empty( $_POST['user_login'] ) ) { 7614 $user_login = $_POST['user_login']; 7615 } 7616 7617 if ( empty( $user_login ) ) { 7618 $errors->add( 'empty_username', __( '<strong>ERROR</strong>: Enter a username or email address.' ) ); 7619 } elseif ( strpos( $user_login, '@' ) ) { 7620 $user_data = get_user_by( 'email', sanitize_email( $user_login ) ); 7621 if ( empty( $user_data ) ) { 7622 $errors->add( 'invalid_email', __( '<strong>ERROR</strong>: There is no account with that username or email address.' ) ); 7623 } 7624 } else { 7625 $user_data = get_user_by( 'login', sanitize_user( $user_login ) ); 7626 } 7627 7628 /** 7629 * Fires before errors are returned from a password reset request. 7630 * 7631 * @since 2.1.0 7632 * @since 4.4.0 Added the `$errors` parameter. 7633 * @since 5.4.0 Added the `$user_data` parameter. 7634 * 7635 * @param WP_Error $errors A WP_Error object containing any errors generated 7636 * by using invalid credentials. 7637 * @param WP_User|false WP_User object if found, false if the user does not exist. 7638 */ 7639 do_action( 'lostpassword_post', $errors ); 7640 7641 if ( $errors->has_errors() ) { 7642 return $errors; 7643 } 7644 7645 if ( ! $user_data ) { 7646 $errors->add( 'invalidcombo', __( '<strong>Error</strong>: There is no account with that username or email address.' ) ); 7647 return $errors; 7648 } 7649 7650 // Redefining user_login ensures we return the right case in the email. 7651 $user_login = $user_data->user_login; 7652 $user_email = $user_data->user_email; 7653 $key = get_password_reset_key( $user_data ); 7654 7655 if ( is_wp_error( $key ) ) { 7656 return $key; 7657 } 7658 7659 if ( is_multisite() ) { 7660 $site_name = get_network()->site_name; 7661 } else { 7662 /* 7663 * The blogname option is escaped with esc_html on the way into the database 7664 * in sanitize_option we want to reverse this for the plain text arena of emails. 7665 */ 7666 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 7667 } 7668 7669 7670 $message = __( 'Someone has requested a password be reset for the following account:' ) . "\r\n\r\n"; 7671 /* translators: %s: Site name. */ 7672 $message .= sprintf( __( 'Site Name: %s' ), $site_name ) . "\r\n\r\n"; 7673 /* translators: %s: User login. */ 7674 $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n"; 7675 $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.' ) . "\r\n\r\n"; 7676 $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n"; 7677 $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . "\r\n"; 7678 7679 /* translators: Password reset notification email subject. %s: Site title. */ 7680 $title = sprintf( __( '[%s] Password Reset' ), $site_name ); 7681 7682 /** 7683 * Filter the subject of the password reset email. 7684 * 7685 * @since 2.8.0 7686 * @since 4.4.0 Added the `$user_login` and `$user_data` parameters. 7687 * 7688 * @param string $title Default email title. 7689 * @param string $user_login The username for the user. 7690 * @param WP_User $user_data WP_User object. 7691 */ 7692 $title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data ); 7693 7694 /** 7695 * Filters the message body of the password reset mail. 7696 * 7697 * If the filtered message is empty, the password reset email will not be sent. 7698 * 7699 * @since 2.8.0 7700 * @since 4.1.0 Added `$user_login` and `$user_data` parameters. 7701 * 7702 * @param string $message Default mail message. 7703 * @param string $key The activation key. 7704 * @param string $user_login The username for the user. 7705 * @param WP_User $user_data WP_User object. 7706 */ 7707 $message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data ); 7708 7709 if ( $message && ! wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) ) { 7710 $errors->add( 7711 'retrieve_password_email_failure', 7712 sprintf( 7713 /* translators: %s: Documentation URL. */ 7714 __( '<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>.' ), 7715 esc_url( __( 'https://wordpress.org/support/article/resetting-your-password/' ) ) 7716 ) 7717 ); 7718 return $errors; 7719 } 7720 7721 return true; 7722 } -
src/wp-includes/script-loader.php
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php index 445d66aeaf..63223fe224 100644
function wp_default_scripts( &$scripts ) { 1146 1146 ) 1147 1147 ); 1148 1148 1149 $user_id = isset( $_GET['user_id'] ) ? (int) $_GET['user_id'] : 0; 1149 1150 $scripts->add( 'user-profile', "/wp-admin/js/user-profile$suffix.js", array( 'jquery', 'password-strength-meter', 'wp-util' ), false, 1 ); 1150 1151 did_action( 'init' ) && $scripts->localize( 1151 1152 'user-profile', … … function wp_default_scripts( &$scripts ) { 1158 1159 'cancel' => __( 'Cancel' ), 1159 1160 'ariaShow' => esc_attr__( 'Show password' ), 1160 1161 'ariaHide' => esc_attr__( 'Hide password' ), 1162 'user_id' => $user_id, 1163 'nonce' => wp_create_nonce( 'reset-password-for-' . $user_id ), 1161 1164 ) 1162 1165 ); 1163 1166 -
src/wp-login.php
diff --git src/wp-login.php src/wp-login.php index 0b552ef326..d6dd3435aa 100644
function wp_login_viewport_meta() { 351 351 <?php 352 352 } 353 353 354 /**355 * Handles sending password retrieval email to user.356 *357 * @since 2.5.0358 *359 * @return bool|WP_Error True: when finish. WP_Error on error360 */361 function retrieve_password() {362 $errors = new WP_Error();363 $user_data = false;364 365 if ( empty( $_POST['user_login'] ) || ! is_string( $_POST['user_login'] ) ) {366 $errors->add( 'empty_username', __( '<strong>Error</strong>: Enter a username or email address.' ) );367 } elseif ( strpos( $_POST['user_login'], '@' ) ) {368 $user_data = get_user_by( 'email', trim( wp_unslash( $_POST['user_login'] ) ) );369 if ( empty( $user_data ) ) {370 $errors->add( 'invalid_email', __( '<strong>Error</strong>: There is no account with that username or email address.' ) );371 }372 } else {373 $login = trim( wp_unslash( $_POST['user_login'] ) );374 $user_data = get_user_by( 'login', $login );375 }376 377 /**378 * Fires before errors are returned from a password reset request.379 *380 * @since 2.1.0381 * @since 4.4.0 Added the `$errors` parameter.382 * @since 5.4.0 Added the `$user_data` parameter.383 *384 * @param WP_Error $errors A WP_Error object containing any errors generated385 * by using invalid credentials.386 * @param WP_User|false WP_User object if found, false if the user does not exist.387 */388 do_action( 'lostpassword_post', $errors, $user_data );389 390 if ( $errors->has_errors() ) {391 return $errors;392 }393 394 if ( ! $user_data ) {395 $errors->add( 'invalidcombo', __( '<strong>Error</strong>: There is no account with that username or email address.' ) );396 return $errors;397 }398 399 // Redefining user_login ensures we return the right case in the email.400 $user_login = $user_data->user_login;401 $user_email = $user_data->user_email;402 $key = get_password_reset_key( $user_data );403 404 if ( is_wp_error( $key ) ) {405 return $key;406 }407 408 if ( is_multisite() ) {409 $site_name = get_network()->site_name;410 } else {411 /*412 * The blogname option is escaped with esc_html on the way into the database413 * in sanitize_option we want to reverse this for the plain text arena of emails.414 */415 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );416 }417 418 $message = __( 'Someone has requested a password reset for the following account:' ) . "\r\n\r\n";419 /* translators: %s: Site name. */420 $message .= sprintf( __( 'Site Name: %s' ), $site_name ) . "\r\n\r\n";421 /* translators: %s: User login. */422 $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n";423 $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.' ) . "\r\n\r\n";424 $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n";425 $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . "\r\n";426 427 /* translators: Password reset notification email subject. %s: Site title. */428 $title = sprintf( __( '[%s] Password Reset' ), $site_name );429 430 /**431 * Filters the subject of the password reset email.432 *433 * @since 2.8.0434 * @since 4.4.0 Added the `$user_login` and `$user_data` parameters.435 *436 * @param string $title Default email title.437 * @param string $user_login The username for the user.438 * @param WP_User $user_data WP_User object.439 */440 $title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data );441 442 /**443 * Filters the message body of the password reset mail.444 *445 * If the filtered message is empty, the password reset email will not be sent.446 *447 * @since 2.8.0448 * @since 4.1.0 Added `$user_login` and `$user_data` parameters.449 *450 * @param string $message Default mail message.451 * @param string $key The activation key.452 * @param string $user_login The username for the user.453 * @param WP_User $user_data WP_User object.454 */455 $message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data );456 457 if ( $message && ! wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) ) {458 $errors->add(459 'retrieve_password_email_failure',460 sprintf(461 /* translators: %s: Documentation URL. */462 __( '<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>.' ),463 esc_url( __( 'https://wordpress.org/support/article/resetting-your-password/' ) )464 )465 );466 return $errors;467 }468 469 return true;470 }471 472 354 // 473 355 // Main. 474 356 //