Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
forbidals
/
admin_panel
/
app
/
Http
/
Controllers
:
OnlineExamController.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php namespace App\Http\Controllers; use App\Models\ClassSchool; use App\Models\ClassSection; use App\Models\OnlineExamCommon; use App\Repositories\ClassSection\ClassSectionInterface; use App\Repositories\ClassSubject\ClassSubjectInterface; use App\Repositories\OnlineExam\OnlineExamInterface; use App\Repositories\OnlineExamCommon\OnlineExamCommonInterface; use App\Repositories\OnlineExamQuestion\OnlineExamQuestionInterface; use App\Repositories\OnlineExamQuestionChoice\OnlineExamQuestionChoiceInterface; use App\Repositories\OnlineExamQuestionCommon\OnlineExamQuestionCommonInterface; use App\Repositories\OnlineExamQuestionOption\OnlineExamQuestionOptionInterface; use App\Repositories\OnlineExamStudentAnswer\OnlineExamStudentAnswerInterface; use App\Repositories\SessionYear\SessionYearInterface; use App\Repositories\Student\StudentInterface; use App\Repositories\StudentOnlineExamStatus\StudentOnlineExamStatusInterface; use App\Repositories\Subject\SubjectInterface; use App\Repositories\SubjectTeacher\SubjectTeacherInterface; use App\Services\BootstrapTableService; use App\Services\CachingService; use App\Services\ResponseService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; use Throwable; use Illuminate\Support\Str; use Carbon\Carbon; class OnlineExamController extends Controller { private ClassSectionInterface $classSection; private SubjectTeacherInterface $subjectTeacher; private OnlineExamInterface $onlineExam; private OnlineExamQuestionChoiceInterface $onlineExamQuestionChoice; private OnlineExamQuestionInterface $onlineExamQuestion; private OnlineExamQuestionOptionInterface $onlineExamQuestionOption; private OnlineExamStudentAnswerInterface $onlineExamStudentAnswer; private CachingService $cache; private StudentInterface $student; private StudentOnlineExamStatusInterface $studentOnlineExamStatus; private ClassSubjectInterface $classSubjects; private SessionYearInterface $sessionYear; private OnlineExamCommonInterface $onlineExamCommon; private SubjectInterface $subject; public function __construct(ClassSectionInterface $classSection, SubjectTeacherInterface $subjectTeacher, OnlineExamInterface $onlineExam, OnlineExamQuestionChoiceInterface $onlineExamQuestionChoice, OnlineExamQuestionInterface $onlineExamQuestion, OnlineExamQuestionOptionInterface $onlineExamQuestionOption, OnlineExamStudentAnswerInterface $onlineExamStudentAnswer, CachingService $cachingService, StudentInterface $student, StudentOnlineExamStatusInterface $studentOnlineExamStatus, ClassSubjectInterface $classSubjects, SessionYearInterface $sessionYear, OnlineExamCommonInterface $onlineExamCommon, SubjectInterface $subject) { $this->classSection = $classSection; $this->subjectTeacher = $subjectTeacher; $this->onlineExam = $onlineExam; $this->onlineExamQuestionChoice = $onlineExamQuestionChoice; $this->onlineExamQuestion = $onlineExamQuestion; $this->onlineExamQuestionOption = $onlineExamQuestionOption; $this->onlineExamStudentAnswer = $onlineExamStudentAnswer; $this->cache = $cachingService; $this->student = $student; $this->studentOnlineExamStatus = $studentOnlineExamStatus; $this->classSubjects = $classSubjects; $this->sessionYear = $sessionYear; $this->onlineExamCommon = $onlineExamCommon; $this->subject = $subject; } public function index() { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-list'); // Get classes for the initial dropdown $classes = $this->getClassesForUser(); $rand_key = random_int(100000, 999999); return response(view('online_exam.index', compact('classes', 'rand_key'))); } /** * Get classes based on user role */ public function getClassesForUser() { $sessionYearId = $this->cache->getSessionYear()->id; if (Auth::user()->hasRole('Teacher')) { // Teachers can only see classes where they are teaching subjects return $this->classSection->builder() ->whereHas('subject_teachers', function ($query) use ($sessionYearId) { $query->where('teacher_id', Auth::user()->id) ->where('session_year_id', $sessionYearId); }) ->with(['class.medium', 'class.stream', 'class.shift', 'section']) ->get() ->groupBy('class.id') ->map(function ($classSections) { $class = $classSections->first()->class; $mediumName = $class->medium ? $class->medium->name : ''; $streamName = $class->stream ? $class->stream->name : ''; return [ 'id' => $class->id, 'name' => $class->name, 'medium' => $mediumName, 'stream' => $streamName, 'full_name' => $class->name . ' - ' . $mediumName . ($streamName ? ' (' . $streamName . ')' : '') . ($class->shift ? ' ' . $class->shift->name : '') ]; }) ->values(); } else { // School Admin can see all classes return $this->classSection->builder() ->with(['class.medium', 'class.stream', 'class.shift', 'section']) ->get() ->groupBy('class.id') ->map(function ($classSections) { $class = $classSections->first()->class; $mediumName = $class->medium ? $class->medium->name : ''; $streamName = $class->stream ? $class->stream->name : ''; $shiftName = $class->shift ? $class->shift->name : ''; return [ 'id' => $class->id, 'name' => $class->name, 'medium' => $mediumName, 'stream' => $streamName, 'full_name' => $class->name . ' - ' . $mediumName . ($streamName ? ' (' . $streamName . ')' : '') . ($class->shift ? ' ' . $class->shift->name : '') ]; }) ->values(); } } /** * Get sections for a specific class */ public function getSectionsByClass(Request $request) { $classId = $request->class_id; $sessionYearId = $this->cache->getSessionYear()->id; if (!$classId) { return response()->json(['error' => 'Class ID is required'], 400); } if (Auth::user()->hasRole('Teacher')) { // Teachers can only see sections where they are teaching subjects $sections = $this->classSection->builder() ->where('class_id', $classId) ->whereHas('subject_teachers', function ($q) use ($sessionYearId) { $q->where('teacher_id', Auth::user()->id) ->where('session_year_id', $sessionYearId); }) ->with(['section', 'class', 'class.shift']) ->get() ->map(function ($classSection) { return [ 'id' => $classSection->id, 'name' => $classSection->section ? $classSection->section->name : '', 'full_name' => $classSection->full_name ]; }) ->filter(function ($section) { return !empty($section['name']); }) ->values(); } else { // School Admin can see all sections for the class $sections = $this->classSection->builder() ->where('class_id', $classId) ->with(['section', 'class', 'class.shift']) ->get() ->map(function ($classSection) { return [ 'id' => $classSection->id, 'name' => $classSection->section ? $classSection->section->name : '', 'full_name' => $classSection->full_name ]; }) ->filter(function ($section) { return !empty($section['name']); }) ->values(); } return response()->json($sections); } /** * Get subjects for a specific class section */ public function getSubjectsByClassSection(Request $request) { $classSectionIds = $request->class_section_id; $classId = $request->class_id; $sessionYearId = $this->cache->getSessionYear()->id; // If class_id is provided and class_section_id is empty, get all subjects for the class if ($classId && (!$classSectionIds || (is_array($classSectionIds) && empty($classSectionIds)))) { if (Auth::user()->hasRole('Teacher')) { // Teachers can only see subjects they are assigned to for this class $subjects = $this->subjectTeacher->builder() ->where('session_year_id', $sessionYearId) ->whereHas('class_section', function ($query) use ($classId) { $query->where('class_id', $classId); }) ->where('teacher_id', Auth::user()->id) ->with(['subject', 'class_subject']) ->get() ->unique('class_subject_id') ->map(function ($subjectTeacher) { return [ 'id' => $subjectTeacher->class_subject_id, 'subject_id' => $subjectTeacher->subject_id, 'subject_name' => $subjectTeacher->subject->name, 'subject_type' => $subjectTeacher->class_subject->type ?? 'Compulsory', 'subject_with_name' => $subjectTeacher->subject_with_name ]; }) ->values(); } else { // School Admin can see all subjects for the class $subjects = $this->classSubjects->builder() ->where('session_year_id', $sessionYearId) ->where('class_id', $classId) ->with('subject') ->get() ->map(function ($classSubject) { return [ 'id' => $classSubject->id, 'subject_id' => $classSubject->subject_id, 'subject_name' => $classSubject->subject->name, 'subject_type' => $classSubject->type, 'subject_with_name' => $classSubject->subject_with_name ]; }); } return response()->json($subjects); } if (!$classSectionIds) { return response()->json(['error' => 'Class Section ID is required'], 400); } // Ensure we have an array if (!is_array($classSectionIds)) { $classSectionIds = [$classSectionIds]; } if (Auth::user()->hasRole('Teacher')) { // Teachers can only see subjects they are assigned to $subjects = $this->subjectTeacher->builder() ->where('session_year_id', $sessionYearId) ->whereIn('class_section_id', $classSectionIds) ->where('teacher_id', Auth::user()->id) ->with(['subject', 'class_subject']) ->get() ->unique('class_subject_id') ->map(function ($subjectTeacher) { return [ 'id' => $subjectTeacher->class_subject_id, 'subject_id' => $subjectTeacher->subject_id, 'subject_name' => $subjectTeacher->subject->name, 'subject_type' => $subjectTeacher->class_subject->type ?? 'Compulsory', 'subject_with_name' => $subjectTeacher->subject_with_name ]; }); } else { // School Admin can see all subjects for the class sections // Get the class_id from the first class section $firstClassSection = $this->classSection->findById($classSectionIds[0]); $subjects = $this->classSubjects->builder() ->where('session_year_id', $sessionYearId) ->where('class_id', $firstClassSection->class_id) ->with('subject') ->get() ->map(function ($classSubject) { return [ 'id' => $classSubject->id, 'subject_id' => $classSubject->subject_id, 'subject_name' => $classSubject->subject->name, 'subject_type' => $classSubject->type, 'subject_with_name' => $classSubject->subject_with_name ]; }); } return response()->json($subjects); } /** * Validate that the user has access to the selected class, sections, and subject */ private function validateUserAccess(Request $request) { $classId = $request->class_id; $classSectionIds = $request->class_section_id; $subjectId = $request->subject_id; $sessionYearId = $this->cache->getSessionYear()->id; if (Auth::user()->hasRole('Teacher')) { // Validate class access - only where teacher is teaching subjects $hasClassAccess = $this->classSection->builder() ->where('class_id', $classId) ->whereHas('subject_teachers', function ($q) use ($sessionYearId) { $q->where('teacher_id', Auth::user()->id) ->where('session_year_id', $sessionYearId); }) ->exists(); if (!$hasClassAccess) { throw new \Exception('You do not have access to this class.'); } // Validate section access - only where teacher is teaching subjects foreach ($classSectionIds as $sectionId) { $hasSectionAccess = $this->classSection->builder() ->where('id', $sectionId) ->where('class_id', $classId) ->whereHas('subject_teachers', function ($q) { $q->where('teacher_id', Auth::user()->id); }) ->exists(); if (!$hasSectionAccess) { throw new \Exception('You do not have access to one or more selected sections.'); } } // dd($classSectionIds); // Validate subject access for each section foreach ($classSectionIds as $sectionId) { $hasSubjectAccess = $this->subjectTeacher->builder() ->where('session_year_id', $sessionYearId) ->where('class_section_id', $sectionId) ->where('subject_id', $subjectId) ->where('teacher_id', Auth::user()->id) ->exists(); if (!$hasSubjectAccess) { throw new \Exception('You do nothave access to this subject for one or more selected sections.'); } } } // School Admin has access to everything, so no additional validation needed } public function store(Request $request) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-create'); $request->validate([ 'class_id' => 'required|numeric', 'subject_id' => 'required|numeric', 'title' => 'required', 'exam_key' => 'required|unique:online_exams,exam_key,NULL,id,school_id,' . Auth::user()->school_id, 'duration' => 'required|numeric|gte:1', 'start_date' => 'required', 'end_date' => 'required|after:start_date', ]); $classSection = ClassSection::where('class_id', $request->class_id)->first(); if (empty($request->class_section_id)) { if ($classSection && $classSection->section_id !== null) { ResponseService::errorResponse('Class Section is Required'); } } if (empty($request->class_section_id) || (is_array($request->class_section_id) && count(array_filter($request->class_section_id)) == 0)) { $classSectionIds = [$classSection->id]; } else { // Ensure it's an array $classSectionIds = is_array($request->class_section_id) ? $request->class_section_id : [$request->class_section_id]; } $request->merge(['class_section_id' => $classSectionIds]); $this->validateUserAccess($request); try { DB::beginTransaction(); $sessionYear = $this->cache->getSessionYear(); // Get classSectionIds from request (already set above) $classSectionIds = $request->class_section_id; $onlineExamList = []; foreach ($classSectionIds as $section_id) { $onlineExamList = array_merge($request->all(), ['class_section_id' => $section_id]); } // Get the related class subject for each section if ($classSectionIds) { foreach ($classSectionIds as $section_id) { if (Auth::user()->hasRole('Teacher')) { $SubjectTeacher = $this->subjectTeacher->builder()->where('class_section_id', $section_id)->where('subject_id', $request->subject_id)->first(); $onlineExamList['exam_key'] = $request->exam_key; $onlineExamList['class_subject_id'] = $SubjectTeacher->class_subject_id; $onlineExamList['session_year_id'] = $sessionYear->id; } else { $classSection = $this->classSection->builder()->where('id', $section_id)->with([ 'class_subject' => function ($q) use ($request) { $q->where('subject_id', $request->subject_id); } ])->first(); // dd($classSection->toArray()); $onlineExamList['exam_key'] = $request->exam_key; $onlineExamList['class_subject_id'] = $classSection->class_subject->id; $onlineExamList['session_year_id'] = $sessionYear->id; } } } unset($onlineExamList['subject_id']); unset($onlineExamList['class_id']); $onlineExam = $this->onlineExam->create($onlineExamList); $onlineExamCommonData = []; $onlineExamCommonData['online_exam_id'] = $onlineExam->id; // Create online_exam_common data for each section foreach ($classSectionIds as $section_id) { if (Auth::user()->hasRole('Teacher')) { $subjectTeacher = $this->subjectTeacher->builder()->where('class_section_id', $section_id)->where('subject_id', $request->subject_id)->first(); $onlineExamCommonData['class_section_id'] = $section_id; $onlineExamCommonData['class_subject_id'] = $subjectTeacher->class_subject_id; $this->onlineExamCommon->create($onlineExamCommonData); } else { $classSection = $this->classSection->builder()->where('id', $section_id)->with([ 'class_subject' => function ($q) use ($request) { $q->where('subject_id', $request->subject_id); } ])->first(); $onlineExamCommonData['class_section_id'] = $section_id; $onlineExamCommonData['class_subject_id'] = $classSection->class_subject->id; $this->onlineExamCommon->create($onlineExamCommonData); } } DB::commit(); ResponseService::successResponse('Data Stored Successfully'); } catch (\Exception $e) { DB::rollBack(); ResponseService::logErrorResponse($e, 'OnlineExamController@store', 'store'); return response()->json(['error' => $e->getMessage()], 422); } catch (Throwable $e) { if ( Str::contains($e->getMessage(), [ 'does not exist', 'file_get_contents' ]) ) { DB::commit(); ResponseService::warningResponse("Data Stored successfully. But App push notification not send."); } else { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> Store method"); ResponseService::errorResponse(); } } } public function show() { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenSendJson('online-exam-list'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'ASC'); $search = request('search'); $showDeleted = request('show_deleted'); $session_year_id = $this->cache->getSessionYear()->id; $exam_status = request('exam_status'); // BASE QUERY: OnlineExamCommon (correct) $sql = $this->onlineExamCommon->builder() ->with([ 'online_exam' => function ($q) { $q->with([ 'class_subject.subject', 'question_choice', 'student_attempt' => function ($query) { $query->with(['student_data.student']); } ]); }, 'class_section.class.stream', 'class_section.class.shift', 'class_section.section', 'class_section.medium', 'class_section.students' => function ($q) { $q->whereHas('user', function ($q) { $q->where('status', 1); }); } ]) ->when(request('class_section_id'), function ($q) { $q->where('class_section_id', request('class_section_id')); }) ->when(request('class_subject_id'), function ($query) { $query->where('class_subject_id', request('class_subject_id')); }) ->when(request('class_id'), function ($query) { $class_id = request('class_id'); $query->whereHas('class_section', function ($q) use ($class_id) { $q->where('class_id', $class_id); }); }) ->when($search, function ($query) use ($search) { $query->where(function ($query) use ($search) { $query->where('id', 'LIKE', "%$search%") ->orWhereHas('online_exam', function ($q) use ($search) { $q->where('title', 'LIKE', "%$search%") ->orWhere('exam_key', 'LIKE', "%$search%") ->orWhere('duration', 'LIKE', "%$search%") ->orWhere('start_date', 'LIKE', "%" . date('Y-m-d H:i:s', strtotime($search)) . "%") ->orWhere('end_date', 'LIKE', "%" . date('Y-m-d H:i:s', strtotime($search)) . "%"); }) ->orWhereHas('online_exam.class_subject.subject', function ($query) use ($search) { $query->where('name', 'LIKE', "%$search%") ->orWhere('type', 'LIKE', "%$search%"); }); }); }) ->when(!empty($showDeleted), function ($q) { $q->whereHas('online_exam', function ($query) { $query->onlyTrashed(); }); }) ->when($session_year_id, function ($query) use ($session_year_id) { $query->whereHas('online_exam', function ($q) use ($session_year_id) { $q->where('session_year_id', $session_year_id); }); }) ->when($exam_status, function ($query) use ($exam_status) { $now = Carbon::now(); $query->whereHas('online_exam', function ($q) use ($exam_status, $now) { if ($exam_status === 'Upcoming') { $q->whereRaw('start_date > ?', [$now]); } elseif ($exam_status === 'On Going') { $q->whereRaw('start_date <= ?', [$now]) ->whereRaw('end_date >= ?', [$now]); } elseif ($exam_status === 'Completed') { $q->whereRaw('end_date < ?', [$now]); } }); }); // Pagination $totalExams = $sql->count(); if ($offset >= $totalExams && $totalExams > 0) { $offset = floor(($totalExams - 1) / $limit) * $limit; } $res = $sql->orderBy($sort, $order)->skip($offset)->take($limit)->get(); $actualTotal = $totalExams; // Get all exam IDs from the results to check for linked exams $examIds = $res->pluck('online_exam_id')->unique(); // Count commons for each exam to determine if linked $examCommonsCount = []; foreach ($examIds as $examId) { $examCommonsCount[$examId] = $this->onlineExamCommon->builder() ->where('online_exam_id', $examId) ->count(); } // Colors $colorPalette = [ '#8B5CF6', '#EC4899', '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#06B6D4', '#8B5CF6', '#A855F7', '#6366F1' ]; $linkedExamColors = []; foreach ($examIds as $examId) { if ($examCommonsCount[$examId] > 1) { $linkedExamColors[$examId] = $colorPalette[$examId % count($colorPalette)]; } } $rows = []; $no = $offset + 1; // Track which exam_ids we've already seen (to identify first occurrence) $seenExamIds = []; foreach ($res as $row) { // FIX: row itself IS online_exam_common $common = $row; $operate = ''; // Check if this exam is linked (has multiple commons) $isLinked = ($examCommonsCount[$row->online_exam_id] ?? 0) > 1; $linkedColor = $isLinked ? ($linkedExamColors[$row->online_exam_id] ?? '#8B5CF6') : null; // Check if this is the first occurrence of this exam in the current page $isFirstOccurrence = !isset($seenExamIds[$row->online_exam_id]); if ($isFirstOccurrence) { $seenExamIds[$row->online_exam_id] = true; } // Class Section Name $classSectionDisplay = ''; if ($common->class_section) { if ( $common->class_section->relationLoaded('class') && $common->class_section->relationLoaded('section') && $common->class_section->relationLoaded('medium') ) { $classSectionDisplay = $common->class_section->full_name; } else { $classSectionDisplay = $common->class_section->full_name; } } // Students $totalStudentsForSection = 0; if ($common->class_section && $common->class_section->relationLoaded('students')) { $totalStudentsForSection = $common->class_section->students->count(); } // Get student attempts for this specific class section $studentAttemptedForSection = 0; if ($common->class_section_id && $row->online_exam) { // Use the relationship if loaded, otherwise query directly if ($row->online_exam->relationLoaded('student_attempt')) { $studentAttemptedForSection = $row->online_exam->student_attempt ->filter(function ($attempt) use ($common) { return $attempt->student_data && $attempt->student_data->student && $attempt->student_data->student->class_section_id == $common->class_section_id; }) ->count(); } else { // Query directly if not loaded $studentAttemptedForSection = $row->online_exam->student_attempt() ->whereHas('student_data.student', function ($q) use ($common) { $q->where('class_section_id', $common->class_section_id); }) ->count(); } } // Buttons - use online_exam_id, not common id $examId = $row->online_exam_id; if ($showDeleted) { $operate .= BootstrapTableService::menuRestoreButton('restore', route('online-exam.restore', $examId)); $operate .= BootstrapTableService::menuTrashButton('delete', route('online-exam.trash', $examId)); } else { if (Auth::user()->can('online-exam-result-list')) { $operate .= BootstrapTableService::menuButton('Result', route('online-exam.result.index', ['id' => $examId]), [], []); } if (Auth::user()->can('online-exam-list')) { $operate .= BootstrapTableService::menuButton('add_questions', route('online-exam.add.questions.index', $examId), [], []); $operate .= BootstrapTableService::menuEditButton('edit', route('online-exam.update', $examId)); $operate .= BootstrapTableService::menuDeleteButton('delete', route('online-exam.destroy', $examId)); } } // Dates - access from online_exam relationship $onlineExam = $row->online_exam; if (!$onlineExam) { continue; // Skip if exam not found } $start = Carbon::parse($onlineExam->getRawOriginal('start_date')); $end = Carbon::parse($onlineExam->getRawOriginal('end_date')); $tempRow = []; $tempRow['id'] = $onlineExam->id; // Use exam ID for the row $tempRow['no'] = $no++; $tempRow['class_section_with_medium'] = $classSectionDisplay; $tempRow['subject_name'] = $onlineExam->class_subject && $onlineExam->class_subject->relationLoaded('subject') ? $onlineExam->class_subject->subject_with_name : ''; $tempRow['title'] = htmlspecialchars_decode($onlineExam->title); $tempRow['start_date'] = $start->format('Y-m-d H:i'); $tempRow['start_date_db'] = $onlineExam->start_date; $tempRow['end_date'] = $end->format('Y-m-d H:i'); $tempRow['end_date_db'] = $onlineExam->end_date; $tempRow['total_questions'] = $onlineExam->question_choice ? $onlineExam->question_choice->count() : 0; $tempRow['participants'] = $studentAttemptedForSection . '/' . $totalStudentsForSection; // Set is_linked = 1 only on the first row of each linked exam (formatter will display badge) $tempRow['is_linked'] = ($isLinked && $isFirstOccurrence) ? 1 : 0; $tempRow['linked_color'] = $linkedColor; $tempRow['created_at'] = $onlineExam->created_at; $tempRow['updated_at'] = $onlineExam->updated_at; $tempRow['exam_status_name'] = $onlineExam->exam_status_name ?? ''; $tempRow['exam_key'] = $onlineExam->exam_key; $tempRow['duration'] = $onlineExam->duration; $tempRow['operate'] = BootstrapTableService::menuItem($operate); $rows[] = $tempRow; } return response()->json([ 'total' => $actualTotal, 'rows' => $rows ]); } public function update(Request $request, $id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenSendJson('online-exam-edit'); $validator = Validator::make($request->all(), [ 'edit_title' => 'required', 'edit_exam_key' => 'required|numeric', 'edit_duration' => 'required|numeric|gte:1', 'edit_start_date' => 'required|date', 'edit_end_date' => 'required|date' ]); if ($validator->fails()) { ResponseService::errorResponse($validator->errors()->first()); } try { DB::beginTransaction(); $this->onlineExam->update($id, array( 'title' => $request->edit_title, 'exam_key' => $request->edit_exam_key, 'duration' => $request->edit_duration, 'start_date' => $request->edit_start_date, 'end_date' => $request->edit_end_date, )); DB::commit(); ResponseService::successResponse("Data Updated Successfully"); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> Update method"); ResponseService::errorResponse(); } } public function destroy($id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenSendJson('online-exam-delete'); try { DB::beginTransaction(); $this->onlineExam->deleteById($id); DB::commit(); ResponseService::successResponse('Data Deleted Successfully'); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> Delete method"); ResponseService::errorResponse(); } } public function restore(int $id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenSendJson('online-exam-delete'); try { $this->onlineExam->findOnlyTrashedById($id)->restore(); ResponseService::successResponse("Data Restored Successfully"); } catch (Throwable $e) { ResponseService::logErrorResponse($e); ResponseService::errorResponse(); } } public function trash($id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenSendJson('online-exam-delete'); try { $this->onlineExam->findOnlyTrashedById($id)->forceDelete(); ResponseService::successResponse("Data Deleted Permanently"); } catch (Throwable $e) { ResponseService::logErrorResponse($e, "Online Exam Controller -> Trash Method", 'cannot_delete_because_data_is_associated_with_other_data'); ResponseService::errorResponse(); } } public function addQuestionIndex($id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-questions-list'); $onlineExam = $this->onlineExam->findById($id, ['*'], ['online_exam_commons.class_subject.subject']); $classSectionIds = $onlineExam->online_exam_commons->pluck('class_section_id'); $classSubjectIds = $onlineExam->online_exam_commons->pluck('class_subject_id'); // Get class_section with medium name $onlineExamCommons = $this->onlineExamCommon->builder() ->where('online_exam_id', $id) ->with('class_section.class.medium', 'class_section.class.shift', 'class_section.section', 'class_section.medium') ->get(); // dd($onlineExamCommons); $examQuestions = $this->onlineExamQuestionChoice->builder()->where('online_exam_id', $id)->with('online_exam', 'questions')->get(); return response(view('online_exam.exam_questions', compact('onlineExam', 'examQuestions', 'onlineExamCommons', 'classSectionIds', 'classSubjectIds'))); } public function storeExamQuestionChoices(Request $request) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-questions-create'); $request->validate([ 'question' => 'required', 'option_data.*.option' => 'required', 'answer.*' => 'required', 'image' => 'nullable|mimes:jpeg,png,jpg|image|max:3048', ]); try { DB::beginTransaction(); $classSectionIds = is_array($request->class_section_id) ? $request->class_section_id : [$request->class_section_id]; foreach ($classSectionIds as $classSectionId) { $classSection = $this->classSection->findById($classSectionId); $classId = $classSection->class_id; } $onlineExamQuestionData = array( 'class_id' => $classId, 'class_subject_id' => $request->class_subject_id, 'question' => htmlspecialchars($request->question), 'image_url' => $request->image, 'note' => $request->note, 'difficulty' => $request->difficulty, 'last_edited_by' => Auth::user()->id, ); $onlineExamQuestion = $this->onlineExamQuestion->create($onlineExamQuestionData); // foreach ($classSectionIds as $classSectionId) { // $this->onlineExamQuestion->create([ // 'question_id' => $onlineExamQuestion->id, // 'class_section_id' => $classSectionId, // 'class_subject_id' => $request->class_subject_id, // ]); // } $onlineExamOptionData = array(); foreach ($request->option_data as $key => $optionValue) { $onlineExamOptionData[$key] = array( 'question_id' => $onlineExamQuestion->id, 'option' => htmlspecialchars($optionValue['option']), 'is_answer' => 0, // Initialize is_answer to 0 ); foreach ($request->answer as $answerValue) { if ($optionValue['number'] == $answerValue) { $onlineExamOptionData[$key]['is_answer'] = 1; // Set is_answer to 1 if a match is found break; // Break the loop as we've found a match } } } foreach ($onlineExamOptionData as $option) { $onlineExamQuestionOption = $this->onlineExamQuestionOption->create($option); } DB::commit(); ResponseService::successResponse('Data Stored Successfully', array( 'exam_id' => $request->online_exam_id, 'question_id' => $onlineExamQuestion->id, 'question' => "<textarea id='qc" . $onlineExamQuestion->id . "'>" . htmlspecialchars_decode($onlineExamQuestion->question) . "</textarea><script>setTimeout(() => {equation_editor = CKEDITOR.inline('qc" . $onlineExamQuestion->id . "', { skin:'moono',extraPlugins: 'mathjax', mathJaxLib: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML', readOnly:true, }); },1000);</script>" )); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> storeExamQuestionChoices method"); ResponseService::errorResponse(); } } public function getClassQuestions($onlineExamId) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-create'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'ASC'); $search = request('search'); $difficulty = request('difficulty'); $onlineExamCommonData = $this->onlineExamCommon->builder()->where('online_exam_id', $onlineExamId)->get(); $excludeQuestionId = $this->onlineExamQuestionChoice->builder()->where('online_exam_id', $onlineExamId)->pluck('question_id'); // Initialize arrays to hold class and subject IDs $classIds = []; $classSubjectIds = []; // Loop through the online exam common data to extract IDs foreach ($onlineExamCommonData as $data) { // Get class_id from class_section_id $classSection = $this->classSection->builder()->where('id', $data->class_section_id)->first(); if ($classSection) { $classIds[] = $classSection->class_id; } $classSubjectIds[] = $data->class_subject_id; } $classIds = array_unique($classIds); $classSubjectIds = array_unique($classSubjectIds); $sql = $this->onlineExamQuestion->builder() ->with('options') ->whereIn('class_id', $classIds) ->whereIn('class_subject_id', $classSubjectIds) ->whereNotIn('id', $excludeQuestionId) ->where(function ($query) use ($search) { $query->when($search, function ($query) use ($search) { $query->where(function ($query) use ($search) { $query->where('id', 'LIKE', "%$search%") ->orWhere('question', 'LIKE', "%$search%") ->orWhere('created_at', 'LIKE', "%" . date('Y-m-d H:i:s', strtotime($search)) . "%") ->orWhere('updated_at', 'LIKE', "%" . date('Y-m-d H:i:s', strtotime($search)) . "%") ->orWhereHas('options', function ($p) use ($search) { $p->where('option', 'LIKE', "%$search%"); }); }); }); }) ->when($difficulty, function ($query) use ($difficulty) { $query->where('difficulty', $difficulty); }); $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $tempRow = array(); $no = 1; foreach ($res as $row) { $tempRow['question_id'] = $row->id; $tempRow['no'] = $no++; $tempRow['class_section_id'] = $row->class_section_id; $tempRow['class_section_name'] = $row->class_section_with_medium; $tempRow['class_subject_id'] = $row->class_subject_id; $tempRow['subject_name'] = $row->subject_with_name; $tempRow['question'] = "<div class='equation-editor-inline' contenteditable=false name='qc" . $row->id . "'>" . htmlspecialchars_decode($row->question) . "</div>"; $tempRow['question_row'] = htmlspecialchars_decode($row->question); $tempRow['options'] = array(); $tempRow['answers'] = array(); foreach ($row->options as $options) { $option_data = array( 'id' => $options->id, 'option' => "<div class='equation-editor-inline' contenteditable=false>" . htmlspecialchars_decode($options->option) . "</div>", 'option_row' => htmlspecialchars_decode($options->option) ); $tempRow['options'][] = $option_data; if ($options->is_answer) { $answer_data = array( 'id' => $options->id, 'answer' => "<div class='equation-editor-inline' contenteditable=false>" . htmlspecialchars_decode($options->option) . "</div>", ); $tempRow['answers'][] = $answer_data; } } $tempRow['image'] = $row->image_url; $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } public function storeQuestionsChoices(Request $request) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-create'); $request->validate([ 'exam_id' => 'required', 'assign_questions.*.question_id' => 'required', 'assign_questions.*.marks' => 'required|numeric' ], [ 'assign_questions.*.marks.required' => trans('marks_are_required') ]); try { DB::beginTransaction(); $onlineExamQuestionChoiceData = array(); foreach ($request->assign_questions as $question) { $onlineExamQuestionChoiceData[] = array( 'id' => $question['edit_id'] ?? null, 'online_exam_id' => $request->exam_id, 'question_id' => $question['question_id'], 'marks' => $question['marks'] ); } $this->onlineExamQuestionChoice->upsert($onlineExamQuestionChoiceData, ["id"], ['online_exam_id', 'question_id', 'marks']); $examData = $this->onlineExam->builder()->where('id', $request->exam_id)->first(); $subjectName = $examData->class_subject->subject; $students = $this->student->builder()->where('application_status', 1)->where('class_section_id', $examData->class_section->id)->whereHas('user', function ($query) { $query->where('status', 1)->withTrashed(); }); if ($examData->class_subject->type == 'Elective') { $students = $students->whereHas('student_subjects', function ($q) use ($examData) { $q->where('class_subject_id', $examData->class_subject->id); }); } $students = $students->get(['user_id', 'guardian_id']); $guardians = $students->pluck('guardian_id'); $users = $students->pluck('user_id'); $userIds = array_merge($users->toArray(), $guardians->toArray()); $customData = ['exam_type' => 'online', 'exam_id' => $examData->id, 'exam_title' => $examData->title]; DB::commit(); send_notification($userIds, 'Online Exam Alert !!!', 'New online exam added for ' . $subjectName->name . '(' . $subjectName->type . ')', 'exam', $customData); ResponseService::successResponse('Data Stored Successfully'); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> storeQuestionsChoices method"); ResponseService::errorResponse(); } } public function removeQuestionsChoices($id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-create'); try { $student_submitted_answers = $this->onlineExamStudentAnswer->builder()->where('question_id', $id)->count(); // dd($student_submitted_answers); if ($student_submitted_answers) { ResponseService::errorResponse("cannot delete because data is associated with other data"); } else { DB::beginTransaction(); $this->onlineExamQuestionChoice->deleteById($id); DB::commit(); ResponseService::successResponse('Data Deleted Successfully'); } } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> removeQuestionsChoices method"); ResponseService::errorResponse(); } } public function storeRandomQuestionsChoices(Request $request) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-create'); $request->validate([ 'exam_id' => 'required', 'class_section_id' => 'required', 'class_subject_id' => 'required', 'total_questions' => 'required|numeric|min:1', 'total_marks' => 'required|numeric|min:1', 'difficulty' => 'required|in:all,easy,medium,hard', ]); $onlineExamId = $request->exam_id; $numberOfQuestions = $request->total_questions; $difficulty = $request->difficulty; if (is_float($request->total_marks / $numberOfQuestions)) { ResponseService::errorResponse("Please enter a number that, when divided by the specified divisor, results in a whole number (integer) rather than a decimal (float)."); } $string = trim($request->class_section_id, '[]'); // Remove square brackets $classSectionIds = explode(',', $string); foreach ($classSectionIds as $key => $value) { $classId = $this->classSection->builder()->where('id', $value)->first()->class_id; } $string = trim($request->class_subject_id, '[]'); // Remove square brackets $classSubjectIds = explode(',', $string); $questionIds = $this->onlineExamQuestion->builder() ->whereIn('class_id', [$classId]) ->whereIn('class_subject_id', $classSubjectIds) ->when($difficulty !== 'all', function ($q) use ($difficulty) { $q->where('difficulty', $difficulty); }) ->limit($numberOfQuestions) ->inRandomOrder() ->get()->pluck('id')->toArray(); if (count($questionIds) < $numberOfQuestions) { ResponseService::errorResponse("Not enough questions available for the selected criteria."); } try { DB::beginTransaction(); $data = []; foreach ($questionIds as $questionId) { $data[] = array( 'online_exam_id' => $onlineExamId, 'question_id' => $questionId, 'marks' => $request->total_marks / $numberOfQuestions, 'school_id' => Auth::user()->school_id, ); } $this->onlineExamQuestionChoice->upsert($data, ["id"], ['online_exam_id', 'question_id', 'marks']); DB::commit(); ResponseService::successResponse('Data Stored Successfully'); } catch (Throwable $e) { DB::rollback(); ResponseService::logErrorResponse($e, "Online Exam Controller -> storeRandomQuestionsChoices method"); ResponseService::errorResponse(); } } public function onlineExamResultIndex($id) { ResponseService::noFeatureThenRedirect('Exam Management'); ResponseService::noPermissionThenRedirect('online-exam-result-list'); $onlineExamData = $this->onlineExam->findById($id, ['*'], ['class_subject', 'class_section']); $onlineExamCommons = OnlineExamCommon::where('online_exam_id', $id)->with('class_section')->get()->pluck('class_section_with_medium_and_shift_and_stream', 'class_section_id')->toArray(); return response(view('online_exam.online_exam_result', compact('onlineExamData', 'onlineExamCommons'))); } // To Be Optimised With API public function showOnlineExamResult($id) { ResponseService::noPermissionThenRedirect('online-exam-list'); $offset = request('offset', 0); $limit = request('limit', 10); $sort = request('sort', 'id'); $order = request('order', 'ASC'); $sql = $this->studentOnlineExamStatus->builder()->with('student_data', 'online_exam.question_choice')->where(['online_exam_id' => $id, 'status' => 2]); $total = $sql->count(); if ($offset >= $total && $total > 0) { $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset $offset = $lastPage; } $sql->orderBy($sort, $order)->skip($offset)->take($limit); $res = $sql->get(); $bulkData = array(); $bulkData['total'] = $total; $rows = array(); $tempRow = array(); $no = 1; foreach ($res as $student_attempt) { //get the total marks and obtained marks // $total_obtained_marks = 0; // $total_marks = 0; $exam_submitted_question_ids = $this->onlineExamStudentAnswer->builder()->where(['student_id' => $student_attempt->student_id, 'online_exam_id' => $student_attempt->online_exam_id])->pluck('question_id'); $question_ids = $this->onlineExamQuestionChoice->builder()->whereIn('id', $exam_submitted_question_ids)->pluck('question_id')->toArray(); $exam_attempted_answers = $this->onlineExamStudentAnswer->builder()->where(['student_id' => $student_attempt->student_id, 'online_exam_id' => $student_attempt->online_exam_id])->pluck('option_id'); //removes the question id of the question if one of the answer of particular question is wrong foreach ($question_ids as $question_id) { $check_questions_answers_exists = $this->onlineExamQuestionOption->builder()->where(['question_id' => $question_id, 'is_answer' => 1])->whereNotIn('id', $exam_attempted_answers)->count(); if ($check_questions_answers_exists) { unset($question_ids[array_search($question_id, $question_ids)]); } } $exam_correct_answers_question_id = $this->onlineExamQuestionOption->builder()->where(['is_answer' => 1])->whereIn('id', $exam_attempted_answers)->whereIn('question_id', $question_ids)->pluck('question_id')->toArray(); // get the data of only attempted data $total_obtained_marks = $this->onlineExamQuestionChoice->builder()->select(DB::raw("sum(marks)"))->where('online_exam_id', $student_attempt->online_exam_id)->whereIn('question_id', $exam_correct_answers_question_id)->first(); $total_obtained_marks = $total_obtained_marks['sum(marks)']; $total_marks = $this->onlineExamQuestionChoice->builder()->select(DB::raw("sum(marks)"))->where('online_exam_id', $student_attempt->online_exam_id)->first(); $total_marks = $total_marks['sum(marks)']; $tempRow['student_id'] = $student_attempt->student_id; $tempRow['no'] = $no++; $tempRow['student_name'] = $student_attempt->student_data->full_name; if ($total_obtained_marks) { $tempRow['marks'] = $total_obtained_marks . ' / ' . $total_marks; } else { $total_obtained_marks = 0; $tempRow['marks'] = $total_obtained_marks . ' / ' . $total_marks; } $rows[] = $tempRow; } $bulkData['rows'] = $rows; return response()->json($bulkData); } }