Fix bracket buttons rendering below text + UI/security improvements

CSS fixes:
- Fix [ ] brackets appearing below button text by replacing display:inline-flex
  with display:inline-block + white-space:nowrap on .btn — removes cross-browser
  flex pseudo-element inconsistency as root cause
- Remove conflicting .btn::before ripple block (position:absolute was overriding
  bracket content positioning)
- Remove overflow:hidden from .btn which was clipping bracket content
- Fix body::after duplicate rule causing GPU layer blink (second position:fixed
  rule re-created compositor layer, overriding display:none suppression)
- Replace all transition:all with scoped property transitions in dashboard.css,
  ticket.css, base.css (prevents full CSS property evaluation on every hover)
- Convert pulse-warning/pulse-critical keyframes from box-shadow to opacity
  animation (GPU-composited, eliminates CPU repaints at 60fps)
- Fix mobile *::before/*::after blanket content:none rule — now targets only
  decorative frame glyphs, preserving button brackets and status indicators
- Remove --terminal-green-dim override that broke .lt-btn hover backgrounds

JS fixes:
- Fix all lt.lt.toast.* double-prefix instances in dashboard.js
- Add null guard before .appendChild() on bulkAssignUser select
- Replace all remaining emoji with terminal bracket notation (dashboard.js,
  ticket.js, markdown.js)
- Migrate all toast.*() shim calls to lt.toast.* across all JS files

View fixes:
- Remove hardcoded [ ] brackets from .btn buttons (CSS now adds them)
- Replace all emoji with terminal bracket notation in all views and admin views
- Add missing CSP nonces to AuditLogView.php and UserActivityView.php script tags
- Bump CSS version strings to ?v=20260319b for cache busting

Security fixes:
- update_ticket.php: add authorization check (non-admins can only edit their own
  or assigned tickets)
- add_comment.php: validate and cast ticket_id to integer with 400 response
- clone_ticket.php: fix unconditional session_start(), add ticket ID validation,
  add internal ticket access check
- bulk_operation.php: add HTTP 401/403 status codes on auth failures
- upload_attachment.php: fix missing $conn arg in AttachmentModel constructor
- assign_ticket.php: add ticket existence check and permission verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:20:43 -04:00
parent dd8833ee2f
commit 27075a62ee
23 changed files with 376 additions and 328 deletions

View File

@@ -5,14 +5,14 @@
// Helper functions for timeline display
function getEventIcon($actionType) {
$icons = [
'create' => '',
'update' => '📝',
'comment' => '💬',
'view' => '👁️',
'assign' => '👤',
'status_change' => '🔄'
'create' => '[ + ]',
'update' => '[ ~ ]',
'comment' => '[ > ]',
'view' => '[ . ]',
'assign' => '[ @ ]',
'status_change' => '[ ! ]',
];
return $icons[$actionType] ?? '';
return $icons[$actionType] ?? '[ * ]';
}
function formatAction($event) {
@@ -51,8 +51,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<title>Ticket #<?php echo $ticket['ticket_id']; ?></title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260131e">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260131e">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319b">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319b">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script>
@@ -82,15 +82,15 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<body>
<header class="user-header">
<div class="user-header-left">
<a href="/" class="back-link">← Dashboard</a>
<a href="/" class="back-link">[ &larr; DASHBOARD ]</a>
</div>
<div class="user-header-right">
<?php if (isset($GLOBALS['currentUser'])): ?>
<span class="user-name">👤 <?php echo htmlspecialchars($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username']); ?></span>
<span class="user-name">[ <?php echo htmlspecialchars(strtoupper($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username'])); ?> ]</span>
<?php if ($GLOBALS['currentUser']['is_admin']): ?>
<span class="admin-badge">Admin</span>
<span class="admin-badge">[ ADMIN ]</span>
<?php endif; ?>
<button class="settings-icon" title="Settings (Alt+S)" id="settingsBtn" aria-label="Settings"></button>
<button class="settings-icon" title="Settings (Alt+S)" id="settingsBtn" aria-label="Settings">[ CFG ]</button>
<?php endif; ?>
</div>
</header>
@@ -134,7 +134,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
}
?>
<div class="ticket-age <?php echo $ageClass; ?>" title="Time since last update">
<span class="age-icon"><?php echo $ageClass === 'age-critical' ? '⚠️' : ($ageClass === 'age-warning' ? '⏰' : '📅'); ?></span>
<span class="age-icon"><?php echo $ageClass === 'age-critical' ? '[ ! ]' : ($ageClass === 'age-warning' ? '[ ~ ]' : '[ t ]'); ?></span>
<span class="age-text">Last activity: <?php echo $ageStr; ?> ago</span>
</div>
<div class="ticket-user-info">
@@ -372,8 +372,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
}
// Edit/Delete buttons for owner or admin
if ($canModify) {
echo "<button type='button' class='comment-action-btn edit-btn' data-action='edit-comment' data-comment-id='{$commentId}' title='Edit' aria-label='Edit comment'>✏️</button>";
echo "<button type='button' class='comment-action-btn delete-btn' data-action='delete-comment' data-comment-id='{$commentId}' title='Delete' aria-label='Delete comment'>🗑️</button>";
echo "<button type='button' class='comment-action-btn edit-btn' data-action='edit-comment' data-comment-id='{$commentId}' title='Edit' aria-label='Edit comment'>[ EDIT ]</button>";
echo "<button type='button' class='comment-action-btn delete-btn' data-action='delete-comment' data-comment-id='{$commentId}' title='Delete' aria-label='Delete comment'>[ DEL ]</button>";
}
echo "</div>";
@@ -422,7 +422,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<h3>Upload Files</h3>
<div class="upload-zone" id="uploadZone">
<div class="upload-zone-content">
<div class="upload-icon">📁</div>
<div class="upload-icon">[ + ]</div>
<p>Drag and drop files here or click to browse</p>
<p class="upload-hint">Max file size: <?php echo $GLOBALS['config']['MAX_UPLOAD_SIZE'] ? number_format($GLOBALS['config']['MAX_UPLOAD_SIZE'] / 1048576, 0) . 'MB' : '10MB'; ?></p>
<input type="file" id="fileInput" multiple class="sr-only" aria-label="Upload files">
@@ -685,7 +685,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="lt-modal-overlay" id="settingsModal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="ticketSettingsTitle">
<div class="lt-modal">
<div class="lt-modal-header">
<span class="lt-modal-title" id="ticketSettingsTitle">⚙ System Preferences</span>
<span class="lt-modal-title" id="ticketSettingsTitle">[ CFG ] SYSTEM PREFERENCES</span>
<button class="lt-modal-close" data-modal-close aria-label="Close settings">✕</button>
</div>