Repository navigation
CampTix: "Unknown Attendee" tickets show up on the public attendees page #2114
Description
Describe the bug
Unclaimed tickets, where the buyer ticked "I don't know who will use this ticket yet", can show up on the public [camptix_attendees] page as Unknown Attendee. One place this is visible right now is https://asia.wordcamp.org/2027/attendees/.
CampTix already tries to keep unclaimed tickets off this page, so these rows shouldn't appear. The filter that does it checks a different field from the one the other unknown-attendee check uses, and some records slip through.
Why it happens
"Unknown Attendee" is placeholder data. add_unknown_attendee_info_stubs() writes it into the attendee's meta at checkout (require-login.php:591, values from get_unknown_attendee_info() at L727):
tix_first_name=Unknown,tix_last_name=Attendeetix_email=unknown.attendee@example.org(UNKNOWN_ATTENDEE_EMAIL)
The Require Login addon has two checks that should exclude these records, and each one tests a different field:
| Check | What it tests |
|---|---|
hide_unconfirmed_attendees() (L916): public attendees list |
tix_username != '[[ unconfirmed ]]' (or tix_username NOT EXISTS) |
prevent_unknown_attendees_viewing_private_content() (L945): private content |
tix_email != UNKNOWN_ATTENDEE_EMAIL |
The public-list check assumes that an unclaimed ticket always has the [[ unconfirmed ]] username. That isn't guaranteed:
add_username_to_attendee_object()(L337) gives row 1 (the buyer) their realuser_loginand gives every other row the sentinel.add_unknown_attendee_info_stubs()has no row check. It adds the placeholder data to any row that hasunknown_attendeeset.- The only thing stopping row 1 from being marked unknown is that the checkbox is hidden on that row (
inject_unknown_attendee_checkbox()/current_row_is_buyer(), L523/L565). The server never checks this. Since Require Login: When landing on the tickets screen, allow the buyer to mark additional tickets as "unknown attendee". #1480, the checkbox is also shown on row 1 whenever$form_data['tix_tickets_selected']is empty. For example, a GET landing throughtix_single_ticket_purchasefills in$_REQUESTbut not$_POST/form_data(camptix.php:4196-4202).
So a record with the placeholder name and email plus a real username gets blocked from private content, which is correct. The public list still shows it, which is the bug.
Steps to reproduce
- Create a published
tix_attendeepost with:tix_first_name=Unknown,tix_last_name=Attendeetix_email=unknown.attendee@example.orgtix_username= any real username
- Render a page with
[camptix_attendees]after clearing thecamptix-attendees-*transients. - Unknown Attendee appears in the list.
Organisers can check which case affects their site: in CampTix → Attendees → Export, the Username column for the "Unknown Attendee" rows will show a real login.
Expected behaviour
Unclaimed tickets never show on the public attendees list, whatever their tix_username holds. This matches how the private-content check already treats them.
Proposed fix
1. Display fix (small, also hides existing records). In hide_unconfirmed_attendees(), also exclude the placeholder email, the same way the private-content check does:
$query_args['meta_query'][] = array(
'key' => 'tix_email',
'value' => self::UNKNOWN_ATTENDEE_EMAIL,
'compare' => '!=',
);It reuses an existing constant and needs no data migration. It also hides the rows already showing on live sites. Named-but-unconfirmed attendees are unaffected.
2. Checkout fix (optional follow-up, only affects new purchases). Make the stored data consistent, so that an unknown attendee always gets the sentinel username, even on row 1:
if ( 1 === $attendee_order && self::UNKNOWN_ATTENDEE_EMAIL !== $attendee->email ) {
$attendee->username = wp_get_current_user()->user_login;
} else {
$attendee->username = self::UNCONFIRMED_USERNAME;
}This also covers the missing server-side check. It changes behaviour that #1480 deliberately widened, so I'd like @dd32's view before doing it. I'm happy to keep it out of the display-fix PR.
Notes
- Cache:
get_attendees_shortcode_content()caches the rendered list for 3 hours (1 day on archived sites). The cache key (generate_attendees_cache_key()) hashes only the shortcode attributes, so sites keep showing the rows until thecamptix-attendees-*transients expire or are flushed after deploy. - Tests:
require-login.phphas no test coverage at the moment. The PR would add a regression test based on the repro above. - Related: CampTix: Help buyers recover claim links and clarify Unconfirmed/Unknown labels #1721 (clarifying the Unconfirmed/Unknown labels; it covers the labels only, not this leak), Require Login: When landing on the tickets screen, allow the buyer to mark additional tickets as "unknown attendee". #1480.
I can open a PR for the display fix.
Activity
CampTix: hide unknown attendees from the public attendees list (#2117)
mcliwanow commentedon Sep 24, 2026
#2117 is deployed, but https://asia.wordcamp.org/2027/attendees/ still lists two "Unknown Attendee" entries.
Asia 2027 has 14 attendees named Unknown Attendee. 11 of them have the placeholder email and the
[[ unconfirmed ]]username, so the username check was already hiding them before #2117. The other 3 are the buyer's own row, with the buyer's real email and username, and neither check catches that. The newest one was created today.The email is real because
add_unknown_attendee_info_stubs()fills each field separately: the name only when it's empty, the email only when it isn't a valid address. Row 1 comes pre-filled with the buyer's email byprepopulate_known_fields(), and the name is often empty because many profiles don't have one. So the buyer ends up with the stub name and their own email.I think we need option 2 after all, but it also has to handle the email: when
unknown_attendeeis set, store the placeholder email and the sentinel username whatever was pre-filled, or rejectunknown_attendeeon the buyer's row on the server. Existing rows will need a one-off fix as well.CampTix: store an unknown attendee on the buyer's row as unknown (#2149)
bor0 commentedon Oct 8, 2026
#2149 is deployed. It fixes new purchases: ticking "I don't know who will use this ticket yet" on the buyer's own row now stores the ticket as an unknown attendee, and the buyer also gets the claim link to forward. Here's what I did about the tickets created before that.
1. Swept every camp (read-only)
I ran the script below with WP-CLI against production, once, for the whole network. For each site with CampTix tickets, it counts the tickets that have the "Unknown Attendee" name next to a real email and a real username. It matches the English name and the site's own translation of it. It writes nothing and prints no personal data.
sweep-2114.phpIt scanned 1,266 sites and found 38 tickets on 22 camps. The oldest was from May 2025 and the newest from 7 October, before the deploy. Asia 2027 had the 3 mcliwanow found, and none have been added since.
2. Repaired them with the script from #2149
For each camp I ran the repair script from the #2149 description in list mode first, checked the count matched the sweep, then ran it with
convert:convertstores each ticket the way checkout now does: the placeholder email,[[ unconfirmed ]]as the username, and the buyer's username intix_buyer_username. The ticket leaves the public attendees list, e-mails about it still go to the buyer, and whoever ends up using it can claim it. Each change is in the CampTix log on the attendee.The three events.wordpress.org camps needed one extra step. Under WP-CLI,
--url=events.wordpress.org/<city>/<year>/<type>/silently resolves to the root events site, becausesunrise-events.php(which matches those multi-level paths) skips itself on the CLI. The first run there did nothing. I forced the lookup with a--requirefile:3. Swept again
The same sweep now finds 1 ticket. 37 of 38 are repaired.
The one left is on us.wordcamp.org/2026. CampTix isn't active on that site anymore, so the repair script can't load there, and the attendees page doesn't render the list either. I left it alone.
Notes
convertdoesn't remove the "attending" entry the participation notifier added to the buyer's profile at purchase.Closing this.