fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]); if (!$class) Response::error('Klasse nicht gefunden', 404); $students = $db->fetchAll( 'SELECT id, username, display_name, first_name, last_name, email, emoji_avatar, is_anonymous, easy_language, password_plaintext, created_at, last_login FROM students WHERE class_id = ? AND deleted_at IS NULL ORDER BY username', [$classId] ); // Letzte Ergebnisse pro Schueler foreach ($students as &$s) { $s['results'] = $db->fetchAll( 'SELECT module_id, score, balance_idx, completed_at FROM student_results WHERE student_id = ? ORDER BY completed_at DESC LIMIT 10', [$s['id']] ); $s['total_score'] = $db->fetchOne( 'SELECT AVG(score) as avg_score, COUNT(*) as count FROM student_results WHERE student_id = ?', [$s['id']] ); } Response::ok($students); } if ($method === 'POST') { $teacherId = Session::requireTeacher(); $body = json_decode(file_get_contents('php://input'), true); $action = $body['action'] ?? ''; // === SCHUELER ERSTELLEN === if ($action === 'create') { $classId = (int)($body['classId'] ?? 0); $username = mb_substr(trim($body['username'] ?? ''), 0, 64); $password = $body['password'] ?? ''; $displayName = mb_substr(trim($body['displayName'] ?? ''), 0, 128); $firstName = mb_substr(trim($body['firstName'] ?? ''), 0, 64); $lastName = mb_substr(trim($body['lastName'] ?? ''), 0, 64); $email = trim($body['email'] ?? ''); $isAnonymous = (bool)($body['isAnonymous'] ?? (!$firstName && !$lastName)); if (!$classId || !$username || !$password) Response::error('classId, username und password erforderlich'); if (strlen($password) < 4) Response::error('Passwort muss mindestens 4 Zeichen lang sein'); if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) Response::error('Ungültige E-Mail-Adresse'); // Klasse pruefen $class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]); if (!$class) Response::error('Klasse nicht gefunden', 404); // Username eindeutig in Klasse? $existing = $db->fetchOne('SELECT id FROM students WHERE class_id = ? AND username = ?', [$classId, $username]); if ($existing) Response::error('Benutzername in dieser Klasse bereits vergeben'); $hash = password_hash($password, PASSWORD_DEFAULT); $avatar = ggs_random_avatar_slug(); $db->execute( 'INSERT INTO students (class_id, username, password, password_plaintext, display_name, first_name, last_name, email, is_anonymous, emoji_avatar) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [$classId, $username, $hash, $password, $displayName ?: null, $firstName ?: null, $lastName ?: null, $email ?: null, $isAnonymous ? 1 : 0, $avatar] ); Response::ok(['id' => (int)$db->lastInsertId(), 'emoji_avatar' => $avatar]); } // === CSV-IMPORT — pro Zeile einen Schüler === if ($action === 'create_csv') { $classId = (int)($body['classId'] ?? 0); $rows = $body['rows'] ?? []; if (!is_array($rows) || !$rows) Response::error('keine Zeilen'); $class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]); if (!$class) Response::error('Klasse nicht gefunden', 404); $created = []; $skipped = []; foreach ($rows as $r) { $username = mb_substr(trim($r['username'] ?? ''), 0, 64); $password = trim($r['password'] ?? ''); $displayName = mb_substr(trim($r['display_name'] ?? ''), 0, 128); $firstName = mb_substr(trim($r['first_name'] ?? ''), 0, 64); $lastName = mb_substr(trim($r['last_name'] ?? ''), 0, 64); $email = trim($r['email'] ?? ''); $easy = !empty($r['easy']) && $r['easy'] !== '0'; if (!$username) { $skipped[] = ['row' => $r, 'reason' => 'kein username']; continue; } if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $skipped[] = ['row' => $r, 'reason' => 'ungueltige E-Mail']; continue; } $existing = $db->fetchOne('SELECT id FROM students WHERE class_id = ? AND username = ?', [$classId, $username]); if ($existing) { $skipped[] = ['row' => $r, 'reason' => 'username existiert bereits']; continue; } // Passwort: angegeben oder 4-Ziffern-Generat if (!$password) $password = str_pad((string)random_int(1000, 9999), 4, '0', STR_PAD_LEFT); if (strlen($password) < 4) { $skipped[] = ['row' => $r, 'reason' => 'Passwort zu kurz']; continue; } $hash = password_hash($password, PASSWORD_DEFAULT); $isAnonymous = !$firstName && !$lastName; $avatar = ggs_random_avatar_slug(); $db->execute( 'INSERT INTO students (class_id, username, password, password_plaintext, display_name, first_name, last_name, email, is_anonymous, easy_language, emoji_avatar) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [$classId, $username, $hash, $password, $displayName ?: $username, $firstName ?: null, $lastName ?: null, $email ?: null, $isAnonymous ? 1 : 0, $easy ? 1 : 0, $avatar] ); $created[] = ['username' => $username, 'password' => $password, 'displayName' => $displayName ?: $username, 'id' => (int)$db->lastInsertId(), 'emoji_avatar' => $avatar]; } Response::ok(['created' => $created, 'skipped' => $skipped]); } // === MEHRERE SCHUELER AUF EINMAL ERSTELLEN === if ($action === 'create_batch') { $classId = (int)($body['classId'] ?? 0); $count = min(50, max(1, (int)($body['count'] ?? 10))); $prefix = mb_substr(trim($body['prefix'] ?? 'schueler'), 0, 20); $class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]); if (!$class) Response::error('Klasse nicht gefunden', 404); $created = []; for ($i = 1; $i <= $count; $i++) { $username = $prefix . str_pad($i, 2, '0', STR_PAD_LEFT); // Einfaches Passwort generieren (4 Ziffern) $pw = str_pad(random_int(1000, 9999), 4, '0', STR_PAD_LEFT); $hash = password_hash($pw, PASSWORD_DEFAULT); $existing = $db->fetchOne('SELECT id FROM students WHERE class_id = ? AND username = ?', [$classId, $username]); if ($existing) continue; // Ueberspringen wenn schon vorhanden $avatar = ggs_random_avatar_slug(); $db->execute( 'INSERT INTO students (class_id, username, password, password_plaintext, display_name, is_anonymous, emoji_avatar) VALUES (?, ?, ?, ?, ?, 1, ?)', [$classId, $username, $hash, $pw, $username, $avatar] ); $created[] = ['username' => $username, 'password' => $pw, 'id' => (int)$db->lastInsertId(), 'emoji_avatar' => $avatar]; } Response::ok(['created' => $created]); } // === SCHUELER BEARBEITEN === if ($action === 'update') { $studentId = (int)($body['studentId'] ?? 0); $displayName = mb_substr(trim($body['displayName'] ?? ''), 0, 128); $newPassword = $body['newPassword'] ?? ''; // Schueler + Klasse pruefen $student = $db->fetchOne( 'SELECT s.id, s.class_id FROM students s JOIN classes c ON c.id = s.class_id WHERE s.id = ? AND c.teacher_id = ?', [$studentId, $teacherId] ); if (!$student) Response::error('Schüler nicht gefunden', 404); $firstName = mb_substr(trim($body['firstName'] ?? ''), 0, 64); $lastName = mb_substr(trim($body['lastName'] ?? ''), 0, 64); $email = trim($body['email'] ?? ''); $emojiAvatar = mb_substr(trim($body['emojiAvatar'] ?? ''), 0, 8); if ($displayName) { $db->execute('UPDATE students SET display_name = ? WHERE id = ?', [$displayName, $studentId]); } if ($firstName !== '') { $db->execute('UPDATE students SET first_name = ? WHERE id = ?', [$firstName, $studentId]); } if ($lastName !== '') { $db->execute('UPDATE students SET last_name = ? WHERE id = ?', [$lastName, $studentId]); } if ($email !== '') { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) Response::error('Ungültige E-Mail-Adresse'); $db->execute('UPDATE students SET email = ? WHERE id = ?', [$email, $studentId]); } if ($emojiAvatar !== '') { $db->execute('UPDATE students SET emoji_avatar = ? WHERE id = ?', [$emojiAvatar, $studentId]); } if ($newPassword && strlen($newPassword) >= 4) { $db->execute( 'UPDATE students SET password = ?, password_plaintext = ? WHERE id = ?', [password_hash($newPassword, PASSWORD_DEFAULT), $newPassword, $studentId] ); } // Leichte Sprache (nur setzen wenn Feld mitgeschickt) if (array_key_exists('easyLanguage', $body)) { $flag = !empty($body['easyLanguage']) ? 1 : 0; $db->execute('UPDATE students SET easy_language = ? WHERE id = ?', [$flag, $studentId]); } Response::ok(); } // === PASSWORT EINES SCHUELERS ZURUECKSETZEN === if ($action === 'reset_password') { $studentId = (int)($body['studentId'] ?? 0); $student = $db->fetchOne( 'SELECT s.id FROM students s JOIN classes c ON c.id = s.class_id WHERE s.id = ? AND c.teacher_id = ?', [$studentId, $teacherId] ); if (!$student) Response::error('Schüler nicht gefunden', 404); $pw = str_pad((string)random_int(1000, 9999), 4, '0', STR_PAD_LEFT); $hash = password_hash($pw, PASSWORD_DEFAULT); $db->execute( 'UPDATE students SET password = ?, password_plaintext = ? WHERE id = ?', [$hash, $pw, $studentId] ); Response::ok(['password' => $pw]); } // === PASSWOERTER EINER AUSWAHL (oder alle der Klasse) ZURUECKSETZEN === if ($action === 'reset_class_passwords') { $classId = (int)($body['classId'] ?? 0); $studentIds = $body['studentIds'] ?? null; // optional: array von ids $class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]); if (!$class) Response::error('Klasse nicht gefunden', 404); if (is_array($studentIds) && $studentIds) { $studentIds = array_values(array_filter(array_map('intval', $studentIds))); if (!$studentIds) Response::error('Keine gültigen IDs'); $placeholders = implode(',', array_fill(0, count($studentIds), '?')); $params = array_merge([$classId], $studentIds); $students = $db->fetchAll( "SELECT id, username, display_name, emoji_avatar FROM students WHERE class_id = ? AND deleted_at IS NULL AND id IN ($placeholders) ORDER BY username", $params ); } else { $students = $db->fetchAll( 'SELECT id, username, display_name, emoji_avatar FROM students WHERE class_id = ? AND deleted_at IS NULL ORDER BY username', [$classId] ); } $reset = []; foreach ($students as $s) { $pw = str_pad((string)random_int(1000, 9999), 4, '0', STR_PAD_LEFT); $hash = password_hash($pw, PASSWORD_DEFAULT); $db->execute( 'UPDATE students SET password = ?, password_plaintext = ? WHERE id = ?', [$hash, $pw, $s['id']] ); $reset[] = [ 'id' => (int)$s['id'], 'username' => $s['username'], 'displayName' => $s['display_name'] ?: $s['username'], 'emoji' => $s['emoji_avatar'] ?: '🧑‍🎓', 'password' => $pw, ]; } Response::ok(['reset' => $reset]); } // === SCHUELER LOESCHEN === if ($action === 'delete') { $studentId = (int)($body['studentId'] ?? 0); $student = $db->fetchOne( 'SELECT s.id FROM students s JOIN classes c ON c.id = s.class_id WHERE s.id = ? AND c.teacher_id = ?', [$studentId, $teacherId] ); if (!$student) Response::error('Schüler nicht gefunden', 404); $db->execute('UPDATE students SET deleted_at = NOW() WHERE id = ?', [$studentId]); Response::ok(); } Response::error('Unbekannte Aktion'); } Response::error('Methode nicht erlaubt', 405);