false, 'error' => 'Accès refusé']); exit; } $id_evaluation = isset($_GET['id_evaluation']) ? (int)$_GET['id_evaluation'] : 0; if ($id_evaluation <= 0) { echo json_encode(['success' => false, 'error' => 'ID évaluation invalide']); exit; } try { $db = Database::getInstance()->getConnection(); // Récupérer questions (AVEC type_question - FIX 1) $stmt = $db->prepare(" SELECT id_question, ordre, type_question, enonce, reponse_correcte_json, options_json FROM questions WHERE id_evaluation = ? ORDER BY ordre ASC "); $stmt->execute([$id_evaluation]); $questions = $stmt->fetchAll(PDO::FETCH_ASSOC); $nb_questions = count($questions); // Récupérer tentatives avec statut connexion $stmt = $db->prepare(" SELECT te.id_tentative, te.id_eleve, te.statut, te.reponses_json, te.date_debut, te.date_fin, te.derniere_sauvegarde, te.note, u.nom, u.prenom, u.login, c.nom_classe, TIMESTAMPDIFF(SECOND, te.date_debut, NOW()) as secondes_ecoulees, TIMESTAMPDIFF(SECOND, COALESCE(te.derniere_sauvegarde, te.date_debut), NOW() ) as secondes_inactif, CASE WHEN te.statut = 'terminee' THEN 'termine' WHEN TIMESTAMPDIFF(SECOND, COALESCE(te.derniere_sauvegarde, te.date_debut), NOW()) > 300 THEN 'hors_ligne' WHEN TIMESTAMPDIFF(SECOND, COALESCE(te.derniere_sauvegarde, te.date_debut), NOW()) > 180 THEN 'inactif' WHEN TIMESTAMPDIFF(SECOND, COALESCE(te.derniere_sauvegarde, te.date_debut), NOW()) > 60 THEN 'en_cours' ELSE 'en_ligne' END as statut_connexion FROM tentatives_eleves te JOIN utilisateurs u ON te.id_eleve = u.id_utilisateur LEFT JOIN classes c ON u.id_classe = c.id_classe WHERE te.id_evaluation = ? ORDER BY FIELD(statut_connexion, 'en_ligne', 'en_cours', 'inactif', 'hors_ligne', 'termine'), te.derniere_sauvegarde DESC, u.nom, u.prenom "); $stmt->execute([$id_evaluation]); $tentatives = $stmt->fetchAll(PDO::FETCH_ASSOC); // Calculer statistiques $stats = [ 'total' => count($tentatives), 'en_ligne' => 0, 'en_cours' => 0, 'inactif' => 0, 'hors_ligne' => 0, 'termine' => 0 ]; foreach ($tentatives as $t) { $stats[$t['statut_connexion']]++; } // Formater données pour frontend $tentatives_formatted = array_map(function($t) use ($questions, $nb_questions) { $reponses = json_decode($t['reponses_json'] ?? '{}', true) ?: []; $nb_reponses = count(array_filter($reponses, function($r) { return $r !== null && $r !== ''; })); $pourcentage = $nb_questions > 0 ? round(($nb_reponses / $nb_questions) * 100) : 0; // Calculer temps écoulé $heures = floor($t['secondes_ecoulees'] / 3600); $minutes = floor(($t['secondes_ecoulees'] % 3600) / 60); $temps_ecoule = sprintf("%d:%02d", $heures, $minutes); // Dernière activité $derniere_activite = ''; if ($t['statut_connexion'] === 'termine') { $derniere_activite = '✅ Terminé'; } else if ($t['secondes_inactif'] < 60) { $derniere_activite = 'À l\'instant'; } else if ($t['secondes_inactif'] < 3600) { $min_inactif = floor($t['secondes_inactif'] / 60); $derniere_activite = "Il y a {$min_inactif} min"; } else { $h_inactif = floor($t['secondes_inactif'] / 3600); $derniere_activite = "Il y a {$h_inactif}h"; } // Détails réponses pour chaque question $reponses_details = []; foreach ($questions as $q) { $reponse_eleve = $reponses[$q['id_question']] ?? null; if ($reponse_eleve === null || $reponse_eleve === '') { $reponses_details[] = [ 'icone' => '⬜', 'tooltip' => 'Pas encore répondu' ]; } else { // Vérifier si réponse correcte (FIX 2 - utiliser nouvelle fonction alignée) $est_correcte = verifierReponseCorrecte($q, $reponse_eleve); $reponses_details[] = [ 'icone' => $est_correcte ? '✅' : '❌', 'tooltip' => $est_correcte ? 'Réponse correcte' : 'Réponse incorrecte' ]; } } return [ 'id_tentative' => $t['id_tentative'], 'nom' => $t['nom'], 'prenom' => $t['prenom'], 'classe' => $t['nom_classe'] ?? 'Sans classe', 'statut_connexion' => $t['statut_connexion'], 'nb_reponses' => $nb_reponses, 'pourcentage_progression' => $pourcentage, 'temps_ecoule' => $temps_ecoule, 'derniere_activite' => $derniere_activite, 'reponses_details' => $reponses_details ]; }, $tentatives); // Réponse JSON echo json_encode([ 'success' => true, 'stats' => $stats, 'tentatives' => $tentatives_formatted, 'timestamp' => date('Y-m-d H:i:s') ]); } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => $e->getMessage() ]); } /** * FIX 2: Fonction vérification ALIGNÉE sur soumettre_evaluation.php (ligne 100-188) * * Changements principaux: * - SELECT: Utilise reponse_correcte_json['id'] au lieu de options['est_correcte'] * - Accepte ID ou texte comme correction réelle * - QCM: Utilise 'reponses' au lieu de 'options' * - CHECKBOX: Compare textes au lieu de IDs */ function verifierReponseCorrecte($question, $reponse_eleve) { if (empty($reponse_eleve) && $reponse_eleve !== '0' && $reponse_eleve !== 0) { return false; } $type = $question['type_question']; $options = json_decode($question['options_json'] ?? '{}', true); $reponse_correcte_data = json_decode($question['reponse_correcte_json'] ?? '{}', true); // Type NUMBER if ($type === 'number') { $reponse_correcte = $reponse_correcte_data['reponse'] ?? null; if ($reponse_correcte === null) { return false; } return abs((float)$reponse_eleve - (float)$reponse_correcte) < 0.01; } // Type TEXT if ($type === 'text') { $reponse_correcte = $reponse_correcte_data['reponse'] ?? null; if ($reponse_correcte === null) { return false; } return strtolower(trim($reponse_eleve)) === strtolower(trim($reponse_correcte)); } // Type SELECT (FIX MAJEUR) if ($type === 'select') { // Réponse correcte dans reponse_correcte_json: {"id": "opt2"} $id_reponse_correcte = $reponse_correcte_data['id'] ?? null; if (!$id_reponse_correcte || !isset($options['options'])) { return false; } // Trouver le texte correspondant à l'ID correct $texte_correct = null; foreach ($options['options'] as $opt) { if (($opt['id'] ?? '') === $id_reponse_correcte) { $texte_correct = $opt['texte'] ?? null; break; } } // Vérifier si réponse élève correspond (ID OU texte - comme correction réelle) return ($reponse_eleve === $id_reponse_correcte || $reponse_eleve === $texte_correct); } // Type QCM (utilise 'reponses' pas 'options') if ($type === 'qcm') { if (!isset($options['reponses'])) { return false; } // Trouver la réponse correcte dans le tableau reponses $reponse_correcte = null; foreach ($options['reponses'] as $rep) { if (isset($rep['est_correcte']) && $rep['est_correcte'] === true) { $reponse_correcte = $rep['texte']; break; } } return $reponse_eleve === $reponse_correcte; } // Type CHECKBOX (compare textes comme correction réelle) if ($type === 'checkbox') { if (!isset($options['reponses'])) { return false; } // Trouver toutes les réponses correctes (TEXTES) $reponses_correctes = []; foreach ($options['reponses'] as $rep) { if (isset($rep['est_correcte']) && $rep['est_correcte'] === true) { $reponses_correctes[] = $rep['texte']; } } // Normaliser réponses élève $reponses_eleve_array = is_array($reponse_eleve) ? $reponse_eleve : [$reponse_eleve]; // Comparer tableaux triés sort($reponses_correctes); sort($reponses_eleve_array); return $reponses_correctes === $reponses_eleve_array; } return false; } ?>