Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions web/api/handlers/bans.php
Original file line number Diff line number Diff line change
Expand Up @@ -826,8 +826,9 @@ function api_bans_view_community(array $params): array
* demo_count: int,
* history_count: int,
* comments_visible: bool,
* can_comment: bool,
* notes_visible: bool,
* comments: list<array{cid: int, added: int, added_human: string, author: string|null, text: string, edited_at: int|null, edited_by: string|null}>
* comments: list<array{cid: int, added: int, added_human: string, author: string|null, author_hidden: bool, text: string, edited_at: int|null, edited_by: string|null, can_edit: bool, can_delete: bool}>
* }
*/
function api_bans_detail(array $params): array
Expand Down Expand Up @@ -947,8 +948,14 @@ function api_bans_detail(array $params): array
$comments = [];
$commentsVisible = Config::getBool('config.enablepubliccomments') || $isAdmin;
if ($commentsVisible) {
// #1544: per-comment edit/delete gating for the drawer, mirroring
// page.banlist.php's `$commentres` loop — edit is own-comment OR
// Owner, delete is Owner only. Never true for a non-admin caller
// (only admins author comments, and Owner is an admin flag).
$viewerAid = $userbank->GetAid();
$isOwner = $isAdmin && $userbank->HasAccess(WebPermission::Owner);
$commentRows = $GLOBALS['PDO']->query(
"SELECT C.cid, C.commenttxt, C.added, C.edittime,
"SELECT C.cid, C.aid, C.commenttxt, C.added, C.edittime,
(SELECT user FROM `:prefix_admins` WHERE aid = C.aid) AS author,
(SELECT user FROM `:prefix_admins` WHERE aid = C.editaid) AS editor
FROM `:prefix_comments` AS C
Expand All @@ -973,6 +980,8 @@ function api_bans_detail(array $params): array
'text' => (string)$crow['commenttxt'],
'edited_at' => $editTime,
'edited_by' => (!$hideAdmin && $crow['editor'] !== null) ? (string)$crow['editor'] : null,
'can_edit' => $isOwner || ($isAdmin && (int)$crow['aid'] === $viewerAid),
'can_delete' => $isOwner,
];
}
}
Expand Down Expand Up @@ -1013,6 +1022,9 @@ function api_bans_detail(array $params): array
'demo_count' => (int)$row['demo_count'],
'history_count' => (int)$row['history_count'],
'comments_visible' => $commentsVisible,
// #1544: gates the drawer's "Add comment" CTA — same login-only
// gate page.banlist.php splats as `can_comment` ($userbank->is_admin()).
'can_comment' => $isAdmin,
// notes_visible is the drawer's signal for whether to render the
// Notes tab at all (#1165). It mirrors the dispatcher gate on
// `notes.list` (requireAdmin=true) so a public visitor sees three
Expand Down
18 changes: 15 additions & 3 deletions web/api/handlers/comms.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,12 @@ function api_comms_prepare_block_from_ban(array $params): array
* admin: array{name: string|null},
* server: array{sid: int, name: string|null, mod_icon: string|null},
* comments_visible: bool,
* can_comment: bool,
* notes_visible: bool,
* comments: list<array{cid: int, added: int, added_human: string,
* author: string|null, text: string,
* edited_at: int|null, edited_by: string|null}>,
* author: string|null, author_hidden: bool, text: string,
* edited_at: int|null, edited_by: string|null,
* can_edit: bool, can_delete: bool}>,
* }
*/
function api_comms_detail(array $params): array
Expand Down Expand Up @@ -529,12 +531,17 @@ function api_comms_detail(array $params): array
$comments = [];
$commentsVisible = Config::getBool('config.enablepubliccomments') || $isAdmin;
if ($commentsVisible) {
// #1544: per-comment edit/delete gating for the drawer — mirrors
// page.commslist.php ($commentres loop): edit is own-comment OR
// Owner, delete is Owner only.
$viewerAid = $userbank->GetAid();
$isOwner = $isAdmin && $userbank->HasAccess(WebPermission::Owner);
// Comm comments live on `:prefix_comments` with `type = 'C'`,
// keyed by the comm row's `bid` column (despite our public
// surface naming it `cid` — the column is shared between the
// bans/comms/protests trio via the `type` letter).
$commentRows = $GLOBALS['PDO']->query(
"SELECT C.cid, C.commenttxt, C.added, C.edittime,
"SELECT C.cid, C.aid, C.commenttxt, C.added, C.edittime,
(SELECT user FROM `:prefix_admins` WHERE aid = C.aid) AS author,
(SELECT user FROM `:prefix_admins` WHERE aid = C.editaid) AS editor
FROM `:prefix_comments` AS C
Expand All @@ -559,6 +566,8 @@ function api_comms_detail(array $params): array
'text' => (string)$crow['commenttxt'],
'edited_at' => $editTime,
'edited_by' => (!$hideAdmin && $crow['editor'] !== null) ? (string)$crow['editor'] : null,
'can_edit' => $isOwner || ($isAdmin && (int)$crow['aid'] === $viewerAid),
'can_delete' => $isOwner,
];
}
}
Expand Down Expand Up @@ -602,6 +611,9 @@ function api_comms_detail(array $params): array
'mod_icon' => !empty($row['mod_icon']) ? (string)$row['mod_icon'] : null,
],
'comments_visible' => $commentsVisible,
// #1544: gates the drawer's "Add comment" CTA — same login-only
// gate page.commslist.php splats as `can_comment`.
'can_comment' => $isAdmin,
// Mirrors `api_bans_detail`: the drawer's Notes tab is
// admin-only, gated on this flag. The dispatcher gate on
// `notes.list` is the load-bearing one; this signal lets the
Expand Down
8 changes: 8 additions & 0 deletions web/includes/View/BanListView.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ public function __construct(
// gets `aria-pressed="true"` on first paint without a JS
// round-trip.
public readonly string $chip_base_link,
// #1544: gates the per-row "Add comment" CTA in the inline
// comments disclosure (and lets the disclosure render on rows
// with zero comments so an admin can start a thread). Splatted
// as `$userbank->is_admin()` from `web/pages/page.banlist.php` —
// matches the `bans.add_comment` API's login-only gate. The
// per-comment edit / delete links stay permission-gated inside
// the page handler ($com.editcomlink / $com.delcomlink).
public readonly bool $can_comment = false,
) {
}
}
6 changes: 6 additions & 0 deletions web/includes/View/CommsListView.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ public function __construct(
// Mirrors the post-submit auto-open contract #1303 introduced
// for admin-admins.
public readonly bool $is_advanced_search_open,
// #1544: gates the per-row "Add comment" CTA in the inline
// comments disclosure (and lets the disclosure render on rows
// with zero comments). Splatted as `$userbank->is_admin()` from
// `web/pages/page.commslist.php`. Per-comment edit / delete
// links stay permission-gated in the page handler.
public readonly bool $can_comment = false,
) {
}
}
19 changes: 15 additions & 4 deletions web/pages/page.banlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,13 @@ function setPostKey()
$cdata = [];
$cdata['morecom'] = ($morecom == 1 ? true : false);
if ($crow['aid'] == $userbank->GetAid() || $userbank->HasAccess(WebPermission::Owner)) {
$cdata['editcomlink'] = CreateLinkR('<i class="fas fa-edit fa-lg"></i>', 'index.php?p=banlist&comment=' . $data['ban_id'] . '&ctype=B&cid=' . $crow['cid'] . $pagelink, 'Edit Comment');
// #1544: icon-only edit link. `data-lucide` (not Font Awesome, which
// the 2.0 theme no longer loads — the old `<i class="fas …">` rendered
// an empty `<a>`); `aria-label` gives the icon-only control an
// accessible name the way the row-action buttons do.
$cdata['editcomlink'] = '<a href="index.php?p=banlist&comment=' . $data['ban_id'] . '&ctype=B&cid=' . (int) $crow['cid'] . $pagelink . '"'
. ' class="tip" target="_self" data-tooltip="Edit Comment" aria-label="Edit comment"'
. '><i data-lucide="pencil" style="width:13px;height:13px" aria-hidden="true"></i></a>';
if ($userbank->HasAccess(WebPermission::Owner)) {
// #1402: `onclick="RemoveComment(...)"` was the v1.x bridge into
// the deleted sourcebans.js helper — every click threw
Expand All @@ -1096,12 +1102,12 @@ function setPostKey()
// four comment-thread surfaces (banlist / commslist / protests
// / submissions). data-page lets the handler land the operator
// back on the same paginated banlist view post-delete.
$cdata['delcomlink'] = '<a href="#" class="tip" title="Delete Comment" target="_self"'
$cdata['delcomlink'] = '<a href="#" class="tip" title="Delete Comment" aria-label="Delete comment" target="_self"'
. ' data-action="comment-delete"'
. ' data-cid="' . (int) $crow['cid'] . '"'
. ' data-ctype="B"'
. ' data-page="' . (isset($_GET["page"]) ? (int) $page : -1) . '"'
. '><i class="fas fa-trash fa-lg"></i></a>';
. '><i data-lucide="trash-2" style="width:13px;height:13px" aria-hidden="true"></i></a>';
}
} else {
$cdata['editcomlink'] = "";
Expand Down Expand Up @@ -1135,7 +1141,9 @@ function setPostKey()
}


$data['addcomment'] = CreateLinkR('<i class="fas fa-comment-dots fa-lg"></i> Add Comment', 'index.php?p=banlist&comment=' . $data['ban_id'] . '&ctype=B' . $pagelink);
// #1544: Lucide icon (the 2.0 theme dropped Font Awesome); keeps the
// visible "Add Comment" label so it degrades gracefully anyway.
$data['addcomment'] = CreateLinkR('<i data-lucide="message-square-plus" style="width:13px;height:13px" aria-hidden="true"></i> Add Comment', 'index.php?p=banlist&comment=' . $data['ban_id'] . '&ctype=B' . $pagelink);
//-----------------------------------

$data['ub_reason'] = (isset($data['ub_reason']) ? $data['ub_reason'] : "");
Expand Down Expand Up @@ -1504,4 +1512,7 @@ function setPostKey()
is_advanced_search_open: $banlistAdvancedOpen,
active_state: $stateFilter,
chip_base_link: $banlistChipBaseLink,
// #1544: restores the per-punishment "Add comment" CTA (and the
// per-comment edit/delete controls) dropped in the 2.0.0 migration.
can_comment: $userbank->is_admin(),
));
19 changes: 15 additions & 4 deletions web/pages/page.commslist.php
Original file line number Diff line number Diff line change
Expand Up @@ -873,15 +873,21 @@ function setPostKey()
$cdata = [];
$cdata['morecom'] = ($morecom == 1 ? true : false);
if ($crow['aid'] == $userbank->GetAid() || $userbank->HasAccess(WebPermission::Owner)) {
$cdata['editcomlink'] = CreateLinkR('<i class="fas fa-edit fa-lg"></i>', 'index.php?p=commslist&comment=' . $data['ban_id'] . '&ctype=C&cid=' . $crow['cid'] . $pagelink, 'Edit Comment');
// #1544: icon-only edit link. `data-lucide` (not Font Awesome, which
// the 2.0 theme no longer loads — the old `<i class="fas …">` rendered
// an empty `<a>`); `aria-label` gives the icon-only control an
// accessible name the way the row-action buttons do.
$cdata['editcomlink'] = '<a href="index.php?p=commslist&comment=' . $data['ban_id'] . '&ctype=C&cid=' . (int) $crow['cid'] . $pagelink . '"'
. ' class="tip" target="_self" data-tooltip="Edit Comment" aria-label="Edit comment"'
. '><i data-lucide="pencil" style="width:13px;height:13px" aria-hidden="true"></i></a>';
if ($userbank->HasAccess(WebPermission::Owner)) {
// #1402: see web/scripts/comment-actions.js for the dispatcher.
$cdata['delcomlink'] = '<a href="#" class="tip" title="Delete Comment" target="_self"'
$cdata['delcomlink'] = '<a href="#" class="tip" title="Delete Comment" aria-label="Delete comment" target="_self"'
. ' data-action="comment-delete"'
. ' data-cid="' . (int) $crow['cid'] . '"'
. ' data-ctype="C"'
. ' data-page="' . (isset($_GET["page"]) ? (int) $_GET["page"] : -1) . '"'
. '><i class="fas fa-trash fa-lg"></i></a>';
. '><i data-lucide="trash-2" style="width:13px;height:13px" aria-hidden="true"></i></a>';
}
} else {
$cdata['editcomlink'] = "";
Expand Down Expand Up @@ -914,7 +920,9 @@ function setPostKey()
$data['commentdata'] = $comment;
}

$data['addcomment'] = CreateLinkR('<i class="fas fa-comment-dots fa-lg"></i> Add Comment', 'index.php?p=commslist&comment=' . $data['ban_id'] . '&ctype=C' . $pagelink);
// #1544: Lucide icon (the 2.0 theme dropped Font Awesome); keeps the
// visible "Add Comment" label so it degrades gracefully anyway.
$data['addcomment'] = CreateLinkR('<i data-lucide="message-square-plus" style="width:13px;height:13px" aria-hidden="true"></i> Add Comment', 'index.php?p=commslist&comment=' . $data['ban_id'] . '&ctype=C' . $pagelink);
//-----------------------------------
$data['counts'] = $delimiter . $mutes . $gags;

Expand Down Expand Up @@ -1275,4 +1283,7 @@ function setPostKey()
view_comments: (bool) $view_comments,
view_bans: $viewBans,
is_advanced_search_open: $commsAdvancedOpen,
// #1544: restores the per-punishment "Add comment" CTA (and the
// per-comment edit/delete controls) dropped in the 2.0.0 migration.
can_comment: $userbank->is_admin(),
));
14 changes: 7 additions & 7 deletions web/scripts/api-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
* page.banlist.php's `$view_comments` switch.
*
* @typedef {Object} ApiBansDetailRequest
* @typedef {{ bid: number, player: {name: string, type: number, steam_id: string, steam_id_3: string, community_id: string, ip: string|null, country: string|null}, ban: {reason: string, banned_at: number, banned_at_human: string, length_seconds: number, length_human: string, expires_at: number|null, expires_at_human: string|null, state: string, unban_reason: string, removed_at: number|null, removed_at_human: string|null, removed_by: string|null}, admin: {name: string|null}, server: {sid: number, name: string|null, mod_icon: string|null}, demo_count: number, history_count: number, comments_visible: boolean, notes_visible: boolean, comments: Array<{cid: number, added: number, added_human: string, author: string|null, text: string, edited_at: number|null, edited_by: string|null}> }} ApiBansDetailResponse
* @typedef {{ bid: number, player: {name: string, type: number, steam_id: string, steam_id_3: string, community_id: string, ip: string|null, country: string|null}, ban: {reason: string, banned_at: number, banned_at_human: string, length_seconds: number, length_human: string, expires_at: number|null, expires_at_human: string|null, state: string, unban_reason: string, removed_at: number|null, removed_at_human: string|null, removed_by: string|null}, admin: {name: string|null}, server: {sid: number, name: string|null, mod_icon: string|null}, demo_count: number, history_count: number, comments_visible: boolean, can_comment: boolean, notes_visible: boolean, comments: Array<{cid: number, added: number, added_human: string, author: string|null, author_hidden: boolean, text: string, edited_at: number|null, edited_by: string|null, can_edit: boolean, can_delete: boolean}> }} ApiBansDetailResponse
*/
/**
* @typedef {Object} ApiBansEditCommentRequest
Expand Down Expand Up @@ -337,7 +337,7 @@
* template emits).
*
* @typedef {Object} ApiCommsDetailRequest
* @typedef {{ cid: number, player: { name: string, steam_id: string, steam_id_3: string, community_id: string, ip: null, country: string|null }, block: { type: number, type_label: string, reason: string, started_at: number, started_at_human: string, length_seconds: number, length_human: string, expires_at: number|null, expires_at_human: string|null, state: string, unblock_reason: string, removed_at: number|null, removed_at_human: string|null, removed_by: string|null }, admin: {name: string|null}, server: {sid: number, name: string|null, mod_icon: string|null}, comments_visible: boolean, notes_visible: boolean, comments: Array<{cid: number, added: number, added_human: string, author: string|null, text: string, edited_at: number|null, edited_by: string|null}> }} ApiCommsDetailResponse
* @typedef {{ cid: number, player: { name: string, steam_id: string, steam_id_3: string, community_id: string, ip: null, country: string|null }, block: { type: number, type_label: string, reason: string, started_at: number, started_at_human: string, length_seconds: number, length_human: string, expires_at: number|null, expires_at_human: string|null, state: string, unblock_reason: string, removed_at: number|null, removed_at_human: string|null, removed_by: string|null }, admin: {name: string|null}, server: {sid: number, name: string|null, mod_icon: string|null}, comments_visible: boolean, can_comment: boolean, notes_visible: boolean, comments: Array<{cid: number, added: number, added_human: string, author: string|null, author_hidden: boolean, text: string, edited_at: number|null, edited_by: string|null, can_edit: boolean, can_delete: boolean}> }} ApiCommsDetailResponse
*/
/**
* @typedef {Object} ApiCommsPasteRequest
Expand Down Expand Up @@ -567,11 +567,11 @@
*/
/**
* Public action: report whether a newer SourceBans++ release is available.
* Sources from `api.github.com/repos/srcdslab/sourcebans-pp/releases/latest` with
* a 1-day on-disk cache + stale-while-error fallback (the cached payload is
* served regardless of TTL when the upstream call fails) so a busy panel can't
* blow through GitHub's 60 req/hr unauthenticated limit and a transient GitHub
* blip doesn't paint the panel red.
* Sources from `api.github.com/repos/srcdslab/sourcebans-pp/releases/latest`
* with a 1-day on-disk cache + stale-while-error fallback (the cached payload
* is served regardless of TTL when the upstream call fails) so a busy panel
* can't blow through GitHub's 60 req/hr unauthenticated limit and a transient
* GitHub blip doesn't paint the panel red.
*
* @typedef {Object} ApiSystemCheckVersionRequest
* @typedef {{release_latest: string, release_url: string, release_msg: string, release_update: boolean}} ApiSystemCheckVersionResponse
Expand Down
4 changes: 4 additions & 0 deletions web/tests/api/BansTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ public function testDetailPublicViewHidesAdminFields(): void
$this->assertNull($env['data']['admin']['name'], 'admin should be hidden for public + hideadminname');
$this->assertFalse($env['data']['comments_visible'], 'comments should be hidden when public + flag off');
$this->assertSame([], $env['data']['comments']);
$this->assertFalse($env['data']['can_comment'], 'public callers cannot add comments from the drawer (#1544)');
$this->assertFalse($env['data']['notes_visible'], 'notes_visible should be false for public callers (#1165)');
$this->assertSnapshot('bans/detail_public_hidden', $env, ['data.bid', 'data.ban.banned_at', 'data.ban.banned_at_human', 'data.ban.expires_at', 'data.ban.expires_at_human']);
}
Expand Down Expand Up @@ -498,6 +499,9 @@ public function testDetailAdminViewExposesEverything(): void
$this->assertTrue($env['data']['notes_visible'], 'notes_visible should be true for admin callers (#1165)');
$this->assertCount(1, $env['data']['comments']);
$this->assertSame('note for the drawer', $env['data']['comments'][0]['text']);
$this->assertTrue($env['data']['can_comment'], 'admin can add comments from the drawer (#1544)');
$this->assertTrue($env['data']['comments'][0]['can_edit'], 'admin (Owner / own comment) can edit (#1544)');
$this->assertTrue($env['data']['comments'][0]['can_delete'], 'admin (Owner) can delete (#1544)');
$this->assertSnapshot('bans/detail_admin_view', $env, [
'data.bid',
'data.ban.banned_at',
Expand Down
Loading
Loading