Trainer v2.1.0
Training
Status draft
Opslaan
Trainingen
Selecteer of maak een training
Zoeken
Filter
Snelstart
1) Nieuw → kies template
2) Secties + items toevoegen
3) Opslaan / Publiceren
Builder
Secties, items, vragen en instellingen
0 secties
0 items
0 vragen
if ($api === 'participants_stats') { $tid = sanitize_id((string)($_GET['training_id'] ?? '')); $filter = (string)($_GET['filter'] ?? ''); if ($tid === '') respond(['ok'=>false,'error'=>'missing_training_id'], 400); $t = load_training($tid); if (!$t) respond(['ok'=>false,'error'=>'training_not_found'], 404); $total_items = 0; $sections = $t['sections'] ?? []; if (is_array($sections)) foreach ($sections as $sec) { $items = $sec['items'] ?? []; if (is_array($items)) $total_items += count($items); } $attemptsByUser = []; $fh = @fopen(ATTEMPTS_FILE, 'r'); if ($fh) { while (($line=fgets($fh))!==false) { $line=trim($line); if ($line==='') continue; $row=json_decode($line,true); if(!is_array($row)) continue; if (($row['training_id']??'') !== $tid) continue; $uid=(string)($row['user_id']??''); if ($uid==='') continue; if ($filter!=='' && strpos($uid, $filter) === false) continue; if (!isset($attemptsByUser[$uid])) $attemptsByUser[$uid]=[]; $attemptsByUser[$uid][]=$row; } fclose($fh); } $rows = []; foreach ($attemptsByUser as $uid=>$atts) { $points=0; $maxPoints=0; $last=0; $itemDone=[]; foreach ($atts as $a) { $ts = strtotime((string)($a['ts']??'')); if ($ts && $ts>$last) $last=$ts; $iid=(string)($a['item_id']??''); $st=(string)($a['status']??''); if ($st==='correct' || $st==='exhausted') $itemDone[$iid]=true; $points += (int)($a['points_awarded']??0); $maxPoints += (int)($a['max_points']??0); } $done = count($itemDone); $percent = ($maxPoints>0) ? round(($points/$maxPoints)*100, 1) : 0; $rows[] = ['user_id'=>$uid,'done'=>$done,'total'=>$total_items,'score_percent'=>$percent,'last_activity'=>$last? gmdate('c',$last):'']; } usort($rows, fn($a,$b)=>strcmp($b['last_activity'],$a['last_activity'])); $kpi = ['users'=>count($rows),'avg_score'=> (count($rows)? round(array_sum(array_map(fn($r)=>$r['score_percent'],$rows))/count($rows),1):0)]; respond(['ok'=>true,'rows'=>$rows,'kpi'=>$kpi,'version'=>APP_VERSION]); } if ($api === 'participants_export_csv') { $tid = sanitize_id((string)($_GET['training_id'] ?? '')); $filter = (string)($_GET['filter'] ?? ''); if ($tid === '') { http_response_code(400); echo 'missing_training_id'; exit; } $t = load_training($tid); if (!$t) { http_response_code(404); echo 'training_not_found'; exit; } $total_items = 0; $sections = $t['sections'] ?? []; if (is_array($sections)) foreach ($sections as $sec) { $items=$sec['items']??[]; if (is_array($items)) $total_items += count($items); } $attemptsByUser = []; $fh = @fopen(ATTEMPTS_FILE, 'r'); if ($fh) { while (($line=fgets($fh))!==false) { $line=trim($line); if ($line==='') continue; $row=json_decode($line,true); if(!is_array($row)) continue; if (($row['training_id']??'') !== $tid) continue; $uid=(string)($row['user_id']??''); if ($uid==='') continue; if ($filter!=='' && strpos($uid, $filter) === false) continue; if (!isset($attemptsByUser[$uid])) $attemptsByUser[$uid]=[]; $attemptsByUser[$uid][]=$row; } fclose($fh); } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="participants-'.$tid.'.csv"'); $out = fopen('php://output','w'); fputcsv($out, ['user_id','done','total','score_percent','last_activity']); foreach ($attemptsByUser as $uid=>$atts) { $points=0; $maxPoints=0; $last=0; $itemDone=[]; foreach ($atts as $a) { $ts=strtotime((string)($a['ts']??'')); if ($ts && $ts>$last) $last=$ts; $iid=(string)($a['item_id']??''); $st=(string)($a['status']??''); if ($st==='correct' || $st==='exhausted') $itemDone[$iid]=true; $points += (int)($a['points_awarded']??0); $maxPoints += (int)($a['max_points']??0); } $done=count($itemDone); $percent = ($maxPoints>0) ? round(($points/$maxPoints)*100, 1) : 0; fputcsv($out, [$uid,$done,$total_items,$percent,($last? gmdate('c',$last):'')]); } fclose($out); exit; }