From 1b8be7321974663362cea07f48371b776b2ce701 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Fri, 19 Dec 2025 15:37:20 +0100 Subject: [PATCH] bugfix --- .../Controllers/Course/PlayerController.php | 170 ++++++++++++++++-- app/Http/Middleware/CheckEnroll.php | 10 +- app/Services/Course/CoursePlayerService.php | 22 ++- app/Services/Course/CourseSectionService.php | 47 ++++- 4 files changed, 215 insertions(+), 34 deletions(-) diff --git a/app/Http/Controllers/Course/PlayerController.php b/app/Http/Controllers/Course/PlayerController.php index e55ceefb..394e9606 100644 --- a/app/Http/Controllers/Course/PlayerController.php +++ b/app/Http/Controllers/Course/PlayerController.php @@ -10,12 +10,74 @@ use App\Services\Course\CourseReviewService; use App\Services\Course\CourseService; use App\Services\Course\CourseSectionService; use App\Services\LiveClass\ZoomLiveService; +use App\Models\Course\Course; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Auth; use Inertia\Inertia; class PlayerController extends Controller { + private function findCourseContentByIdAndType(Course $course, string $contentId, string $contentType): ?array + { + if (!in_array($contentType, ['lesson', 'quiz'], true)) { + return null; + } + + foreach ($course->sections as $section) { + if ($contentType === 'lesson') { + foreach ($section->section_lessons as $lesson) { + if ((string) $lesson->id === (string) $contentId) { + return ['type' => 'lesson', 'id' => $lesson->id]; + } + } + } else { + foreach ($section->section_quizzes as $quiz) { + if ((string) $quiz->id === (string) $contentId) { + return ['type' => 'quiz', 'id' => $quiz->id]; + } + } + } + } + + return null; + } + + private function findCourseContentById(Course $course, string $contentId): ?array + { + foreach ($course->sections as $section) { + foreach ($section->section_lessons as $lesson) { + if ((string) $lesson->id === (string) $contentId) { + return ['type' => 'lesson', 'id' => $lesson->id]; + } + } + + foreach ($section->section_quizzes as $quiz) { + if ((string) $quiz->id === (string) $contentId) { + return ['type' => 'quiz', 'id' => $quiz->id]; + } + } + } + + return null; + } + + private function findFirstCourseContent(Course $course): ?array + { + foreach ($course->sections as $section) { + $lesson = $section->section_lessons->first(); + if ($lesson) { + return ['type' => 'lesson', 'id' => $lesson->id]; + } + + $quiz = $section->section_quizzes->first(); + if ($quiz) { + return ['type' => 'quiz', 'id' => $quiz->id]; + } + } + + return null; + } + public function __construct( protected CourseService $courseService, protected CoursePlayerService $coursePlay, @@ -37,6 +99,10 @@ class PlayerController extends Controller $user = Auth::user(); $watchHistory = $this->sectionService->initWatchHistory($request->course_id, 'lesson', $user->id); + if (! $watchHistory) { + return back()->with('error', 'Dieser Kurs hat noch keine Lektionen oder Quizze.'); + } + return redirect()->route('course.player', [ 'type' => $watchHistory->current_watching_type, 'watch_history' => $watchHistory->id, @@ -53,7 +119,47 @@ class PlayerController extends Controller $watching_type = in_array($type, ['lesson', 'quiz'], true) ? $type : ($watch_history->current_watching_type ?? 'lesson'); $course = $this->courseService->getUserCourseById($watch_history->course_id, $user); - $watching = $this->coursePlay->getWatchingLesson($watching_id, $watching_type); + + if (! $course) { + return redirect() + ->route('category.courses', ['category' => 'all']) + ->with('error', 'Der Kurs konnte nicht gefunden werden.'); + } + + // Fix wrong `type`/`lesson_id` combinations by resolving the content inside the course. + $resolved = $this->findCourseContentByIdAndType($course, (string) $watching_id, $watching_type); + if (! $resolved) { + $resolved = $this->findCourseContentById($course, (string) $watching_id); + } + if (! $resolved) { + $resolved = $this->findCourseContentByIdAndType($course, (string) $watch_history->current_watching_id, (string) $watch_history->current_watching_type); + } + if (! $resolved) { + $resolved = $this->findCourseContentById($course, (string) $watch_history->current_watching_id); + } + if (! $resolved) { + $resolved = $this->findFirstCourseContent($course); + } + + if (! $resolved) { + return redirect() + ->route('course.details', ['slug' => $course->slug, 'id' => $course->id]) + ->with('error', 'Dieser Kurs hat noch keine Lektionen oder Quizze.'); + } + + $watching_id = (string) $resolved['id']; + $watching_type = $resolved['type']; + + // Canonicalize URL if it doesn’t match the resolved content. + if ($type !== $watching_type || (string) $lesson_id !== (string) $watching_id) { + return redirect()->route('course.player', [ + 'type' => $watching_type, + 'watch_history' => $watch_history->id, + 'lesson_id' => $watching_id, + ]); + } + + $watching = $this->coursePlay->getWatchingLesson($watching_id, $watching_type, (string) $course->id); $reviews = $this->reviewService->getReviews(['course_id' => $course->id, ...$request->all()], true); $userReview = $this->reviewService->userReview($course->id, $user->id); $totalReviews = $this->reviewService->totalReviews($course->id); @@ -74,7 +180,7 @@ class PlayerController extends Controller // } return Inertia::render('course-player/index', [ - 'type' => $type, + 'type' => $watching_type, 'course' => $course, 'section' => $section, 'reviews' => $reviews, @@ -86,29 +192,55 @@ class PlayerController extends Controller 'zoomConfig' => $zoomConfig, ]); } catch (ModelNotFoundException $th) { - $fallbackId = $watch_history->current_watching_id; - $fallbackType = $watch_history->current_watching_type; - $requestedType = $watching_type ?? $type; + $user = Auth::user(); + $course = $this->courseService->getUserCourseById($watch_history->course_id, $user); - if ($fallbackId && in_array($fallbackType, ['lesson', 'quiz'], true)) { - $isDifferentFromRequested = ((string) $fallbackId !== (string) $lesson_id) || ($fallbackType !== $requestedType); + if ($course) { + $watchHistoryForUser = WatchHistory::where('course_id', $course->id) + ->where('user_id', $user->id) + ->first(); - if ($isDifferentFromRequested) { - try { - $this->coursePlay->getWatchingLesson((string) $fallbackId, $fallbackType); + $watchHistoryId = $watchHistoryForUser?->id ?? $watch_history->id; - return redirect()->route('course.player', [ - 'type' => $fallbackType, - 'watch_history' => $watch_history->id, - 'lesson_id' => $fallbackId, - ]); - } catch (ModelNotFoundException $fallbackException) { - // Fall through to generic error below. - } + $requestedId = $lesson_id; + $requestedType = $watching_type ?? $type; + + // If the ID exists in the course but the type is wrong, redirect with the correct type. + $resolved = $this->findCourseContentById($course, $requestedId); + if ($resolved && $resolved['type'] !== $requestedType) { + return redirect()->route('course.player', [ + 'type' => $resolved['type'], + 'watch_history' => $watchHistoryId, + 'lesson_id' => $resolved['id'], + ]); } + + // If the watch history points to a valid item, jump there. + $resolved = $this->findCourseContentById($course, (string) $watch_history->current_watching_id); + if ($resolved && ((string) $resolved['id'] !== (string) $requestedId || $resolved['type'] !== $requestedType)) { + return redirect()->route('course.player', [ + 'type' => $resolved['type'], + 'watch_history' => $watchHistoryId, + 'lesson_id' => $resolved['id'], + ]); + } + + // Last resort: send the user to the first available content in the course. + $first = $this->findFirstCourseContent($course); + if ($first && ((string) $first['id'] !== (string) $requestedId || $first['type'] !== $requestedType)) { + return redirect()->route('course.player', [ + 'type' => $first['type'], + 'watch_history' => $watchHistoryId, + 'lesson_id' => $first['id'], + ]); + } + + return redirect() + ->route('course.details', ['slug' => $course->slug, 'id' => $course->id]) + ->with('error', 'Dieser Kurs hat noch keine Lektionen oder Quizze.'); } - return redirect()->route('category.courses', ['category' => 'all'])->with('error', 'The requested content could not be found.'); + return redirect()->route('category.courses', ['category' => 'all'])->with('error', 'Der angeforderte Inhalt konnte nicht gefunden werden.'); } catch (\Throwable $th) { return redirect()->route('category.courses', ['category' => 'all'])->with('error', $th->getMessage()); } diff --git a/app/Http/Middleware/CheckEnroll.php b/app/Http/Middleware/CheckEnroll.php index c1dd0ad2..1eb13d5d 100644 --- a/app/Http/Middleware/CheckEnroll.php +++ b/app/Http/Middleware/CheckEnroll.php @@ -26,11 +26,19 @@ class CheckEnroll } $watchHistory = $request->route('watch_history'); - if (!WatchHistory::find($watchHistory)) { + + if (!($watchHistory instanceof WatchHistory)) { + $watchHistory = WatchHistory::find($watchHistory); + } + + if (!$watchHistory) { return back()->with('error', 'Invalid watch history'); } $course = Course::find($watchHistory->course_id); + if (!$course) { + return back()->with('error', 'Invalid course'); + } if ($user->role == 'instructor' && $user->instructor_id == $course->instructor_id) { return $next($request); diff --git a/app/Services/Course/CoursePlayerService.php b/app/Services/Course/CoursePlayerService.php index 6f94ae76..d9ff3902 100644 --- a/app/Services/Course/CoursePlayerService.php +++ b/app/Services/Course/CoursePlayerService.php @@ -12,12 +12,12 @@ use Illuminate\Support\Facades\Auth; class CoursePlayerService { - function getWatchingLesson(string $lesson_id, string $watching_type): SectionLesson | SectionQuiz + function getWatchingLesson(string $lesson_id, string $watching_type, ?string $course_id = null): SectionLesson | SectionQuiz { $user = Auth::user(); if ($watching_type === 'lesson') { - return SectionLesson::with([ + $query = SectionLesson::with([ 'resources', 'forums' => function ($query) { $query->with([ @@ -27,15 +27,27 @@ class CoursePlayerService }, ]); }, - ])->findOrFail($lesson_id); + ]); + + if ($course_id) { + $query->where('course_id', $course_id); + } + + return $query->findOrFail($lesson_id); } - return SectionQuiz::with([ + $query = SectionQuiz::with([ 'quiz_questions', 'quiz_submissions' => function ($query) use ($user) { $query->where('user_id', $user->id); } - ])->findOrFail($lesson_id); + ]); + + if ($course_id) { + $query->where('course_id', $course_id); + } + + return $query->findOrFail($lesson_id); } function getWatchHistory(string $course_id, ?string $user_id): ?WatchHistory diff --git a/app/Services/Course/CourseSectionService.php b/app/Services/Course/CourseSectionService.php index 3d4e76ef..7c532d34 100644 --- a/app/Services/Course/CourseSectionService.php +++ b/app/Services/Course/CourseSectionService.php @@ -289,21 +289,50 @@ class CourseSectionService extends MediaService public function initWatchHistory(string $course_id, string $watching_type, string $user_id): ?WatchHistory { - $lesson = SectionLesson::query()->where('course_id', $course_id); $history = WatchHistory::where('course_id', $course_id) ->where('user_id', $user_id) ->first(); - if ($lesson->count() >= 0 && !$history) { - $lesson = $lesson->orderBy('sort', 'asc')->first(); - - $coursePlay = new CoursePlayerService(); - $course = Course::where('id', $course_id)->with('sections')->first(); - - return $coursePlay->watchHistory($course, $lesson->id, $watching_type, $user_id); + if ($history) { + return $history; } - return $history; + $course = Course::where('id', $course_id)->with([ + 'sections.section_lessons', + 'sections.section_quizzes', + ])->first(); + + if (! $course) { + return null; + } + + $preferredTypes = $watching_type === 'quiz' ? ['quiz', 'lesson'] : ['lesson', 'quiz']; + $firstItem = null; + + foreach ($preferredTypes as $preferredType) { + foreach ($course->sections as $section) { + if ($preferredType === 'lesson') { + $lesson = $section->section_lessons->first(); + if ($lesson) { + $firstItem = ['type' => 'lesson', 'id' => $lesson->id]; + break 2; + } + } else { + $quiz = $section->section_quizzes->first(); + if ($quiz) { + $firstItem = ['type' => 'quiz', 'id' => $quiz->id]; + break 2; + } + } + } + } + + if (! $firstItem) { + return null; + } + + $coursePlay = new CoursePlayerService(); + return $coursePlay->watchHistory($course, (string) $firstItem['id'], $firstItem['type'], $user_id); } /**