Commit 05659f2
authored
* fix(admin): rewire dead sourcebans.js helpers across admin surfaces (#1402)
Sweeps the post-#1397 cluster of admin surfaces where the click chain
dead-ended at a helper deleted with `web/scripts/sourcebans.js` at
#1123 D1. Same shape as #1397's `RemoveMod` fix (silent / loud
`ReferenceError` on every click), applied to the rest of the cluster:
- **Mods → Add new MOD → Submit** (`ProcessMod`): the form's
`onsubmit="return ProcessMod()"` referenced a v1.x helper that
validated input + called `sb.api.call(Actions.ModsAdd)`. Replaced
with an inline page-tail dispatcher that intercepts `submit`,
runs the same client-side gates (name + URL + icon non-empty),
then `sb.api.call(Actions.ModsAdd, …)` per the
`page_admin_groups_add.tpl` reference shape. `setBusy` flips the
three-attribute busy contract during the in-flight call.
- **Mods → Upload icon popup** (`window.opener.icon(...)`):
`UploadHandler::handle()` emits `<script>window.opener.icon(filename)</script>`
on successful upload, but `window.icon` was undefined in both
`page_admin_mods_add.tpl` (new-mod flow) and `page_admin_edit_mod.tpl`
(edit-mod flow), so the popup never closed and the icon filename
never landed on the parent's `#icon_hid` hidden input. Both
templates now define `window.icon = function (filename) { … }`
inside a `{literal}…{/literal}` block that patches the hidden
input + updates the visible affordances (preview / "Choose file"
label). Same shape `window.demo` uses on the ban pages.
- **Admins → Add new admin → Submit** (`ProcessAddAdmin`): same
shape as `ProcessMod`. Intercept submit, validate (username +
password + SteamID + server-group + web-group), then
`sb.api.call(Actions.AdminsAdd)`. `event.preventDefault()` blocks
the native fallback POST so a JSON failure no longer races a
full-page reload.
- **Admins → Add → Generate password button** (`LoadGeneratePassword`):
rewired to `sb.api.call(Actions.AdminsGeneratePassword)`; success
fills both `password` + `confirm` inputs in place. No CSRF
surface (read-only call).
- **Admins → Add → server / web permissions `<select>` reveal**
(`update_server` / `update_web`): the conditional dependent-input
reveal (Custom permissions → flag picker; New admin group →
new-name + SM flags) is pure client-side DOM. Replaced the two
dead helpers with `data-action="adminadd-update-server" /
"adminadd-update-web"` `<select>` change handlers that toggle
the right `hidden` attributes per the original v1.x semantics.
- **Comms → Edit block → submit with validation error**
(`window.addEvent('domready', …)`): the MooTools DOMready wrapper
in `$errorScript` referenced an undefined global, so the
validation-error toast never painted. Per scope guardrails on
this issue, I dropped the MooTools wrapper and replaced its
body with vanilla `document.addEventListener('DOMContentLoaded',
…)` calling `window.SBPP.showToast` directly. The matching
`$('id').innerHTML` / `setStyle` calls inside the
`changeReason()` helper became
`document.getElementById('id').textContent` /
`el.style.display = 'block'`. The broader sweep of
`<script>ShowBox(...)</script>` toast blobs across other pages
stays for #1403.
- **Bans → Group ban — URL submit + bulk-from-friends**
(`LoadGroupBan` / `ProcessGroupBan` / `CheckGroupBan`): chained
through a page-tail dispatcher in `admin.bans.php` that picks
up `data-action="groupban-submit" / "groupban-bulk-submit"`
and walks the legacy two-step
`Actions.BansGroupBan` → `Actions.BansBanMemberOfGroup` chain.
The first step parses the URL into a group name, the second
enumerates + bans members. Bulk path tracks the `last` checkbox
sentinel so a 20-group bulk-ban only fires one success toast.
No new `Actions.GroupbanCheck` was needed — `bans.group_ban`
IS the URL-parse step `LoadGroupBan` used to perform first.
- **Bans → Group ban → Tick select-all** (`TickSelectAll`):
rewired to `data-action="groupban-select-all"` (covers both
the table-header button + the "Select all" link below).
Toggle state is computed from the `chkb_<n>` checkboxes' live
state per the v1.x semantics (any-unchecked → check all;
all-checked → uncheck all).
- **Banlist / Commslist / admin.bans comment editor trash**
(`RemoveComment`): the four call sites (page.banlist.php,
page.commslist.php, admin.bans.php protests + submissions)
used to inline the same dead helper. Replaced with a single
shared `web/scripts/comment-actions.js` dispatcher loaded
from `core/footer.tpl` (`<script src="./scripts/comment-actions.js" defer>`).
Each trigger emits `data-action="comment-delete"` +
`data-cid="<int>"` + `data-ctype="<B|C|S|P>"` + `data-page="<int>"`;
the dispatcher `window.confirm`s the destructive intent, then
`sb.api.call(Actions.BansRemoveComment, { cid, ctype, page })`.
The `ctype` letter matches `:prefix_comments.type` (B=ban,
C=comm-block, S=submission, P=protest); `api_bans_remove_comment`'s
`ctype` arm consumes all four. Single mount point is the
contract — don't duplicate inline per page.
E2E regression coverage
-----------------------
Four new specs under `web/tests/e2e/specs/flows/`:
- `mods-add-form.spec.ts` — happy-path submit + missing-icon
client-side gate. Stubs `Actions.ModsAdd` via `page.route`
to assert the wire format without mutating the e2e DB.
- `admins-add-form.spec.ts` — happy-path submit, Generate-password
button fills both password inputs, "New admin group" /
"Custom permissions" reveals the right dependent inputs.
Pinned to chromium (form-shaped; mobile would just burn CI
minutes).
- `comment-delete-dispatcher.spec.ts` — `data-action="comment-delete"`
trigger fires `Actions.BansRemoveComment` with the right
`cid` / `ctype` / `page`, `confirm()` cancel suppresses the
API call, and the dispatcher loads globally (the
`<script src="./scripts/comment-actions.js" defer>` include
is in `core/footer.tpl`).
- `groupban-dispatcher.spec.ts` — empty URL → inline error +
no API call, valid URL → chains
`Actions.BansGroupBan` → `Actions.BansBanMemberOfGroup`,
`data-action="groupban-select-all"` toggle flips synthetic
`chkb_<n>` checkboxes.
The group-ban surface ships behind `config.enablegroupbanning`,
which `data.sql` defaults to 0. The spec flips it on in
`beforeAll` and reverts in `afterAll` via a new
`setSettingE2e(key, value)` helper in `fixtures/db.ts` that
shells out to `web/tests/e2e/scripts/set-setting-e2e.php`
(mirror of the `REPLACE INTO sb_settings` shape `BansTest.php`
uses for the same reason).
AGENTS.md updates
-----------------
- Extended the existing `onclick="<Helper>()"` / `onclick="if
(typeof <Helper> === 'function') …"` anti-pattern bullet to
cross-reference #1402 and enumerate the cluster (`ProcessMod`,
`ProcessAddAdmin`, `LoadGeneratePassword`,
`update_server` / `update_web`, `LoadGroupBan` /
`ProcessGroupBan` / `CheckGroupBan` / `TickSelectAll`,
`RemoveComment`, `window.opener.icon(...)`,
`window.addEvent('domready', …)`).
- Added a new bullet for the MooTools `window.addEvent('domready', …)`
DOMready idiom (silent no-op every time it surfaced since
#1123 D1; replace with `document.addEventListener('DOMContentLoaded', …)`
or drop the wrapper outright when the script tag lands after
the elements it touches).
- New "Where to find what" rows for:
- Wiring a `window.opener.<callback>(...)` slot on a parent
template (the icon / demo / mapimg callback shape).
- Wiring a comment-delete trash icon (the shared
`comment-actions.js` dispatcher).
- Flipping a `:prefix_settings` row from an E2E spec
(`setSettingE2e` helper + its caller-cleanup contract).
Test plan
---------
- ./sbpp.sh phpstan ✓
- ./sbpp.sh test ✓ (677 tests)
- ./sbpp.sh ts-check ✓
- ./sbpp.sh composer api-contract ✓ (no diff)
- ./sbpp.sh e2e --workers=1 ✓ (279 passed)
- ./sbpp.sh e2e --grep "1402|mods-add|admins-add|comment-delete|LoadGroupBan" --workers=1
✓ (12 passed)
Out of scope (deferred per the issue body)
-----------------------------------------
- `admin.admins.php` `$serverscript` blob (sister #1404, parallel
worktree).
- `admin.groups.php` `LoadServerHostPlayersList` echo (sister
#1404).
- The remaining `<script>ShowBox(...)</script>` toast blobs on
lostpassword / protest / banlist / commslist /
admin.edit.comms (sister #1403).
* fix(admin): address adversarial review findings on #1402 rewire
Second commit on the same branch addressing the eight defects the
adversarial review surfaced after the initial #1402 rewire landed.
Kept as a separate commit so the audit trail stays visible.
HIGH (must-fix)
---------------
1. Owner-flag privilege escalation through the rewired Add-admin
form. The pre-existing api_admins_add handler had no
HasAccess(WebPermission::Owner) check (vs api_admins_edit_perms
which does), and the rewire exposed the OWNER checkbox to every
admin with ADMIN_ADD_ADMINS — full panel takeover with one click.
* api_admins_add: mirror api_admins_edit_perms's guard at the
top of the handler (Log::add + Api::redirect to no_access).
Covers both the `wg='c'` (direct mask) and `wg='n'` (new
group inherits mask) escalation paths.
* AdminAdminsAddView: new `can_grant_owner` View prop.
* admin.admins.php: pass
`$userbank->HasAccess(WebPermission::Owner)`.
* page_admin_admins_add.tpl: gate the OWNER checkbox with
`{if \$can_grant_owner}`.
* web/tests/api/AdminsTest.php: two new tests —
testAddBlocksGrantingOwnerWithoutOwner (non-owner is
redirected, no row landed) + testAddAllowsGrantingOwnerForOwner
(positive case so the guard isn't over-zealous).
2. Add-admin success path silently dropped Actions.SystemRehashAdmins.
The handler returns `rehash` in the envelope (sid CSV) — the
legacy ProcessAddAdmin chained it; the rewrite navigated away
without firing. config.enableadminrehashing defaults to '1', so
without the chain a new admin can log in to the panel but can't
moderate on game servers until the next restart.
* page_admin_admins_add.tpl: thread `data.rehash` into a
SystemRehashAdmins call before the navigate timer. Mirrors
_admin_edit_helpers.php:fireRehash's .then/.catch shape.
* admins-add-form.spec.ts: new e2e arm that stubs both
Actions.AdminsAdd (returning rehash:'1,2') and
Actions.SystemRehashAdmins, asserts the call sequence
and that servers=1,2 was forwarded.
3. Stale flags from hidden #web-flags-block / #server-flags rode
into Actions.AdminsAdd after a dropdown flip. updateWeb /
updateServer toggled `hidden` but left checkbox state +
text-input values intact; collectWebFlags walked the unscoped
#web-flags-block. Repro: "Custom permissions" → tick Owner →
"No permissions" → submit → mask: ADMIN_OWNER. Second route
to accidental OWNER grant.
* page_admin_admins_add.tpl: updateServer / updateWeb now
clear dependent state (uncheck flag checkboxes, clear name
inputs, clear srv flags string) when the dropdown swings
back to a non-revealing value. collectWebFlags is also
scoped to `:not([hidden])` as defense-in-depth on top of
the clear; sibling collectServerFlags / collect*NewName
helpers ride the same hidden-ancestor guard.
* admins-add-form.spec.ts: new e2e arm flips the dropdown
through the trap and asserts the API receives mask: 0.
MEDIUM (concerns)
-----------------
4. Missing .catch() arms on every new sb.api.call chain — sb.api.call
doesn't reject on network failures (it synthesises an envelope),
but a throw escaping the success callback would leave the button
stuck in setBusy forever. Per AGENTS.md "Loading state on action
buttons" — setBusy(btn, false) on every non-navigating response
branch.
* page_admin_admins_add.tpl: AdminsAdd + AdminsGeneratePassword.
* page_admin_mods_add.tpl: ModsAdd (sibling surface, same
rewire wave).
* admin.bans.php loadGroupBan: both outer BansGroupBan and
inner BansBanMemberOfGroup.
* comment-actions.js: BansRemoveComment.
5. Generated password left visible (type='text') indefinitely. The
rewrite flipped both password fields to type=text "to match v1.x
UX", but v1.x LoadGeneratePassword never changed .type. Privacy /
shoulder-surf / screenshot leak.
* page_admin_admins_add.tpl: keep the fields at type='password'
(matching legacy).
* admins-add-form.spec.ts: update the type assertion to pin
'password' (was 'text').
LOW (nits)
----------
6. Drop unused data-action="groupban-submit-form" attribute on
page_admin_bans_groups.tpl's <form> — no dispatcher wires it.
7. Drop page.waitForTimeout(200/300) negative-assertion settles from
three e2e specs (comment-delete / groupban / mods-add). The inline
error renders / dispatcher returns synchronously — once the
positive state is visible, the negative assertion can land.
8. Drop `defer` from the body-tail comment-actions.js include.
`defer` is a no-op on a script that lives at the body tail (the
parser is already past the body), so the markup matched what the
runtime did, just less misleadingly.
Reviewer-flagged concerns kept out of scope (preserved from
origin/main, not introduced by this PR): the delcomlink permission
asymmetry (Owner-only delete vs. anyone-can-edit-their-own) and the
per-template `window.icon` callback duplication. Both tracked
separately if they need addressing.
Quality gates (all green at workers=1):
* PHPStan: 240/240 files, no errors
* PHPUnit: 679 tests, 2703 assertions
* ts-check: clean
* API contract: regenerated, no diff
* E2E: 187 passed / 100 skipped (mobile), no failures
1 parent cd65fca commit 05659f2
22 files changed
Lines changed: 2553 additions & 109 deletions
File tree
- web
- api/handlers
- includes/View
- pages
- scripts
- tests
- api
- e2e
- fixtures
- scripts
- specs/flows
- themes/default
- core
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
137 | 160 | | |
138 | 161 | | |
139 | 162 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
28 | 34 | | |
29 | 35 | | |
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
33 | 39 | | |
34 | 40 | | |
| 41 | + | |
35 | 42 | | |
36 | 43 | | |
37 | 44 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
156 | 161 | | |
157 | 162 | | |
158 | 163 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
895 | 895 | | |
896 | 896 | | |
897 | 897 | | |
898 | | - | |
899 | | - | |
900 | | - | |
901 | | - | |
902 | | - | |
903 | | - | |
904 | | - | |
905 | | - | |
906 | | - | |
907 | | - | |
908 | | - | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
909 | 918 | | |
910 | 919 | | |
911 | | - | |
912 | | - | |
913 | | - | |
914 | | - | |
915 | | - | |
916 | | - | |
917 | | - | |
918 | | - | |
919 | | - | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
920 | 934 | | |
921 | | - | |
922 | | - | |
923 | | - | |
924 | | - | |
925 | | - | |
926 | | - | |
927 | | - | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
928 | 939 | | |
929 | | - | |
930 | | - | |
931 | | - | |
932 | | - | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
933 | 944 | | |
934 | 945 | | |
935 | | - | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
936 | 1120 | | |
937 | 1121 | | |
938 | 1122 | | |
| |||
964 | 1148 | | |
965 | 1149 | | |
966 | 1150 | | |
967 | | - | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
968 | 1161 | | |
969 | 1162 | | |
970 | 1163 | | |
| |||
0 commit comments