Fix CSP blocking inline handlers - add unsafe-inline fallback

- Refactored TicketView.php to use event listeners instead of onclick
- Added unsafe-inline to CSP as fallback for legacy handlers in other views
- TODO: Complete refactoring of DashboardView and admin views

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 10:42:09 -05:00
parent 674a427edb
commit 55209e0b05
2 changed files with 122 additions and 26 deletions

View File

@@ -87,7 +87,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<?php if ($GLOBALS['currentUser']['is_admin']): ?>
<span class="admin-badge">Admin</span>
<?php endif; ?>
<button class="settings-icon" title="Settings (Alt+S)" onclick="openSettingsModal()">⚙</button>
<button class="settings-icon" title="Settings (Alt+S)" id="settingsBtn">⚙</button>
<?php endif; ?>
</div>
</div>
@@ -226,7 +226,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<?php endforeach; ?>
</select>
</div>
<button id="editButton" class="btn" onclick="toggleEditMode()">Edit Ticket</button>
<button id="editButton" class="btn">Edit Ticket</button>
</div>
</div>
</div>
@@ -240,11 +240,11 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-section-header">Content Sections</div>
<div class="ascii-content">
<div class="ticket-tabs">
<button class="tab-btn active" onclick="showTab('description')">Description</button>
<button class="tab-btn" onclick="showTab('comments')">Comments</button>
<button class="tab-btn" onclick="showTab('attachments')">Attachments</button>
<button class="tab-btn" onclick="showTab('dependencies')">Dependencies</button>
<button class="tab-btn" onclick="showTab('activity')">Activity</button>
<button class="tab-btn active" data-tab="description">Description</button>
<button class="tab-btn" data-tab="comments">Comments</button>
<button class="tab-btn" data-tab="attachments">Attachments</button>
<button class="tab-btn" data-tab="dependencies">Dependencies</button>
<button class="tab-btn" data-tab="activity">Activity</button>
</div>
</div>
@@ -288,7 +288,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<span class="toggle-label">Preview Markdown</span>
</div>
</div>
<button onclick="addComment()" class="btn">Add Comment</button>
<button id="addCommentBtn" class="btn">Add Comment</button>
</div>
<div id="markdownPreview" class="markdown-preview" style="display: none;"></div>
</div>
@@ -320,8 +320,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
// Edit/Delete buttons for owner or admin
if ($canModify) {
echo "<div class='comment-actions'>";
echo "<button type='button' class='comment-action-btn edit-btn' onclick='editComment({$commentId})' title='Edit comment'>✏️</button>";
echo "<button type='button' class='comment-action-btn delete-btn' onclick='deleteComment({$commentId})' title='Delete comment'>🗑️</button>";
echo "<button type='button' class='comment-action-btn edit-btn' data-action='edit-comment' data-comment-id='{$commentId}' title='Edit comment'>✏️</button>";
echo "<button type='button' class='comment-action-btn delete-btn' data-action='delete-comment' data-comment-id='{$commentId}' title='Delete comment'>🗑️</button>";
echo "</div>";
}
@@ -357,7 +357,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<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 style="display: none;">
<button type="button" onclick="document.getElementById('fileInput').click();" class="btn" style="margin-top: 1rem;">Browse Files</button>
<button type="button" id="browseFilesBtn" class="btn" style="margin-top: 1rem;">Browse Files</button>
</div>
</div>
<div id="uploadProgress" style="display: none; margin-top: 1rem;">
@@ -393,7 +393,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<option value="relates_to">Relates To</option>
<option value="duplicates">Duplicates</option>
</select>
<button onclick="addDependency()" class="btn">Add</button>
<button id="addDependencyBtn" class="btn">Add</button>
</div>
</div>
@@ -447,30 +447,124 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div>
<!-- END OUTER FRAME -->
<script nonce="<?php echo $nonce; ?>">
// Initialize the ticket view
// Initialize the ticket view and attach event listeners (CSP-compliant)
document.addEventListener('DOMContentLoaded', function() {
// Ticket data alias for compatibility
window.ticketData.id = window.ticketData.ticket_id;
// Initialize with description tab
if (typeof showTab === 'function') {
showTab('description');
} else {
console.error('showTab function not defined');
}
// Tab buttons - use event delegation
document.querySelectorAll('.tab-btn[data-tab]').forEach(function(btn) {
btn.addEventListener('click', function() {
var tab = this.getAttribute('data-tab');
if (typeof showTab === 'function') {
showTab(tab);
}
});
});
// Settings button
var settingsBtn = document.getElementById('settingsBtn');
if (settingsBtn) {
settingsBtn.addEventListener('click', function() {
if (typeof openSettingsModal === 'function') openSettingsModal();
});
}
// Edit button
var editBtn = document.getElementById('editButton');
if (editBtn) {
editBtn.addEventListener('click', function() {
if (typeof toggleEditMode === 'function') toggleEditMode();
});
}
// Add comment button
var addCommentBtn = document.getElementById('addCommentBtn');
if (addCommentBtn) {
addCommentBtn.addEventListener('click', function() {
if (typeof addComment === 'function') addComment();
});
}
// Comment edit/delete buttons - use event delegation
document.addEventListener('click', function(e) {
var target = e.target.closest('[data-action]');
if (!target) return;
var action = target.getAttribute('data-action');
var commentId = target.getAttribute('data-comment-id');
if (action === 'edit-comment' && commentId) {
if (typeof editComment === 'function') editComment(parseInt(commentId));
} else if (action === 'delete-comment' && commentId) {
if (typeof deleteComment === 'function') deleteComment(parseInt(commentId));
}
});
// Browse files button
var browseFilesBtn = document.getElementById('browseFilesBtn');
if (browseFilesBtn) {
browseFilesBtn.addEventListener('click', function() {
document.getElementById('fileInput').click();
});
}
// Add dependency button
var addDepBtn = document.getElementById('addDependencyBtn');
if (addDepBtn) {
addDepBtn.addEventListener('click', function() {
if (typeof addDependency === 'function') addDependency();
});
}
// Settings modal buttons
var closeSettingsBtn = document.getElementById('closeSettingsBtn');
if (closeSettingsBtn) {
closeSettingsBtn.addEventListener('click', function() {
if (typeof closeSettingsModal === 'function') closeSettingsModal();
});
}
var saveSettingsBtn = document.getElementById('saveSettingsBtn');
if (saveSettingsBtn) {
saveSettingsBtn.addEventListener('click', function() {
if (typeof saveSettings === 'function') saveSettings();
});
}
var cancelSettingsBtn = document.getElementById('cancelSettingsBtn');
if (cancelSettingsBtn) {
cancelSettingsBtn.addEventListener('click', function() {
if (typeof closeSettingsModal === 'function') closeSettingsModal();
});
}
// Settings modal backdrop click
var settingsModal = document.getElementById('settingsModal');
if (settingsModal) {
settingsModal.addEventListener('click', function(e) {
if (e.target.classList.contains('settings-modal')) {
if (typeof closeSettingsModal === 'function') closeSettingsModal();
}
});
}
});
</script>
<script nonce="<?php echo $nonce; ?>">
// Ticket data already initialized in head, add id alias for compatibility
window.ticketData.id = window.ticketData.ticket_id;
console.log('Ticket data loaded:', window.ticketData);
</script>
<!-- Settings Modal (same as dashboard) -->
<div class="settings-modal" id="settingsModal" style="display: none;" onclick="closeOnBackdropClick(event)">
<div class="settings-modal" id="settingsModal" style="display: none;">
<div class="settings-content">
<span class="bottom-left-corner">╚</span>
<span class="bottom-right-corner">╝</span>
<div class="settings-header">
<h3>⚙ System Preferences</h3>
<button class="close-settings" onclick="closeSettingsModal()">✗</button>
<button class="close-settings" id="closeSettingsBtn">✗</button>
</div>
<div class="settings-body">
@@ -592,8 +686,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div>
<div class="settings-footer">
<button class="btn btn-primary" onclick="saveSettings()">Save Preferences</button>
<button class="btn btn-secondary" onclick="closeSettingsModal()">Cancel</button>
<button class="btn btn-primary" id="saveSettingsBtn">Save Preferences</button>
<button class="btn btn-secondary" id="cancelSettingsBtn">Cancel</button>
</div>
</div>
</div>