fix bug player course

This commit is contained in:
Baghiz Zuhdi Adzin 2026-01-13 10:32:05 +07:00
parent e3458433f2
commit e4ee4b3f72
7 changed files with 202 additions and 95 deletions

View File

@ -81,19 +81,6 @@ class CurriculumController extends Controller
$data['video_type'] = $request->lesson_provider; $data['video_type'] = $request->lesson_provider;
$data['lesson_src'] = $request->lesson_src; $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)) { if (empty($request->duration)) {
$data['duration'] = '00:00:00'; $data['duration'] = '00:00:00';
} else { } else {
@ -121,39 +108,6 @@ class CurriculumController extends Controller
} }
$data['attachment'] = $file; $data['attachment'] = $file;
$data['attachment_type'] = $request->attachment_type; $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') { } elseif ($request->lesson_type == 'image') {
if ($request->attachment == '') { if ($request->attachment == '') {
@ -186,7 +140,6 @@ class CurriculumController extends Controller
$data['duration'] = $hour . ':' . $min . ':' . $sec; $data['duration'] = $hour . ':' . $min . ':' . $sec;
} }
} elseif ($request->lesson_type == 'iframe') { } elseif ($request->lesson_type == 'iframe') {
$data['lesson_src'] = $request->iframe_source; $data['lesson_src'] = $request->iframe_source;
} elseif ($request->lesson_type == 'google_drive') { } elseif ($request->lesson_type == 'google_drive') {

View File

@ -10,6 +10,7 @@ use App\Models\Lesson;
use App\Models\Watch_history; use App\Models\Watch_history;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Log;
class PlayerController extends Controller class PlayerController extends Controller
{ {
@ -49,11 +50,12 @@ class PlayerController extends Controller
//cek histori tontonan //cek histori tontonan
$check_lesson_history = Watch_history::where('course_id', $course->id) $check_lesson_history = Watch_history::where('course_id', $course->id)
->where('student_id', auth()->user()->id)->first(); ->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 == '') { if ($id == '') {
$id = $check_lesson_history->watching_lesson_id ?? $first_lesson_of_course; $id = $check_lesson_history->watching_lesson_id ?? $first_lesson_of_course;
} }
// if user has any watched history or not // if user has any watched history or not
if (! $check_lesson_history && $id > 0) { if (! $check_lesson_history && $id > 0) {
$data = [ $data = [
@ -110,14 +112,11 @@ class PlayerController extends Controller
$watch_history = Watch_history::where('course_id', $request->course_id) $watch_history = Watch_history::where('course_id', $request->course_id)
->where('student_id', auth()->user()->id)->first(); ->where('student_id', auth()->user()->id)->first();
if (isset($watch_history) && $watch_history->id) { if (isset($watch_history) && $watch_history->id) {
$lessons = (array) json_decode($watch_history->completed_lesson); $lessons = (array) json_decode($watch_history->completed_lesson);
if (! in_array($request->lesson_id, $lessons)) { if (! in_array($request->lesson_id, $lessons)) {
array_push($lessons, $request->lesson_id); 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['completed_lesson'] = json_encode($lessons);

View File

@ -3,10 +3,13 @@
namespace App\Http\Controllers\student; namespace App\Http\Controllers\student;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Enrollment;
use App\Models\Course; use App\Models\Course;
use App\Models\Lesson; use App\Models\Lesson;
use App\Models\Question; use App\Models\Watch_history;
use App\Models\Certificate;
use App\Models\QuizSubmission; use App\Models\QuizSubmission;
use App\Models\Question;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
@ -16,79 +19,107 @@ class QuizController extends Controller
{ {
$quiz = Lesson::findOrFail($request->quiz_id); $quiz = Lesson::findOrFail($request->quiz_id);
// 1. Cek Retake (Kesempatan mengulang)
$retake = $quiz->retake; $retake = $quiz->retake;
$submit = QuizSubmission::where('quiz_id', $quiz->id) $submit_count = QuizSubmission::where('quiz_id', $quiz->id)
->where('user_id', auth()->user()->id) ->where('user_id', auth()->user()->id)
->count(); ->count();
if ($submit >= $retake) { if ($submit_count >= $retake) {
Session::flash('warning', get_phrase('Attempt has been over.')); Session::flash('warning', get_phrase('Attempt has been over.'));
return redirect()->back(); return redirect()->back();
} }
$inputs = collect($request->all()); // 2. Rapikan Input Jawaban
$quiz_id = $inputs->pull('quiz_id'); $inputs = $request->except(['_token', 'quiz_id']);
$inputs->forget(['_token', 'quiz_id']); $submits = [];
$submits = $inputs->whereNotNull(); // Loop input untuk membersihkan format data (terutama jika dari JS frontend)
foreach ($submits as $key => $submit) { foreach ($inputs as $question_id => $answer) {
if (is_string($submit) && ($submit != 'true' && $submit != 'false')) { if ($answer !== null) {
$submits[$key] = array_column(json_decode($submit), 'value'); // 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(); // 3. Ambil Soal dari Database
$submitted_answers = $submits->values(); // Kita ambil soal berdasarkan key dari jawaban yang dikirim
$question_ids = array_keys($submits);
$questions = Question::whereIn('id', $question_ids)->get(); $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); $correct_answer = json_decode($question->answer, true);
$submitted = $submitted_answers[$key]; $isCorrect = false;
// Logika per tipe soal
if ($question->type == 'mcq') { if ($question->type == 'mcq') {
// Cek array diff (bolak-balik) untuk memastikan isi array sama persis
$isCorrect = empty(array_diff($correct_answer, $submitted)) $isCorrect = empty(array_diff($correct_answer, $submitted))
&& empty(array_diff($submitted, $correct_answer)); && empty(array_diff($submitted, $correct_answer));
} elseif ($question->type == 'fill_blanks') { } elseif ($question->type == 'fill_blanks') {
$isCorrect = count($correct_answer) === count($submitted); if (count($correct_answer) === count($submitted)) {
if ($isCorrect) { $isCorrect = true;
foreach ($correct_answer as $i => $answer) { foreach ($correct_answer as $i => $ans) {
if (strtolower($answer) !== strtolower($submitted[$i])) { // Trim dan strtolower agar tidak case-sensitive
if (trim(strtolower($ans)) !== trim(strtolower($submitted[$i]))) {
$isCorrect = false; $isCorrect = false;
break; break;
} }
} }
} }
} elseif ($question->type == 'true_false') { } elseif ($question->type == 'true_false') {
$isCorrect = strtolower(json_encode($correct_answer)) === strtolower($submitted); $isCorrect = strtolower(json_encode($correct_answer)) === strtolower($submitted);
} }
$isCorrect // Masukkan ke bucket Benar/Salah
? $right_answers[] = $question->id if ($isCorrect) {
: $wrong_answers[] = $question->id; $right_answers[] = $question->id;
} else {
$wrong_answers[] = $question->id;
}
} }
// 5. Simpan Hasil Submission
QuizSubmission::create([ QuizSubmission::create([
'quiz_id' => $quiz->id, 'quiz_id' => $quiz->id,
'user_id' => auth()->user()->id, 'user_id' => auth()->user()->id,
'correct_answer' => $right_answers ? json_encode($right_answers) : null, 'correct_answer' => json_encode($right_answers),
'wrong_answer' => $wrong_answers ? json_encode($wrong_answers) : null, 'wrong_answer' => json_encode($wrong_answers),
'submits' => $submits->count() ? json_encode($submits->toArray()) : null, 'submits' => json_encode($submits),
]); ]);
// ✅ HITUNG NILAI // 6. Hitung Nilai
$total_questions = $questions->count(); $total_questions = $questions->count();
$mark_per_question = $total_questions > 0 $mark_per_question = $total_questions > 0 ? ($quiz->total_mark / $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) { 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->id,
$quiz->course_id, $quiz->course_id,
auth()->user()->id auth()->user()->id
@ -102,6 +133,89 @@ class QuizController extends Controller
return redirect()->back(); 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) public function load_result(Request $request)
{ {
$page_data['quiz'] = Lesson::where('id', $request->quiz_id)->first(); $page_data['quiz'] = Lesson::where('id', $request->quiz_id)->first();

View File

@ -9,6 +9,13 @@ class Certificate extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [
'user_id',
'course_id',
'identifier',
'created_at'
];
public function course() public function course()
{ {
return $this->belongsTo(Course::class); return $this->belongsTo(Course::class);

View File

@ -6,6 +6,8 @@
@php @php
$certificate = App\Models\Certificate::where('course_id', $course_details->id)->where('user_id', auth()->user()->id); $certificate = App\Models\Certificate::where('course_id', $course_details->id)->where('user_id', auth()->user()->id);
@endphp @endphp
<!-- @dump($certificate->count());
@dump($course_progress_out_of_100); -->
@if ($certificate->count() > 0 && $course_progress_out_of_100 >= 100) @if ($certificate->count() > 0 && $course_progress_out_of_100 >= 100)
<div class="alert alert-success text-center" role="alert"> <div class="alert alert-success text-center" role="alert">
<h4 class="alert-heading mb-4 mt-3">{{ get_phrase('Congratulations!') }}</h4> <h4 class="alert-heading mb-4 mt-3">{{ get_phrase('Congratulations!') }}</h4>

View File

@ -1,6 +1,6 @@
<script> <script>
$(document).on('click', '.lesson-link', function (e) { $(document).on('click', '.lesson-link', function (e) {
e.preventDefault(); // ⛔ stop redirect sementara e.preventDefault();
let url = $(this).attr('href'); let url = $(this).attr('href');
let lessonId = $(this).data('lesson-id'); let lessonId = $(this).data('lesson-id');
@ -19,22 +19,53 @@ $(document).on('click', '.lesson-link', function (e) {
success: function (response) { success: function (response) {
console.log('Watch history updated:', response); console.log('Watch history updated:', response);
// 🔥 optional: refresh sidebar sebelum pindah // Refresh sidebar jika di halaman course player
if (typeof reloadSidebar === 'function') {
reloadSidebar(); reloadSidebar();
}
// ⏩ lanjutkan navigasi
setTimeout(function () { setTimeout(function () {
window.location.href = url; window.location.href = url;
}, 200); }, 200);
}, },
error: function () { error: function (xhr, status, error) {
// kalau gagal, tetap lanjutkan navigasi console.error('AJAX Error:', error);
window.location.href = url; window.location.href = url;
} }
}); });
}); });
</script> </script>
<script type="text/javascript">
// Hanya definisikan fungsi jika di halaman yang punya course_details
@if(isset($course_details) && isset($lesson_details))
function reloadSidebar() {
let courseId = '{{ $course_details->id ?? 0 }}';
let lessonId = '{{ $lesson_details->id ?? 0 }}';
// Pastikan lessonId valid
if (!lessonId || lessonId == 0) {
console.log('No lesson ID available for sidebar reload');
return;
}
let url = "{{ route('course.sidebar.reload', ['course_id' => '__COURSE__', 'lesson_id' => '__LESSON__']) }}";
url = url.replace('__COURSE__', courseId)
.replace('__LESSON__', lessonId);
$('#player_side_bar').load(url, function () {
console.log('Sidebar updated');
});
}
@else
// Placeholder function untuk halaman lain
function reloadSidebar() {
console.log('Sidebar reload not available on this page');
}
@endif
</script>
<script> <script>
"use strict"; "use strict";

View File

@ -90,6 +90,7 @@
->first(); ->first();
$lesson = App\Models\Lesson::where('course_id', $course->course_id) $lesson = App\Models\Lesson::where('course_id', $course->course_id)
->orderBy('section_id', 'asc')
->orderBy('sort', 'asc') ->orderBy('sort', 'asc')
->first(); ->first();