Skip to content

Refactor: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search - #477

Open
baltinerdist wants to merge 37 commits into
amtgard:masterfrom
baltinerdist:refactor/playersearch
Open

Refactor: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search#477
baltinerdist wants to merge 37 commits into
amtgard:masterfrom
baltinerdist:refactor/playersearch

Conversation

@baltinerdist

@baltinerdist baltinerdist commented May 26, 2026

Copy link
Copy Markdown
Contributor

Opened early for visibility/review. Reviewed separately from #476 (units work + standalone award-giver fix).

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

  • One ranking coreSearchService::RankedPlayers(): concentric-ring ranking centered on the surface (Park → Kingdom → Everywhere), rank-don't-exclude by default, opt-in restrictTo / restrictKingdomIds / excludeKingdomId / excludeParkId / excludeIds. Kills the recurring "can't find people outside my kingdom" bug class.
  • One canonical endpointSearchAjax/players (+ session-less SOAP Search/Players for testing), returning an envelope {rows, hasMore, offset}. The 6 legacy endpoints (KingdomAjax / ParkAjax / SearchAjax-universal / AdminAjax / EventAjax playersearch + SearchService::Player) delegate to the core so ranking can't drift again.
  • One componentOrkPlayerSearch (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:

  • Active first, then Inactive as a last resort (always shown, ranked after all active).
  • Banned (suspended OR penalty_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).
  • Distinct Banned (red) / Suspended (amber) / Inactive (gray) badges; inactive rows dimmed, banned rows tinted; dark-mode aware.

Advanced Search page (new)

A full-page power-search at Search/advanced, opened in a new tab from the inline component's split Load more… | …or Advanced Search footer (carrying the typed term + current scope):

  • Filters: Active / Inactive toggles, officer-gated Banned (Amtgard-wide), kingdom→park cascade, optional last-attendance date range.
  • Results render as a DataTable (Reports styling): zebra striping, pagination, "Show N rows" / "Showing X–Y of N", sortable headers, responsive (no horizontal scrollbar on desktop, columns collapse on mobile).
  • Columns: persona, real name (officers only; a restricted player'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!").
  • Kingdom names are shortened in-cell ("The Kingdom of the Wetlands" → Wetlands).
  • Paste-an-ID flow: a pure-numeric query (in the inline component or Advanced Search) is a direct mundane_id lookup — 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_string is a no-op polyfill in this codebase, so player/term LIKE interpolation was unescaped (SQL-injectable). Replaced every term interpolation in RankedPlayers, the legacy Player(), and the Event/Kingdom/Park name searches with a real likeEscape() (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

  • Award "Given At" handler no longer wipes the selected recipient id.
  • Reports_roster "Suspended By" passes the correct entity for park-scoped rosters.
  • Admin Move Player "current park" select re-scopes the search; Attendance re-scopes on kingdom/park change.
  • Removed dead #PlayerSearch jQuery-UI bindings and the superseded EventAjax/AdminAjax playersearch 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.sh 12/12.
  • tests/playersearch/chrome-matrix-protocol.md — full in-Chrome test protocol.
  • Verified live in Chrome (dark mode): one-level-up banned scoping, attendance scope toggle, Move Player context, the Advanced Search DataTable, and the inline → Advanced Search → paste-ID round-trip.

Notes for review

  • Banned players are shown AND selectable on every surface (product decision) — rows are badged but not blocked; server-side operations still enforce their own rules.
  • Legacy default-template admin pages (Set Officers, Authorization, Event, Audit Log, the public Search page) are out of scope here (not part of the 3.5.x design language).
  • The newly-unused KingdomAjax/ParkAjax playersearch endpoints were left in place (harmless) now that move-player routes through SearchAjax/players.

🤖 Generated with Claude Code

baltinerdist and others added 30 commits June 9, 2026 17:14
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>
@baltinerdist
baltinerdist force-pushed the refactor/playersearch branch from 83bb92b to fcb5bdc Compare June 9, 2026 21:25
baltinerdist and others added 3 commits June 10, 2026 11:12
…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>
@baltinerdist baltinerdist changed the title WIP: Playersearch component standardization WIP: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search Jun 10, 2026
@baltinerdist baltinerdist changed the title WIP: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search Refactor: Playersearch standardization — active/inactive/banned tiers, full surface conversion, Advanced Search Jun 10, 2026
@baltinerdist
baltinerdist marked this pull request as ready for review June 10, 2026 15:27
baltinerdist and others added 2 commits June 10, 2026 13:38
…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
@baltinerdist

Copy link
Copy Markdown
Contributor Author

Branch brought current with master

Synced up to master @ 6ab028a3 (merge commit 363fe869). This branch was 523 commits behind; now 0 behind / 35 ahead.

The core collision

Upstream's R-11: "Consolidate search and player search in SearchService domain" did the same consolidation this branch did — differently. Upstream moved the legacy per-controller SQL into SearchService::UniversalSearch/ScopedPlayerSearch; this branch moved it into SearchService::RankedPlayers/AdvancedPlayers with the 3-tier model.

Both satisfy the "DB work in system/lib/ork3/" intent, so the resolution kept upstream's structure and made this branch's RankedPlayers the engine behind it.

Conflicts: 12 files

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 no restrictTo key — only kingdomId/parkId as ranking centre. Every restrictTo in revised.js belongs to a Move/Merge Player field, where scoping is correct.
  • Unit member add stays GLOBALunAttachPlayerSearch passes parkId/kingdomId as ranking centre only, no restrictTo.
  • 18 OrkPlayerSearch.attach + 3 reattach sites byte-identical to the branch tip; all 19 template call sites survive; ork-player-search.js/.css still loaded globally in default.theme; Search_advanced.tpl intact.
  • No jQuery UI autocomplete on any player field. The two remaining .autocomplete() calls are knWireParkAutocomplete (Park search) and the parent-kingdom search — both pre-existing non-player fields.
  • RankedPlayers output 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

  1. ⚠️ Upstream's playersearch endpoints were kept in KingdomAjax, ParkAjax, and EventAjax, against this branch's retirement of them. Upstream's new Event Staff / Schedule Leads feature (added after the branch point) calls KingdomAjax/playersearch in 4 places in revised.js — dropping the endpoint would have broken new upstream functionality. The Kingdom/Park ones now run through RankedPlayers; EventAjax's still runs through upstream's ScopedPlayerSearch.
  2. Two new upstream surfaces are unconverted — the Event Staff name field and the Schedule Leads field still use hand-rolled kn-ac- dropdowns with tnPositionAcFixed. They work, but they fall outside the standardization this PR claims. Worth a follow-up commit here.
  3. ScopedPlayerSearch + parkOrderClause + formatScopedPlayerRow are now only used by EventAjax. Upstream code wasn't deleted during a merge; worth deciding whether to converge EventAjax onto RankedPlayers and drop them.
  4. UniversalSearch no longer uses its $includeInactive/$callerUserId/$fold locals for the player query (tier logic moved into RankedPlayers). Harmless, but a linter may flag the unused assignments.

PR now shows MERGEABLE / CLEAN.

🤖 Generated with Claude Code

baltinerdist and others added 2 commits August 18, 2026 09:45
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>
@baltinerdist

Copy link
Copy Markdown
Contributor Author

Follow-up: Event Staff and Schedule Leads converted (18118780)

Closes the standardization gap flagged above. Both surfaces now use OrkPlayerSearch, bringing the branch to 20 attach sites (was 18) with zero remaining KingdomAjax/playersearch calls in revised.js.

Event Staff (#ev-staff-player-name / hidden #ev-staff-player-id) and Schedule Leads (#ev-sched-lead-input, chips in evSchedLeads[]) both previously ran the same hand-rolled pattern: KingdomAjax/playersearch/{kingdomId}&q=…&scope=own, then — if fewer than 5 hits — a second &scope=exclude fetch appended and deduped to top up to 10, with a global SearchService.php fallback when the event had no kingdom.

Scope is preserved exactly. That old behavior was kingdom-ranked, not kingdom-restricted, so neither attach carries restrictToparkId/kingdomId act as ring centre only. Independently confirmed on the pushed branch: no restrictTo key on either new call site, and the no-kingdom case still degrades to a global search (both ids resolve to 0). Neither field became global, and neither previously-scoped field was narrowed.

Three deliberate deltas, all recorded in the commit body:

  • parkId joins the ring centre — the old endpoint had no park notion. Ranking only; it cannot widen or narrow the result set.
  • Schedule Leads gains excludeIds so already-chosen leads stop appearing.
  • Adding a lead by search now calls evRefreshStaffQuickAdd(), matching what the + Add and remove paths already did.

Both match the attendance search already on this page (ev-PlayerName).

Modal positioning. Both fields sit inside modals (#ev-staff-modal, #ev-schedule-modal, overflow-y:auto). The old code hand-set position:fixed; z-index:9999 and called tnPositionAcFixed on every render. OrkPlayerSearch handles this internally — it appends .ops-ac-results to document.body, sets position:fixed in its own position(), reflows on scroll/resize/visualViewport, and carries z-index:2147483000. It also stopPropagation()s Escape while open, so dismissing the dropdown no longer closes the whole modal. No tnFixedAcPosition call is needed for these two.

Dead code removed, with grep proof: the #ev-staff-ac / #ev-sched-lead-ac divs, evStaffAcTimer, evSchedLeadAcTimer, OPEN_CLASS, ITEM_SEL, and the render/position/blur/focusout handlers — all zero remaining references (re-verified post-push). Also removed gid('ev-staff-ac').classList.remove('kn-ac-open') from evOpenStaffModal, which would have thrown once the div was gone.

Kept because they still have live callers: escHtmlSt (3 callers), acKeyNav (4 call sites), tnPositionAcFixed (4 call sites including park search).

Verification: node --check passes; 20 attach sites; php -l clean on the template. The only two remaining .autocomplete() calls are the pre-existing non-player searches — knWireParkAutocomplete (line 4152) and the parent-kingdom search (line 5823) — so no player field regressed to jQuery UI.

Not runtime-tested — no browser check of dropdown rendering inside ev-staff-modal / ev-schedule-modal. Worth a click before merge, since modal positioning is the one thing static analysis can't confirm.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant