6c6dd26823
- Quizzes table with questions, answer options, attempts, answers - Question types: multiple_choice, exclusion, true_false, free_text - Progressive scoring: [1,1,2,2,3,3,4,6,8,10] = max 40 per quiz - Alpine.js countdown timer per question with auto-submit on timeout - Admin: CRUD for quizzes + per-question editor, JSON export/import - Child: quiz overview with best scores, question view, result breakdown - Nav: Quiz link in child header and admin sidebar
105 lines
4.6 KiB
PHP
105 lines
4.6 KiB
PHP
<?php
|
||
namespace App\Http\Controllers\Admin;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\{Quiz, QuizQuestion, QuizAnswerOption, Subject};
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class QuizController extends Controller {
|
||
|
||
public function index() {
|
||
$quizzes = Quiz::with('subject')->withCount('questions')->latest()->get();
|
||
return view('admin.quizzes.index', compact('quizzes'));
|
||
}
|
||
|
||
public function create() {
|
||
$subjects = Subject::all();
|
||
return view('admin.quizzes.create', compact('subjects'));
|
||
}
|
||
|
||
public function store(Request $r) {
|
||
$r->validate(['title'=>'required|string|max:120','subject_id'=>'required|exists:subjects,id','description'=>'nullable|string|max:500']);
|
||
$quiz = Quiz::create($r->only('title','subject_id','description') + ['active'=>true]);
|
||
return redirect()->route('admin.quizzes.edit', $quiz)->with('success','Quiz erstellt – jetzt Fragen hinzufügen.');
|
||
}
|
||
|
||
public function edit(Quiz $quiz) {
|
||
$subjects = Subject::all();
|
||
$questions = $quiz->questions()->with('answerOptions')->get();
|
||
return view('admin.quizzes.edit', compact('quiz','subjects','questions'));
|
||
}
|
||
|
||
public function update(Request $r, Quiz $quiz) {
|
||
$r->validate(['title'=>'required|string|max:120','subject_id'=>'required|exists:subjects,id','description'=>'nullable|string|max:500']);
|
||
$quiz->update($r->only('title','subject_id','description') + ['active'=>$r->boolean('active')]);
|
||
return back()->with('success','Quiz gespeichert.');
|
||
}
|
||
|
||
public function destroy(Quiz $quiz) {
|
||
$quiz->delete();
|
||
return redirect()->route('admin.quizzes.index')->with('success','Quiz gelöscht.');
|
||
}
|
||
|
||
public function export(Quiz $quiz) {
|
||
$quiz->load('subject','questions.answerOptions');
|
||
$data = [
|
||
'subject' => $quiz->subject->slug,
|
||
'title' => $quiz->title,
|
||
'description' => $quiz->description,
|
||
'questions' => $quiz->questions->map(fn($q) => [
|
||
'type' => $q->type,
|
||
'question_text' => $q->question_text,
|
||
'time_limit' => $q->time_limit,
|
||
'correct_answer' => $q->correct_answer,
|
||
'options' => $q->answerOptions->map(fn($o) => [
|
||
'text' => $o->text,
|
||
'is_correct' => (bool)$o->is_correct,
|
||
])->values(),
|
||
])->values(),
|
||
];
|
||
$filename = 'quiz-'.now()->format('Y-m-d').'-'.str($quiz->title)->slug().'.json';
|
||
return response()->streamDownload(
|
||
fn() => print(json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)),
|
||
$filename, ['Content-Type'=>'application/json']
|
||
);
|
||
}
|
||
|
||
public function import(Request $r) {
|
||
$r->validate(['file'=>'required|file|mimes:json|max:4096']);
|
||
$raw = json_decode(file_get_contents($r->file('file')->getRealPath()), true);
|
||
if (!is_array($raw) || !isset($raw['title'],$raw['subject'],$raw['questions'])) {
|
||
return back()->with('error','Ungültiges JSON-Format. Felder: title, subject, questions erwartet.');
|
||
}
|
||
$subject = Subject::where('slug',$raw['subject'])->first();
|
||
if (!$subject) return back()->with('error','Unbekanntes Fach: '.$raw['subject']);
|
||
|
||
DB::transaction(function() use ($raw,$subject) {
|
||
$quiz = Quiz::create([
|
||
'subject_id' => $subject->id,
|
||
'title' => $raw['title'],
|
||
'description' => $raw['description'] ?? null,
|
||
'active' => true,
|
||
]);
|
||
foreach (array_slice($raw['questions'],0,10) as $i => $item) {
|
||
$q = QuizQuestion::create([
|
||
'quiz_id' => $quiz->id,
|
||
'sort_order' => $i,
|
||
'type' => $item['type'] ?? 'multiple_choice',
|
||
'question_text' => $item['question_text'],
|
||
'time_limit' => $item['time_limit'] ?? null,
|
||
'correct_answer' => $item['correct_answer'] ?? null,
|
||
]);
|
||
foreach (($item['options'] ?? []) as $j => $opt) {
|
||
QuizAnswerOption::create([
|
||
'quiz_question_id' => $q->id,
|
||
'text' => $opt['text'],
|
||
'is_correct' => $opt['is_correct'] ?? false,
|
||
'sort_order' => $j,
|
||
]);
|
||
}
|
||
}
|
||
});
|
||
return redirect()->route('admin.quizzes.index')->with('success','Quiz "'.$raw['title'].'" importiert.');
|
||
}
|
||
}
|