Fix watcher self-notification, unescaped output in views
- NotificationHelper::notifyWatchers: excludeUserId parameter was accepted but never used; actors were notified of their own actions. Fix: add AND tw.user_id != ? clause to watcher query when exclusion is requested. - TicketView.php: formatAction() default case returned raw $event['action_type'] unescaped into HTML context. Fix: wrap with htmlspecialchars(). - Admin views: field_id, recurring_id, template_id, transition_id in data-id attributes were uncast; field_type was unescaped in CustomFieldsView; from/to_status slugs derived from DB values were used directly in class attributes in WorkflowDesignerView. Fix: (int) cast for IDs, htmlspecialchars for field_type, preg_replace to sanitize DB-derived CSS class slugs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -156,10 +156,16 @@ class NotificationHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch watcher usernames
|
||||
// Fetch watcher usernames, excluding the actor so they don't notify themselves
|
||||
if ($excludeUserId !== null) {
|
||||
$sql = "SELECT u.username FROM ticket_watchers tw JOIN users u ON tw.user_id = u.user_id WHERE tw.ticket_id = ? AND tw.user_id != ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("ii", $ticketId, $excludeUserId);
|
||||
} else {
|
||||
$sql = "SELECT u.username FROM ticket_watchers tw JOIN users u ON tw.user_id = u.user_id WHERE tw.ticket_id = ?";
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("i", $ticketId);
|
||||
}
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$stmt->close();
|
||||
|
||||
@@ -62,7 +62,7 @@ function formatAction(array $event): string {
|
||||
}
|
||||
return 'updated this ticket';
|
||||
default:
|
||||
return $event['action_type'];
|
||||
return htmlspecialchars($event['action_type']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<td data-label="Order" class="lt-text-xs lt-text-muted"><?= (int)$field['display_order'] ?></td>
|
||||
<td data-label="Field Name"><code class="lt-text-cyan lt-text-xs"><?= htmlspecialchars($field['field_name']) ?></code></td>
|
||||
<td data-label="Label"><strong><?= htmlspecialchars($field['field_label']) ?></strong></td>
|
||||
<td data-label="Type" class="lt-text-xs"><?= ucfirst($field['field_type']) ?></td>
|
||||
<td data-label="Type" class="lt-text-xs"><?= htmlspecialchars(ucfirst($field['field_type'])) ?></td>
|
||||
<td data-label="Category" class="lt-text-xs"><?= htmlspecialchars($field['category'] ?? 'All') ?></td>
|
||||
<td data-label="Required" class="lt-text-center">
|
||||
<?= $field['is_required'] ? '<span class="lt-text-amber">✓</span>' : '<span class="lt-text-muted">—</span>' ?>
|
||||
@@ -61,9 +61,9 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<td data-label="Actions">
|
||||
<div class="lt-btn-group">
|
||||
<button type="button" class="lt-btn lt-btn-sm"
|
||||
data-action="edit-field" data-id="<?= $field['field_id'] ?>">EDIT</button>
|
||||
data-action="edit-field" data-id="<?= (int)$field['field_id'] ?>">EDIT</button>
|
||||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
|
||||
data-action="delete-field" data-id="<?= $field['field_id'] ?>">DEL</button>
|
||||
data-action="delete-field" data-id="<?= (int)$field['field_id'] ?>">DEL</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -71,13 +71,13 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<td data-label="Actions">
|
||||
<div class="lt-btn-group">
|
||||
<button type="button" class="lt-btn lt-btn-sm"
|
||||
data-action="edit-recurring" data-id="<?= $rt['recurring_id'] ?>">EDIT</button>
|
||||
data-action="edit-recurring" data-id="<?= (int)$rt['recurring_id'] ?>">EDIT</button>
|
||||
<button type="button" class="lt-btn lt-btn-sm"
|
||||
data-action="toggle-recurring" data-id="<?= $rt['recurring_id'] ?>">
|
||||
data-action="toggle-recurring" data-id="<?= (int)$rt['recurring_id'] ?>">
|
||||
<?= $rt['is_active'] ? 'PAUSE' : 'RESUME' ?>
|
||||
</button>
|
||||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
|
||||
data-action="delete-recurring" data-id="<?= $rt['recurring_id'] ?>">DEL</button>
|
||||
data-action="delete-recurring" data-id="<?= (int)$rt['recurring_id'] ?>">DEL</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -56,9 +56,9 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<td data-label="Actions">
|
||||
<div class="lt-btn-group">
|
||||
<button type="button" class="lt-btn lt-btn-sm"
|
||||
data-action="edit-template" data-id="<?= $tpl['template_id'] ?>">EDIT</button>
|
||||
data-action="edit-template" data-id="<?= (int)$tpl['template_id'] ?>">EDIT</button>
|
||||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
|
||||
data-action="delete-template" data-id="<?= $tpl['template_id'] ?>">DEL</button>
|
||||
data-action="delete-template" data-id="<?= (int)$tpl['template_id'] ?>">DEL</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -64,7 +64,7 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<?php if (empty($workflows)): ?>
|
||||
<tr><td colspan="7" class="lt-empty">No transitions defined. Add transitions to enable status changes.</td></tr>
|
||||
<?php else: foreach ($workflows as $wf): ?>
|
||||
<?php $fromSlug = strtolower(str_replace(' ', '-', $wf['from_status'])); $toSlug = strtolower(str_replace(' ', '-', $wf['to_status'])); ?>
|
||||
<?php $fromSlug = preg_replace('/[^a-z-]/', '', strtolower(str_replace(' ', '-', $wf['from_status']))); $toSlug = preg_replace('/[^a-z-]/', '', strtolower(str_replace(' ', '-', $wf['to_status']))); ?>
|
||||
<tr>
|
||||
<td data-label="From">
|
||||
<span class="lt-status lt-status-<?= $fromSlug ?>"><?= htmlspecialchars($wf['from_status']) ?></span>
|
||||
@@ -87,9 +87,9 @@ include __DIR__ . '/../../views/layout_header.php';
|
||||
<td data-label="Actions">
|
||||
<div class="lt-btn-group">
|
||||
<button type="button" class="lt-btn lt-btn-sm"
|
||||
data-action="edit-transition" data-id="<?= $wf['transition_id'] ?>">EDIT</button>
|
||||
data-action="edit-transition" data-id="<?= (int)$wf['transition_id'] ?>">EDIT</button>
|
||||
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
|
||||
data-action="delete-transition" data-id="<?= $wf['transition_id'] ?>">DEL</button>
|
||||
data-action="delete-transition" data-id="<?= (int)$wf['transition_id'] ?>">DEL</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user