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:
185
index.php
185
index.php
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
// Main entry point for the application
|
||||
require_once 'config/config.php';
|
||||
require_once 'middleware/SecurityHeadersMiddleware.php';
|
||||
require_once 'middleware/AuthMiddleware.php';
|
||||
require_once 'models/AuditLogModel.php';
|
||||
|
||||
// Apply security headers early
|
||||
SecurityHeadersMiddleware::apply();
|
||||
|
||||
// Parse the URL - no need to remove base path since we're at document root
|
||||
$request = $_SERVER['REQUEST_URI'];
|
||||
|
||||
@@ -62,7 +66,186 @@ switch (true) {
|
||||
case $requestPath == '/api/add_comment.php':
|
||||
require_once 'api/add_comment.php';
|
||||
break;
|
||||
|
||||
|
||||
// Admin Routes - require admin privileges
|
||||
case $requestPath == '/admin/recurring-tickets':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
require_once 'models/RecurringTicketModel.php';
|
||||
$recurringModel = new RecurringTicketModel($conn);
|
||||
$recurringTickets = $recurringModel->getAll(true);
|
||||
include 'views/admin/RecurringTicketsView.php';
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/custom-fields':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
require_once 'models/CustomFieldModel.php';
|
||||
$fieldModel = new CustomFieldModel($conn);
|
||||
$customFields = $fieldModel->getAllDefinitions(null, false);
|
||||
include 'views/admin/CustomFieldsView.php';
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/workflow':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
$result = $conn->query("SELECT * FROM status_transitions ORDER BY from_status, to_status");
|
||||
$workflows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$workflows[] = $row;
|
||||
}
|
||||
include 'views/admin/WorkflowDesignerView.php';
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/templates':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
$result = $conn->query("SELECT * FROM ticket_templates ORDER BY template_name");
|
||||
$templates = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$templates[] = $row;
|
||||
}
|
||||
include 'views/admin/TemplatesView.php';
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/audit-log':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
$perPage = 50;
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$filters = [];
|
||||
$whereConditions = [];
|
||||
$params = [];
|
||||
$types = '';
|
||||
|
||||
if (!empty($_GET['action_type'])) {
|
||||
$whereConditions[] = "al.action_type = ?";
|
||||
$params[] = $_GET['action_type'];
|
||||
$types .= 's';
|
||||
$filters['action_type'] = $_GET['action_type'];
|
||||
}
|
||||
if (!empty($_GET['user_id'])) {
|
||||
$whereConditions[] = "al.user_id = ?";
|
||||
$params[] = (int)$_GET['user_id'];
|
||||
$types .= 'i';
|
||||
$filters['user_id'] = $_GET['user_id'];
|
||||
}
|
||||
if (!empty($_GET['date_from'])) {
|
||||
$whereConditions[] = "DATE(al.created_at) >= ?";
|
||||
$params[] = $_GET['date_from'];
|
||||
$types .= 's';
|
||||
$filters['date_from'] = $_GET['date_from'];
|
||||
}
|
||||
if (!empty($_GET['date_to'])) {
|
||||
$whereConditions[] = "DATE(al.created_at) <= ?";
|
||||
$params[] = $_GET['date_to'];
|
||||
$types .= 's';
|
||||
$filters['date_to'] = $_GET['date_to'];
|
||||
}
|
||||
|
||||
$where = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : '';
|
||||
|
||||
$countSql = "SELECT COUNT(*) as total FROM audit_log al $where";
|
||||
if (!empty($params)) {
|
||||
$stmt = $conn->prepare($countSql);
|
||||
$stmt->bind_param($types, ...$params);
|
||||
$stmt->execute();
|
||||
$countResult = $stmt->get_result();
|
||||
} else {
|
||||
$countResult = $conn->query($countSql);
|
||||
}
|
||||
$totalLogs = $countResult->fetch_assoc()['total'];
|
||||
$totalPages = ceil($totalLogs / $perPage);
|
||||
|
||||
$sql = "SELECT al.*, u.display_name, u.username
|
||||
FROM audit_log al
|
||||
LEFT JOIN users u ON al.user_id = u.user_id
|
||||
$where
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT $perPage OFFSET $offset";
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param($types, ...$params);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
} else {
|
||||
$result = $conn->query($sql);
|
||||
}
|
||||
|
||||
$auditLogs = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$auditLogs[] = $row;
|
||||
}
|
||||
|
||||
$usersResult = $conn->query("SELECT user_id, username, display_name FROM users ORDER BY display_name");
|
||||
$users = [];
|
||||
while ($row = $usersResult->fetch_assoc()) {
|
||||
$users[] = $row;
|
||||
}
|
||||
|
||||
include 'views/admin/AuditLogView.php';
|
||||
break;
|
||||
|
||||
case $requestPath == '/admin/user-activity':
|
||||
if (!$currentUser || !$currentUser['is_admin']) {
|
||||
header("HTTP/1.0 403 Forbidden");
|
||||
echo 'Admin access required';
|
||||
break;
|
||||
}
|
||||
|
||||
$dateRange = [
|
||||
'from' => $_GET['date_from'] ?? date('Y-m-d', strtotime('-30 days')),
|
||||
'to' => $_GET['date_to'] ?? date('Y-m-d')
|
||||
];
|
||||
|
||||
$sql = "SELECT
|
||||
u.user_id, u.username, u.display_name, u.is_admin,
|
||||
(SELECT COUNT(*) FROM tickets t WHERE t.created_by = u.user_id AND DATE(t.created_at) BETWEEN ? AND ?) as tickets_created,
|
||||
(SELECT COUNT(*) FROM tickets t WHERE t.assigned_to = u.user_id AND t.status = 'Closed' AND DATE(t.updated_at) BETWEEN ? AND ?) as tickets_resolved,
|
||||
(SELECT COUNT(*) FROM ticket_comments tc WHERE tc.user_id = u.user_id AND DATE(tc.created_at) BETWEEN ? AND ?) as comments_added,
|
||||
(SELECT COUNT(*) FROM tickets t WHERE t.assigned_to = u.user_id AND DATE(t.created_at) BETWEEN ? AND ?) as tickets_assigned,
|
||||
(SELECT MAX(al.created_at) FROM audit_log al WHERE al.user_id = u.user_id) as last_activity
|
||||
FROM users u
|
||||
WHERE u.is_active = 1
|
||||
ORDER BY tickets_created DESC, tickets_resolved DESC";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param('ssssssss',
|
||||
$dateRange['from'], $dateRange['to'],
|
||||
$dateRange['from'], $dateRange['to'],
|
||||
$dateRange['from'], $dateRange['to'],
|
||||
$dateRange['from'], $dateRange['to']
|
||||
);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$userStats = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$userStats[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
include 'views/admin/UserActivityView.php';
|
||||
break;
|
||||
|
||||
// Legacy support for old URLs
|
||||
case $requestPath == '/dashboard.php':
|
||||
header("Location: /");
|
||||
|
||||
Reference in New Issue
Block a user