Opened 5 years ago
Last modified 3 weeks ago
#52976 new defect (bug)
user emails comparison should be case insensitive
| Reported by: | asaifm | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Users | Version: | 4.3 |
| Severity: | normal | Keywords: | good-first-bug has-patch has-screenshots has-unit-tests has-testing-info |
| Cc: | Focuses: | administration, coding-standards |
Description (last modified by )
In user.php for WordPress 5.7, email update comparisons are case sensitive. Is there a specific reason for this? Because emails are case insensitive. Here is the line that does that:
if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] )
Can the function:
strcasecmp
be used instead? The problem is that there is a plugin that uses the function:
wp_update_user
And it would send a notification for email change even if it was the casing of the characters only.
Thanks for your time and consideration
Attachments (4)
Change History (32)
#2
in reply to: ↑ 1
@
5 years ago
Hello @dd32,
Thanks for your response. The plugin that uses
wp_update_userdoes not alter the casing. It simply passes the email address to it and does not perform any checks on the similarity of the old and new addresses.
Ok, I have seen your note in https://tools.ietf.org/html/rfc5321 now. I am not sure how to take this further, so feel free to close the ticket if you prefer to stick to the RFC.
#3
@
5 years ago
- Description modified (diff)
- Version 5.7 → 4.3
Related: #32158
strcasecmp was used for the wp_insert_user function (but not wp_update_user)
#4
@
5 years ago
- Keywords needs-patch good-first-bug added
Just noting the locations I could find that use != for email comparisons:
wp-includes/user.php: if ( $user_email !== $old_user_data->user_email || $user_pass !== $old_user_data->user_pass ) {
wp-includes/user.php: if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] ) {
wp-admin/user-edit.php: if ( $new_email && $new_email['newemail'] != $current_user->user_email && $profileuser->ID == $current_user->ID ) :
wp-admin/index.php: $hide = ( 0 === $option || ( 2 === $option && wp_get_current_user()->user_email !== get_option( 'admin_email' ) ) );
wp-admin/includes/class-wp-screen.php: if ( 2 === $welcome_checked && wp_get_current_user()->user_email !== get_option( 'admin_email' ) ) {
wp-admin/options-general.php:if ( $new_admin_email && get_option( 'admin_email' ) !== $new_admin_email ) :
And for completeness, those which use strcasecmp():
wp-includes/capabilities.php: if ( $user && 0 !== strcasecmp( $user->user_email, get_site_option( 'admin_email' ) ) ) {
wp-includes/pluggable.php: if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) {
wp-includes/pluggable.php: if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) {
wp-includes/user.php: if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) )
wp-admin/user-edit.php: <?php if ( 0 !== strcasecmp( $profileuser->user_email, get_site_option( 'admin_email' ) ) || ! is_super_admin( $profileuser->ID ) ) : ?>
#5
@
5 years ago
Hi, I found some other locations that use !== and === for emails comparisons.
wp-admin\includes\class-wp-automatic-updater.php:
if ( $notified && get_site_option( 'admin_email' ) === $notified['email'] && $notified['version'] == $item->current ) {
wp-admin\includes\class-wp-automatic-updater.php:
if ( $n && 'fail' === $n['type'] && get_site_option( 'admin_email' ) === $n['email'] && $n['version'] == $core_update->current ) {
wp-admin\includes\misc.php:
if ( get_option( 'admin_email' ) === $value || ! is_email( $value ) ) {
wp-admin\network\settings.php:
if ( $new_admin_email && get_site_option( 'admin_email' ) !== $new_admin_email ) :
wp-includes\ms-functions.php:
if ( get_site_option( 'admin_email' ) === $value || ! is_email( $value ) ) {
Because I'm new to contributing to WordPress, should I create a patch that uses strcasecmp() for email comparisons, or should I wait until it's clear that we need case insensitive email comparison?
#7
@
4 years ago
Hello,
I've updated the initial patch because it wasn't compatible with the latest WP version
This ticket was mentioned in PR #4751 on WordPress/wordpress-develop by @adamsilverstein.
3 years ago
#9
Trac ticket: https://core.trac.wordpress.org/ticket/52976
This ticket was mentioned in PR #9196 on WordPress/wordpress-develop by PANawkar.
14 months ago
#10
Trac ticket:
#11
@
8 months ago
I reviewed PR #9196 and the approach using strcasecmp() for case-insensitive email comparisons looks correct. This aligns with how other parts of WordPress Core already handle email comparisons (as noted in comment 4 by @dd32).
The fix properly addresses the issue where users receive unnecessary "email changed" notifications when only the letter casing differs (e.g., User@… vs user@…).
While RFC 5321 technically allows case-sensitive local parts, virtually all mail servers treat emails as case-insensitive. This change improves user experience without breaking compatibility.
+1 for committing this patch. The PR has been open for 7 months and would benefit from a review from a committer.
@showravhasan commented on PR #9196:
8 months ago
#12
Reviewed the code changes. The approach using strcasecmp() for case-insensitive email comparisons is correct and consistent with how other parts of WordPress Core already handle email comparisons.
LGTM 👍
Left a comment on the Trac ticket as well: https://core.trac.wordpress.org/ticket/52976
#13
@
8 months ago
- Keywords has-screenshots added; needs-testing removed
Reproduction Report
Description
This report validates whether the issue can be reproduced.
Environment
- WordPress: 7.0-alpha-61500
- PHP: 8.3.30
- Server: PHP.wasm
- Database: WP_SQLite_Driver (Server: 8.0.38 / Client: 3.51.0)
- Browser: Chrome 143.0.0.0
- OS: macOS
- Theme: Twenty Twenty-One 2.7
- MU Plugins: None activated
- Plugins:
- Test Reports 1.2.1
Steps to Reproduce
Use playground:
https://playground.wordpress.net/?php=8.3&wp=trunk&networking=yes
Method1 - Manually
- WordPress dashboard > Users > View your user email (e.g. admin@…)
- Click username and change email to ADMIN@…
- Profile updates but no notification is triggered. Email is visible with the uppercase version.
- Change it to a different email such as user@… and notification is triggered.
Method2 - Programmatically
- Install any classic theme
- Add the following code to functions.php
// Get a test user
$user_id = 1;
$user = get_userdata($user_id);
$original_email = $user->user_email; // e.g., "admin@localhost.com"
// Update with same email but different case
$result = wp_update_user([
'ID' => $user_id,
'user_email' => strtoupper($original_email) // "ADMIN@LOCALHOST.COM"
]);
- Visit Users page and check the user email.
- Email is updated but no notification is triggered.
Actual Results
- ❌ Email notification is not triggered with the uppercase version.
Supplemental Artifacts
Method1 - Manually:
Users screen after manually updating the email to uppercase version (notification NOT triggered)

Users screen after manually updating the email to a different email (notification triggered)

Method2 - Programmatically:
Users screen after changing email programmatically (notification NOT triggered)

@
5 months ago
Refreshed patch for current trunk. Uses strcasecmp() for case-insensitive email comparison in both the activation key reset (line 2550) and email change notification (line 2766). 2 unit tests. All 124 user tests pass.
#16
@
5 months ago
Hi @motylanogha, I tried to reproduce the bug against the trunk again, but similarly to my previous bug reproduction test, changing lowercase to uppercase email did not trigger the notification. Am I missing something?
#17
@
5 months ago
Hi @ozgursar,
What you observed is the expected, buggy behaviour on the current trunk, and that's exactly the issue this ticket aims to fix.
Right now, on trunk, the comparison at line 2766 uses strict !==, which treats admin@… and ADMIN@… as different strings. However, the way the code flows works, the email gets silently updated without properly triggering the confirmation/notification email. So the user ends up with a changed email, and no confirmation was ever sent, which is a problem.
The patch in 52976.2.diff fixes this by using strcasecmp() so that case-only differences are treated as "same email"; no update, no notification needed. This is the correct behaviour since email addresses are case-insensitive per RFC 5321, and virtually all mail servers treat them that way.
To verify the fix works, you'd need to apply the patch to your local trunk checkout and then confirm that:
- Changing admin@… -> ADMIN@… is treated as no change (no notification, no update).
- Changing admin@… -> different@… still properly triggers the confirmation email.
The 2 unit tests included in the patch cover both scenarios.
#18
@
5 months ago
- Keywords reporter-feedback removed
Thanks for the clarification @motylanogha . I'll do the patch testing accordingly.
#19
@
5 months ago
Patch Testing Report
Patch Tested: https://core.trac.wordpress.org/attachment/ticket/52976/52976.2.diff
Environment
- WordPress: 7.1-alpha-20260406.185701
- PHP: 8.3.30
- Server: PHP.wasm
- Database: WP_SQLite_Driver (Server: 8.0.38 / Client: 3.51.0)
- Browser: Chrome 146.0.0.0
- OS: macOS
- Theme: Twenty Fourteen 4.4
- MU Plugins: None activated
- Plugins:
- Email Logger
- Test Reports 1.2.1
Steps taken
Install Email Logger plugin from https://make.wordpress.org/test/files/2026/02/wp-email-logger.zip
Method1 - Manually
- Change your user's email from admin@… to ADMIN@…
- Observe email notification that contains the email change
- Apply patch and repeat the steps 1-2
- Observe email notification is not triggered
- ✅ Patch is solving the problem
Method2 - Programmatically
- Install any classic theme
- Add the following code to your active theme's
functions.php
$user_id = 1; // ID of the user you are testing
$user = get_userdata($user_id);
$original_email = $user->user_email; // e.g., "admin@localhost.com"
$result = wp_update_user([
'ID' => $user_id,
'user_email' => strtoupper($original_email) // "ADMIN@LOCALHOST.COM"
]);
- Visit Users page and observe the user's email
- Check email logs and observe the email change notification sent
- Apply patch and repeat the steps with a new user
- Observe that no notification email sent even though email changes.
- ✅ Patch is solving the problem
In both Method 1 and Method 2, changing the email in any way other than capitalization successfully triggers the email change notification.
Expected result
- We expect no notifications to be triggered if only the letter case changes.
Additional Notes
- Other locations that use
!==and===for emails comparisons should also be checked.
#20
@
5 months ago
- Focuses administration coding-standards added
Tested the patch: https://core.trac.wordpress.org/attachment/ticket/52976/52976.2.diff
Environment:
- WordPress: 7.0-RC2
- PHP: 8.3.30
- Browser: Chrome
- Database: MySQL 8.4.8
- OS: Ubuntu
Testing Method:
- Update the user's email from user@… to different case variations such as:
- USER@…
- User@…
- User@…
- Check if an email notification is triggered for this change
- Apply the patch
- Repeat the same steps with the above variations
- Verify that no email notification is triggered after applying the patch
Also tested programmatically using the wp_update_user() function, and it behaves as expected.
Result:
This confirms that the patch resolves the issue. If the update only modifies the letter case, it should not trigger any notification.
This ticket was mentioned in PR #11697 on WordPress/wordpress-develop by @motylanogha.
4 months ago
#21
Email addresses are case-insensitive in their domain part (per RFC 5321) and treated as such by virtually all mail providers in the local part too. Previously, comparing user_email with !== could:
- Reset
user_activation_keyeven when only the case of an email changed - Trigger the email-change notification for a case-only "change"
This PR replaces those strict comparisons with strcasecmp().
Tests cover:
- Case-only email change does not fire
send_email_change_email - Genuinely different email change still fires the notification
- Case-only email change does not clear
user_activation_key
Complements GH-9196 with proper PHPUnit coverage. Narrower scope than GH-9196 (focused on user.php only); happy to expand to admin/multisite call sites in a follow-up if reviewers prefer one bundled change.
#22
@
4 months ago
Tested PR https://github.com/WordPress/wordpress-develop/pull/11697
I created a user with testcase@example.com, set user_activation_key to key, and then updated the email to TestCase@Example.COM.
Before the patch, the send_email_change_email flow fired for this case-only change and the activation key was cleared.
After the patch, the notification flow does not fire and the activation key remains key.
Patch tests well for the case-only email change scenario.
@orionaselite commented on PR #11697:
3 months ago
#23
Tested PR #11697 locally against current trunk.
Environment:
- WordPress develop at 7.1-alpha-src
- PHP 8.3.31 in the Docker local environment
- MySQL 9.7 Docker service
- PHPUnit 9.6.34
Results:
- Targeted regression tests added in this PR pass:
- test_case_only_email_change_does_not_trigger_email_change_notification
- test_real_email_change_triggers_email_change_notification
- test_case_only_email_change_does_not_clear_user_activation_key
- Full user test file passes: npm run test:php -- tests/phpunit/tests/user.php
- 125 tests, 419 assertions, 1 skipped
- PHPCS on src/wp-includes/user.php and tests/phpunit/tests/user.php reports no errors. It does report existing prepared-SQL warnings in unrelated parts of src/wp-includes/user.php.
The PR behavior looks correct to me: case-only email changes do not trigger the email-change notification or clear the activation key, while genuinely different email changes still trigger the notification.
#24
@
3 months ago
PR #11697 was independently tested by @GeorgeWebDevCy against current trunk (7.1-alpha): the targeted regression tests and the full user.php suite pass (125 tests, 419 assertions), with PHPCS clean. It still applies cleanly and CI is green. Requesting a review for 7.1 consideration.
@motylanogha commented on PR #11697:
6 weeks ago
#25
@GeorgeWebDevCy thank you for the test report, and sorry it sat unanswered for so long — that's on me, not on the quality of the report. Reproducing the three regression tests, running the full user.php file and checking PHPCS is exactly what this needed, and it's more than most patches at this stage get.
For anyone arriving later, the substance of what was verified on 2026-06-01, against 7.1-alpha-src on PHP 8.3 / MySQL 9.7:
test_case_only_email_change_does_not_trigger_email_change_notificationtest_real_email_change_triggers_email_change_notificationtest_case_only_email_change_does_not_clear_user_activation_key- full
tests/phpunit/tests/user.php: 125 tests, 419 assertions, 1 skipped - PHPCS clean on
src/wp-includes/
I'll carry this over to #52976 so the testing is recorded on the ticket rather than only here, since that's where a committer will look.
#26
@
6 weeks ago
- Keywords has-testing-info added
Recording testing that happened on the PR but never made it onto this ticket.
@GeorgeWebDevCy tested PR #11697 on 2026-06-01 against 7.1-alpha-src (PHP 8.3.31, MySQL 9.7, PHPUnit 9.6.34):
- The three regression tests added in the PR pass:
- test_case_only_email_change_does_not_trigger_email_change_notification
- test_real_email_change_triggers_email_change_notification
- test_case_only_email_change_does_not_clear_user_activation_key
- Full tests/phpunit/tests/user.php passes: 125 tests, 419 assertions, 1 skipped.
- PHPCS clean on src/wp-includes/.
Adding has-testing-info so this is visible to a committer scanning the ticket rather than only in the PR thread. Credit for the testing is @GeorgeWebDevCy's.
The patch itself is unchanged since May and still applies; the ticket is only waiting on review.
@orionaselite commented on PR #11697:
6 weeks ago
#27
@GeorgeWebDevCy thank you for the test report, and sorry it sat unanswered for so long — that's on me, not on the quality of the report. Reproducing the three regression tests, running the full
user.phpfile and checking PHPCS is exactly what this needed, and it's more than most patches at this stage get.
For anyone arriving later, the substance of what was verified on 2026-06-01, against
7.1-alpha-srcon PHP 8.3 / MySQL 9.7:
test_case_only_email_change_does_not_trigger_email_change_notificationtest_real_email_change_triggers_email_change_notificationtest_case_only_email_change_does_not_clear_user_activation_key- full
tests/phpunit/tests/user.php: 125 tests, 419 assertions, 1 skipped- PHPCS clean on
src/wp-includes/I'll carry this over to #52976 so the testing is recorded on the ticket rather than only here, since that's where a committer will look.
@wppoland don't worry about it I understand. I have only recently started contributing after being at WCEU 2026 contributor day. I am teaching myself how to contribute. Not sure if I am following SOP. I am busy with freelancing but we have some free time I just jump on and for something to work on so don't worry about it:)
#28
@
3 weeks ago
Tested this using a reproduction script based on the ticket's own unit test: created a user, then called wp_update_user() changing only the case of their email (e.g. testcase@example.com → TESTCASE@EXAMPLE.COM), checking whether the send_email_change_email filter fired.
Without the patch:
Case-only email change fired notification: YES (bug present)
Confirms the bug: WordPress treats a case-only email change as a real change and sends an unnecessary "your email was changed" notification.
With the patch applied (strcasecmp() replacing the !== comparison in wp_update_user()):
Case-only email change fired notification: NO (working correctly)
The patch works as intended — case-only changes no longer trigger the notification.
I also reviewed the included unit tests (test_case_only_email_change_does_not_trigger_email_change_notification and test_real_email_change_triggers_email_change_notification) — they correctly cover both the case-only scenario and a genuine email change, confirming the fix doesn't suppress legitimate change notifications.
Environment: WordPress trunk (wordpress-develop local env), PHP 8.3.33, Windows.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)


Hi @asaifm and welcome to Trac!
Updating this to use lower-case comparisons (Just throw it through strtolower() IMHO) seems reasonable, however.. a plugin altering the user email address to lower case does seem unexpected and potentially a bigger bug than this is.
Just as a note,
user@example.comandUSER@example.comcould technically be different users, as the email standard delegates that to the mail servers, however in reality no mail servers that I'm aware of have case sensitive handling..