Dienstag, 26 August 2025

Top 5 diese Woche

Ähnliche Tutorials

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="http://www.dreamcodes.net" target="_blank" style="color:#06b6d4;">Dreamcodes</a>
</div>
</body>
</html>
Vorheriges Tutorial

Hier etwas für dich dabei?