$classId, ':t' => $teacherId]); if (!$own) v2_response_error(403, 'forbidden', 'Klasse nicht deine.'); if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { v2_response_error(400, 'upload_failed', 'CSV-Datei fehlt oder Upload-Fehler.'); } if ($_FILES['file']['size'] > 1024 * 1024) { v2_response_error(413, 'file_too_large', 'CSV max. 1 MB.'); } $content = file_get_contents($_FILES['file']['tmp_name']); // BOM entfernen, Zeilenenden normalisieren $content = preg_replace('/^\xEF\xBB\xBF/', '', $content); $content = str_replace(["\r\n", "\r"], "\n", $content); // Trenner auto-erkennen aus erster Zeile $firstLine = strtok($content, "\n"); $delimiter = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ','; $lines = array_filter(array_map('trim', explode("\n", $content)), fn($l) => $l !== ''); if (count($lines) < 2) { v2_response_error(400, 'csv_empty', 'CSV muss Header und mindestens eine Datenzeile haben.'); } $header = str_getcsv(array_shift($lines), $delimiter); $header = array_map(fn($h) => strtolower(trim($h)), $header); $colIdx = function (string $key) use ($header) { $i = array_search($key, $header, true); return $i === false ? null : $i; }; $idx = [ 'username' => $colIdx('username'), 'password' => $colIdx('password'), 'display_name' => $colIdx('display_name') ?? $colIdx('displayname'), 'first_name' => $colIdx('first_name') ?? $colIdx('firstname') ?? $colIdx('vorname'), 'last_name' => $colIdx('last_name') ?? $colIdx('lastname') ?? $colIdx('nachname'), 'avatar_slug' => $colIdx('avatar_slug') ?? $colIdx('avatarslug') ?? $colIdx('avatar'), ]; if ($idx['username'] === null || $idx['password'] === null) { v2_response_error(400, 'csv_invalid_header', 'CSV-Header muss mindestens "username" und "password" enthalten.', ['headerGefunden' => $header]); } $pdo = v2_db(); $pdo->beginTransaction(); $created = 0; $skipped = []; $lineNo = 1; try { $stmtCheck = $pdo->prepare( 'SELECT id FROM students WHERE class_id = :c AND username = :u AND deleted_at IS NULL' ); $stmtInsert = $pdo->prepare( 'INSERT INTO students (class_id, username, password, display_name, first_name, last_name, avatar_slug, is_anonymous) VALUES (:cid, :u, :p, :dn, :fn, :ln, :av, 0)' ); foreach ($lines as $line) { $lineNo++; $row = str_getcsv($line, $delimiter); $username = trim((string) ($row[$idx['username']] ?? '')); $password = (string) ($row[$idx['password']] ?? ''); if (!$username || !$password) { $skipped[] = ['line' => $lineNo, 'username' => $username, 'reason' => 'username oder password fehlt']; continue; } if (strlen($password) < 4) { $skipped[] = ['line' => $lineNo, 'username' => $username, 'reason' => 'Passwort < 4 Zeichen']; continue; } $stmtCheck->execute([':c' => $classId, ':u' => $username]); if ($stmtCheck->fetch()) { $skipped[] = ['line' => $lineNo, 'username' => $username, 'reason' => 'Username schon vergeben in dieser Klasse']; continue; } $displayName = trim((string) ($row[$idx['display_name']] ?? '')) ?: $username; $firstName = $idx['first_name'] !== null ? (trim((string) ($row[$idx['first_name']] ?? '')) ?: null) : null; $lastName = $idx['last_name'] !== null ? (trim((string) ($row[$idx['last_name']] ?? '')) ?: null) : null; $avatarSlug = $idx['avatar_slug'] !== null ? (trim((string) ($row[$idx['avatar_slug']] ?? '')) ?: 'fuchs-explorer') : 'fuchs-explorer'; $stmtInsert->execute([ ':cid' => $classId, ':u' => $username, ':p' => password_hash($password, PASSWORD_DEFAULT), ':dn' => $displayName, ':fn' => $firstName, ':ln' => $lastName, ':av' => $avatarSlug, ]); $created++; } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } v2_response_ok([ 'created' => $created, 'skipped' => $skipped, 'total' => count($lines), ]);