Fix data-layer bugs: bind_param fatals, ticket_id bindings, cache poisoning
- CustomFieldModel: assign ?? fallbacks to variables before bind_param
(by-reference args cannot be ?? expressions; fatal on PHP 8.2, custom
fields were uncreatable/uneditable)
- RecurringTicketModel::create: fix swapped bind type for schedule_type
(enum bound as int coerced 'daily' to 0, breaking the cron)
- TicketModel/CommentModel: bind varchar ticket_id as string not int so
the unique index is usable and leading-zero IDs match; ticket_watchers
(int column) left as integer
- TicketModel::deleteTicket: delete from custom_field_values (real table)
not the nonexistent ticket_custom_fields
- TicketModel search: honor literal '0'; never emit AGAINST('*') on
all-special-char input (fall back to LIKE)
- TicketModel::updateTicket: disambiguate not-found vs no-op vs genuine
optimistic-lock conflict on zero affected rows
- WorkflowModel: do not cache transitions/statuses on DB failure (a
transient error no longer blocks all status changes for the TTL)
- DependencyModel: filter linked tickets by visibility (new optional user
context params) to stop confidential metadata leaking via dependencies
- BulkOperationsModel: validate status/priority/assignee before mutating
- AuditLogModel: gate getClientIP forwarded headers on trusted proxies;
add missing action/entity types so audit-log filters work
- WorkflowModel: add transitionRequiresComment() accessor for enforcement
- CommentModel: stop leaking raw DB errors to clients (log instead)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,25 +12,67 @@ class DependencyModel
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the extra WHERE fragment (and bound params) that restricts the joined
|
||||
* ticket alias `t` to tickets the requesting user may see. Reuses
|
||||
* TicketModel::getVisibilityFilter so the rules stay in one place.
|
||||
*
|
||||
* @return array{sql:string,types:string,params:array}
|
||||
*/
|
||||
private function buildVisibilityClause($userId, array $userGroups, $isAdmin): array
|
||||
{
|
||||
if ($isAdmin) {
|
||||
return ['sql' => '', 'types' => '', 'params' => []];
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/models/TicketModel.php';
|
||||
$ticketModel = new TicketModel($this->conn);
|
||||
$filter = $ticketModel->getVisibilityFilter([
|
||||
'user_id' => (int)$userId,
|
||||
'groups' => implode(',', $userGroups),
|
||||
'is_admin' => false,
|
||||
]);
|
||||
|
||||
if ($filter['sql'] === '1=1' || $filter['sql'] === '') {
|
||||
return ['sql' => '', 'types' => '', 'params' => []];
|
||||
}
|
||||
|
||||
return [
|
||||
'sql' => ' AND ' . $filter['sql'],
|
||||
'types' => $filter['types'],
|
||||
'params' => $filter['params'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dependencies for a ticket
|
||||
*
|
||||
* The linked ticket's title/status/priority are only returned for tickets the
|
||||
* requesting user is allowed to see (same rules as TicketModel::getVisibilityFilter).
|
||||
* With the default (null user, non-admin) only public tickets are exposed.
|
||||
*
|
||||
* @param string $ticketId Ticket ID
|
||||
* @param int|null $userId Requesting user's ID (null = anonymous)
|
||||
* @param array $userGroups Requesting user's group names
|
||||
* @param bool $isAdmin Whether the requesting user is an admin (bypasses filtering)
|
||||
* @return array Dependencies grouped by type
|
||||
*/
|
||||
public function getDependencies($ticketId)
|
||||
public function getDependencies($ticketId, $userId = null, array $userGroups = [], $isAdmin = false)
|
||||
{
|
||||
$visibility = $this->buildVisibilityClause($userId, $userGroups, $isAdmin);
|
||||
|
||||
$sql = "SELECT d.*, t.title, t.status, t.priority
|
||||
FROM ticket_dependencies d
|
||||
LEFT JOIN tickets t ON d.depends_on_id = t.ticket_id
|
||||
WHERE d.ticket_id = ?
|
||||
WHERE d.ticket_id = ?" . $visibility['sql'] . "
|
||||
ORDER BY d.dependency_type, d.created_at DESC";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
throw new Exception('Prepare failed: ' . $this->conn->error);
|
||||
}
|
||||
$stmt->bind_param("s", $ticketId);
|
||||
$types = 's' . $visibility['types'];
|
||||
$stmt->bind_param($types, $ticketId, ...$visibility['params']);
|
||||
if (!$stmt->execute()) {
|
||||
throw new Exception('Execute failed: ' . $stmt->error);
|
||||
}
|
||||
@@ -54,22 +96,32 @@ class DependencyModel
|
||||
/**
|
||||
* Get tickets that depend on this ticket
|
||||
*
|
||||
* The linked ticket's title/status/priority are only returned for tickets the
|
||||
* requesting user is allowed to see (same rules as TicketModel::getVisibilityFilter).
|
||||
* With the default (null user, non-admin) only public tickets are exposed.
|
||||
*
|
||||
* @param string $ticketId Ticket ID
|
||||
* @param int|null $userId Requesting user's ID (null = anonymous)
|
||||
* @param array $userGroups Requesting user's group names
|
||||
* @param bool $isAdmin Whether the requesting user is an admin (bypasses filtering)
|
||||
* @return array Dependent tickets
|
||||
*/
|
||||
public function getDependentTickets($ticketId)
|
||||
public function getDependentTickets($ticketId, $userId = null, array $userGroups = [], $isAdmin = false)
|
||||
{
|
||||
$visibility = $this->buildVisibilityClause($userId, $userGroups, $isAdmin);
|
||||
|
||||
$sql = "SELECT d.*, t.title, t.status, t.priority
|
||||
FROM ticket_dependencies d
|
||||
LEFT JOIN tickets t ON d.ticket_id = t.ticket_id
|
||||
WHERE d.depends_on_id = ?
|
||||
WHERE d.depends_on_id = ?" . $visibility['sql'] . "
|
||||
ORDER BY d.dependency_type, d.created_at DESC";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
if (!$stmt) {
|
||||
throw new Exception('Prepare failed: ' . $this->conn->error);
|
||||
}
|
||||
$stmt->bind_param("s", $ticketId);
|
||||
$types = 's' . $visibility['types'];
|
||||
$stmt->bind_param($types, $ticketId, ...$visibility['params']);
|
||||
if (!$stmt->execute()) {
|
||||
throw new Exception('Execute failed: ' . $stmt->error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user