false, 'error' => $message]); exit; } // CSRF via header $csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; error_log('[relink] ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | csrf=' . (isset($_SESSION['csrf_token']) ? 'set' : 'missing') . ' | header=' . (strlen($csrfHeader) > 0 ? substr($csrfHeader, 0, 8) . '...' : 'empty') . ' | body_len=' . strlen(file_get_contents('php://input'))); if (!isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $csrfHeader)) { relinkError(403, 'Token CSRF invalide.'); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { relinkError(405, 'Méthode non autorisée.'); } $body = json_decode(file_get_contents('php://input'), true); if (!is_array($body)) { relinkError(400, 'JSON invalide.'); } $thesisId = filter_var($body['thesis_id'] ?? '', FILTER_VALIDATE_INT); $filePath = trim($body['file_path'] ?? ''); $fileName = trim($body['file_name'] ?? basename($filePath)); $fileSize = (int)($body['file_size'] ?? 0); $fileType = trim($body['file_type'] ?? ''); $queueType = trim($body['queue_type'] ?? ''); $mimeType = trim($body['mime_type'] ?? 'application/octet-stream'); if (!$thesisId || $filePath === '') { relinkError(400, 'Paramètres invalides (thesis_id + file_path requis).'); } // Security: only allow paths under tfe/ these/ frart/ documents/ or theses/ if (!preg_match('#^(tfe|these|frart|documents|theses)/#', $filePath)) { relinkError(403, 'Chemin de fichier non autorisé.'); } $absPath = STORAGE_ROOT . '/' . $filePath; $realPath = realpath($absPath); $realStorage = realpath(STORAGE_ROOT); if ($realPath === false || !str_starts_with($realPath, $realStorage)) { error_log('[relink] FILE NOT FOUND | absPath=' . $absPath . ' | realPath=' . var_export($realPath, true) . ' | realStorage=' . $realStorage); relinkError(404, 'Fichier introuvable ou chemin interdit.'); } if (!is_file($realPath)) { relinkError(404, 'Le chemin ne pointe pas vers un fichier.'); } // Detect MIME if not provided if ($mimeType === 'application/octet-stream' && class_exists('finfo')) { $finfo = new finfo(FILEINFO_MIME_TYPE); $detected = $finfo->file($realPath); if ($detected !== false && $detected !== '') { $mimeType = $detected; } } // Map queue_type to file_type if not explicitly given if ($fileType === '') { $fileTypeMap = [ 'cover' => 'cover', 'note_intention' => 'note_intention', 'tfe' => 'main', 'annexe' => 'annex', ]; $fileType = $fileTypeMap[$queueType] ?? 'main'; } require_once APP_ROOT . '/src/Database.php'; $db = Database::getInstance(); // Check if this file is already linked to this thesis (avoid duplicate) $pdo = $db->getConnection(); $stmt = $pdo->prepare('SELECT id FROM thesis_files WHERE thesis_id = ? AND file_path = ?'); $stmt->execute([$thesisId, $filePath]); if ($stmt->fetch()) { relinkError(409, 'Ce fichier est déjà lié à ce TFE.'); } $db->insertThesisFile( $thesisId, $fileType, $filePath, $fileName, $fileSize, $mimeType, null, null ); // Get the new file's ID $newId = $pdo->lastInsertId(); error_log("[relink] thesis_id=$thesisId file_path=$filePath file_type=$fileType new_id=$newId"); header('Content-Type: application/json'); echo json_encode([ 'ok' => true, 'id' => (int)$newId, 'message' => 'Fichier relié avec succès.', ]); exit;