Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2): - Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc. - Add RateLimitMiddleware for API rate limiting - Add security event logging to AuditLogModel - Add ResponseHelper for standardized API responses - Update config.php with security constants Database (Phase 3): - Add migration 014 for additional indexes - Add migration 015 for ticket dependencies - Add migration 016 for ticket attachments - Add migration 017 for recurring tickets - Add migration 018 for custom fields Features (Phase 4-5): - Add ticket dependencies with DependencyModel and API - Add duplicate detection with check_duplicates API - Add file attachments with AttachmentModel and upload/download APIs - Add @mentions with autocomplete and highlighting - Add quick actions on dashboard rows Collaboration (Phase 5): - Add mention extraction in CommentModel - Add mention autocomplete dropdown in ticket.js - Add mention highlighting CSS styles Admin & Export (Phase 6): - Add StatsModel for dashboard widgets - Add dashboard stats cards (open, critical, unassigned, etc.) - Add CSV/JSON export via export_tickets API - Add rich text editor toolbar in markdown.js - Add RecurringTicketModel with cron job - Add CustomFieldModel for per-category fields - Add admin views: RecurringTickets, CustomFields, Workflow, Templates, AuditLog, UserActivity - Add admin APIs: manage_workflows, manage_templates, manage_recurring, custom_fields, get_users - Add admin routes in index.php Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
230
models/CustomFieldModel.php
Normal file
230
models/CustomFieldModel.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* CustomFieldModel - Manages custom field definitions and values
|
||||
*/
|
||||
|
||||
class CustomFieldModel {
|
||||
private $conn;
|
||||
|
||||
public function __construct($conn) {
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Field Definitions
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get all field definitions
|
||||
*/
|
||||
public function getAllDefinitions($category = null, $activeOnly = true) {
|
||||
$sql = "SELECT * FROM custom_field_definitions WHERE 1=1";
|
||||
$params = [];
|
||||
$types = '';
|
||||
|
||||
if ($activeOnly) {
|
||||
$sql .= " AND is_active = 1";
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$sql .= " AND (category = ? OR category IS NULL)";
|
||||
$params[] = $category;
|
||||
$types .= 's';
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY display_order ASC, field_id ASC";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param($types, ...$params);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
} else {
|
||||
$result = $this->conn->query($sql);
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
if ($row['field_options']) {
|
||||
$row['field_options'] = json_decode($row['field_options'], true);
|
||||
}
|
||||
$fields[] = $row;
|
||||
}
|
||||
|
||||
if (isset($stmt)) {
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single field definition
|
||||
*/
|
||||
public function getDefinition($fieldId) {
|
||||
$sql = "SELECT * FROM custom_field_definitions WHERE field_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('i', $fieldId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if ($row && $row['field_options']) {
|
||||
$row['field_options'] = json_decode($row['field_options'], true);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new field definition
|
||||
*/
|
||||
public function createDefinition($data) {
|
||||
$options = null;
|
||||
if (isset($data['field_options']) && !empty($data['field_options'])) {
|
||||
$options = json_encode($data['field_options']);
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO custom_field_definitions
|
||||
(field_name, field_label, field_type, field_options, category, is_required, display_order, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('sssssiii',
|
||||
$data['field_name'],
|
||||
$data['field_label'],
|
||||
$data['field_type'],
|
||||
$options,
|
||||
$data['category'],
|
||||
$data['is_required'] ?? 0,
|
||||
$data['display_order'] ?? 0,
|
||||
$data['is_active'] ?? 1
|
||||
);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$id = $this->conn->insert_id;
|
||||
$stmt->close();
|
||||
return ['success' => true, 'field_id' => $id];
|
||||
}
|
||||
|
||||
$error = $stmt->error;
|
||||
$stmt->close();
|
||||
return ['success' => false, 'error' => $error];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a field definition
|
||||
*/
|
||||
public function updateDefinition($fieldId, $data) {
|
||||
$options = null;
|
||||
if (isset($data['field_options']) && !empty($data['field_options'])) {
|
||||
$options = json_encode($data['field_options']);
|
||||
}
|
||||
|
||||
$sql = "UPDATE custom_field_definitions SET
|
||||
field_name = ?, field_label = ?, field_type = ?, field_options = ?,
|
||||
category = ?, is_required = ?, display_order = ?, is_active = ?
|
||||
WHERE field_id = ?";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('sssssiiiii',
|
||||
$data['field_name'],
|
||||
$data['field_label'],
|
||||
$data['field_type'],
|
||||
$options,
|
||||
$data['category'],
|
||||
$data['is_required'] ?? 0,
|
||||
$data['display_order'] ?? 0,
|
||||
$data['is_active'] ?? 1,
|
||||
$fieldId
|
||||
);
|
||||
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
return ['success' => $success];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a field definition
|
||||
*/
|
||||
public function deleteDefinition($fieldId) {
|
||||
// This will cascade delete all values due to FK constraint
|
||||
$sql = "DELETE FROM custom_field_definitions WHERE field_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('i', $fieldId);
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
return ['success' => $success];
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Field Values
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get all field values for a ticket
|
||||
*/
|
||||
public function getValuesForTicket($ticketId) {
|
||||
$sql = "SELECT cfv.*, cfd.field_name, cfd.field_label, cfd.field_type, cfd.field_options
|
||||
FROM custom_field_values cfv
|
||||
JOIN custom_field_definitions cfd ON cfv.field_id = cfd.field_id
|
||||
WHERE cfv.ticket_id = ?
|
||||
ORDER BY cfd.display_order ASC";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('s', $ticketId);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$values = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
if ($row['field_options']) {
|
||||
$row['field_options'] = json_decode($row['field_options'], true);
|
||||
}
|
||||
$values[$row['field_name']] = $row;
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a field value for a ticket (insert or update)
|
||||
*/
|
||||
public function setValue($ticketId, $fieldId, $value) {
|
||||
$sql = "INSERT INTO custom_field_values (ticket_id, field_id, field_value)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE field_value = VALUES(field_value), updated_at = CURRENT_TIMESTAMP";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('sis', $ticketId, $fieldId, $value);
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
return ['success' => $success];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple field values for a ticket
|
||||
*/
|
||||
public function setValues($ticketId, $values) {
|
||||
$results = [];
|
||||
foreach ($values as $fieldId => $value) {
|
||||
$results[$fieldId] = $this->setValue($ticketId, $fieldId, $value);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all field values for a ticket
|
||||
*/
|
||||
public function deleteValuesForTicket($ticketId) {
|
||||
$sql = "DELETE FROM custom_field_values WHERE ticket_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('s', $ticketId);
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
return ['success' => $success];
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user