Make WordPress Core

Ticket #34281: 34281.9.diff

File 34281.9.diff, 18.4 KB (added by bookdude13, 5 years ago)

Refreshed patch

  • 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
     
    9393                });
    9494        }
    9595
     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
    96158        function bindPasswordForm() {
    97159                var $passwordWrapper,
    98160                        $generateButton,
     
    377439                });
    378440
    379441                bindPasswordForm();
     442                bindPasswordRestLink();
    380443        });
    381444
    382445        $( '#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( 
    139139        'health-check-background-updates',
    140140        'health-check-loopback-requests',
    141141        'health-check-get-sizes',
     142        'send-password-reset',
    142143);
    143144
    144145// 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() { 
    52655265function wp_ajax_rest_nonce() {
    52665266        exit( wp_create_nonce( 'wp_rest' ) );
    52675267}
     5268
     5269/**
     5270 * Ajax handler sends a password reset link.
     5271 *
     5272 * @since 5.4.0
     5273 */
     5274function 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 { 
    274274                        }
    275275                }
    276276
     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
    277283                return $actions;
    278284        }
    279285
    class WP_Users_List_Table extends WP_List_Table { 
    470476                                );
    471477                        }
    472478
     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&amp;users=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Send password reset' ) . "</a>";
     482                        }
     483
     484
    473485                        /**
    474486                         * Filters the action links displayed under each user in the Users list table.
    475487                         *
  • 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; 
    604604                </p>
    605605        </td>
    606606</tr>
     607<?php endif; ?>
     608<?php
     609// Allow admins to send reset password link
     610if ( ! 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>
    607628<?php endif; ?>
    608629
    609630                <?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() ) { 
    213213                wp_redirect( $redirect );
    214214                exit();
    215215
     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&#8217;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&#8217;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
    216258        case 'delete':
    217259                if ( is_multisite() ) {
    218260                        wp_die( __( 'User deletion is not allowed from this screen.' ), 400 );
    switch ( $wp_list_table->current_action() ) { 
    509551                                                );
    510552                                        }
    511553
     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                                        }
    512563                                        $messages[] = '<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>';
    513564                                        break;
    514565                                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 ) { 
    75937593function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
    75947594        return abs( (float) $expected - (float) $actual ) <= $precision;
    75957595}
     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 */
     7607function 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 ) { 
    11461146                )
    11471147        );
    11481148
     1149        $user_id = isset( $_GET['user_id'] ) ? (int) $_GET['user_id'] : 0;
    11491150        $scripts->add( 'user-profile', "/wp-admin/js/user-profile$suffix.js", array( 'jquery', 'password-strength-meter', 'wp-util' ), false, 1 );
    11501151        did_action( 'init' ) && $scripts->localize(
    11511152                'user-profile',
    function wp_default_scripts( &$scripts ) { 
    11581159                        'cancel'   => __( 'Cancel' ),
    11591160                        'ariaShow' => esc_attr__( 'Show password' ),
    11601161                        'ariaHide' => esc_attr__( 'Hide password' ),
     1162                        'user_id'  => $user_id,
     1163                        'nonce'    => wp_create_nonce( 'reset-password-for-' . $user_id ),
    11611164                )
    11621165        );
    11631166
  • src/wp-login.php

    diff --git src/wp-login.php src/wp-login.php
    index 0b552ef326..d6dd3435aa 100644
    function wp_login_viewport_meta() { 
    351351        <?php
    352352}
    353353
    354 /**
    355  * Handles sending password retrieval email to user.
    356  *
    357  * @since 2.5.0
    358  *
    359  * @return bool|WP_Error True: when finish. WP_Error on error
    360  */
    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.0
    381          * @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 generated
    385          *                         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 database
    413                  * 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.0
    434          * @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.0
    448          * @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 
    472354//
    473355// Main.
    474356//