Imported from Rosmi720/XC_VM@260ca7ab, with the scoping and permission gaps
closed before merge.
Original work:
- Keep tickets listed through a LEFT JOIN so one does not vanish when its
author's user row is edited or deleted; username falls back to 'Unknown'.
- Stop double-escaping message bodies: the repository returned
htmlspecialchars() output that both ticket_view templates escape again, so
markup showed up as entities. The repository now returns raw text and the
views keep their nl2br(htmlspecialchars()).
- Drop the str_repeat(' ', ...) padding hack that rendered literal
' ' inside short replies.
- Resolve last_reply and the derived status flags without undefined-key
warnings on tickets that have no replies yet.
- Add Close / Re-Open to the admin ticket view with a confirmation dialog, a
quick reply form under the thread, and a Reply entry in the tickets table.
- Add English and Arabic strings for the confirmation dialogs.
Changed on import:
- Tenant isolation: the original widened the admin branch to every ticket on
the server whenever $rAdmin was set, and admin/tickets.php always sets it.
Since users_groups.is_admin is a flag several groups can carry, that exposed
every tenant's tickets to any admin-panel user. Only the super-admin group
(member_group_id = 1) now gets the unscoped list; any other admin stays
scoped to the users it owns. TicketVisibilityTest covers all three scopes.
- Gate Close / Re-Open, the quick reply form and the Reply links behind the
same Authorization::check('adv', 'ticket') the tickets table already used.
- Quote 'Unknown' as a string literal, not "Unknown", which ANSI_QUOTES would
read as an identifier.
- Escape the textarea placeholder.
- Drop the admin/functions.php hunk: that file no longer exists after the PSR-4
move and AdminApiStage already populates $GLOBALS['rAdminUserInfo']. The
post.php fallback is kept, since post.php can be served standalone.
Verified: 857 tests, make gates, CRAP gate.