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['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') {

View File

@ -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 = [
@ -110,14 +112,11 @@ 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);

View File

@ -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();

View File

@ -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);

View File

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

View File

@ -1,6 +1,6 @@
<script>
$(document).on('click', '.lesson-link', function (e) {
e.preventDefault(); // ⛔ stop redirect sementara
e.preventDefault();
let url = $(this).attr('href');
let lessonId = $(this).data('lesson-id');
@ -19,22 +19,53 @@ $(document).on('click', '.lesson-link', function (e) {
success: function (response) {
console.log('Watch history updated:', response);
// 🔥 optional: refresh sidebar sebelum pindah
reloadSidebar();
// Refresh sidebar jika di halaman course player
if (typeof reloadSidebar === 'function') {
reloadSidebar();
}
// ⏩ lanjutkan navigasi
setTimeout(function () {
window.location.href = url;
}, 200);
},
error: function () {
// kalau gagal, tetap lanjutkan navigasi
error: function (xhr, status, error) {
console.error('AJAX Error:', error);
window.location.href = url;
}
});
});
</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>
"use strict";

View File

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