Commit 9c79cccc99f for woocommerce

commit 9c79cccc99fba1489a103881b37e64d5cc72b0ce
Author: Ricardo Sawir <37329575+sawirricardo@users.noreply.github.com>
Date:   Fri Jul 3 01:44:59 2026 +0700

    Fix duplicate "Password Changed" admin email on customer password reset (#66145)

    * Fix duplicate Password Changed admin email on customer password reset

    WC_Shortcode_My_Account::reset_password() added an after_password_reset
    action in 10.9.0 (#64380). WordPress core hooks wp_password_change_notification()
    onto that action, so it fired alongside the pre-existing direct call, sending
    the admin two identical notifications.

    Detach core's handler around the do_action() and restore it, leaving
    WooCommerce's own filter-guarded direct call as the single notification source.

    Bug introduced in PR #64380.

    * Restore core notification hook in finally block

    Ensures wp_password_change_notification is re-attached even if a third-party
    after_password_reset listener throws.

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Delete plugins/woocommerce/changelog/66145-fix-66103-duplicate-password-changed-email

    * Remove "Comment:" from changelog

diff --git a/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email b/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email
new file mode 100644
index 00000000000..7b6de66be48
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix duplicate "Password Changed" admin notification email sent when a customer resets their password.
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 4dbb81a8d56..c5dda9a9102 100644
--- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
+++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
@@ -387,16 +387,31 @@ class WC_Shortcode_My_Account {
 		// 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.
-		 *
-		 * This provides parity with WordPress core's reset_password() function.
-		 *
-		 * @since 10.9.0
-		 * @param WP_User $user     The user.
-		 * @param string  $new_pass New user password in plaintext.
-		 */
-		do_action( 'after_password_reset', $user, $new_pass );
+		// WordPress core hooks wp_password_change_notification() onto after_password_reset. Detach it around
+		// the action so it doesn't duplicate the notification WooCommerce sends directly below (guarded by the
+		// woocommerce_disable_password_change_notification filter), then restore it to its original priority.
+		$core_notification_priority = has_action( 'after_password_reset', 'wp_password_change_notification' );
+		if ( false !== $core_notification_priority ) {
+			remove_action( 'after_password_reset', 'wp_password_change_notification', $core_notification_priority );
+		}
+
+		try {
+			/**
+			 * Fires after the user's password has been reset via WooCommerce.
+			 *
+			 * This provides parity with WordPress core's reset_password() function.
+			 *
+			 * @since 10.9.0
+			 * @param WP_User $user     The user.
+			 * @param string  $new_pass New user password in plaintext.
+			 */
+			do_action( 'after_password_reset', $user, $new_pass );
+		} finally {
+			if ( false !== $core_notification_priority ) {
+				add_action( 'after_password_reset', 'wp_password_change_notification', $core_notification_priority );
+			}
+		}
+
 		self::set_reset_password_cookie();
 		wc_set_customer_auth_cookie( $user->ID );