false, 'error' => "PHP Error: $errstr", 'file' => basename($errfile), 'line' => $errline ]); exit; }); // Custom exception handler set_exception_handler(function($e) { http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'success' => false, 'error' => 'Exception: ' . $e->getMessage(), 'file' => basename($e->getFile()), 'line' => $e->getLine() ]); exit; }); // Apply rate limiting (also starts session) require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php'; RateLimitMiddleware::apply('api'); // Ensure session is started if (session_status() === PHP_SESSION_NONE) { session_start(); } require_once dirname(__DIR__) . '/config/config.php'; require_once dirname(__DIR__) . '/models/DependencyModel.php'; require_once dirname(__DIR__) . '/models/AuditLogModel.php'; require_once dirname(__DIR__) . '/helpers/ResponseHelper.php'; header('Content-Type: application/json'); // Check authentication if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) { ResponseHelper::unauthorized(); } $userId = $_SESSION['user']['user_id']; // CSRF Protection for POST/DELETE if ($_SERVER['REQUEST_METHOD'] === 'POST' || $_SERVER['REQUEST_METHOD'] === 'DELETE') { require_once dirname(__DIR__) . '/middleware/CsrfMiddleware.php'; $csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (!CsrfMiddleware::validateToken($csrfToken)) { ResponseHelper::forbidden('Invalid CSRF token'); } } // Create database connection $conn = new mysqli( $GLOBALS['config']['DB_HOST'], $GLOBALS['config']['DB_USER'], $GLOBALS['config']['DB_PASS'], $GLOBALS['config']['DB_NAME'] ); if ($conn->connect_error) { ResponseHelper::serverError('Database connection failed'); } try { $dependencyModel = new DependencyModel($conn); $auditLog = new AuditLogModel($conn); } catch (Exception $e) { ResponseHelper::serverError('Failed to initialize models: ' . $e->getMessage()); } $method = $_SERVER['REQUEST_METHOD']; try { switch ($method) { case 'GET': // Get dependencies for a ticket $ticketId = $_GET['ticket_id'] ?? null; if (!$ticketId) { ResponseHelper::error('Ticket ID required'); } try { $dependencies = $dependencyModel->getDependencies($ticketId); $dependents = $dependencyModel->getDependentTickets($ticketId); } catch (Exception $e) { ResponseHelper::serverError('Query error: ' . $e->getMessage()); } ResponseHelper::success([ 'dependencies' => $dependencies, 'dependents' => $dependents ]); break; case 'POST': // Add a new dependency $data = json_decode(file_get_contents('php://input'), true); $ticketId = $data['ticket_id'] ?? null; $dependsOnId = $data['depends_on_id'] ?? null; $type = $data['dependency_type'] ?? 'blocks'; if (!$ticketId || !$dependsOnId) { ResponseHelper::error('Both ticket_id and depends_on_id are required'); } $result = $dependencyModel->addDependency($ticketId, $dependsOnId, $type, $userId); if ($result['success']) { // Log to audit $auditLog->log($userId, 'create', 'dependency', (string)$result['dependency_id'], [ 'ticket_id' => $ticketId, 'depends_on_id' => $dependsOnId, 'type' => $type ]); ResponseHelper::created($result); } else { ResponseHelper::error($result['error']); } break; case 'DELETE': // Remove a dependency $data = json_decode(file_get_contents('php://input'), true); $dependencyId = $data['dependency_id'] ?? null; // Alternative: delete by ticket IDs if (!$dependencyId && isset($data['ticket_id']) && isset($data['depends_on_id'])) { $ticketId = $data['ticket_id']; $dependsOnId = $data['depends_on_id']; $type = $data['dependency_type'] ?? 'blocks'; $result = $dependencyModel->removeDependencyByTickets($ticketId, $dependsOnId, $type); if ($result) { $auditLog->log($userId, 'delete', 'dependency', null, [ 'ticket_id' => $ticketId, 'depends_on_id' => $dependsOnId, 'type' => $type ]); ResponseHelper::success([], 'Dependency removed'); } else { ResponseHelper::error('Failed to remove dependency'); } } elseif ($dependencyId) { $result = $dependencyModel->removeDependency($dependencyId); if ($result) { $auditLog->log($userId, 'delete', 'dependency', (string)$dependencyId); ResponseHelper::success([], 'Dependency removed'); } else { ResponseHelper::error('Failed to remove dependency'); } } else { ResponseHelper::error('Dependency ID or ticket IDs required'); } break; default: ResponseHelper::error('Method not allowed', 405); } } catch (Exception $e) { ResponseHelper::serverError('An error occurred: ' . $e->getMessage()); } $conn->close();