pembenaran player course

This commit is contained in:
Baghiz Zuhdi Adzin 2026-01-12 08:05:36 +07:00
parent e30ac8a5c8
commit e3458433f2
21 changed files with 388 additions and 545 deletions

View File

@ -213,32 +213,6 @@ function is_added_to_wishlist($user_id = 0, $course_id = "")
} }
} }
// function is_purchased($user_id = 0, $course_id = "")
// {
// // 0 represents Not purchased, 1 represents Purchased, 2 represents Pending
// if ($user_id > 0) {
// if (enroll_status($course_id, $user_id) == 'valid') {
// return true;
// } else {
// return false;
// }
// } else {
// return false;
// }
// }
// function is_purchased($user_id = 0, $course_id = "")
// {
// // 0 represents not purchased, 1 represents purchased
// if ($user_id > 0) {
// $status = enroll_status($course_id, $user_id);
// return $status === 'valid'; // Return true only if status is 'valid'
// } else {
// return false; // User ID is not valid
// }
// }
if (!function_exists('is_purchased')) { if (!function_exists('is_purchased')) {
function is_purchased($user_id = 0, $course_id = "") function is_purchased($user_id = 0, $course_id = "")
{ {
@ -271,7 +245,6 @@ if (!function_exists('enroll_status_api')) {
} }
} }
function get_total_duration_of_lesson_by_course_id($course_id) function get_total_duration_of_lesson_by_course_id($course_id)
{ {
$total_duration = 0; $total_duration = 0;
@ -433,75 +406,6 @@ if (!function_exists('update_watch_history_manually')) {
} }
} }
// if (!function_exists('update_watch_history_manually')) {
// // code of mark this lesson as completed
// function update_watch_history_manually($lesson_id = "", $course_id = "", $user_id = "")
// {
// $is_completed = 0;
// $query = DB::table('watch_histories')->where('course_id', $course_id)->where('student_id', $user_id)->first();
// $course_progress = course_progress($course_id, $user_id);
// if (!empty($query)) {
// $lesson_ids = json_decode($query->completed_lesson, true);
// if (!is_array($lesson_ids))
// $lesson_ids = array();
// if (!in_array($lesson_id, $lesson_ids)) {
// array_push($lesson_ids, $lesson_id);
// $total_lesson = DB::table('lessons')->where('course_id', $course_id)->get();
// $course_progress = (100 / count($total_lesson)) * count($lesson_ids);
// if ($course_progress >= 100 && $query->completed_date == null) {
// $completed_date = time();
// } else {
// $completed_date = $query->completed_date;
// }
// DB::table('watch_histories')->where('id', $query->id)->update([
// 'completed_lesson' => json_encode($lesson_ids),
// 'completed_date' => $completed_date,
// ]);
// $is_completed = 1;
// } else {
// if (($key = array_search($lesson_id, $lesson_ids)) !== false) {
// unset($lesson_ids[$key]);
// }
// $total_lesson = DB::table('lessons')->where('course_id', $course_id)->get();
// $course_progress = (100 / count($total_lesson)) * count($lesson_ids);
// if ($course_progress >= 100 && $query->completed_date == null) {
// $completed_date = time();
// } else {
// $completed_date = $query->completed_date;
// }
// DB::table('watch_histories')->where('id', $query->id)->update([
// 'completed_lesson' => json_encode($lesson_ids),
// 'completed_date' => $completed_date,
// ]);
// $is_completed = 0;
// }
// } else {
// $total_lesson = DB::table('lessons')->where('course_id', $course_id)->get();
// $course_progress = (100 / count($total_lesson));
// $insert_data['course_id'] = $course_id;
// $insert_data['student_id'] = $user_id;
// $insert_data['completed_lesson'] = json_encode(array($lesson_id));
// $insert_data['watching_lesson_id'] = $lesson_id;
// DB::table('watch_histories')->create($insert_data);
// }
// return json_encode(array('lesson_id' => $lesson_id, 'course_progress' => round($course_progress), 'is_completed' => $is_completed));
// }
// }
function course_completion_data($course_id = "", $user_id = "") function course_completion_data($course_id = "", $user_id = "")
{ {
$response = array(); $response = array();

View File

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
class CommonController extends Controller class CommonController extends Controller
{ {
// Get video details new code
function get_video_details(Request $request, $url = "") function get_video_details(Request $request, $url = "")
{ {
if ($url == "") { if ($url == "") {
@ -17,36 +16,9 @@ class CommonController extends Controller
$host = explode('.', str_replace('www.', '', strtolower(parse_url($url, PHP_URL_HOST)))); $host = explode('.', str_replace('www.', '', strtolower(parse_url($url, PHP_URL_HOST))));
$host = isset($host[0]) ? $host[0] : $host; $host = isset($host[0]) ? $host[0] : $host;
$vimeo_api_key = get_settings('vimeo_api_key');
$youtube_api_key = get_settings('youtube_api_key'); $youtube_api_key = get_settings('youtube_api_key');
if ($host == 'vimeo') { if ($host == 'youtube' || $host == 'youtu') {
$video_id = substr(parse_url($url, PHP_URL_PATH), 1);
$options = array('http' => array(
'method' => 'GET',
'header' => 'Authorization: Bearer ' . $vimeo_api_key
));
$context = stream_context_create($options);
try {
$hash = json_decode(file_get_contents("https://api.vimeo.com/videos/{$video_id}", false, $context));
} catch (\Throwable $th) {
$hash = '';
}
if ($hash == '') return;
return array(
'provider' => 'Vimeo',
'video_id' => $video_id,
'title' => $hash->name,
'thumbnail' => $hash->pictures->sizes[0]->link,
'video' => $hash->link,
'embed_video' => "https://player.vimeo.com/video/" . $video_id,
'duration' => gmdate("H:i:s", $hash->duration)
);
} elseif ($host == 'youtube' || $host == 'youtu') {
preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match); preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match);
$video_id = $match[1]; $video_id = $match[1];
@ -68,7 +40,6 @@ class CommonController extends Controller
'embed_video' => "http://www.youtube.com/embed/" . $hash->items[0]->id, 'embed_video' => "http://www.youtube.com/embed/" . $hash->items[0]->id,
'duration' => $duration->format('%H:%I:%S'), 'duration' => $duration->format('%H:%I:%S'),
); );
} elseif ($host == 'drive') {
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Course; use App\Models\Course;
use App\Models\Live_class; use App\Models\Live_class;
use App\Models\Setting; use App\Models\Setting;

View File

@ -13,6 +13,17 @@ use Illuminate\Support\Facades\Session;
class PlayerController extends Controller class PlayerController extends Controller
{ {
public function reload_sidebar($course_id, $lesson_id)
{
$course_details = Course::findOrFail($course_id);
$lesson_details = Lesson::findOrFail($lesson_id);
return view('course_player.side_bar', compact(
'course_details',
'lesson_details'
));
}
public function course_player(Request $request, $slug, $id = '') public function course_player(Request $request, $slug, $id = '')
{ {
$course = Course::where('slug', $slug)->firstOrNew(); $course = Course::where('slug', $slug)->firstOrNew();
@ -35,7 +46,7 @@ class PlayerController extends Controller
} }
} }
} }
//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('sort', 'asc')->value('id');

View File

@ -134,7 +134,13 @@ class MessageController extends Controller
public function search_student(Request $request) public function search_student(Request $request)
{ {
$page_data['user_details'] = User::where('email', $request->search_mail)->first(); $search = $request->search_mail;
if (empty(trim($search))) {
$page_data['user_details'] = null; // or collect() for no results
} else {
$page_data['user_details'] = User::where('email', 'LIKE', '%' . $search . '%') ->first();
}
$view_path = 'frontend.' . get_frontend_settings('theme') . '.student.message.search_result'; $view_path = 'frontend.' . get_frontend_settings('theme') . '.student.message.search_result';
return view($view_path, $page_data); return view($view_path, $page_data);
} }

View File

@ -14,9 +14,14 @@ class QuizController extends Controller
{ {
public function quiz_submit(Request $request) public function quiz_submit(Request $request)
{ {
$retake = Lesson::where('id', $request->quiz_id)->value('retake'); $quiz = Lesson::findOrFail($request->quiz_id);
$submit = QuizSubmission::where('quiz_id', $request->quiz_id)->where('user_id', auth()->user()->id)->count();
if ($submit > $retake) { $retake = $quiz->retake;
$submit = QuizSubmission::where('quiz_id', $quiz->id)
->where('user_id', auth()->user()->id)
->count();
if ($submit >= $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();
} }
@ -37,41 +42,63 @@ class QuizController extends Controller
$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) { foreach ($questions as $key => $question) {
$correct_answer = json_decode($question->answer, true); $correct_answer = json_decode($question->answer, true);
$submitted = $submitted_answers[$key]; $submitted = $submitted_answers[$key];
if ($question->type == 'mcq') { if ($question->type == 'mcq') {
$isCorrect = empty(array_diff($correct_answer, $submitted)) && empty(array_diff($submitted, $correct_answer)); $isCorrect = empty(array_diff($correct_answer, $submitted))
&& empty(array_diff($submitted, $correct_answer));
} elseif ($question->type == 'fill_blanks') { } elseif ($question->type == 'fill_blanks') {
$isCorrect = count($correct_answer) === count($submitted); $isCorrect = count($correct_answer) === count($submitted);
if ($isCorrect) { if ($isCorrect) {
for ($i = 0; $i < count($correct_answer); $i++) { foreach ($correct_answer as $i => $answer) {
if (strtolower($correct_answer[$i]) != strtolower($submitted[$i])) { if (strtolower($answer) !== strtolower($submitted[$i])) {
$isCorrect = false; $isCorrect = false;
break; break;
} }
} }
} else {
$isCorrect = false;
} }
} 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 ? $right_answers[] = $question->id : $wrong_answers[] = $question->id; $isCorrect
? $right_answers[] = $question->id
: $wrong_answers[] = $question->id;
} }
$data['quiz_id'] = $quiz_id; QuizSubmission::create([
$data['user_id'] = auth()->user()->id; 'quiz_id' => $quiz->id,
$data['correct_answer'] = $right_answers ? json_encode($right_answers) : null; 'user_id' => auth()->user()->id,
$data['wrong_answer'] = $wrong_answers ? json_encode($wrong_answers) : null; 'correct_answer' => $right_answers ? json_encode($right_answers) : null,
$data['submits'] = $submits->count() > 0 ? json_encode($submits->toArray()) : null; 'wrong_answer' => $wrong_answers ? json_encode($wrong_answers) : null,
'submits' => $submits->count() ? json_encode($submits->toArray()) : null,
]);
// ✅ HITUNG NILAI
$total_questions = $questions->count();
$mark_per_question = $total_questions > 0
? ($quiz->total_mark / $total_questions)
: 0;
$obtained_mark = count($right_answers) * $mark_per_question;
// ✅ JIKA LULUS → MARK LESSON COMPLETED
if ($obtained_mark >= $quiz->pass_mark) {
update_watch_history_manually(
$quiz->id,
$quiz->course_id,
auth()->user()->id
);
Session::flash('success', get_phrase('Congratulations, you passed the quiz.'));
} else {
Session::flash('warning', get_phrase('You did not reach the minimum score.'));
}
QuizSubmission::insert($data);
Session::flash('success', get_phrase('Your answers have been submitted.'));
return redirect()->back(); return redirect()->back();
} }

View File

@ -14,37 +14,14 @@
<hr class="bg-secondary my-4"> <hr class="bg-secondary my-4">
<div class="row mb-3"> <div class="row mb-3" id="preview_link_input">
@php
$preview_video_type = str_contains($course_details->preview, 'youtu') ? 'youtube' : '';
$preview_video_type = str_contains($course_details->preview, 'vimeo') && $preview_video_type == '' ? 'vimeo' : '';
$preview_video_type = str_contains($course_details->preview, 'http') && $preview_video_type == '' ? 'html5' : '';
@endphp
<label for="preview_video_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Preview Video') }}</label>
<div class="col-sm-10">
<input type="radio" onchange="$('#preview_link_input').toggleClass('d-hidden'); $('#preview_file_input').toggleClass('d-hidden');" class="form-check-input" value="link" name="preview_video_provider" id="preview_video_link" @if($preview_video_type != '') checked @endif>&nbsp;<label for="preview_video_link">{{ get_phrase('Video Link') }}</label>
&nbsp;&nbsp;
<input type="radio" onchange="$('#preview_link_input').toggleClass('d-hidden'); $('#preview_file_input').toggleClass('d-hidden');" class="form-check-input" value="file" name="preview_video_provider" id="preview_video_file" @if($preview_video_type == '') checked @endif>&nbsp;<label for="preview_video_file">{{ get_phrase('Video File') }}
</div>
</div>
<div class="row mb-3 @if($preview_video_type == '') d-hidden @endif" id="preview_link_input">
<label for="preview_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Video link') }}</label> <label for="preview_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Video link') }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name = "preview_link" id = "preview_link" class="form-control ol-form-control" value="{{ $course_details->preview }}"> <input type="text" name = "preview_link" id = "preview_link" class="form-control ol-form-control" value="{{ $course_details->preview }}">
<small class="text-muted">{{get_phrase('Supported URL')}}: <b>{{get_phrase('Youtube')}}</b> {{get_phrase('or')}} <b>{{get_phrase('Vimeo')}}</b> {{get_phrase('or')}} <b>{{get_phrase('HTML5')}}</b></small> <small class="text-muted">{{get_phrase('Supported URL')}}: <b>{{get_phrase('Youtube')}}</b> {{get_phrase('or')}} <b>{{get_phrase('Google Drive')}}</b></small>
</div> </div>
</div> </div>
<div class="row mb-3 @if($preview_video_type != '') d-hidden @endif" id="preview_file_input">
<label for="preview" class="form-label ol-form-label col-sm-2 col-form-label">{{get_phrase('Preview Video File')}}</label>
<div class="col-sm-10">
<input type="file" name="preview" class="form-control ol-form-control" id="preview" accept="video/*" />
<small class="text-muted">{{get_phrase('Supported Video file')}}: <b>.{{get_phrase('mp4')}}</b> {{get_phrase('or')}} <b>.{{get_phrase('webm')}}</b> {{get_phrase('or')}} <b>.{{get_phrase('ogg')}}</b></small>
</div>
</div>
<script type="text/javascript"> <script type="text/javascript">
"use strict"; "use strict";
document.getElementById('thumbnail').addEventListener('change', function(event) { document.getElementById('thumbnail').addEventListener('change', function(event) {

View File

@ -8,7 +8,7 @@
{{ get_phrase('Video url') . ' [.mp4]' }} {{ get_phrase('Video url') . ' [.mp4]' }}
@elseif ($lesson_type == 'video') @elseif ($lesson_type == 'video')
{{ get_phrase('Video file') }} {{ get_phrase('Video file') }}
@elseif ($lesson_type == 'youtube' || $lesson_type == 'academy cloud' || $lesson_type == 'vimeo') @elseif ($lesson_type == 'youtube')
{{ ucfirst(get_phrase($lesson_type)) }} {{ get_phrase('Video') }} {{ ucfirst(get_phrase($lesson_type)) }} {{ get_phrase('Video') }}
@elseif($lesson_type == 'google_drive_video') @elseif($lesson_type == 'google_drive_video')
{{ get_phrase('Google drive video') }} {{ get_phrase('Google drive video') }}
@ -47,16 +47,6 @@
@if ($lesson_type == 'youtube') @if ($lesson_type == 'youtube')
@include('admin.course.youtube_type_lesson_add') @include('admin.course.youtube_type_lesson_add')
@elseif ($lesson_type == 'academy_cloud')
@include('admin.course.academy_cloud_type_lesson_add')
@elseif ($lesson_type == 'vimeo')
@include('admin.course.vimeo_type_lesson_add')
@elseif ($lesson_type == 'html5')
@include('admin.course.html5_type_lesson_add')
@elseif ($lesson_type == 'video')
@include('admin.course.video_type_lesson_add')
@elseif ($lesson_type == 'amazon-s3')
@include('amazon_s3_type_lesson_add.php')
@elseif ($lesson_type == 'google_drive_video') @elseif ($lesson_type == 'google_drive_video')
@include('admin.course.google_drive_type_lesson_add') @include('admin.course.google_drive_type_lesson_add')
@elseif ($lesson_type == 'document') @elseif ($lesson_type == 'document')
@ -67,8 +57,6 @@
@include('admin.course.image_file_type_lesson_add') @include('admin.course.image_file_type_lesson_add')
@elseif ($lesson_type == 'iframe') @elseif ($lesson_type == 'iframe')
@include('admin.course.iframe_type_lesson_add') @include('admin.course.iframe_type_lesson_add')
@elseif ($lesson_type == 'scorm')
@include('admin.course.scorm_type_lesson_add')
@endif @endif
<div class="form-group mb-3"> <div class="form-group mb-3">
@ -123,16 +111,9 @@
} }
function checkURLValidity(video_url) { function checkURLValidity(video_url) {
var youtubePregMatch = var youtube = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
/^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/; return youtube.test(video_url);
var vimeoPregMatch = /^(http\:\/\/|https\:\/\/)?(www\.)?(vimeo\.com\/)([0-9]+)$/;
if (video_url.match(youtubePregMatch)) {
return true;
} else if (vimeoPregMatch.test(video_url)) {
return true;
} else {
return false;
}
} }
</script> </script>
@include('admin.init') @include('admin.init')

View File

@ -30,47 +30,7 @@
</label> </label>
</div> </div>
<!-- <div class="col">
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-vimeo">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/video-circle-black-18.svg" alt="">
<p class="title">{{ get_phrase('Vimeo Video') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-vimeo" value="vimeo" @if ($selected_lesson == 'vimeo') checked @endif>
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-videofile">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/video-black-18.svg" alt="">
<p class="title">{{ get_phrase('Video file') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-videofile" value="video" @if ($selected_lesson == 'video') checked @endif>
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-url">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/link-black-18.svg" alt="">
<p class="title">{{ get_phrase('Video url [ .mp4 ]') }}</p>
</div>
<input value="html5" class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-url">
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" <label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-drive"> for="radio-drive">
<div class="title-icon d-flex align-items-center"> <div class="title-icon d-flex align-items-center">
@ -81,8 +41,7 @@
id="radio-drive" value="google_drive_video" id="radio-drive" value="google_drive_video"
@if ($selected_lesson == 'google_drive_video') {{ get_phrase('checked') }} @endif> @if ($selected_lesson == 'google_drive_video') {{ get_phrase('checked') }} @endif>
</label> </label>
</div> </div> -->
<div class="col"> <div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" <label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
@ -130,7 +89,7 @@
for="radio-iframe"> for="radio-iframe">
<div class="title-icon d-flex align-items-center"> <div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/volume-black-18.svg" alt=""> <img src="assets/images/icons/volume-black-18.svg" alt="">
<p class="title">{{ get_phrase('Iframe embed') }}</p> <p class="title">{{ get_phrase('File embed(ppt, pdf)') }}</p>
</div> </div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" <input class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-iframe" value="iframe" id="radio-iframe" value="iframe"
@ -138,19 +97,6 @@
</label> </label>
</div> </div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-scorm">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/volume-black-18.svg" alt="">
<p class="title">{{ get_phrase('Scorm Content') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-scorm" value="scorm"
@if ($selected_lesson == 'scorm') {{ get_phrase('checked') }} @endif>
</label>
</div>
</div> </div>
<div class="mt-3"> <div class="mt-3">

View File

@ -31,14 +31,6 @@
</div> </div>
</div> </div>
@include('frontend.course_player.player_config') @include('frontend.course_player.player_config')
@elseif($lesson_details->lesson_type == 'system-video')
<div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light">
<video poster="" id="player" playsinline controls>
<source src="{{ asset($lesson_details->lesson_src) }}"
type="video/mp4">
</video>
</div>
@include('frontend.course_player.player_config')
@elseif($lesson_details->lesson_type == 'image') @elseif($lesson_details->lesson_type == 'image')
<div class="overflow-hidden bd-r-10 mb-16 position-relative bg-light"> <div class="overflow-hidden bd-r-10 mb-16 position-relative bg-light">
@php @php
@ -47,47 +39,41 @@
@endphp @endphp
<img width="100%" class="max-w-auto" height="auto" src="{{ $img }}" /> <img width="100%" class="max-w-auto" height="auto" src="{{ $img }}" />
</div> </div>
@elseif($lesson_details->lesson_type == 'vimeo-url' && $lesson_details->video_type == 'vimeo')
@php
$video_url = $lesson_details->lesson_src;
$video_id = explode('https://vimeo.com/', $video_url);
$video_id = str_replace('https://vimeo.com/', '', $video_url);
@endphp
<div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light">
<div class="plyr__video-embed " id="player">
<iframe height="500"
src="https://player.vimeo.com/video/{{ $video_id }}?loop=false&amp;byline=false&amp;portrait=false&amp;title=false&amp;speed=true&amp;transparent=0&amp;gesture=media"
allowfullscreen allowtransparency allow="autoplay"></iframe>
<div class="position-absolute top-50 start-50 translate-middle">
</div>
</div>
</div>
@include('frontend.course_player.player_config')
@elseif($lesson_details->lesson_type == 'google_drive') @elseif($lesson_details->lesson_type == 'google_drive')
@php @php
$video_url = $lesson_details->lesson_src; $video_url = trim($lesson_details->lesson_src);
$url_array_1 = explode('/', $video_url . '/');
$url_array_2 = explode('=', $video_url); // Extract Google Drive file ID reliably from standard share links
$video_id = null; preg_match('/\/d\/([a-zA-Z0-9_-]+)/', $video_url, $matches);
if ($url_array_1[4] == 'd'): $video_id = $matches[1] ?? null;
$video_id = $url_array_1[5];
else:
$video_id = $url_array_2[1];
endif;
@endphp @endphp
@if($video_id)
<div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light"> <div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light">
<video id="player" playsinline controls> <!-- Responsive 16:9 ratio wrapper -->
<source class="remove_video_src" <div class="ratio ratio-16x9 w-100 h-100">
src="https://www.googleapis.com/drive/v3/files/{{ $video_id }}?alt=media&key={{get_settings('youtube_api_key')}}" <iframe
type="video/mp4"> src="https://drive.google.com/file/d/{{ $video_id }}/preview"
</video> allowfullscreen
allow="autoplay; fullscreen; picture-in-picture"
class="border-0 rounded-10"
frameborder="0"
loading="lazy">
</iframe>
</div>
</div> </div>
@include('frontend.course_player.player_config') @include('frontend.course_player.player_config')
@else
<div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light d-flex align-items-center justify-content-center">
<div class="text-center text-danger">
<h5>Invalid Google Drive Link</h5>
<p class="mb-2">Could not extract video ID.</p>
<small class="text-muted">Use a link like:<br>
https://drive.google.com/file/d/<strong>FILE_ID</strong>/view
</small>
</div>
</div>
@endif
@elseif($lesson_details->lesson_type == 'html5') @elseif($lesson_details->lesson_type == 'html5')
<div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light"> <div class="overflow-hidden bd-r-10 mb-16 h-314 h-md-428 position-relative bg-light">

View File

@ -1,3 +1,40 @@
<script>
$(document).on('click', '.lesson-link', function (e) {
e.preventDefault(); // ⛔ stop redirect sementara
let url = $(this).attr('href');
let lessonId = $(this).data('lesson-id');
let courseId = $(this).data('course-id');
$.ajax({
url: "{{ route('set.watch.history') }}",
type: "POST",
data: {
lesson_id: lessonId,
course_id: courseId
},
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function (response) {
console.log('Watch history updated:', response);
// 🔥 optional: refresh sidebar sebelum pindah
reloadSidebar();
// ⏩ lanjutkan navigasi
setTimeout(function () {
window.location.href = url;
}, 200);
},
error: function () {
// kalau gagal, tetap lanjutkan navigasi
window.location.href = url;
}
});
});
</script>
<script> <script>
"use strict"; "use strict";

View File

@ -210,6 +210,7 @@
if (lesson_id && course_id && (currentDuration % 5) == 0 && previousSavedDuration != currentDuration) { if (lesson_id && course_id && (currentDuration % 5) == 0 && previousSavedDuration != currentDuration) {
previousSavedDuration = currentDuration; previousSavedDuration = currentDuration;
let url = "{{ route('update_watch_history') }}"; let url = "{{ route('update_watch_history') }}";
let sidebarReloaded = false;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
@ -224,7 +225,11 @@
}, },
success: function(response) { success: function(response) {
console.log(response); console.log(response);
console.log(response.course_progress);
if (response.is_completed === 1 && !sidebarReloaded) {
sidebarReloaded = true;
reloadSidebar();
}
} }
}); });
} }

View File

@ -9,126 +9,97 @@
<div class="course-video-area border-primary border"> <div class="course-video-area border-primary border">
<!-- Video --> <!-- Video -->
<div class="course-video-wrap"> <div class="course-video-wrap">
<div id="player"> <div id="player" class="ratio ratio-16x9">
<iframe src="{{ $lesson_details->lesson_src }}?origin=https://plyr.io&amp;iv_load_policy=3&amp;modestbranding=1&amp;playsinline=1&amp;showinfo=0&amp;rel=0&amp;enablejsapi=1" allowfullscreen allowtransparency allow="autoplay"></iframe> <iframe src="{{ $lesson_details->lesson_src }}?origin=https://plyr.io&amp;iv_load_policy=3&amp;modestbranding=1&amp;playsinline=1&amp;showinfo=0&amp;rel=0&amp;enablejsapi=1" allowfullscreen allowtransparency allow="autoplay"></iframe>
</div> </div>
@include('course_player.player_config') @include('course_player.player_config')
</div> </div>
</div> </div>
@elseif ($lesson_details->lesson_type == 'scorm') @elseif($lesson_details->lesson_type == 'google_drive')
<div class="course-video-area">
<div class="course-video-wrap">
<div>
@if ($lesson_details->attachment_type == 'iSpring')
<iframe class="embed-responsive-item"
src="{{ asset('uploads/lesson_file/scorm_content/' . $lesson_details->attachment . '/res/index.html') }}"
allowfullscreen allowtransparency width="100%" height="100%" allow="autoplay"></iframe>
@elseif ($lesson_details->attachment_type == 'articulate')
<iframe class="embed-responsive-item"
src="{{ asset('uploads/lesson_file/scorm_content/' . $lesson_details->attachment . '/index.html') }}"
allowfullscreen allowtransparency width="100%" height="100%" allow="autoplay"></iframe>
@elseif ($lesson_details->attachment_type == 'adobeCaptivate')
<iframe class="embed-responsive-item"
src="{{ asset('uploads/lesson_file/scorm_content/' . $lesson_details->attachment . '/index.html') }}"
allowfullscreen allowtransparency width="100%" height="100%" allow="autoplay"></iframe>
@endif
</div>
</div>
</div>
@elseif($lesson_details->lesson_type == 'system-video')
@php @php
$watermark_type = get_player_settings('watermark_type'); $video_url = trim($lesson_details->lesson_src);
$lesson_video = $lesson_details->lesson_src; preg_match('/\/d\/([a-zA-Z0-9_-]+)/', $video_url, $matches);
if ($watermark_type == 'ffmpeg') { $video_id = $matches[1] ?? null;
$origin = dirname($lesson_details->lesson_src);
$dir = $origin . '/watermark';
$file = str_replace($origin, '', $lesson_details->lesson_src);
$lesson_video = "{$dir}{$file}";
}
@endphp @endphp
<div class="course-video-area border-primary border">
<!-- Video --> @if($video_id)
<div class="course-video-wrap"> <div class="course-video-area border-primary border position-relative">
<div class=" bd-r-10 mb-16 position-relative bg-light custom-system-video"> <!-- Responsive wrapper with 16:9 aspect ratio -->
<video id="player" playsinline controls oncontextmenu="return false;"> <div class="ratio ratio-16x9 w-100">
{{-- <source src="{{ asset($lesson_details->lesson_src) }}" type="video/mp4"> --}} <iframe
<source src="{{ route('course.get_file', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]) }}" type="video/mp4"> src="https://drive.google.com/file/d/{{ $video_id }}/preview"
</video> allowfullscreen
allow="autoplay; fullscreen; picture-in-picture"
class="border-0 rounded"
frameborder="0"
loading="lazy">
</iframe>
</div>
</div>
@include('course_player.player_config') @include('course_player.player_config')
@else
<div class="alert alert-danger text-center py-5">
<h5>Invalid Google Drive Link</h5>
<p>Could not extract video ID from the provided URL.</p>
</div> </div>
</div> @endif
</div>
@elseif($lesson_details->lesson_type == 'image') @elseif($lesson_details->lesson_type == 'image')
@php @php
// $img = asset('uploads/lesson_file/attachment/' . $lesson_details->attachment); // $img = asset('uploads/lesson_file/attachment/' . $lesson_details->attachment);
$img = route('course.get_file', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]) $img = route('course.get_file', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id])
@endphp @endphp
<img width="100%" class="max-w-auto" height="auto" src="{{ $img }}" /> <img width="100%" class="max-w-auto" height="auto" src="{{ $img }}" />
@elseif($lesson_details->lesson_type == 'vimeo-url' && $lesson_details->video_type == 'vimeo')
@php
$video_url = $lesson_details->lesson_src;
$video_id = explode('https://vimeo.com/', $video_url);
$video_id = str_replace('https://vimeo.com/', '', $video_url);
@endphp
<div class="course-video-area border-primary border">
<!-- Video -->
<div class="course-video-wrap">
<div id="player">
<iframe height="500" src="https://player.vimeo.com/video/{{ $video_id }}?loop=false&amp;byline=false&amp;portrait=false&amp;title=false&amp;speed=true&amp;transparent=0&amp;gesture=media" allowfullscreen allowtransparency allow="autoplay"></iframe>
@include('course_player.player_config')
</div>
</div>
</div>
@elseif($lesson_details->lesson_type == 'google_drive')
@php
$video_url = $lesson_details->lesson_src;
$url_array_1 = explode('/', $video_url . '/');
$url_array_2 = explode('=', $video_url);
$video_id = null;
if ($url_array_1[4] == 'd'):
$video_id = $url_array_1[5];
else:
$video_id = $url_array_2[1];
endif;
@endphp
<div class="course-video-area border-primary border">
<!-- Video -->
<div class="course-video-wrap">
<video width="100%" height="680" id="player" playsinline controls>
<source class="" src="https://www.googleapis.com/drive/v3/files/{{ $video_id }}?alt=media&key={{ get_settings('youtube_api_key') }}" type="video/mp4">
</video>
@include('course_player.player_config')
</div>
</div>
@elseif($lesson_details->lesson_type == 'html5')
<div class="course-video-area border-primary border">
<!-- Video -->
<div class="course-video-wrap">
<video width="100%" height="680" id="player" playsinline controls>
<source class="remove_video_src" src="{{ $lesson_details->lesson_src }}" type="video/mp4">
</video>
@include('course_player.player_config')
</div>
</div>
@elseif($lesson_details->lesson_type == 'document_type') @elseif($lesson_details->lesson_type == 'document_type')
@php @php
$src = route('course.get_file', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]) $src = route('course.get_file', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]);
$pdf_src = route('pdf_canvas', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]);
$direct_pdf = $src; // URL langsung ke file PDF
$fullscreen_id = 'lesson-content-' . $lesson_details->id;
@endphp @endphp
<div class="position-relative d-inline-block w-100">
<div id="{{ $fullscreen_id }}" class="w-100 border rounded overflow-hidden bg-light">
@if ($lesson_details->attachment_type == 'pdf') @if ($lesson_details->attachment_type == 'pdf')
{{-- <iframe class="embed-responsive-item" width="100%" src="{{ $src }}" allowfullscreen></iframe> --}} <a href="{{ $direct_pdf }}" target="_blank" class="btn btn-sm position-absolute top-0 end-0 mt-2 me-2 shadow" style="z-index: 1050; color: white; background: #001151;" title="Buka PDF di Tab Baru (tekan F11 untuk full screen)">
<i class="bi bi-box-arrow-up-right"></i> Buka Full PDF
<iframe class="embed-responsive-item" width="100%" height="600px" src="{{ route('pdf_canvas', ['course_id' => $lesson_details->course_id, 'lesson_id' => $lesson_details->id]) }}" allowfullscreen></iframe> </a>
<iframe src="{{ $pdf_src }}#toolbar=1&navpanes=0&scrollbar=1&view=FitH"
class="w-100 border-0"
style="height: 80vh; min-height: 600px;"
allowfullscreen
@elseif($lesson_details->attachment_type == 'doc' || $lesson_details->attachment_type == 'ppt') allow="fullscreen">
<iframe class="embed-responsive-item" width='100%' src="https://view.officeapps.live.com/op/embed.aspx?src={{ $src }}" frameborder='0'></iframe> </iframe>
@elseif($lesson_details->attachment_type == 'txt') @elseif($lesson_details->attachment_type == 'txt')
<iframe class="embed-responsive-item" width='100%' src="{{ $src }}" frameborder='0'></iframe> <iframe src="{{ $src }}"
class="w-100 border-0"
style="height: 80vh; min-height: 600px;"
frameborder="0">
</iframe>
@else
<div class="d-flex flex-column justify-content-center align-items-center"
style="height: 60vh; min-height: 300px;">
<div class="text-center mb-3">
<p class="mt-3 mb-1 fw-bold">
Dokumen tidak dapat ditampilkan secara langsung
</p>
<p class="text-muted small">
Silakan unduh file untuk melihat isi dokumen
</p>
</div>
<a href="{{ $src }}"
class="btn btn-primary"
target="_blank"
download>
<i class="fa fa-download me-1"></i>
Download Dokumen
</a>
</div>
@endif @endif
</div>
</div>
@elseif($lesson_details->lesson_type == 'quiz') @elseif($lesson_details->lesson_type == 'quiz')
<div class="course-video-area border-primary pb-5"> <div class="course-video-area border-primary pb-5">
@include('course_player.quiz.index') @include('course_player.quiz.index')
@ -144,3 +115,18 @@
return false; // Prevent right-click menu return false; // Prevent right-click menu
}; };
</script> </script>
<script type="text/javascript">
function reloadSidebar() {
let courseId = '{{ $course_details->id }}';
let lessonId = '{{ $lesson_details->id }}';
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');
});
}
</script>

View File

@ -70,6 +70,28 @@
->get(); ->get();
@endphp @endphp
@php
$is_passed = false;
if ($submits->count() > 0 && $questions->count() > 0) {
$mark_per_question = $quiz->total_mark / $questions->count();
foreach ($submits as $submit) {
$correct_answers = $submit->correct_answer
? json_decode($submit->correct_answer, true)
: [];
$obtained_mark = count($correct_answers) * $mark_per_question;
if ($obtained_mark >= $quiz->pass_mark) {
$is_passed = true;
break;
}
}
}
@endphp
<div class="row px-4"> <div class="row px-4">
<div class="col-12"> <div class="col-12">
<h4 class="quiz-title text-center mt-4"><span>{{ $quiz->title }}</span></h4> <h4 class="quiz-title text-center mt-4"><span>{{ $quiz->title }}</span></h4>
@ -135,10 +157,13 @@
id="{{ $submit->id }}">{{ get_phrase('View Result') }} {{ ++$key }}</button> id="{{ $submit->id }}">{{ get_phrase('View Result') }} {{ ++$key }}</button>
@endforeach @endforeach
@if ($submits->count() < $quiz->retake) @if (!$is_passed && $submits->count() < $quiz->retake)
<button type="button" class="eBtn gradient border-0" <button type="button" class="eBtn gradient border-0"
id="starterBtn">{{ get_phrase('Start Quiz') }}</button> id="starterBtn">
{{ get_phrase('Start Quiz') }}
</button>
@endif @endif
</div> </div>
</div> </div>

View File

@ -138,29 +138,7 @@
// Submit quiz // Submit quiz
function submitQuiz() { function submitQuiz() {
var quizId = "{{ $quiz->id }}";
var completed_lesson_arr = @json($completed_lesson_arr); // Convert the PHP array to a JavaScript array
// Check if quizId is in the completed_lesson_arr array using JavaScript's `includes()` method
if (!completed_lesson_arr.includes(quizId)) {
$.ajax({
url: "{{ route('set.watch.history') }}", // Your route
type: "post",
data: {
lesson_id: "{{ $quiz->id }}",
course_id: "{{ $course_details->id }}"
},
success: function(response) {
submitForm.submit(); submitForm.submit();
},
error: function(xhr, status, error) {
console.error("Error updating watch history:", xhr.responseText);
}
});
} else {
submitForm.submit();
}
} }
</script> </script>

View File

@ -63,23 +63,30 @@
@php $type = $lesson->lesson_type; @endphp @php $type = $lesson->lesson_type; @endphp
<li class="coourse-playlist-item @if (isset($history->watching_lesson_id) && $lesson->id == $history->watching_lesson_id || $lesson->id == $lesson_details->id) active @else lock @endif"> <li class="coourse-playlist-item @if (isset($history->watching_lesson_id) && $lesson->id == $history->watching_lesson_id || $lesson->id == $lesson_details->id) active @else lock @endif">
<div class="check-title-area align-items-center"> <div class="check-title-area align-items-center">
@if($course_details->enable_drip_content) @if($course_details->enable_drip_content)
@if($is_locked) @if($is_locked)
<i class="fas fa-lock" title="<?php echo get_phrase('Complete previous lesson to unlock it'); ?>"></i> <i class="fas fa-lock" title="<?php echo get_phrase('Complete previous lesson to unlock it'); ?>"></i>
@else @else
@if(in_array($lesson->id, $completed_lesson_arr)) @if(in_array($lesson->id, $completed_lesson_arr))
<i class="fas fa-check-circle checkbox-icon" title="<?php echo get_phrase('Lesson completed'); ?>"></i> <i class="fas fa-check-circle checkbox-icon" title="<?php echo get_phrase('Lesson completed'); ?>"></i>
@elseif(in_array($type, ['video-url', 'system-video', 'vimeo-url', 'google_drive'])) @elseif(in_array($type, ['video-url', 'google_drive']))
<i class="form-check-input flexCheckChecked mt-0" title="<?php echo get_phrase('Play Now'); ?>"></i> <i class="form-check-input flexCheckChecked mt-0" title="<?php echo get_phrase('Play Now'); ?>"></i>
@else @else
<input class="form-check-input flexCheckChecked mt-0" @if (in_array($lesson->id, $completed_lesson)) checked @endif type="checkbox" id="{{ $lesson->id }}"> <input
class="form-check-input mt-0"
type="checkbox"
id="{{ $lesson->id }}"
@if (in_array($lesson->id, $completed_lesson)) checked @endif
disabled
>
@endif @endif
@endif @endif
<div class="play-lock-number"> <div class="play-lock-number">
<span> <span>
@if (in_array($type, ['text', 'document_type', 'iframe'])) @if (in_array($type, ['text', 'document_type', 'iframe']))
<i class="fa-solid fa-file"></i> <i class="fa-solid fa-file"></i>
@elseif (in_array($type, ['video-url', 'system-video', 'vimeo-url'])) @elseif (in_array($type, ['video-url']))
<i class="fa-solid fa-video"></i> <i class="fa-solid fa-video"></i>
@elseif ($type == 'image') @elseif ($type == 'image')
<i class="fa-solid fa-image"></i> <i class="fa-solid fa-image"></i>
@ -91,7 +98,19 @@
</span> </span>
</div> </div>
<p class="d-none">{{ $lesson->lesson_type }}</p> <p class="d-none">{{ $lesson->lesson_type }}</p>
<a href="{{ route('course.player', ['slug' => $course_details->slug, 'id' => $lesson->id]) }}" class="video-title">{{ $lesson->title }}</a> @if (in_array($type, ['google_drive', 'video-url', 'quiz']))
<a href="{{ route('course.player', ['slug' => $course_details->slug, 'id' => $lesson->id]) }}"
class="video-title">
{{ $lesson->title }}
</a>
@else
<a href="{{ route('course.player', ['slug' => $course_details->slug, 'id' => $lesson->id]) }}"
class="video-title lesson-link"
data-lesson-id="{{ $lesson->id }}"
data-course-id="{{ $course_details->id }}">
{{ $lesson->title }}
</a>
@endif
@else @else
<input class="form-check-input flexCheckChecked mt-0" @if (in_array($lesson->id, $completed_lesson)) checked @endif type="checkbox" id="{{ $lesson->id }}"> <input class="form-check-input flexCheckChecked mt-0" @if (in_array($lesson->id, $completed_lesson)) checked @endif type="checkbox" id="{{ $lesson->id }}">
<div class="play-lock-number"> <div class="play-lock-number">

View File

@ -20,7 +20,6 @@
$average_rating = $rating / $total; $average_rating = $rating / $total;
} }
@endphp @endphp
<!------------------- Breadcum Area Start ------>
<section class="breadcum-area page-content-pb-100 bg-white"> <section class="breadcum-area page-content-pb-100 bg-white">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@ -34,14 +33,11 @@
</ol> </ol>
</nav> </nav>
</div> </div>
<div class="course-details pe-auto pe-lg-5"> <div class="course-details pe-auto pe-lg-5">
<h2 class="g-title ellipsis-line-4">{{ $course_details->title }}</h2> <h2 class="g-title ellipsis-line-4">{{ $course_details->title }}</h2>
<p class="g-text text-dark ellipsis-line-2"> <p class="g-text text-dark ellipsis-line-2">
{{ ellipsis($course_details->short_description, 160) }} {{ ellipsis($course_details->short_description, 160) }}
</p> </p>
<div class="row row-gap-4"> <div class="row row-gap-4">
<div class="col-6 col-sm-6 col-md-4"> <div class="col-6 col-sm-6 col-md-4">
<a class="d-flex align-items-center text-dark" href="{{ route('instructor.details', ['name' => slugify($course_details->creator->name), 'id' => $course_details->creator->id]) }}"> <a class="d-flex align-items-center text-dark" href="{{ route('instructor.details', ['name' => slugify($course_details->creator->name), 'id' => $course_details->creator->id]) }}">
@ -80,20 +76,15 @@
{{ total_durations($course_details->id) }} {{ total_durations($course_details->id) }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<section> <section>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-8 order-2 order-lg-1"> <div class="col-lg-8 order-2 order-lg-1">
<div class="details-page-content"> <div class="details-page-content">
<div class="ps-box static-menu mt-5 w-100"> <div class="ps-box static-menu mt-5 w-100">
<ul class="nav nav-bordered" id="pills-tab" role="tablist"> <ul class="nav nav-bordered" id="pills-tab" role="tablist">
@ -138,54 +129,83 @@
@include('frontend.default.course.pricing_card') @include('frontend.default.course.pricing_card')
</div> </div>
</div> </div>
<!------------------- Player Feature Area End --------->
</div> </div>
</section> </section>
<!------------------- Breadcum Area End --------->
<!-- Vertically centered modal -->
<div class="modal fade-in-effect" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal fade-in-effect" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl"> <div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content"> <div class="modal-content radius-22 overflow-hidden border-0">
<div class="modal-body bg-dark"> <div class="modal-header border-0 pb-0 position-relative">
<button type="button" class="btn-close btn-close-white shadow-none position-absolute end-0 top-0 m-3" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body bg-dark p-0">
<link rel="stylesheet" href="{{ asset('assets/global/plyr/plyr.css') }}"> <link rel="stylesheet" href="{{ asset('assets/global/plyr/plyr.css') }}">
@php @php
$preview_video_type = str_contains($course_details->preview, 'youtu') ? 'youtube' : ''; $preview = $course_details->preview ?? '';
$preview_video_type = str_contains($course_details->preview, 'vimeo') && $preview_video_type == '' ? 'vimeo' : $preview_video_type;
$preview_video_type = str_contains($course_details->preview, 'http') && $preview_video_type == '' ? 'html5' : $preview_video_type; // Detect video type
$video_type = 'html5'; // default fallback
if (str_contains($preview, 'youtu')) {
$video_type = 'youtube';
} elseif (preg_match('/drive\.google\.com\/file\/d\/([a-zA-Z0-9_-]+)/', $preview, $matches)) {
$video_type = 'google_drive';
$google_drive_id = $matches[1];
} elseif (str_contains($preview, 'http') && (str_ends_with($preview, '.mp4') || str_contains($preview, '.mp4'))) {
$video_type = 'html5';
} elseif (!empty($preview)) {
// Local asset (stored in storage or public folder)
$video_type = 'html5_local';
}
@endphp @endphp
@if ($preview_video_type == 'youtube') @if ($video_type == 'youtube')
<div class="plyr__video-embed" id="promoPlayer"> <div class="plyr__video-embed" id="promoPlayer">
<iframe height="500" src="{{ $course_details->preview }}?origin=https://plyr.io&amp;iv_load_policy=3&amp;modestbranding=1&amp;playsinline=1&amp;showinfo=0&amp;rel=0&amp;enablejsapi=1" allowfullscreen allowtransparency allow="autoplay"></iframe> <iframe
src="{{ str_replace('watch?v=', 'embed/', $preview) }}?autoplay=1&rel=0&modestbranding=1&playsinline=1"
allowfullscreen
allow="autoplay">
</iframe>
</div> </div>
@elseif ($preview_video_type == 'vimeo')
@elseif ($video_type == 'google_drive')
<div class="plyr__video-embed" id="promoPlayer"> <div class="plyr__video-embed" id="promoPlayer">
<iframe height="500" id="promoPlayer" src="https://player.vimeo.com/video/{{ $course_details->preview }}?loop=false&amp;byline=false&amp;portrait=false&amp;title=false&amp;speed=true&amp;transparent=0&amp;gesture=media" allowfullscreen allowtransparency allow="autoplay"></iframe> <iframe
src="https://drive.google.com/file/d/{{ $google_drive_id }}/preview"
width="100%"
height="500"
allowfullscreen
allow="autoplay">
</iframe>
</div> </div>
@elseif($preview_video_type == 'html5')
<video id="promoPlayer" playsinline controls> @elseif ($video_type == 'html5' || $video_type == 'html5_local')
<source src="{{ $course_details->preview }}" type="video/mp4"> <video id="promoPlayer" playsinline controls crossorigin>
<source src="{{ $video_type == 'html5_local' ? asset($preview) : $preview }}" type="video/mp4">
Your browser does not support the video tag.
</video> </video>
@else @else
<video id="promoPlayer" playsinline controls> <div class="d-flex align-items-center justify-content-center text-white" style="height: 500px;">
<source src="{{ asset($course_details->preview) }}" type="video/mp4"> <p class="h4">{{ get_phrase('No preview video available') }}</p>
</video> </div>
@endif @endif
<script src="{{ asset('assets/global/plyr/plyr.js') }}"></script> <script src="{{ asset('assets/global/plyr/plyr.js') }}"></script>
<script> <script>
"use strict"; "use strict";
const promoPlayer = new Plyr('#promoPlayer'); const promoPlayer = new Plyr('#promoPlayer', {
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'fullscreen'],
autoplay: true,
clickToPlay: true,
youtube: { noCookie: true, rel: 0, modestbranding: 1 },
});
</script> </script>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
"use strict"; "use strict";
const myModalElement = document.getElementById('exampleModal') const myModalElement = document.getElementById('exampleModal')

View File

@ -80,33 +80,64 @@
"use strict"; "use strict";
$(document).ready(function() { $(document).ready(function() {
// Hide dropdown when clicking outside
$(document).on('click', function(event) { $(document).on('click', function(event) {
if (!$(event.target).closest('.search-box').length) { if (!$(event.target).closest('.search-box').length) {
$('#msg-search-list').removeClass('active'); $('#msg-search-list').removeClass('active');
} }
}); });
// Show dropdown when clicking/focusing the search input
$('.Esearch_entry').on('click', function(e) { $('.Esearch_entry').on('click', function(e) {
e.preventDefault(); e.preventDefault();
$('#msg-search-list').addClass('active'); $('#msg-search-list').addClass('active');
}); });
let debounceTimer;
const debounceDelay = 800; // 0.8 seconds
$('#search_student').keyup(function(e) { $('#search_student').on('keyup', function(e) {
let searchTerm = $(this).val(); let searchTerm = $(this).val().trim();
if (searchTerm.indexOf('@') !== -1) { // Always clear previous timer
clearTimeout(debounceTimer);
// If input is empty, clear results and hide dropdown
if (searchTerm === '') {
$('#msg-search-list').empty().removeClass('active');
return;
}
// Optional: Only search if at least 2-3 characters (better UX + performance)
// Remove this block if you want to search even with 1 character
if (searchTerm.length < 2) {
$('#msg-search-list').empty().removeClass('active');
return;
}
// Show dropdown immediately while waiting
$('#msg-search-list').addClass('active');
// Set new timer — API will be called after 800ms of no typing
debounceTimer = setTimeout(function() {
$.ajax({ $.ajax({
type: "post", type: "POST",
url: "{{ route('search.student') }}", url: "{{ route('search.student') }}",
data: { data: {
search_mail: searchTerm search_mail: searchTerm,
_token: '{{ csrf_token() }}'
}, },
success: function(response) { success: function(response) {
$('#msg-search-list').empty().append(response); $('#msg-search-list')
.empty()
.append(response)
.addClass('active');
},
error: function() {
$('#msg-search-list').empty();
} }
}); });
} }, debounceDelay);
}); });
}); });
</script> </script>

View File

@ -14,33 +14,11 @@
<hr class="bg-secondary my-4"> <hr class="bg-secondary my-4">
<div class="row mb-3"> <div class="row mb-3" id="preview_link_input">
@php
$preview_video_type = str_contains($course_details->preview, 'youtu') ? 'youtube' : '';
$preview_video_type = str_contains($course_details->preview, 'vimeo') && $preview_video_type == '' ? 'vimeo' : '';
$preview_video_type = str_contains($course_details->preview, 'http') && $preview_video_type == '' ? 'html5' : '';
@endphp
<label for="preview_video_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Preview Video') }}</label>
<div class="col-sm-10">
<input type="radio" onchange="$('#preview_link_input').toggleClass('d-hidden'); $('#preview_file_input').toggleClass('d-hidden');" class="form-check-input" value="link" name="preview_video_provider" id="preview_video_link" @if ($preview_video_type != '') checked @endif>&nbsp;<label for="preview_video_link">{{ get_phrase('Video Link') }}</label>
&nbsp;&nbsp;
<input type="radio" onchange="$('#preview_link_input').toggleClass('d-hidden'); $('#preview_file_input').toggleClass('d-hidden');" class="form-check-input" value="file" name="preview_video_provider" id="preview_video_file" @if ($preview_video_type == '') checked @endif>&nbsp;<label for="preview_video_file">{{ get_phrase('Video File') }}
</div>
</div>
<div class="row mb-3 @if ($preview_video_type == '') d-hidden @endif" id="preview_link_input">
<label for="preview_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Video link') }}</label> <label for="preview_link" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Video link') }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name = "preview_link" id = "preview_link" class="form-control ol-form-control" value="{{ $course_details->preview }}"> <input type="text" name = "preview_link" id = "preview_link" class="form-control ol-form-control" value="{{ $course_details->preview }}">
<small class="text-muted">{{ get_phrase('Supported URL') }}: <b>{{ get_phrase('Youtube') }}</b> {{ get_phrase('or') }} <b>{{ get_phrase('Vimeo') }}</b> {{ get_phrase('or') }} <b>{{ get_phrase('HTML5') }}</b></small> <small class="text-muted">{{ get_phrase('Supported URL') }}: <b>{{ get_phrase('Youtube') }}</b> {{ get_phrase('or') }}<b>{{ get_phrase('Google Drive') }}</b></small>
</div>
</div>
<div class="row mb-3 @if ($preview_video_type != '') d-hidden @endif" id="preview_file_input">
<label for="preview" class="form-label ol-form-label col-sm-2 col-form-label">{{ get_phrase('Preview Video File') }}</label>
<div class="col-sm-10">
<input type="file" name="preview" class="form-control ol-form-control" id="preview" accept="video/*" />
<small class="text-muted">{{ get_phrase('Supported Video file') }}: <b>.{{ get_phrase('mp4') }}</b> {{ get_phrase('or') }} <b>.{{ get_phrase('webm') }}</b> {{ get_phrase('or') }} <b>.{{ get_phrase('ogg') }}</b></small>
</div> </div>
</div> </div>

View File

@ -28,41 +28,7 @@
</label> </label>
</div> </div>
<!-- <div class="col">
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-vimeo">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/video-circle-black-18.svg" alt="">
<p class="title">{{ get_phrase('Vimeo Video') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-vimeo" value="vimeo" @if ($selected_lesson == 'vimeo') checked @endif>
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-videofile">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/video-black-18.svg" alt="">
<p class="title">{{ get_phrase('Video file') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-videofile" value="video" @if ($selected_lesson == 'video') checked @endif>
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-url">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/link-black-18.svg" alt="">
<p class="title">{{ get_phrase('Video url [ .mp4 ]') }}</p>
</div>
<input value="html5" class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-url">
</label>
</div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-drive"> <label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-drive">
<div class="title-icon d-flex align-items-center"> <div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/document-black-18.svg" alt=""> <img src="assets/images/icons/document-black-18.svg" alt="">
@ -70,7 +36,7 @@
</div> </div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-drive" value="google_drive_video" @if ($selected_lesson == 'google_drive_video') {{ get_phrase('checked') }} @endif> <input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-drive" value="google_drive_video" @if ($selected_lesson == 'google_drive_video') {{ get_phrase('checked') }} @endif>
</label> </label>
</div> </div> -->
<div class="col"> <div class="col">
@ -110,25 +76,12 @@
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-iframe"> <label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap" for="radio-iframe">
<div class="title-icon d-flex align-items-center"> <div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/volume-black-18.svg" alt=""> <img src="assets/images/icons/volume-black-18.svg" alt="">
<p class="title">{{ get_phrase('Iframe embed') }}</p> <p class="title">{{ get_phrase('File embed(ppt, pdf)') }}</p>
</div> </div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-iframe" value="iframe" @if ($selected_lesson == 'iframe') {{ get_phrase('checked') }} @endif> <input class="form-check-input form-check-input-radio" type="radio" name="lesson_type" id="radio-iframe" value="iframe" @if ($selected_lesson == 'iframe') {{ get_phrase('checked') }} @endif>
</label> </label>
</div> </div>
<div class="col">
<label class="ol-radiobox-1 d-flex align-items-center justify-content-between flex-wrap"
for="radio-scorm">
<div class="title-icon d-flex align-items-center">
<img src="assets/images/icons/volume-black-18.svg" alt="">
<p class="title">{{ get_phrase('Scorm Content') }}</p>
</div>
<input class="form-check-input form-check-input-radio" type="radio" name="lesson_type"
id="radio-scorm" value="scorm"
@if ($selected_lesson == 'scorm') {{ get_phrase('checked') }} @endif>
</label>
</div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<a href="javascript:void(0)" type="button" class="btn btn-primary" data-toggle="modal" data-dismiss="modal" id="lesson-add-modal" onclick="showLessonAddModal()">{{ get_phrase('Next') }} <a href="javascript:void(0)" type="button" class="btn btn-primary" data-toggle="modal" data-dismiss="modal" id="lesson-add-modal" onclick="showLessonAddModal()">{{ get_phrase('Next') }}

View File

@ -11,6 +11,7 @@ Route::middleware(['auth'])->group(function () {
Route::get('play-course/{slug}/{id?}', 'course_player')->name('course.player'); Route::get('play-course/{slug}/{id?}', 'course_player')->name('course.player');
Route::post('set-watch-history/', 'set_watch_history')->name('set.watch.history'); Route::post('set-watch-history/', 'set_watch_history')->name('set.watch.history');
Route::get('player/prepend/watermark', 'prepend_watermark')->name('player.prepend.watermark'); Route::get('player/prepend/watermark', 'prepend_watermark')->name('player.prepend.watermark');
Route::get('course-player/sidebar/{course_id}/{lesson_id}', 'reload_sidebar')->name('course.sidebar.reload');
}); });
Route::controller(ForumController::class)->group(function () { Route::controller(ForumController::class)->group(function () {