is_restart ? true : false;
$progressId = $request->progress_id ?? null;
$practiceProgress = null;
$lastPracticeProgress = $test->lastUserProgress($progressId);
if (!$isRestart && $lastPracticeProgress) {
$alreadyTakenTime = $lastPracticeProgress->taken_time ?? 0;
$lastPracticeProgress->setRelation('practice', $test);
if ($this->examService->hasPracticeTimeExpired(
$lastPracticeProgress,
$alreadyTakenTime
)) {
throw new \Exception('Practice time has expired', 403);
}
if ($lastPracticeProgress->status !== CourseProgress::COMPLETED) {
$practiceProgress = $lastPracticeProgress;
} else {
throw new \Exception('Practice Already Completed', 403);
}
} else {
$practiceProgress = $this->examService->createPracticeProgress($test, $request->exam_time_type);
}
$modules = $test->modules;
$module = $modules->first();
$moduleProgress = $this->examService->getOrCreateModuleProgress($module, $practiceProgress);
$totalQuestions = $modules->sum(fn ($m) => $m->settings['totalQuestions']);
$data = [
'test' => [
'name' => $test->name,
'description' => $test->description,
'max_attempts' => $test->max_attempts
],
'module' => [
'progress_id' => $moduleProgress->id,
'name' => $module->name,
'directions' => $module->directions,
'isCalculatorEnable' => $module->is_calculator == 1
],
'progress_id' => $practiceProgress->id,
'taken_time' => $practiceProgress->taken_time,
'totalNumberOfQuestions' => $totalQuestions,
'allowed_time' => (optional($test->markingAndTimeAllocation)->value['total_exam_time'] ?? 0),
];
// dd($module->settings['readingComprehension']);
if ($module->subject->slug == 'ela') {
$readingCompSection = $module->settings['readingComprehension'];
$moduleProgress->update(['get_module' => 'readingComprehension']);
$passage = $readingCompSection['passages'][0];
if ($passage['type'] == 'passage') {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with(['quizzes' => function ($query) use ($passage) {
$query->with(['options'])->limit($passage['numQuestion']);
}])
->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $passage['format'])
->where('quizzes.difficulty', 'medium')
->first();
if($quiz && $quiz->quizzes) {
$this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);
$quiz->quizzes->each(function ($q, $i) use ($quiz) {
$q->no = $i + 1;
$q->setRelation('parent', (new Quiz())->forceFill([
'description' => $quiz->description
]));
});
$data['module']['question'] = QuizResource::collection($quiz->quizzes);
} else {
$data['module']['question'] = [];
}
} else {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with('options')
// ->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $passage['format'])
->where('quizzes.difficulty', 'medium')
->first();
if($quiz) {
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
$quiz->no = 1;
$data['module']['question'] = QuizResource::make($quiz);
} else {
$data['module']['question'] = [];
}
}
} else {
$questionFormat = $module->settings['questionFormats'][0]['key'];
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with(['options', 'parent.quizzes.options'])
->where('quizzes.is_grouped_question', 0)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $questionFormat)
->where('quizzes.difficulty', 'easy')
->inRandomOrder()
->first();
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
$data['module']['question'] = $quiz->parent
? QuizResource::collection($quiz->parent->quizzes)
: QuizResource::make($quiz);
}
/* $data = [
'test' => [
'name' => $test->name,
'description' => $test->description,
'max_attempts' => $test->max_attempts
],
'module' => [
'progress_id' => $moduleProgress->id,
'name' => $module->name,
'directions' => $module->directions,
'question' => $quiz->parent
? QuizResource::collection($quiz->parent->quizzes)
: QuizResource::make($quiz),
],
'progress_id' => $practiceProgress->id,
'taken_time' => $practiceProgress->taken_time,
'totalNumberOfQuestions' => $totalQuestions,
'allowed_time' => (optional($test->markingAndTimeAllocation)->value['total_exam_time'] ?? 0),
]; */
return $data;
}
/**
* SUBMIT ANSWER
*/
public function submitAnswer($practice_progress, $currentModuleProgress, $givenAnswer, $takenTime, $answer)
{
$timeType = $practice_progress->time_type;
$isTimeEnd = $this->examService->checkHasExamTimeEnd($takenTime, $timeType, $practice_progress->adaptivetest);
if ($isTimeEnd) {
throw new \Exception('Module exam time limit exceeded', 403);
}
$quiz = $givenAnswer->quiz;
$isCorrect = $this->matcher->checkAnswer($quiz->type, $quiz, $answer);
$givenAnswer->update([
'selected_option' => request('selected_option') ?? null,
'answer' => request('answer') ?? null,
'is_watched' => true,
'is_correct' => $isCorrect,
'score' => $isCorrect ? $quiz->credit : 0
]);
$practice_progress->update(['taken_time' => $takenTime]);
return $this->nextQuestion($practice_progress, $currentModuleProgress, $quiz, $isCorrect);
}
/**
* NEXT QUESTION LOGIC
*/
public function nextQuestion($practice_progress, $moduleProgress, $currentQuiz, $isCorrect)
{
// Handle grouped quiz
if (
$currentQuiz->parent
&& $moduleProgress->givenAnswers()
->whereIn('quiz_id', $currentQuiz->parent->quizzes->pluck('id'))
->where('is_watched', false)
->exists()
) {
return [];
}
$answeredQuizIds = $moduleProgress->givenAnswers()->pluck('quiz_id')->toArray(); // TODO:: use n1 query for given answers
$module = $moduleProgress->adaptiveModule;
$moduleIds = $practice_progress
->adaptiveModuleProgresses
->pluck('adaptive_module_id')
->toArray();
$isNewModule = false;
// Module completion check
if (count($answeredQuizIds) >= $module->settings['totalQuestions']) {
$isNewModule = true;
$answeredQuizIds = [];
$module = $practice_progress->adaptivetest->modules()
->with('subject:id,slug')
->whereNotIn('id', $moduleIds)
->first();
if (!$module) return [];
$moduleIds[] = $module->id;
$moduleProgress = $this->examService->getOrCreateModuleProgress($module, $practice_progress);
}
$data = [
'progress_id' => $moduleProgress->id,
'name' => $module->name,
'directions' => $module->directions,
'isCalculatorEnable' => $module->is_calculator == 1,
'isNewModule' => $isNewModule
];
if ($module->subject->slug == 'ela') {
// Difficulty logic
$levels = ['easy', 'medium', 'hard'];
//get current section
$getModuleSection = $moduleProgress->get_module;
if($getModuleSection == 'readingComprehension') {
$currentSection = $module->settings[$moduleProgress->get_module];
} else {
[$secName, $secPart] = explode('-', $getModuleSection);
$currentSection = $module->settings[$secName][$secPart];
}
if ($currentQuiz->parent) {
$qIds = $currentQuiz->parent->quizzes->pluck('id'); //siblings quiz ids
$totalCorrectAnswer = $moduleProgress->givenAnswers()->whereIn('quiz_id', $qIds->toArray())->where('is_correct', true)->count();
$correctAnsParcentage = ($totalCorrectAnswer / $qIds->count()) * 100;
//checking current section total question
//get given passages ids
$passageIds = $moduleProgress->givenAnswers()->with('quiz:id,parent_id')->get()->pluck('quiz.parent_id')->unique();
// get current answer passage
$currentPassage = collect($currentSection['passages'])
->where('type', 'passage')->where('format', $currentQuiz->parent->type)
->first();
// get given passages
$usedType = Quiz::whereIn('id', $passageIds)->pluck('type');
//get current difficulty
$diffIndex = array_search($currentQuiz->parent->difficulty, $levels);
//get next difficulty
if ($correctAnsParcentage >= 50 && $isCorrect) {
$diffIndex = min($diffIndex + 1, 2);
} else {
$diffIndex = max($diffIndex - 1, 0);
}
$nextDifficulty = $levels[$diffIndex];
//checking next hard question available
if ($nextDifficulty == 'hard') {
// checking hardness parcentage
$totalHardQuestionGiven = Quiz::whereIn('id', $passageIds->toArray())->where('difficulty', 'hard')->count();
// dd($counthard, $currentSection['totalQuestions'], $module->settings['hardnessPercentage']);
$hardParcentageGiven = intval(($totalHardQuestionGiven / $currentSection['totalQuestions']) * 100);
//is available to give hard type question
$isAvailableToGiveHard = $hardParcentageGiven < $module->settings['hardnessPercentage'];
if (!$isAvailableToGiveHard) {
$nextDifficulty = $levels[$diffIndex - 1];
}
}
//count current type passage
$currentGivenPassageCount = $usedType->filter(fn ($item) => $item === $currentPassage['format'])->count();
//check given passage greater than current passage
// dd($currentGivenPassageCount);
if ($currentGivenPassageCount < $currentPassage['numPassage']) {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with(['quizzes' => function ($query) use ($currentPassage) {
$query->with(['options'])->limit($currentPassage['numQuestion']);
}])
->whereNotIn('quizzes.id', $passageIds->toArray())
->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $currentPassage['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz && $quiz->quizzes) {
$this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);
$quiz->quizzes->each(function ($q, $i) use ($quiz, $answeredQuizIds) {
$q->no = count($answeredQuizIds) + $i + 1;
$q->setRelation('parent', (new Quiz())->forceFill([
'description' => $quiz->description
]));
});
$data['question'] = QuizResource::collection($quiz->quizzes);
} else {
logger("1, not found for" . $currentPassage['format'] . $nextDifficulty);
$data['question'] = [];
}
} else {
//next pasage finding for current given passage limit exceed
$allFormats = collect($currentSection['passages'])->pluck('format');
$nextPassageIndex = $allFormats->search($currentQuiz->parent->type);
if ($allFormats->count() > $nextPassageIndex + 1) {
$next = $currentSection['passages'][$nextPassageIndex + 1];
if ($next['type'] == 'passage') {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with(['quizzes' => function ($query) use ($next) {
$query->with(['options'])->limit($next['numQuestion']);
}])
// ->whereNotIn('quizzes.id', $passageIds->toArray())
->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $next['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz && $quiz->quizzes) {
$this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);
$quiz->quizzes->each(function ($q, $i) use ($quiz, $answeredQuizIds) {
$q->no = count($answeredQuizIds) + $i + 1;
$q->setRelation('parent', (new Quiz())->forceFill([
'description' => $quiz->description
]));
});
$data['question'] = QuizResource::collection($quiz->quizzes);
} else {
logger("2, not found for" . $next['format'] . $nextDifficulty);
$data['question'] = [];
}
} else {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with('options')
// ->where('quizzes.is_grouped_question', 1)
->whereNotIn('quizzes.id', $answeredQuizIds)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $next['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz) {
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
$quiz->no = count($answeredQuizIds) + 1;
$data['question'] = QuizResource::make($quiz);
} else {
logger("3, not found for" . $next['format'] . $nextDifficulty);
$data['question'] = [];
}
}
} else {
//change section
if($moduleProgress->get_module == 'readingComprehension') {
$next = 'revisionEditing-partA';
$moduleProgress->update(['get_module' => $next]);
//call function for next section question
} else if($moduleProgress->get_module == 'revisionEditing-partA') {
$next = 'revisionEditing-partB';
$moduleProgress->update(['get_module' => $next]);
//call function for next section question
} else {
// end of module
return [];// next module load and give question
}
}
}
} else {
$currentFormat = collect($currentSection['passages'])
->where('type', 'question')->where('format', $currentQuiz->type)
->first();
$diffIndex = array_search($currentQuiz->difficulty, $levels);
if ($isCorrect) {
$diffIndex = min($diffIndex + 1, 2);
} else {
$diffIndex = max($diffIndex - 1, 0);
}
$nextDifficulty = $levels[$diffIndex];
$givenAnsIds = $moduleProgress->givenAnswers()->whereHas('quiz', fn ($q) => $q->whereNull('parent_id'))->pluck('quiz_id');
if ($nextDifficulty == 'hard') {
// checking hardness parcentage
$totalHardQuestionGiven = Quiz::whereIn('id', $givenAnsIds->toArray())->where('difficulty', 'hard')->count();
// dd($counthard, $currentSection['totalQuestions'], $module->settings['hardnessPercentage']);
$hardParcentageGiven = intval(($totalHardQuestionGiven / $currentSection['totalQuestions']) * 100);
//is available to give hard type question
$isAvailableToGiveHard = $hardParcentageGiven < $module->settings['hardnessPercentage'];
if (!$isAvailableToGiveHard) {
$nextDifficulty = $levels[$diffIndex - 1];
}
}
$givenQuestionIds = $moduleProgress->givenAnswers()->where('type', $currentQuiz->type)->pluck('quiz_id');
if ($givenQuestionIds->count() < $currentFormat['numQuestion']) {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with('options')
->whereNotIn('quizzes.id', $givenQuestionIds->toArray())
->whereNull('quizzes.parent_id')
->where('quizzes.type', $currentFormat['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz) {
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
$data['question'] = QuizResource::make($quiz);
} else {
logger("4, not found for" . $currentFormat['format'] . $nextDifficulty);
$data['question'] = [];
}
} else {
$allFormats = collect($currentSection['passages'])->pluck('format');
$nextFormatIndex = $allFormats->search($currentQuiz->type);
if ($allFormats->count() > $nextFormatIndex + 1) {
$next = $currentSection['passages'][$nextFormatIndex + 1];
if ($next['type'] == 'passage') {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with(['quizzes' => function ($query) use ($next) {
$query->with(['options'])->limit($next['numQuestion']);
}])
->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $next['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz && $quiz->quizzes) {
$this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);
$quiz->quizzes->each(function ($q) use ($quiz) {
$q->setRelation('parent', (new Quiz())->forceFill([
'description' => $quiz->description
]));
});
$data['question'] = QuizResource::collection($quiz->quizzes);
} else {
logger("5, not found for" . $next['format'] . $nextDifficulty);
$data['question'] = [];
}
} else {
$quiz = QuestionBank::find($module->settings['selectedBankId'])
->quizzes()
->with('options')
// ->where('quizzes.is_grouped_question', 1)
->whereNull('quizzes.parent_id')
->where('quizzes.type', $next['format'])
->where('quizzes.difficulty', $nextDifficulty)
->first();
if($quiz) {
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
$data['question'] = QuizResource::make($quiz);
} else {
logger("6, not found for" . $next['format'] . $nextDifficulty);
$data['question'] = [];
}
}
} else {
//next section
}
}
}
// $nextDifficulty = $levels[$isCorrect ? min($diffIndex + 1, 2) : max($diffIndex - 1, 0)];
} else {
$bank = QuestionBank::with('quizzes:id,type,question_type,difficulty,is_grouped_question,parent_id')
->find($module->settings['selectedBankId']);
$questionFormats = collect($module->settings['questionFormats']);
$questionTypes = collect($module->settings['questionTopics']);
// Difficulty logic
$levels = ['easy', 'medium', 'hard'];
$diffIndex = array_search($currentQuiz->difficulty, $levels);
$nextDifficulty = $levels[$isCorrect ? min($diffIndex + 1, 2) : max($diffIndex - 1, 0)];
// Track reached limits
$usedTypes = $questionFormats->filter(
fn ($f) =>
$moduleProgress->givenAnswers()
->whereHas('quiz', fn ($q) => $q->where('type', $f['key']))
->count() >= $f['value']
)->pluck('key')->toArray();
$usedQtypes = $questionTypes->filter(
fn ($qt) =>
$moduleProgress->givenAnswers()
->whereHas('quiz', fn ($q) => $q->where('question_type', $qt['key']))
->count() >= $qt['value']
)->pluck('key')->toArray();
// Find next quiz within limits
$quiz = $bank->quizzes()
->whereNotIn('quizzes.id', $answeredQuizIds)
->whereNotIn('quizzes.type', $usedTypes)
->whereNotIn('quizzes.question_type', $usedQtypes)
->where('quizzes.is_grouped_question', false)
->whereNull('quizzes.parent_id')
->where('quizzes.difficulty', $nextDifficulty)
->inRandomOrder()
->first();
// If not found, move to next module
if (!$quiz) {
$nextModule = $practice_progress->adaptivetest->modules()
->whereNotIn('id', $moduleIds)->first();
if (!$nextModule) return [];
$isNewModule = true;
$moduleProgress = $this->examService->getOrCreateModuleProgress($nextModule, $practice_progress);
$bank = QuestionBank::with('quizzes')->find($nextModule->settings['selectedBankId']);
$quiz = $bank->quizzes()
->where('quizzes.is_grouped_question', false)
->whereNull('parent_id')
->where('difficulty', $nextDifficulty)
->inRandomOrder()
->first();
if (!$quiz) return [];
}
$this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
}
return $data;
}
}