280 lines
9.8 KiB
PHP
280 lines
9.8 KiB
PHP
<?php
|
|
/**
|
|
* API MONITORING TEMPS RÉEL - VERSION FINALE CORRIGÉE V3
|
|
* Fix: Alignement logique vérification avec soumettre_evaluation.php
|
|
*
|
|
* Changements:
|
|
* 1. Ligne 39: Ajout type_question dans SELECT (fix précédent) ✅
|
|
* 2. Ligne 183-271: Fonction verifierReponseCorrecte() alignée sur correction réelle ✅
|
|
* 3. Ligne 11-20: Authentification corrigée (session directe) ✅
|
|
*/
|
|
|
|
require_once __DIR__ . '/../config/database.php';
|
|
require_once __DIR__ . '/../config/session.php';
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
SessionManager::startSession();
|
|
}
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
// Vérifier authentification enseignant
|
|
if (!isset($_SESSION['user_id']) || $_SESSION['type_libelle'] !== 'enseignant') {
|
|
echo json_encode(['success' => 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;
|
|
}
|
|
?>
|