false, 'error' => 'A server error occurred' ]); } }); ini_set('display_errors', 0); error_reporting(E_ALL); // Custom error handler set_error_handler(function($errno, $errstr, $errfile, $errline) { // Log detailed error server-side error_log("PHP Error in ticket_dependencies.php: $errstr in $errfile:$errline"); ob_end_clean(); http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'success' => false, 'error' => 'A server error occurred' ]); exit; }); // Custom exception handler set_exception_handler(function($e) { // Log detailed error server-side error_log('Exception in ticket_dependencies.php: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); ob_end_clean(); http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'success' => false, 'error' => 'A server error occurred' ]); 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'); } // Check if ticket_dependencies table exists $tableCheck = $conn->query("SHOW TABLES LIKE 'ticket_dependencies'"); if ($tableCheck->num_rows === 0) { ResponseHelper::serverError('Ticket dependencies feature not available. The ticket_dependencies table does not exist. Please run the migration.'); } try { $dependencyModel = new DependencyModel($conn); $auditLog = new AuditLogModel($conn); } catch (Exception $e) { error_log('Failed to initialize models in ticket_dependencies.php: ' . $e->getMessage()); ResponseHelper::serverError('Failed to initialize required components'); } $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) { error_log('Query error in ticket_dependencies.php GET: ' . $e->getMessage()); ResponseHelper::serverError('Failed to retrieve dependencies'); } 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) { // Log detailed error server-side error_log('Ticket dependencies API error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); ResponseHelper::serverError('An error occurred while processing the dependency request'); } $conn->close();