get(); return $this->success($categories, 'موفق', 'لیست دسته‌بندی‌ها دریافت شد'); } /** * 2. Get all Subjects */ public function subjects() { $subjects = QuizSubject::all(); return $this->success($subjects, 'موفق', 'لیست دروس دریافت شد'); } /** * 3. List Quizzes (Filterable by Category or Subject) */ public function index(Request $request) { $query = Quiz::where('is_published', true)->with('category'); if ($request->filled('category_id')) { $query->where('category_id', $request->category_id); } if ($request->filled('subject_id')) { $query->whereHas('questions', function ($q) use ($request) { $q->where('subject_id', $request->subject_id); }); } $quizzes = $query->latest()->get(); return $this->success($quizzes, 'موفق', 'لیست آزمون‌ها دریافت شد'); } /** * 4. Start a Quiz & Fetch Questions (SECURELY) */ public function start(Quiz $quiz) { if (!$quiz->is_published) { return response()->json(['message' => 'این آزمون در دسترس نیست.'], 403); } $attempt = UserQuizAttempt::create([ 'user_id' => auth()->id(), 'quiz_id' => $quiz->id, 'started_at' => now(), 'status' => 'in_progress' ]); $quiz->load(['questions' => function($q) { $q->orderBy('order', 'asc'); }, 'questions.options']); // SECURITY: Hide the correct answers and explanations before sending to mobile app $quiz->questions->each(function ($question) { $question->makeHidden(['solution_explanation']); $question->options->each->makeHidden(['is_correct']); }); $data = [ 'attempt_id' => $attempt->id, 'quiz' => $quiz ]; return $this->success($data, 'موفق', 'آزمون با موفقیت شروع شد'); } /** * 5. Submit the Quiz & Calculate Score */ public function submit(Request $request, UserQuizAttempt $attempt) { if ($attempt->user_id !== auth()->id() || $attempt->status !== 'in_progress') { return response()->json(['message' => 'دسترسی غیرمجاز یا آزمون قبلاً پایان یافته است.'], 403); } $request->validate([ 'answers' => 'required|array', 'answers.*.question_id' => 'required|exists:questions,id', 'answers.*.selected_option_id' => 'nullable|exists:question_options,id', 'answers.*.text_answer' => 'nullable|string', ]); $quiz = $attempt->quiz; $totalScore = 0.00; $correctCount = 0; $wrongCount = 0; $unansweredCount = 0; $needsGrading = false; DB::beginTransaction(); try { foreach ($request->answers as $ans) { $question = Question::with('options')->find($ans['question_id']); $isCorrect = false; $earnedScore = 0.00; // Handle Multiple Choice if ($question->type === 'multiple_choice') { if (empty($ans['selected_option_id'])) { $unansweredCount++; $isCorrect = null; } else { $correctOption = $question->options->where('is_correct', true)->first(); if ($correctOption && $correctOption->id == $ans['selected_option_id']) { $isCorrect = true; $earnedScore = $question->score; $totalScore += $earnedScore; $correctCount++; } else { $isCorrect = false; if ($quiz->has_negative_score) { $earnedScore = -($question->score / 3); $totalScore += $earnedScore; } $wrongCount++; } } } // Handle Descriptive else if ($question->type === 'descriptive') { $needsGrading = true; $isCorrect = null; } UserQuizAnswer::create([ 'attempt_id' => $attempt->id, 'question_id' => $question->id, 'selected_option_id' => $ans['selected_option_id'] ?? null, 'text_answer' => $ans['text_answer'] ?? null, 'is_correct' => $isCorrect, 'earned_score' => $earnedScore, ]); } $attempt->update([ 'completed_at' => now(), 'total_score' => $totalScore, 'correct_answers' => $correctCount, 'wrong_answers' => $wrongCount, 'unanswered' => $unansweredCount, 'status' => $needsGrading ? 'pending_grading' : 'completed', ]); DB::commit(); $data = [ 'attempt_id' => $attempt->id, 'status' => $attempt->status ]; return $this->success($data, 'موفق', 'آزمون با موفقیت ثبت شد.'); } catch (\Exception $e) { DB::rollBack(); return response()->json(['message' => 'خطا در ثبت اطلاعات آزمون.'], 500); } } /** * 6. Fetch Results */ public function result(UserQuizAttempt $attempt) { if ($attempt->user_id !== auth()->id()) { return response()->json(['message' => 'غیرمجاز'], 403); } $attempt->load([ 'quiz.category', 'answers.question.options', 'answers.question.subject', 'answers.selectedOption' ]); return $this->success($attempt, 'موفق', 'نتیجه آزمون با موفقیت دریافت شد'); } }