Name people by their name, not their email address
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / Tests (PHP 8.1) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 2m51s
CI / PHPStan (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / No Debug Code (pull_request) Successful in 3s
CI / Build Plugin Zip (pull_request) Skipped

Anywhere the plugin named a person it could show their email instead —
"Managed by [email protected]" in the students table, the same under
Booked by, instructor names on the class pages.

WordPress defaults a new account's `nickname` to its `user_login`, and
signup uses the email address as the login. So every self-registered
account carried its own address as its nickname, and UserName::format()
fell straight through to it. The name they typed was in `display_name`
all along. Accounts created by a guardian were never affected —
GuardianService::createChild() sets `nickname` outright, which is exactly
why children read correctly and their parents did not.

UserName::format() now walks nickname then display name, skipping either
when it is really the login or the email, so existing accounts read
correctly with nothing to migrate. An identifier still never reaches the
screen: an account with nothing but its address on file falls back to the
id, as before. Signup also sets `nickname` at insert, so new accounts are
right at the source rather than relying on the fallback.

Tests: composer test (866), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-07-30 12:24:04 -03:00
co-authored by Claude Opus 5
parent 122f7a0f53
commit 8fd7bf983d
4 changed files with 111 additions and 14 deletions
+10 -1
View File
@@ -377,12 +377,21 @@ class RegistrationPage {
return esc_html__( 'An account already exists for this email.', 'unsupervised-schedular' );
}
// Nickname as well as display name. WordPress defaults nickname to
// `user_login`, which here is the email address — so without this the
// account's own address became its nickname, and every screen that names
// a person through `UserName` showed the address instead of the name they
// had just typed. `UserName` copes with the accounts already created that
// way; this stops any more of them.
$name = '' !== $displayName ? $displayName : $email;
$userId = wp_insert_user(
[
'user_login' => $email,
'user_email' => $email,
'user_pass' => $password,
'display_name' => '' !== $displayName ? $displayName : $email,
'display_name' => $name,
'nickname' => $name,
'role' => $inviteValid ? $invite->role : RoleManager::STUDENT,
]
);
+32 -8
View File
@@ -5,15 +5,25 @@ namespace Unsupervised\Schedular\Auth;
/**
* Resolves a person's public-facing name for display. Prefers their real name
* (first + last), then their nickname — deliberately avoiding the account's
* login/username, which `display_name` can otherwise expose.
* (first + last), then their nickname, then the display name — skipping any of
* them that is really the account's login or email address, which is the thing
* this class exists to keep off the screen.
*/
class UserName {
/**
* The display name for a user: "First Last" when a real name is set,
* otherwise the WordPress nickname. Falls back to the numeric id (or an empty
* string when none is given) when the user cannot be loaded or has no name.
* The display name for a user: "First Last" when a real name is set, else the
* first of nickname / display name that is an actual name. Falls back to the
* numeric id (or an empty string when none is given) when the user cannot be
* loaded or has nothing but identifiers on file.
*
* Display name is consulted at all because WordPress defaults **nickname** to
* `user_login`, and signup uses the email address as the login — so a
* self-registered account carries its own email as its nickname, and every
* screen naming that person showed the address instead. The name they typed
* was on file the whole time, in `display_name`. (Accounts created by a
* guardian never hit this: `GuardianService::createChild()` sets `nickname`
* outright, which is why children read correctly and their parents did not.)
*/
public static function format( ?\WP_User $user, int $fallbackId = 0 ): string {
if ( ! $user instanceof \WP_User ) {
@@ -25,11 +35,25 @@ class UserName {
return $full;
}
$nickname = trim( $user->nickname );
if ( '' !== $nickname ) {
return $nickname;
foreach ( [ $user->nickname, $user->display_name ] as $candidate ) {
$candidate = trim( (string) $candidate );
if ( '' !== $candidate && ! self::isIdentifier( $candidate, $user ) ) {
return $candidate;
}
}
return $fallbackId > 0 ? (string) $fallbackId : '';
}
/**
* Whether a candidate name is really the account's login or email address
* wearing a name's clothing — the case this class must never pass through.
*/
private static function isIdentifier( string $candidate, \WP_User $user ): bool {
$candidate = strtolower( $candidate );
return strtolower( (string) $user->user_login ) === $candidate
|| strtolower( (string) $user->user_email ) === $candidate;
}
}