File "OnlineExamController.php"
Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/OnlineExamController.php
File size: 55.1 KB
MIME-type: text/x-php
Charset: utf-8
<?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);
}
}