Commit 8b9abdaa70e for woocommerce
commit 8b9abdaa70ebec1c56e5b9bc65340199cc802915
Author: Mike Jolley <mike.jolley@me.com>
Date: Thu Jul 2 16:14:27 2026 +0100
Add resend action to the temporary-password My Account notice (#65821)
* Add resend action to the temporary-password My Account notice
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Load mailer before firing reset-password notification on resend
The resend handler fired woocommerce_reset_password_notification without
loading the email classes first, so no listener was registered and no
email was sent. Mirror the lost-password flow by calling WC()->mailer()
before the action.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Move mailer-load comment to its own line (PHPCS PostStatementComment)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Rate-limit set-password resends and suppress notice during cooldown
* Delete resend rate-limit meta when the password nag is cleared
Once the customer sets their password the temporary-password notice is
gone for good, so the resend cooldown timestamp is dead weight. Drop it
alongside the default_password_nag reset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/add-resend-set-password-link b/plugins/woocommerce/changelog/add-resend-set-password-link
new file mode 100644
index 00000000000..b72804543e7
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-resend-set-password-link
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a resend action to the temporary-password notice on My Account so customers can request a fresh change-password email.
diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php
index daea46ee9ca..f6bfb58b2b7 100644
--- a/plugins/woocommerce/includes/class-wc-form-handler.php
+++ b/plugins/woocommerce/includes/class-wc-form-handler.php
@@ -16,11 +16,22 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_Form_Handler {
+ /**
+ * User meta key tracking the last time the set-password link was resent. Used to rate-limit resends.
+ */
+ const SET_PASSWORD_RESEND_META = '_wc_set_password_resend_at';
+
+ /**
+ * Minimum seconds between back-to-back set-password resend requests.
+ */
+ const SET_PASSWORD_RESEND_RATE_LIMIT_SECONDS = 60;
+
/**
* Hook in methods.
*/
public static function init() {
add_action( 'template_redirect', array( __CLASS__, 'redirect_reset_password_link' ) );
+ add_action( 'template_redirect', array( __CLASS__, 'resend_set_password' ) );
add_action( 'template_redirect', array( __CLASS__, 'save_address' ) );
add_action( 'template_redirect', array( __CLASS__, 'save_account_details' ) );
add_action( 'wp_loaded', array( __CLASS__, 'checkout_action' ), 20 );
@@ -76,6 +87,59 @@ class WC_Form_Handler {
}
}
+ /**
+ * Resend the change-password link to a logged-in customer who still has a temporary password.
+ *
+ * Triggered by the temporary-password notice on the My Account pages. Generates a fresh
+ * password-reset key for the current user and dispatches the reset-password email, mirroring
+ * the lost-password flow but for the already-authenticated user.
+ *
+ * @since 11.0.0
+ */
+ public static function resend_set_password(): void {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! isset( $_GET['wc-resend-set-password'] ) || ! is_user_logged_in() ) {
+ return;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $nonce_value = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
+
+ if ( ! wp_verify_nonce( $nonce_value, 'wc-resend-set-password' ) ) {
+ return;
+ }
+
+ $user = wp_get_current_user();
+ $redirect = wc_get_page_permalink( 'myaccount' );
+
+ // Rate-limit resends so the button can't be used to spam the customer's inbox.
+ $last_sent_at = (int) get_user_meta( $user->ID, self::SET_PASSWORD_RESEND_META, true );
+ if ( $last_sent_at > 0 && ( time() - $last_sent_at ) < self::SET_PASSWORD_RESEND_RATE_LIMIT_SECONDS ) {
+ wc_add_notice( __( 'Please wait a moment before requesting another link to change your password.', 'woocommerce' ), 'notice' );
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+
+ $key = get_password_reset_key( $user );
+
+ if ( is_wp_error( $key ) ) {
+ wc_add_notice( __( 'Sorry, we were unable to resend the link. Please try again.', 'woocommerce' ), 'error' );
+ } else {
+ // Persist the rate-limit timestamp before dispatching so two near-simultaneous requests can't both pass.
+ // This timestamp also suppresses the temporary-password notice during the cooldown — see
+ // WC_Shortcode_My_Account::my_account_add_notices() — so the confirmation below isn't contradicted.
+ update_user_meta( $user->ID, self::SET_PASSWORD_RESEND_META, (string) time() );
+ // Load email classes so the reset-password notification has a listener.
+ WC()->mailer();
+ // phpcs:ignore WooCommerce.Commenting.CommentHooks -- Re-fires woocommerce_reset_password_notification, documented in WC_Shortcode_My_Account::retrieve_password().
+ do_action( 'woocommerce_reset_password_notification', $user->user_login, $key );
+ wc_add_notice( __( 'We have emailed you a new link to change your password.', 'woocommerce' ) );
+ }
+
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+
/**
* Save and and update a billing or shipping address if the
* form was submitted through the user account page.
diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
index cc081a9d3c8..4dbb81a8d56 100644
--- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
+++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
@@ -90,16 +90,22 @@ class WC_Shortcode_My_Account {
wc_add_notice( sprintf( __( 'Are you sure you want to log out? <a href="%s">Confirm and log out</a>', 'woocommerce' ), wc_logout_url() ) );
}
- if ( get_user_option( 'default_password_nag' ) && ( wc_is_current_account_menu_item( 'dashboard' ) || wc_is_current_account_menu_item( 'edit-account' ) ) ) {
+ // Suppress the nag during the resend cooldown so it doesn't contradict the "we emailed you" confirmation.
+ // Driven off the same timestamp WC_Form_Handler::resend_set_password() writes, so the notice reappears
+ // once the cooldown lapses and the link can be requested again.
+ $last_resend_at = (int) get_user_meta( get_current_user_id(), WC_Form_Handler::SET_PASSWORD_RESEND_META, true );
+ $within_cooldown = $last_resend_at > 0 && ( time() - $last_resend_at ) < WC_Form_Handler::SET_PASSWORD_RESEND_RATE_LIMIT_SECONDS;
+
+ if ( ! $within_cooldown && get_user_option( 'default_password_nag' ) && ( wc_is_current_account_menu_item( 'dashboard' ) || wc_is_current_account_menu_item( 'edit-account' ) ) ) {
+ $resend_url = wp_nonce_url( add_query_arg( 'wc-resend-set-password', '1', wc_get_page_permalink( 'myaccount' ) ), 'wc-resend-set-password' );
wc_add_notice(
sprintf(
- // translators: %s: site name.
- __( 'Your account with %s is using a temporary password. We emailed you a link to change your password.', 'woocommerce' ),
- esc_html( wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) )
- ),
- 'notice',
- array(),
- true
+ /* translators: %1$s and %2$s are opening and closing anchor tags for the resend-link button. */
+ __( '%1$sResend%2$s', 'woocommerce' ),
+ '<a href="' . esc_url( $resend_url ) . '" class="button wc-forward">',
+ '</a>'
+ ) . ' ' . __( 'Your account is using a temporary password. We emailed you a link to change your password.', 'woocommerce' ),
+ 'notice'
);
}
}
@@ -378,6 +384,8 @@ class WC_Shortcode_My_Account {
wp_set_password( $new_pass, $user->ID );
update_user_meta( $user->ID, 'default_password_nag', false );
+ // The temporary-password notice is gone for good now, so drop its resend rate-limit timestamp.
+ delete_user_meta( $user->ID, WC_Form_Handler::SET_PASSWORD_RESEND_META );
/**
* Fires after the user's password has been reset via WooCommerce.
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 5ac7528b465..a41c4e3b44a 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -31665,12 +31665,6 @@ parameters:
count: 4
path: includes/shortcodes/class-wc-shortcode-my-account.php
- -
- message: '#^Function wc_add_notice invoked with 4 parameters, 1\-3 required\.$#'
- identifier: arguments.count
- count: 1
- path: includes/shortcodes/class-wc-shortcode-my-account.php
-
-
message: '#^Method WC_Shortcode_My_Account\:\:add_payment_method\(\) has no return type specified\.$#'
identifier: missingType.return