From e4ee4b3f7218ee57c81a6285498efeb87574ae66 Mon Sep 17 00:00:00 2001 From: Baghiz Zuhdi Adzin <74885652+baghizzhd@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:32:05 +0700 Subject: [PATCH] fix bug player course --- app/Http/Controllers/CurriculumController.php | 47 ----- app/Http/Controllers/PlayerController.php | 11 +- .../Controllers/student/QuizController.php | 182 ++++++++++++++---- app/Models/Certificate.php | 7 + .../course_player/certificate/index.blade.php | 2 + resources/views/course_player/init.blade.php | 47 ++++- .../student/my_courses/index.blade.php | 1 + 7 files changed, 202 insertions(+), 95 deletions(-) diff --git a/app/Http/Controllers/CurriculumController.php b/app/Http/Controllers/CurriculumController.php index 353c15b..4855584 100644 --- a/app/Http/Controllers/CurriculumController.php +++ b/app/Http/Controllers/CurriculumController.php @@ -81,19 +81,6 @@ class CurriculumController extends Controller $data['video_type'] = $request->lesson_provider; $data['lesson_src'] = $request->lesson_src; - if (empty($request->duration)) { - $data['duration'] = '00:00:00'; - } else { - $duration_formatter = explode(':', $request->duration); - $hour = sprintf('%02d', $duration_formatter[0]); - $min = sprintf('%02d', $duration_formatter[1]); - $sec = sprintf('%02d', $duration_formatter[2]); - $data['duration'] = $hour . ':' . $min . ':' . $sec; - } - } elseif ($request->lesson_type == 'html5') { - - $data['video_type'] = $request->lesson_provider; - $data['lesson_src'] = $request->lesson_src; if (empty($request->duration)) { $data['duration'] = '00:00:00'; } else { @@ -121,39 +108,6 @@ class CurriculumController extends Controller } $data['attachment'] = $file; $data['attachment_type'] = $request->attachment_type; - } elseif ($request->lesson_type == 'scorm') { - if ($request->scorm_file == '') { - $file = ''; - } else { - $item = $request->file('scorm_file'); - $file_name = strtotime('now') . random(4) . '.' . $item->getClientOriginalExtension(); - - $upload_path = public_path('uploads/lesson_file/scorm_content'); - - if (! File::isDirectory($upload_path)) { - File::makeDirectory($upload_path, 0777, true, true); - } - - $item->move($upload_path, $file_name); - - $zip = new \ZipArchive(); - $zip_path = $upload_path . '/' . $file_name; - $extract_path = $upload_path . '/' . pathinfo($file_name, PATHINFO_FILENAME); - - if ($zip->open($zip_path) === true) { - $zip->extractTo($extract_path); - $zip->close(); - - File::delete($zip_path); - } else { - return response()->json(['error' => 'Failed to extract the SCORM file.'], 500); - } - - $file = pathinfo($file_name, PATHINFO_FILENAME); - } - - $data['attachment'] = $file; - $data['attachment_type'] = $request->scorm_provider; } elseif ($request->lesson_type == 'image') { if ($request->attachment == '') { @@ -186,7 +140,6 @@ class CurriculumController extends Controller $data['duration'] = $hour . ':' . $min . ':' . $sec; } } elseif ($request->lesson_type == 'iframe') { - $data['lesson_src'] = $request->iframe_source; } elseif ($request->lesson_type == 'google_drive') { diff --git a/app/Http/Controllers/PlayerController.php b/app/Http/Controllers/PlayerController.php index 3faf860..8ada88e 100644 --- a/app/Http/Controllers/PlayerController.php +++ b/app/Http/Controllers/PlayerController.php @@ -10,6 +10,7 @@ use App\Models\Lesson; use App\Models\Watch_history; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; +use Illuminate\Support\Facades\Log; class PlayerController extends Controller { @@ -49,11 +50,12 @@ class PlayerController extends Controller //cek histori tontonan $check_lesson_history = Watch_history::where('course_id', $course->id) ->where('student_id', auth()->user()->id)->first(); - $first_lesson_of_course = Lesson::where('course_id', $course->id)->orderBy('sort', 'asc')->value('id'); + $first_lesson_of_course = Lesson::where('course_id', $course->id)->orderBy('section_id', 'asc')->value('id'); if ($id == '') { $id = $check_lesson_history->watching_lesson_id ?? $first_lesson_of_course; } + // if user has any watched history or not if (! $check_lesson_history && $id > 0) { $data = [ @@ -109,16 +111,13 @@ class PlayerController extends Controller $watch_history = Watch_history::where('course_id', $request->course_id) ->where('student_id', auth()->user()->id)->first(); + if (isset($watch_history) && $watch_history->id) { $lessons = (array) json_decode($watch_history->completed_lesson); if (! in_array($request->lesson_id, $lessons)) { array_push($lessons, $request->lesson_id); - } else { - while (($key = array_search($request->lesson_id, $lessons)) !== false) { - unset($lessons[$key]); - } - } + } $data['completed_lesson'] = json_encode($lessons); $data['watching_lesson_id'] = $request->lesson_id; diff --git a/app/Http/Controllers/student/QuizController.php b/app/Http/Controllers/student/QuizController.php index da7fa74..b78949f 100644 --- a/app/Http/Controllers/student/QuizController.php +++ b/app/Http/Controllers/student/QuizController.php @@ -3,10 +3,13 @@ namespace App\Http\Controllers\student; use App\Http\Controllers\Controller; +use App\Models\Enrollment; use App\Models\Course; use App\Models\Lesson; -use App\Models\Question; +use App\Models\Watch_history; +use App\Models\Certificate; use App\Models\QuizSubmission; +use App\Models\Question; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; @@ -16,79 +19,107 @@ class QuizController extends Controller { $quiz = Lesson::findOrFail($request->quiz_id); + // 1. Cek Retake (Kesempatan mengulang) $retake = $quiz->retake; - $submit = QuizSubmission::where('quiz_id', $quiz->id) + $submit_count = QuizSubmission::where('quiz_id', $quiz->id) ->where('user_id', auth()->user()->id) ->count(); - if ($submit >= $retake) { + if ($submit_count >= $retake) { Session::flash('warning', get_phrase('Attempt has been over.')); return redirect()->back(); } - $inputs = collect($request->all()); - $quiz_id = $inputs->pull('quiz_id'); - $inputs->forget(['_token', 'quiz_id']); + // 2. Rapikan Input Jawaban + $inputs = $request->except(['_token', 'quiz_id']); + $submits = []; - $submits = $inputs->whereNotNull(); - foreach ($submits as $key => $submit) { - if (is_string($submit) && ($submit != 'true' && $submit != 'false')) { - $submits[$key] = array_column(json_decode($submit), 'value'); + // Loop input untuk membersihkan format data (terutama jika dari JS frontend) + foreach ($inputs as $question_id => $answer) { + if ($answer !== null) { + // Jika input berupa string JSON (kecuali 'true'/'false'), decode dulu + if (is_string($answer) && !in_array($answer, ['true', 'false'])) { + $decoded = json_decode($answer, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { + // Ambil value jika strukturnya array of objects + $submits[$question_id] = array_column($decoded, 'value'); + } else { + $submits[$question_id] = $answer; + } + } else { + $submits[$question_id] = $answer; + } } } - $question_ids = $submits->keys(); - $submitted_answers = $submits->values(); - $questions = Question::whereIn('id', $question_ids)->get(); + // 3. Ambil Soal dari Database + // Kita ambil soal berdasarkan key dari jawaban yang dikirim + $question_ids = array_keys($submits); + $questions = Question::whereIn('id', $question_ids)->get(); - $right_answers = $wrong_answers = []; + $right_answers = []; + $wrong_answers = []; - foreach ($questions as $key => $question) { + // 4. Proses Koreksi Jawaban + foreach ($questions as $question) { + // PENTING: Ambil jawaban user berdasarkan ID Soal, bukan urutan index loop + if (!isset($submits[$question->id])) { + continue; + } + $submitted = $submits[$question->id]; $correct_answer = json_decode($question->answer, true); - $submitted = $submitted_answers[$key]; + $isCorrect = false; + // Logika per tipe soal if ($question->type == 'mcq') { + // Cek array diff (bolak-balik) untuk memastikan isi array sama persis $isCorrect = empty(array_diff($correct_answer, $submitted)) && empty(array_diff($submitted, $correct_answer)); + } elseif ($question->type == 'fill_blanks') { - $isCorrect = count($correct_answer) === count($submitted); - if ($isCorrect) { - foreach ($correct_answer as $i => $answer) { - if (strtolower($answer) !== strtolower($submitted[$i])) { + if (count($correct_answer) === count($submitted)) { + $isCorrect = true; + foreach ($correct_answer as $i => $ans) { + // Trim dan strtolower agar tidak case-sensitive + if (trim(strtolower($ans)) !== trim(strtolower($submitted[$i]))) { $isCorrect = false; break; } } } + } elseif ($question->type == 'true_false') { $isCorrect = strtolower(json_encode($correct_answer)) === strtolower($submitted); } - $isCorrect - ? $right_answers[] = $question->id - : $wrong_answers[] = $question->id; + // Masukkan ke bucket Benar/Salah + if ($isCorrect) { + $right_answers[] = $question->id; + } else { + $wrong_answers[] = $question->id; + } } + // 5. Simpan Hasil Submission QuizSubmission::create([ 'quiz_id' => $quiz->id, 'user_id' => auth()->user()->id, - 'correct_answer' => $right_answers ? json_encode($right_answers) : null, - 'wrong_answer' => $wrong_answers ? json_encode($wrong_answers) : null, - 'submits' => $submits->count() ? json_encode($submits->toArray()) : null, + 'correct_answer' => json_encode($right_answers), + 'wrong_answer' => json_encode($wrong_answers), + 'submits' => json_encode($submits), ]); - // ✅ HITUNG NILAI + // 6. Hitung Nilai $total_questions = $questions->count(); - $mark_per_question = $total_questions > 0 - ? ($quiz->total_mark / $total_questions) - : 0; + $mark_per_question = $total_questions > 0 ? ($quiz->total_mark / $total_questions) : 0; + $obtained_mark = count($right_answers) * $mark_per_question; - $obtained_mark = count($right_answers) * $mark_per_question; - - // ✅ JIKA LULUS → MARK LESSON COMPLETED + // 7. Cek Kelulusan if ($obtained_mark >= $quiz->pass_mark) { - update_watch_history_manually( + + // PANGGIL FUNGSI YANG BERADA DI CLASS YANG SAMA + $this->update_watch_history2( $quiz->id, $quiz->course_id, auth()->user()->id @@ -102,6 +133,89 @@ class QuizController extends Controller return redirect()->back(); } + public function update_watch_history2($lesson_id, $course_id, $student_id) + { + // Validasi Course + $course = Course::where('id', $course_id)->first(); + if (!$course) return; + + // Validasi Enrollment + $enrollment = Enrollment::where('course_id', $course->id) + ->where('user_id', $student_id) + ->exists(); + + // Cek Role (Jika admin/instruktur boleh bypass enrollment check) + $currentUserRole = auth()->check() ? auth()->user()->role : 'student'; + if (!$enrollment && ($currentUserRole != 'admin' && !is_course_instructor($course->id))) { + return; + } + + // Ambil semua lesson ID untuk hitung progress manual + $total_lesson_ids = Lesson::where('course_id', $course_id)->pluck('id')->toArray(); + + // Ambil data history user + $watch_history = Watch_history::where('course_id', $course_id) + ->where('student_id', $student_id) + ->first(); + + $completed_lessons = []; + + // Parse data lama + if ($watch_history && $watch_history->completed_lesson) { + $decoded = json_decode($watch_history->completed_lesson, true); + if (is_array($decoded)) { + $completed_lessons = $decoded; + } + } + + // Tambahkan lesson_id baru jika belum ada + if (!in_array($lesson_id, $completed_lessons)) { + $completed_lessons[] = $lesson_id; + } + + // Tentukan apakah course selesai + $is_completed = count($total_lesson_ids) <= count($completed_lessons); + + $data = [ + 'course_id' => $course_id, + 'student_id' => $student_id, + 'watching_lesson_id' => $lesson_id, + 'completed_lesson' => json_encode($completed_lessons), + 'completed_date' => $is_completed ? time() : null, + ]; + + // Simpan ke DB + if ($watch_history) { + Watch_history::where('id', $watch_history->id)->update($data); + } else { + // Gunakan create agar timestamp created_at terisi otomatis + Watch_history::create($data); + } + + // --- LOGIKA SERTIFIKAT --- + // Hitung progress secara manual agar lebih akurat + $progress_percentage = 0; + if (count($total_lesson_ids) > 0) { + $progress_percentage = (count($completed_lessons) / count($total_lesson_ids)) * 100; + } + + if ($progress_percentage >= 100) { + $certificateExists = Certificate::where('user_id', $student_id) + ->where('course_id', $course_id) + ->exists(); + + if (!$certificateExists) { + Certificate::create([ + 'user_id' => $student_id, + 'course_id' => $course_id, + 'identifier' => substr(md5(uniqid(mt_rand(), true)), 0, 10), // Generate random ID + ]); + } + } + + return true; + } + public function load_result(Request $request) { $page_data['quiz'] = Lesson::where('id', $request->quiz_id)->first(); diff --git a/app/Models/Certificate.php b/app/Models/Certificate.php index 7a911d9..4e5a7c0 100644 --- a/app/Models/Certificate.php +++ b/app/Models/Certificate.php @@ -9,6 +9,13 @@ class Certificate extends Model { use HasFactory; + protected $fillable = [ + 'user_id', + 'course_id', + 'identifier', + 'created_at' + ]; + public function course() { return $this->belongsTo(Course::class); diff --git a/resources/views/course_player/certificate/index.blade.php b/resources/views/course_player/certificate/index.blade.php index 6c9cb33..ee7431e 100644 --- a/resources/views/course_player/certificate/index.blade.php +++ b/resources/views/course_player/certificate/index.blade.php @@ -6,6 +6,8 @@ @php $certificate = App\Models\Certificate::where('course_id', $course_details->id)->where('user_id', auth()->user()->id); @endphp + @if ($certificate->count() > 0 && $course_progress_out_of_100 >= 100)