Mittwoch, 21 Januar 2026

Diese Woche am beliebtesten

Vertiefendes Material

Prüfungs Trainer

Abschlussprüfungstrainer – Dein digitales Prüfungszentrum

Mit dieser hochwertigen Webseite erhältst du ein vollwertiges Trainingssystem für Abschlussprüfungen. Das Design ist modern, professionell und speziell auf Schüler und Bildungseinrichtungen zugeschnitten.

Funktionen im Überblick:

  • PDF-Upload alter Prüfungen – Lade bestehende Prüfungen hoch, das Skript extrahiert automatisch die Fragen.
  • Test-Simulation – Schüler können wie in einer echten Prüfung trainieren: klare Darstellung, Fragen nacheinander, Antwortmöglichkeiten und strukturierter Ablauf.
  • Zeitlimit mit Countdown – Jede Simulation läuft unter realistischen Bedingungen, inklusive Timer und Abgabe.
  • User-Accounts für Schüler – Sichere Anmeldung mit persönlichem Dashboard, Übersicht der Tests und Ergebnisse.
  • Admin-Bereich – Benutzerverwaltung, Upload von Prüfungen, Lösch- und Kontrollmöglichkeiten.
  • Dashboard-Übersicht – Klare Statistiken, gespeicherte Ergebnisse, Auswertung der Prüfungssimulation.
  • Automatische Ordner- und Dateistruktur – Alles wird aus einer Hauptdatei heraus erstellt, keine manuelle Konfiguration nötig.
  • Professionelles Design – Optisch so hochwertig, als hätte es mehrere tausend Euro gekostet, responsive und intuitiv bedienbar.

Optionale Features:

  • Textextraktion via pdftotext (falls auf dem Server verfügbar)
  • Anpassbare Prüfungsläufe (z. B. Fächer, Schwierigkeitsgrad)
  • Fortschrittsanzeige und Ergebnisbewertung

Installation:

  1. Datei auf den Server laden
  2. MySQL-Zugangsdaten in der Datei eintragen
  3. Erste Anmeldung über Admin-Account (Standard: admin / admin123, Passwort bitte sofort ändern)
  4. Alte Prüfungen hochladen und erste Tests starten

Vorteile:

  • Realistische Prüfungsvorbereitung rund um die Uhr
  • Ideal für Schüler, Nachhilfelehrer oder Bildungseinrichtungen
  • Keine teuren Zusatzprogramme notwendig, läuft direkt auf jedem gängigen Webserver
  • Übersichtliche Auswertung sorgt für schnelle Lernfortschritte

Hinweis:
Dieses Skript ersetzt keine offiziellen Prüfungen, bietet aber eine optimale Vorbereitung unter nahezu realistischen Bedingungen.

<?php
// Konfiguration
$dbHost = '127.0.0.1';
$dbName = 'exam_trainer_db';
$dbUser = 'root';
$dbPass = ''; // setze hier das Passwort

$baseDir = __DIR__;
$uploadDir = __DIR__ . '/uploads';
$dataDir = __DIR__ . '/data';

if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
if (!is_dir($dataDir)) mkdir($dataDir, 0755, true);

// Mysql Verbindung und Tabellen anlegen falls noetig
try {
    $pdo = new PDO("mysql:host={$dbHost};charset=utf8mb4", $dbUser, $dbPass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    // Datenbank erstellen falls nicht vorhanden
    $pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
    $pdo->exec("USE `{$dbName}`");

    // Tabellen
    $pdo->exec("CREATE TABLE IF NOT EXISTS users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(100) UNIQUE,
        password VARCHAR(255),
        display_name VARCHAR(200),
        is_admin TINYINT(1) DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS exams (
        id INT AUTO_INCREMENT PRIMARY KEY,
        title VARCHAR(255),
        filename VARCHAR(255),
        uploader INT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (uploader) REFERENCES users(id) ON DELETE SET NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS questions (
        id INT AUTO_INCREMENT PRIMARY KEY,
        exam_id INT,
        qtext TEXT,
        qnum VARCHAR(50),
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS attempts (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id INT,
        exam_id INT,
        started_at TIMESTAMP NULL,
        finished_at TIMESTAMP NULL,
        score INT DEFAULT 0,
        duration_sec INT DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
        FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    $pdo->exec("CREATE TABLE IF NOT EXISTS answers (
        id INT AUTO_INCREMENT PRIMARY KEY,
        attempt_id INT,
        question_id INT,
        answer_text TEXT,
        correct TINYINT(1) DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (attempt_id) REFERENCES attempts(id) ON DELETE CASCADE,
        FOREIGN KEY (question_id) REFERENCES questions(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

} catch (Exception $e) {
    echo 'Datenbankfehler: ' . htmlspecialchars($e->getMessage());
    exit;
}

session_start();

function json($d) { header('Content-Type: application/json; charset=utf-8'); echo json_encode($d); exit; }

// Hilfsfunktionen
function currentUserId() { return $_SESSION['user_id'] ?? null; }
function isAdmin() { return !empty($_SESSION['is_admin']); }

// initial admin anlegen falls noch keiner existiert
$stmt = $pdo->query("SELECT COUNT(*) as c FROM users"); $cnt = $stmt->fetch(PDO::FETCH_ASSOC)['c'];
if ($cnt == 0) {
    $pw = password_hash('admin123', PASSWORD_DEFAULT);
    $pdo->prepare("INSERT INTO users (username,password,display_name,is_admin) VALUES (?,?,?,1)")->execute(['admin',$pw,'Administrator']);
}

// API Endpunkte
$action = $_REQUEST['action'] ?? '';

if ($action === 'register' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $display = trim($_POST['display'] ?? $username);
    if (!$username || !$password) json(['error' => 'Bitte Benutzername und Passwort angeben']);
    try {
        $pw = password_hash($password, PASSWORD_DEFAULT);
        $stm = $pdo->prepare("INSERT INTO users (username,password,display_name) VALUES (?,?,?)");
        $stm->execute([$username,$pw,$display]);
        json(['success' => true]);
    } catch (Exception $e) { json(['error' => 'Benutzer existiert bereits']); }
}

if ($action === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $stm = $pdo->prepare("SELECT * FROM users WHERE username = ? LIMIT 1");
    $stm->execute([$username]); $u = $stm->fetch(PDO::FETCH_ASSOC);
    if ($u && password_verify($password, $u['password'])) {
        $_SESSION['user_id'] = $u['id']; $_SESSION['display_name'] = $u['display_name']; $_SESSION['is_admin'] = $u['is_admin'];
        json(['success' => true]);
    } else json(['error' => 'Ungültige Zugangsdaten']);
}

if ($action === 'logout') { session_destroy(); json(['success' => true]); }

if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!currentUserId()) json(['error' => 'Bitte einloggen']);
    if (!isset($_FILES['pdf'])) json(['error' => 'Datei fehlt']);
    $f = $_FILES['pdf'];
    if ($f['error'] !== UPLOAD_ERR_OK) json(['error' => 'Upload Fehler']);
    $ext = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION));
    if ($ext !== 'pdf') json(['error' => 'Nur PDF erlaubt']);
    $fname = time() . '-' . bin2hex(random_bytes(6)) . '.pdf';
    $target = $uploadDir . '/' . $fname;
    if (!move_uploaded_file($f['tmp_name'], $target)) json(['error' => 'Speicherfehler']);
    // eintrag in exams
    $title = trim($_POST['title'] ?? $f['name']);
    $pdo->prepare("INSERT INTO exams (title,filename,uploader) VALUES (?,?,?)")->execute([$title,$fname,currentUserId()]);
    $examId = $pdo->lastInsertId();

    // versuch text zu extrahieren mit pdftotext falls vorhanden
    $text = '';
    $pdftotext = trim(shell_exec('which pdftotext'));
    if ($pdftotext) {
        $out = tempnam(sys_get_temp_dir(), 'txt');
        @exec(escapeshellcmd("{$pdftotext} " . escapeshellarg($target) . " " . escapeshellarg($out) . " 2>&1"));
        $text = @file_get_contents($out) ?: '';
        @unlink($out);
    } else {
        // fallback leer
        $text = '';
    }

    // einfache frage extraktion: split nach zeilen die mit Zahl beginnen oder mit Wort Frage
    $questions = [];
    if ($text) {
        $lines = preg_split('/\r?\n/', $text);
        $buffer = '';
        $qnum = 0;
        foreach ($lines as $ln) {
            $ln = trim($ln);
            if (!$ln) continue;
            if (preg_match('/^(Frage|F|[0-9]{1,3}[\.|\)] )/i', $ln) || preg_match('/^[0-9]+\./', $ln) ) {
                if ($buffer) { $questions[] = ['qnum' => ++$qnum, 'text' => $buffer]; }
                $buffer = $ln;
            } else {
                $buffer .= ' ' . $ln;
            }
        }
        if ($buffer) $questions[] = ['qnum' => ++$qnum, 'text' => $buffer];
    }

    // wenn keine fragen automatisch gefunden wurden, lege placeholder fragen an
    if (count($questions) === 0) {
        for ($i=1;$i<=10;$i++) $questions[] = ['qnum' => $i, 'text' => "Beispielfrage {$i} aus dem Upload bitte manuell prüfen"]; 
    }

    // speicher fragen
    $ins = $pdo->prepare("INSERT INTO questions (exam_id,qtext,qnum) VALUES (?,?,?)");
    foreach ($questions as $q) $ins->execute([$examId, $q['text'], $q['qnum']]);

    json(['success' => true, 'exam_id' => $examId, 'questions' => count($questions)]);
}

if ($action === 'list_exams') {
    $res = $pdo->query("SELECT e.*, u.display_name FROM exams e LEFT JOIN users u ON u.id = e.uploader ORDER BY e.created_at DESC");
    $out = [];
    while ($r = $res->fetch(PDO::FETCH_ASSOC)) $out[] = $r;
    json($out);
}

if ($action === 'get_questions' && isset($_GET['exam_id'])) {
    $exam_id = intval($_GET['exam_id']);
    $res = $pdo->prepare("SELECT * FROM questions WHERE exam_id = ? ORDER BY id ASC"); $res->execute([$exam_id]);
    $out = [];
    while ($r = $res->fetch(PDO::FETCH_ASSOC)) $out[] = $r;
    json($out);
}

if ($action === 'start_attempt' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!currentUserId()) json(['error' => 'Bitte einloggen']);
    $exam_id = intval($_POST['exam_id']);
    $time_limit = intval($_POST['time_limit']) ?? 1800; // default 30 min
    $pdo->prepare("INSERT INTO attempts (user_id,exam_id,started_at) VALUES (?,?,NOW())")->execute([currentUserId(), $exam_id]);
    $attempt_id = $pdo->lastInsertId();
    json(['success' => true, 'attempt_id' => $attempt_id]);
}

if ($action === 'submit_answer' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $attempt_id = intval($_POST['attempt_id']);
    $question_id = intval($_POST['question_id']);
    $answer = trim($_POST['answer'] ?? '');
    // einfache Speicherung ohne automatische Bewertung
    $stm = $pdo->prepare("INSERT INTO answers (attempt_id,question_id,answer_text) VALUES (?,?,?)");
    $stm->execute([$attempt_id, $question_id, $answer]);
    json(['success' => true]);
}

if ($action === 'finish_attempt' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $attempt_id = intval($_POST['attempt_id']);
    // berechne dauer als beispiel
    $stm = $pdo->prepare("SELECT started_at FROM attempts WHERE id = ?"); $stm->execute([$attempt_id]); $row = $stm->fetch(PDO::FETCH_ASSOC);
    $started = strtotime($row['started_at']); $dur = time() - $started;
    $pdo->prepare("UPDATE attempts SET finished_at = NOW(), duration_sec = ? WHERE id = ?")->execute([$dur, $attempt_id]);
    json(['success' => true, 'duration' => $dur]);
}

if ($action === 'admin_delete_exam' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isAdmin()) json(['error' => 'Zugriff verweigert']);
    $exam_id = intval($_POST['exam_id']);
    // entferne datei
    $stm = $pdo->prepare("SELECT filename FROM exams WHERE id = ?"); $stm->execute([$exam_id]); $r = $stm->fetch(PDO::FETCH_ASSOC);
    if ($r && $r['filename']) @unlink($uploadDir . '/' . $r['filename']);
    $pdo->prepare("DELETE FROM exams WHERE id = ?")->execute([$exam_id]);
    json(['success' => true]);
}

// Ende API logik

// UI
?><!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Dreamcodes - Abschlussprüfungstrainer</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#071027;--card:#0b1220;--accent:#ff7a59;--muted:#9aa4b2}
body{margin:0;font-family:Inter,system-ui,Arial;background:linear-gradient(180deg,var(--bg),#021022);color:#e6eef8;padding:36px}
.container{max-width:1100px;margin:0 auto}
.header{display:flex;justify-content:space-between;align-items:center}
.brand{font-weight:700;font-size:20px}
.card{background:linear-gradient(180deg,var(--card),#071022);padding:18px;border-radius:12px;box-shadow:0 12px 40px rgba(2,6,23,0.7);margin-top:18px}
.grid{display:grid;grid-template-columns:1fr 360px;gap:18px}
.input,select{background:transparent;border:1px solid rgba(255,255,255,0.04);padding:10px;border-radius:8px;color:inherit;width:100%}
.btn{background:var(--accent);border:none;padding:10px 14px;border-radius:10px;color:#081018;font-weight:700;cursor:pointer}
.list{max-height:480px;overflow:auto}
.exam{padding:10px;border-radius:8px;background:rgba(255,255,255,0.02);margin-bottom:10px}
.small{font-size:13px;color:var(--muted)}
.badge{background:rgba(255,255,255,0.04);padding:6px 10px;border-radius:999px;font-weight:600}
.timer{font-size:20px;font-weight:700}
</style>
</head>
<body>
<div class="container">
  <div class="header">
    <div class="brand">Abschlussprüfungstrainer</div>
    <div id="userbox" class="small"></div>
  </div>

  <div class="card grid">
    <div>
      <div class="card">
        <h3>Prüfung Upload</h3>
        <form id="uploadForm" enctype="multipart/form-data">
          <input type="text" name="title" placeholder="Titel der Prüfung" class="input" />
          <input type="file" name="pdf" accept="application/pdf" class="input" />
          <div style="margin-top:10px;display:flex;gap:8px;align-items:center">
            <button class="btn" type="submit">Hochladen und Fragen extrahieren</button>
            <div class="small">pdftotext wird verwendet falls installiert</div>
          </div>
        </form>
      </div>

      <div class="card">
        <h3>Verfügbare Prüfungen</h3>
        <div id="exams" class="list"></div>
      </div>
    </div>

    <div>
      <div class="card">
        <h4>Benutzer</h4>
        <div id="authbox"></div>
      </div>

      <div class="card">
        <h4>Dashboard</h4>
        <div id="dashboard"></div>
      </div>
    </div>

  </div>
</div>

<script>
async function api(action, data){
  const opts = { method: 'POST' };
  if (data instanceof FormData) opts.body = data; else opts.body = new URLSearchParams(data || {});
  const res = await fetch('?action=' + action, opts);
  return res.json();
}

async function loadExams(){
  const res = await fetch('?action=list_exams');
  const exams = await res.json();
  const el = document.getElementById('exams'); el.innerHTML = '';
  exams.forEach(e => {
    const d = document.createElement('div'); d.className = 'exam';
    d.innerHTML = `<div style="display:flex;justify-content:space-between;align-items:center"><div><strong>${e.title}</strong><div class="small">von ${e.display_name} · ${e.created_at}</div></div><div><button class='btn' onclick='startExam(${e.id})'>Prüfung starten</button> <button style='background:#ef4444;color:#fff' onclick='adminDelete(${e.id})'>Löschen</button></div></div>`;
    el.appendChild(d);
  });
}

async function startExam(id){
  if(!confirm('Starte Prüfung mit Zeitlimit 30 Minuten?')) return;
  const res = await api('start_attempt', { exam_id: id, time_limit: 1800 });
  if(res.error) alert(res.error); else location.href = '?exam=' + res.attempt_id;
}

async function adminDelete(id){ if(!confirm('Prüfung wirklich löschen')) return; const fd = new URLSearchParams(); fd.append('exam_id', id); const res = await api('admin_delete_exam', fd); if(res.success) loadExams(); else alert(res.error);
}

// upload
document.getElementById('uploadForm').addEventListener('submit', async function(e){
  e.preventDefault();
  const fd = new FormData(this);
  const res = await api('upload', fd);
  if(res.error) alert(res.error); else { alert('Upload OK. Gefundene Fragen ' + res.questions); loadExams(); }
});

// simple auth UI
async function loadAuth(){
  const uid = await fetch('?action=whoami').then(r=>r.json()).catch(()=>null);
}

// check for attempt view
(function(){
  const params = new URLSearchParams(location.search);
  if(params.has('exam')){
    const attemptId = params.get('exam');
    // render exam interface in full page
    document.body.innerHTML = `<div style="padding:30px;color:#fff;font-family:Inter"><div style='max-width:900px;margin:0 auto'><h2>Prüfung</h2><div id='examroot'></div></div></div>`;
    renderExam(attemptId);
  } else {
    loadExams();
  }
})();

async function renderExam(attemptId){
  const root = document.getElementById('examroot');
  // fetch questions by exam id mapping
  // for demo wir nehmen exam id aus attempt
  const att = await fetch('?action=get_attempt&attempt_id=' + attemptId).then(r=>r.json());
  if(att.error){ root.innerHTML = '<div>Error</div>'; return; }
  const examId = att.exam_id;
  const qs = await fetch('?action=get_questions&exam_id=' + examId).then(r=>r.json());
  root.innerHTML = `<div class='card' style='padding:20px'><div style='display:flex;justify-content:space-between;align-items:center'><div><strong>${att.title}</strong><div class='small'>Zeitlimit 30 Minuten</div></div><div class='timer' id='timer'>30:00</div></div><hr><div id='qwrap'></div><div style='margin-top:12px'><button id='finishBtn' class='btn'>Prüfung beenden</button></div></div>`;
  const qwrap = document.getElementById('qwrap');
  qs.forEach((q,i)=>{
    const div = document.createElement('div'); div.className='exam'; div.innerHTML = `<div><strong>Frage ${i+1}</strong><p>${q.qtext}</p><textarea id='ans_${q.id}' style='width:100%;min-height:80px' placeholder='Antwort hier'></textarea></div>`;
    qwrap.appendChild(div);
  });

  // countdown simple
  let sec = 30 * 60; const timerEl = document.getElementById('timer');
  const interval = setInterval(()=>{ sec--; const m = Math.floor(sec/60); const s = sec%60; timerEl.textContent = (m<10? '0'+m:m) + ':' + (s<10? '0'+s:s); if(sec<=0){ clearInterval(interval); submitAll(); } },1000);

  async function submitAll(){
    for(const q of qs){ const ans = document.getElementById('ans_'+q.id).value; const fd = new URLSearchParams(); fd.append('attempt_id', attemptId); fd.append('question_id', q.id); fd.append('answer', ans); await api('submit_answer', fd); }
    const fd2 = new URLSearchParams(); fd2.append('attempt_id', attemptId); const res = await api('finish_attempt', fd2); alert('Prüfung beendet Dauer Sek ' + res.duration); location.href = './'; }

  document.getElementById('finishBtn').addEventListener('click', async ()=>{ if(confirm('Prüfung wirklich beenden')){ submitAll(); }});
}
</script>
<div class="footer" style="text-align:center;color:#9aa4b2;margin-top:20px;font-size:13px;">
    Powered by <a href="https://www.dreamcodes.net" target="_blank" style="color:#06b6d4;">Dreamcodes</a>
</div>
</body>
</html>
Dreamcodes Redaktion
Dreamcodes Redaktion
Qualität als Standard. Verantwortung als Prinzip. Jede Ressource auf Dreamcodes basiert auf geprüften Best Practices und fundierter Praxiserfahrung. Unser Anspruch ist ein belastbares Fundament statt experimenteller Lösungen. Die Integration und Absicherung der Inhalte liegt in Ihrem Ermessen. Wir liefern die fachliche Basis, die Verantwortung für den produktiven Einsatz verbleibt bei Ihnen.
Vorheriges Tutorial
Nächstes Tutorial

Vielleicht einen Blick WERT?