false, 'error' => 'Method not allowed']); } $data = json_decode(file_get_contents('php://input'), true); $ticketId = isset($data['ticket_id']) ? trim((string)$data['ticket_id']) : ''; $values = is_array($data['values'] ?? null) ? $data['values'] : []; if ($ticketId === '') { http_response_code(400); apiRespond(['success' => false, 'error' => 'ticket_id required']); } $ticketModel = new TicketModel($conn); $ticket = $ticketModel->getTicketById($ticketId); if (!$ticket || !$ticketModel->canUserAccessTicket($ticket, $currentUser)) { http_response_code(404); apiRespond(['success' => false, 'error' => 'Ticket not found']); } $fieldModel = new CustomFieldModel($conn); $definitions = $fieldModel->getAllDefinitions($ticket['category'], true); $errors = []; $toSave = []; foreach ($definitions as $def) { $fieldId = (int)$def['field_id']; $raw = $values[$fieldId] ?? ($values[(string)$fieldId] ?? null); if ($def['field_type'] === 'checkbox') { $normalized = !empty($raw) ? '1' : '0'; } else { $normalized = is_scalar($raw) ? trim((string)$raw) : ''; } if (!empty($def['is_required']) && $def['field_type'] !== 'checkbox' && $normalized === '') { $errors[] = $def['field_label'] . ' is required'; continue; } if ($def['field_type'] === 'select' && $normalized !== '') { $allowedOptions = $def['field_options']['options'] ?? []; if (!in_array($normalized, $allowedOptions, true)) { $errors[] = $def['field_label'] . ' has an invalid selection'; continue; } } if ($def['field_type'] === 'number' && $normalized !== '' && !is_numeric($normalized)) { $errors[] = $def['field_label'] . ' must be a number'; continue; } $toSave[$fieldId] = $normalized; } if (!empty($errors)) { http_response_code(422); apiRespond(['success' => false, 'error' => implode('; ', $errors)]); } $fieldModel->setValues($ticketId, $toSave); require_once dirname(__DIR__) . '/models/AuditLogModel.php'; (new AuditLogModel($conn))->log($userId, 'update', 'ticket', $ticketId, [ 'reason' => 'custom fields updated', ]); apiRespond(['success' => true]);