Refactor: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search - #477
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds SearchService::RankedPlayers() with concentric-ring ranking (park->kingdom->global), abbreviation-prefix support via resolveAbbrevPrefix(), and SOAP registration for the session-less Search/Players action. 12-assertion curl ranking test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… SearchService::RankedPlayers Adds Controller_SearchAjax::players() as a same-origin, session-gated endpoint that delegates entirely to the existing SearchService::RankedPlayers() core — no SQL duplication. Returns [] for unauthenticated callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k 3) New JS + CSS files implementing the one canonical player-search dropdown used by all search surfaces. Custom dropdown (no jQuery UI), position:fixed for modal safety, dark-mode via html[data-theme="dark"], &q= URL param, 220 ms debounce, keyboard navigation, and onSelect callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add CSS and JS includes for the new OrkPlayerSearch component to the master theme file, making it available app-wide. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ilot) Replaces both jQuery UI .autocomplete() player-search blocks with OrkPlayerSearch.attach(); removes unused preloadedGivenByOfficers array. GivenBy is global (cross-kingdom, includeInactive+Suspended); GivenTo is kingdom-centered, active-only. Establishes the conversion recipe for the rollout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nent Replace jQuery UI autocomplete on #PlayerName in Attendance_kingdom, Attendance_park, and Attendance_event with OrkPlayerSearch.attach — single ranked server-side call (park→kingdom→global), no more 3-parallel-request hack in Attendance_park. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace three bespoke autocomplete implementations (edit-suspension "Suspended By", suspend-player "Player", suspend-player "Suspended By") with OrkPlayerSearch.attach. Park-scoped player search re-attaches on park dropdown change to preserve park filtering. Removes ~120 lines of duplicated fetch/render/keynav wiring and the three bespoke result divs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ponent Convert #GivenBy (Admin_player), #PlayerName (Admin_suspendplayer, Admin_banplayer, Admin_moveplayer) and #PlayerName + #CreatePlayerName (Admin_manageevent) from jQuery UI .autocomplete() to OrkPlayerSearch.attach(). Uses ranked SearchAjax/players route; PenaltyBox radio logic updated to use p.Suspended from RankedPlayers response. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h (superset of parallel + concurrent work) Converts 8 clean-path Xnew award/recommendation searches to OrkPlayerSearch.attach (pn-award-givenby, pn-edit-givenby, kn-award-player/givenby/rec, pk-award-player/givenby/rec), preserving officer-chip clearing and awardranks-on-select. Move/merge/event/reconcile deferred to the exception register. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dditive rules) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Additive, backward-compatible: excludeIds (array or per-search fn); setOpts()/reattach() for live re-scoping (move-player cascades); preload list on empty-focus. Existing attach() unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…:RankedPlayers KingdomAjax/ParkAjax/EventAjax/AdminAjax playersearch + SearchAjax::universal player branch delegate to the shared ranking core (consistent park->kingdom->everywhere). Adds excludeKingdomId/excludeParkId for scope=exclude. Response shapes preserved; Search/Player SOAP untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Converts 5 bespoke exception surfaces in revised.js to the standard OrkPlayerSearch component, adding excludeKingdomId/excludeParkId support to SearchAjax/players and the OrkPlayerSearch component along the way. - kn-moveplayer: attach+reattach on mode change; 'in'→excludeKingdomId, 'within'/'out'→restrictTo:'kingdom' - pk-moveplayer: attach+reattach on mode change; 'in'→excludeParkId, 'out'→restrictTo:'park' - kn-merge (keep+remove): attach with cross-field excludeIds fn - pk-merge (keep+remove): attach with restrictTo:'park' + cross-field excludeIds fn - ev-PlayerName: attach with parkId+kingdomId ranking + dynamic excludeIds from attended rows; includeInactive+includeSuspended preserved Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h component Replaces legacy cpAc()+cpSearchPlayersGlobal() calls in Admin_index.tpl with OrkPlayerSearch.attach() for the Move Player and Merge Players modals. Merge Players uses excludeIds lambdas so each field suppresses the player already chosen in the other field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omponent
Replace both jQuery UI autocomplete calls in Admin_unit.tpl with
OrkPlayerSearch.attach — global, unscoped (no parkId/kingdomId/restrictTo),
includeInactive:true per the documented cross-kingdom/cross-park exception.
Also removes the stale dead `park_id = $('#ParkId').val()` assignment that
was never passed to the old AJAX call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… search to OrkPlayerSearch Replaces the hand-rolled kn-ac fetch/render/keynav wiring in both permission templates with OrkPlayerSearch.attach, using appropriate scope: kingdom-scoped or park-scoped for the scoped page (determined at runtime from $type), and global (no restrictTo) for the global admin page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…th cross-field excludeIds Replaces both #FromPlayerName and #ToPlayerName jQuery UI .autocomplete() calls with OrkPlayerSearch.attach(). Each field passes excludeIds: function() returning the other field's currently-selected MundaneId, preventing a player from being merged into themselves. Global scope (no center/restrictTo), includeInactive: true. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nent Replace bespoke acSearch-based givenby autocomplete in wireRow() with OrkPlayerSearch.attach(), keyed to RcConfig.kingdomId for ranking with global scope (no restrictTo), includeInactive+includeSuspended for historical attribution. Location search left untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…yerSearch Last straggler jQuery-UI player autocomplete (#Suspendator), kingdom-scoped to the recording officer's kingdom. Completes the player-search standardization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Clear, a11y) + backend fixes Component: AbortController cancels stale in-flight searches; module-level shared click/scroll listeners (no per-attach leak); new onClear callback fired on edit so callers clear stale hidden ids; live uir read; ArrowUp guard; dark-mode badge contrast. Backend: AdminAjax/global passes session token (admins can find restricted-name players); Search/Players SOAP registration gains excludeKingdomId/excludeParkId; Reports_roster sp-player search uses reattach so park-change re-scopes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ale hidden id on re-edit Converted templates dropped the old jQuery-UI change-handler that cleared the hidden MundaneId when a user edited the text after selecting. Wire onClear on every attach/reattach so the hidden id (and submit-enable state) is cleared the moment the user modifies the input, eliminating stale-id form submissions. Also replaces the redundant manual input-event listeners in Admin_permissions.tpl and Admin_permissions_global.tpl with the canonical onClear callback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arch.attach call sites Adds onClear callback to all 15 OrkPlayerSearch.attach calls in revised.js. Without it, editing the player name field after a selection left a stale hidden id, causing the wrong player to be submitted. Award givenby surfaces also deselect officer chips on clear, matching their onSelect behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After selecting a player and editing the text, the hidden id stayed stale. Adds onClear to cp-mp-player-name (Move Player), cp-mgp-keep-name and cp-mgp-remove-name (Merge Players) in Admin_index.tpl, and to the wireRow givenby attach in Playernew_reconcile.tpl, each mirroring its onSelect minus the id-set so the submit guard re-evaluates correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ehind modals) Modal overlays use z-index up to 999999; the .ops-ac-results dropdown was 10000, so inside any modal (Xnew award/rec/move/merge, Admin modals) the ranked results rendered BEHIND the modal and appeared as zero results. Raised to 2147483000. Non-modal pages were unaffected. Found via in-browser visual testing (headless DOM reads saw the items but not the occlusion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(was static)
Conversion baked a static $kid/$pid; the old code read $('#KingdomId').val() live so the
player search followed the kingdom/park selector. Restored: init scope from the live hidden
fields and reattach() on #KingdomName/#ParkName select/change so ranking tracks the chosen
scope. (Attendance_event keeps the event's fixed location.) Found via in-browser testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rs autopopulate; convert Edit Officers search to playersearch
Officer data uses lowercase roles (monarch/regent/prime_minister/gmr) but code matched
title-case ('Monarch'/'Regent'):
- controller.Kingdom/Park: $preloadOfficers built nothing → award 'Given By' quick-buttons
(kingdom/park monarch+regent) never rendered. Now case-insensitive.
- revised.js Edit Officers: officerMap keyed/looked-up by title-case → no autopopulate, Vacate
hidden. Now normalized (normRole) so current holders prefill + Vacate shows.
- Edit Officers per-role search was a MISSED conversion (jQuery UI Search/Player) → now
OrkPlayerSearch (kingdom-scoped), modal style unchanged.
Pre-existing role-case bug; found via in-browser testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nversion as Kingdom Mirror of the Kingdom Edit Officers fix for the park officer editor (normRole autopopulate so current holders prefill + Vacate shows; per-role search converted from jQuery UI to OrkPlayerSearch, kept kingdom-wide per the existing comment). Verified: 5 park holders autopopulate with Vacate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ero monarch/regent display Same lowercase-role mismatch as Kingdom/Park: $preloadOfficers (award Given By quick-chips) on Playernew/Admin/legacy-Award pages never populated, and Kingdomnew/Parknew hero monarch/regent extraction never matched. All officer-role comparisons now case-insensitive; chip Role labels prettified (e.g. 'Kingdom Monarch'). Verified: Playernew add-award shows 4 chips (kingdom + park monarch/regent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83bb92b to
fcb5bdc
Compare
…SQLi fix
Rework SearchService::RankedPlayers into a three-tier model (active → inactive
last-resort → banned) behind every OrkPlayerSearch surface:
- Inactive players always included, ranked after all active (last resort).
- Banned (suspended OR penalty_box) shown ONLY to viewers with CREATE/EDIT
authority relevant to the surface, scoped ONE LEVEL UP: a park surface
surfaces the kingdom's banned players, a kingdom/global surface surfaces
Amtgard-wide. Plain users never see banned.
- restrictTo:'kingdom' is now principality-family-aware (centralized).
- Added restrictKingdomIds (fixes KingdomAjax scope=own silent-drop bug),
excludeIds, offset, and a stable mundane_id paging tiebreak.
- SECURITY: mysql_real_escape_string is a no-op polyfill here — replaced every
player/term LIKE interpolation in RankedPlayers + legacy Player() + the
Event/Kingdom/Park name searches with a real likeEscape().
- Pure-numeric query is a direct mundane_id lookup (paste a copied id).
Endpoint: SearchAjax/players now returns {rows,hasMore,offset} (limit+1 probe)
and accepts the full surface context; the 5 other RankedPlayers callers convert
to the options array. Component: Load more pagination, server-side excludeIds,
context opts, ARIA combobox + live region, distinct Banned/Suspended/Inactive
badges, robustness (abort-on-close, error/loading rows, resize reposition,
blur-close, scrollIntoView, flip-up), and refresh() for scope-toggle re-query.
Adds tests/playersearch/banned_matrix.sh (authenticated full matrix, 15/15).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ fixes Convert every remaining hand-rolled 3.5.x player search to the shared component: - Parknew "Enter Attendance" modal (scope toggle park/kingdom/global via reattach+refresh, already-entered excludeIds, inactive-reactivation confirm and last-class prefill preserved). - Kingdom/Park "Move Player" (mode "in" → excludeKingdomId/excludeParkId, "within"/"out" → restrictTo) and Admin control-panel Move Player (cascade = hard kingdom/park filter). - Unit add member/manager (stays GLOBAL per the documented exception; excludeIds = current members). Surface bug fixes: - Award GivenAt handler no longer wipes the recipient MundaneId. - Reports_roster Edit-Suspension "Suspended By" passes the correct entity (park id on a park-scoped roster, not as kingdomId). - Admin Move Player SrcPark select re-scopes the player search. - Attendance_event re-scopes when the kingdom/park selector changes. - Kingdom merge gains restrictTo:'kingdom' (parity with park merge). Dead code removed: #PlayerSearch jQuery-UI bindings in default.js + orkui.js, and the superseded EventAjax auth/playersearch + AdminAjax global/playersearch action branches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New full-page rich player search (route Search/advanced, opens in a new tab from
the inline component's split "…or Advanced Search" footer, carrying the typed
term + surface scope). Officer/admin tool styled with the Reports look.
- Backend SearchService::AdvancedPlayers(): same tier/auth model as RankedPlayers
plus home + last-attendance (kingdom/park/date) columns and gated real names
(officers see them; a "restricted" player's real name shows only to global
admins). Filters: Active/Inactive toggles, officer-gated Banned (Amtgard-wide),
kingdom→park cascade, optional last-attendance date range. needFilter guard
prevents a full-table scan.
- Page: jQuery DataTable (responsive — no horizontal scrollbar on desktop,
collapses on mobile) with zebra striping, pagination, "Show N rows" /
"Showing X–Y of N", sortable headers; shortened kingdom names
("The Kingdom of the Wetlands" → "Wetlands"); per-row link-out + copy-ID
button (instant CSS tooltips, never native title); dark-mode throughout.
Fixes a theme-wide duplicate-scrollbar (html,body height:100% + overflow-x
forcing body into its own scroll container), page-scoped.
Adds tests/playersearch/chrome-matrix-protocol.md (full in-Chrome test matrix).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overflow-y:auto)
The mobile-nav width clamp set `html, body { overflow-x: hidden }`, which per the
CSS spec forces `overflow-y` to compute as `auto`. Combined with `height: 100%`,
that turned <body> into its own vertical scroll container on top of the window —
a duplicate scrollbar on every tall page.
Switch to `overflow-x: clip`: it still clamps horizontal overflow (preserving the
Android fixed-bar fix) but does NOT establish a scroll container or couple
overflow-y, so the body no longer scrolls and only the viewport's single scrollbar
remains. Verified across Home, Kingdom, and Advanced Search (light + dark).
Removes the now-redundant page-scoped html/body override added to Search_advanced.tpl.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # orkui/controller/controller.Admin.php # orkui/controller/controller.AdminAjax.php # orkui/controller/controller.EventAjax.php # orkui/controller/controller.Kingdom.php # orkui/controller/controller.KingdomAjax.php # orkui/controller/controller.Park.php # orkui/controller/controller.ParkAjax.php # orkui/controller/controller.Player.php # orkui/controller/controller.Search.php # orkui/controller/controller.SearchAjax.php # orkui/template/revised-frontend/script/revised.js # system/lib/ork3/class.SearchService.php
Branch brought current with
|
| File | Resolution |
|---|---|
class.SearchService.php (12 hunks) |
Kept both method sets — no name collisions. Took upstream for Event() draft-status/event_type/banner fields. Kept this branch's likeEscape()/(int)-cast SQLi fixes over upstream's no-op mysql_real_escape_string. Merged upstream's unset($_SESSION['is_authorized_mundane_id']) fix in. Then rewired UniversalSearch()'s player block to delegate to RankedPlayers so header search keeps the tier model. |
controller.SearchAjax.php |
universal() → upstream's Model_Search::universal_search pass-through + 'Token'; kept this branch's new players() and advanced() endpoints verbatim. |
controller.Search.php |
Upstream's $this->Authorization->has_authority + get_unit_activity_counts delegation; kept this branch's advanced() action. |
controller.AdminAjax.php |
Kept the branch's retirement of global/playersearch; took upstream's model-based add_auth + audit and AdminDashboard delegation. |
controller.KingdomAjax.php (23 hunks) |
All upstream, then re-applied this branch's playersearch body (RankedPlayers with family-kingdom restrictKingdomIds for scope=own, excludeKingdomId for scope=exclude). |
controller.ParkAjax.php (11 hunks) |
Same pattern — upstream everywhere, branch's RankedPlayers body restored (restrictTo:'park' / excludeParkId). |
controller.Admin/Player/Kingdom/Park.php |
Git mis-aligned these (branch tab-indented vs upstream PSR-12). Took upstream wholesale, then re-applied the branch's officer role-case fix (strtolower(...) + ucwords(str_replace('_',' ',...))) to every PreloadOfficers block — 6 in Player, 2 each in Admin/Park, 1 in Kingdom. |
revised.js |
Took the branch's OrkPlayerSearch.attach conversion for the Event-attendance field; upstream's side would have reintroduced a hand-rolled multi-fetch kn-ac- dropdown. Verified no orphaned evPn* helpers remain. |
controller.EventAjax.php |
All upstream (EventPlanning refactor, staff/schedule, draft status). |
Feature integrity — independently re-verified post-push
- ✅ Award "Given By" giver stays GLOBAL. All three sites (
pn-/kn-/pk-award-givenby-text) carry norestrictTokey — onlykingdomId/parkIdas ranking centre. EveryrestrictToinrevised.jsbelongs to a Move/Merge Player field, where scoping is correct. - ✅ Unit member add stays GLOBAL —
unAttachPlayerSearchpassesparkId/kingdomIdas ranking centre only, norestrictTo. - ✅ 18
OrkPlayerSearch.attach+ 3reattachsites byte-identical to the branch tip; all 19 template call sites survive;ork-player-search.js/.cssstill loaded globally indefault.theme;Search_advanced.tplintact. - ✅ No jQuery UI autocomplete on any player field. The two remaining
.autocomplete()calls areknWireParkAutocomplete(Park search) and the parent-kingdom search — both pre-existing non-player fields. RankedPlayersoutput is a superset of the legacy row shape (MundaneId/Persona/KAbbr/PAbbr+ tier fields), so surviving legacy callers stay compatible.
Verification: no conflict markers; php -l clean on all 308 changed .php/.tpl/.theme; node --check OK on revised.js and ork-player-search.js; class.Authorization.php unchanged vs branch tip, no bypass. Merge recomputed from its parents with rerere disabled and diffed against what was pushed — exactly the 12 conflicted files, nothing unexplained. (Two other branches in this batch were contaminated by a stray line from an unrelated rerere resolution; this one is clean.)
For a human
⚠️ Upstream'splayersearchendpoints were kept inKingdomAjax,ParkAjax, andEventAjax, against this branch's retirement of them. Upstream's new Event Staff / Schedule Leads feature (added after the branch point) callsKingdomAjax/playersearchin 4 places inrevised.js— dropping the endpoint would have broken new upstream functionality. The Kingdom/Park ones now run throughRankedPlayers; EventAjax's still runs through upstream'sScopedPlayerSearch.- Two new upstream surfaces are unconverted — the Event Staff name field and the Schedule Leads field still use hand-rolled
kn-ac-dropdowns withtnPositionAcFixed. They work, but they fall outside the standardization this PR claims. Worth a follow-up commit here. ScopedPlayerSearch+parkOrderClause+formatScopedPlayerRoware now only used byEventAjax. Upstream code wasn't deleted during a merge; worth deciding whether to converge EventAjax ontoRankedPlayersand drop them.UniversalSearchno longer uses its$includeInactive/$callerUserId/$foldlocals for the player query (tier logic moved intoRankedPlayers). Harmless, but a linter may flag the unused assignments.
PR now shows MERGEABLE / CLEAN.
🤖 Generated with Claude Code
The playersearch branch of Controller_ParkAjax::park() resolved the park's kingdom with a raw \$DB->DataSet() against the park table. Park already exposes GetParkKingdomId() in the domain layer, so the controller now goes through a new thin Model_Park::get_park_kingdom_id() pass-through instead of touching \$DB directly. No new SQL was added. Behavior is unchanged: the ring-ranking kingdom id is the park's kingdom, or 0 when the park row does not exist (GetParkKingdomId returns false, which the controller casts to 0 exactly as before). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Event page's Staff-modal player field and the Schedule modal's Item Lead(s) field were the last two hand-rolled kn-ac- dropdowns on this branch — added upstream after the standardization pass was written. Both drove KingdomAjax/playersearch directly with a bespoke "own kingdom first, then top up from outside the kingdom" two-fetch dance, their own render/position/blur/focusout handlers, and tnPositionAcFixed to escape the modal's overflow. Both now use OrkPlayerSearch.attach() with the same ring centre as the attendance search already on this page (parkId + kingdomId from EvConfig, no restrictTo) — so park members rank first, then kingdom, then everyone else, and an out-of-kingdom player is still reachable exactly as before. The component owns positioning: it appends its dropdown to document.body as position:fixed and repositions on scroll/resize, so no tnPositionAcFixed call is needed inside either modal, and Escape is swallowed by the open dropdown instead of closing the modal. Leads additionally pass excludeIds so already-chosen leads no longer appear in the results, and adding a lead by search now refreshes the staff quick-add list the same way the +Add and remove paths do. Removed with them: the now-unreferenced ev-staff-ac / ev-sched-lead-ac container divs, the evStaffAcTimer / evSchedLeadAcTimer debounce vars, and the OPEN_CLASS / ITEM_SEL constants. escHtmlSt, acKeyNav and tnPositionAcFixed all have other live callers and are left in place. OrkPlayerSearch.attach call sites: 18 -> 20. No player field uses jQuery UI autocomplete; the two remaining .autocomplete() calls are the non-player park and parent-kingdom searches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up: Event Staff and Schedule Leads converted (
|
What this does
Replaces ~40 divergent player-search implementations (6 incompatible backend endpoints, jQuery-UI + bespoke dropdowns) with one standardized player search, then layers on a consistent active → inactive → banned result model and a rich Advanced Search page.
One core, one endpoint, one component
SearchService::RankedPlayers(): concentric-ring ranking centered on the surface (Park → Kingdom → Everywhere), rank-don't-exclude by default, opt-inrestrictTo/restrictKingdomIds/excludeKingdomId/excludeParkId/excludeIds. Kills the recurring "can't find people outside my kingdom" bug class.SearchAjax/players(+ session-less SOAPSearch/Playersfor testing), returning an envelope{rows, hasMore, offset}. The 6 legacy endpoints (KingdomAjax/ParkAjax/SearchAjax-universal/AdminAjax/EventAjaxplayersearch +SearchService::Player) delegate to the core so ranking can't drift again.OrkPlayerSearch(custom dropdown, never jQuery UI; fixed-position for modals; dark-mode; ARIA combobox;excludeIds/reattach/preload/onClear), loaded app-wide.Active / inactive / banned result model (new)
Every surface now returns three tiers in one consistent order:
suspendedORpenalty_box) shown only to viewers with CREATE/EDIT authority relevant to the surface, and scoped one level up: a park surface surfaces the kingdom's banned players; a kingdom / global / universal surface surfaces Amtgard-wide bans. Plain users never see banned players anywhere.restrictTo:'kingdom'is now principality-family-aware (a "within kingdom" / kingdom-merge search still finds principality members).Advanced Search page (new)
A full-page power-search at
Search/advanced, opened in a new tab from the inline component's splitLoad more… | …or Advanced Searchfooter (carrying the typed term + current scope):restrictedplayer's real name is admin-only), home kingdom/park, last-attendance kingdom/park + date, status pill, link-out, and a copy-Player-ID button (instant CSS tooltip "Copy Player ID - Paste this into your search!").mundane_idlookup — copy an id here, paste it into any player search.Surfaces converted (~40 call sites)
Award giver/recipient, attendance (kingdom / park / event + the Parknew sign-in modal with its scope toggle, already-attended exclusion, and inactive-reactivation confirm), recommendations, Move Player (Kingdom / Park / Admin — mode-driven exclude/restrict + cascade re-scope), Merge (cross-field exclude; kingdom merge now
restrictTo:'kingdom'), Unit add member/manager (intentionally global), officer / permission grants (scoped + global), reconcile, Edit Officers (Kingdom + Park), and the universal header search.Security
mysql_real_escape_stringis a no-op polyfill in this codebase, so player/termLIKEinterpolation was unescaped (SQL-injectable). Replaced every term interpolation inRankedPlayers, the legacyPlayer(), and the Event/Kingdom/Park name searches with a reallikeEscape()(quote-doubling +% _ \escaping). Banned-visibility and restricted-real-name gating are enforced server-side from the session token — client params can't widen them.Surface bug fixes & cleanup
#PlayerSearchjQuery-UI bindings and the supersededEventAjax/AdminAjaxplayersearch endpoints.Testing
tests/playersearch/banned_matrix.sh— authenticated full matrix (active/inactive/banned × park/kingdom/global × anon/plain/officer/admin), 15/15 green;tests/playersearch/ranking.sh12/12.tests/playersearch/chrome-matrix-protocol.md— full in-Chrome test protocol.Notes for review
KingdomAjax/ParkAjaxplayersearchendpoints were left in place (harmless) now that move-player routes throughSearchAjax/players.🤖 Generated with Claude Code