This commit is contained in:
parent
3d67eedf0c
commit
1b8be73219
@ -10,12 +10,74 @@ use App\Services\Course\CourseReviewService;
|
|||||||
use App\Services\Course\CourseService;
|
use App\Services\Course\CourseService;
|
||||||
use App\Services\Course\CourseSectionService;
|
use App\Services\Course\CourseSectionService;
|
||||||
use App\Services\LiveClass\ZoomLiveService;
|
use App\Services\LiveClass\ZoomLiveService;
|
||||||
|
use App\Models\Course\Course;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class PlayerController extends Controller
|
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(
|
public function __construct(
|
||||||
protected CourseService $courseService,
|
protected CourseService $courseService,
|
||||||
protected CoursePlayerService $coursePlay,
|
protected CoursePlayerService $coursePlay,
|
||||||
@ -37,6 +99,10 @@ class PlayerController extends Controller
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$watchHistory = $this->sectionService->initWatchHistory($request->course_id, 'lesson', $user->id);
|
$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', [
|
return redirect()->route('course.player', [
|
||||||
'type' => $watchHistory->current_watching_type,
|
'type' => $watchHistory->current_watching_type,
|
||||||
'watch_history' => $watchHistory->id,
|
'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');
|
$watching_type = in_array($type, ['lesson', 'quiz'], true) ? $type : ($watch_history->current_watching_type ?? 'lesson');
|
||||||
|
|
||||||
$course = $this->courseService->getUserCourseById($watch_history->course_id, $user);
|
$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);
|
$reviews = $this->reviewService->getReviews(['course_id' => $course->id, ...$request->all()], true);
|
||||||
$userReview = $this->reviewService->userReview($course->id, $user->id);
|
$userReview = $this->reviewService->userReview($course->id, $user->id);
|
||||||
$totalReviews = $this->reviewService->totalReviews($course->id);
|
$totalReviews = $this->reviewService->totalReviews($course->id);
|
||||||
@ -74,7 +180,7 @@ class PlayerController extends Controller
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return Inertia::render('course-player/index', [
|
return Inertia::render('course-player/index', [
|
||||||
'type' => $type,
|
'type' => $watching_type,
|
||||||
'course' => $course,
|
'course' => $course,
|
||||||
'section' => $section,
|
'section' => $section,
|
||||||
'reviews' => $reviews,
|
'reviews' => $reviews,
|
||||||
@ -86,29 +192,55 @@ class PlayerController extends Controller
|
|||||||
'zoomConfig' => $zoomConfig,
|
'zoomConfig' => $zoomConfig,
|
||||||
]);
|
]);
|
||||||
} catch (ModelNotFoundException $th) {
|
} catch (ModelNotFoundException $th) {
|
||||||
$fallbackId = $watch_history->current_watching_id;
|
$user = Auth::user();
|
||||||
$fallbackType = $watch_history->current_watching_type;
|
$course = $this->courseService->getUserCourseById($watch_history->course_id, $user);
|
||||||
|
|
||||||
|
if ($course) {
|
||||||
|
$watchHistoryForUser = WatchHistory::where('course_id', $course->id)
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$watchHistoryId = $watchHistoryForUser?->id ?? $watch_history->id;
|
||||||
|
|
||||||
|
$requestedId = $lesson_id;
|
||||||
$requestedType = $watching_type ?? $type;
|
$requestedType = $watching_type ?? $type;
|
||||||
|
|
||||||
if ($fallbackId && in_array($fallbackType, ['lesson', 'quiz'], true)) {
|
// If the ID exists in the course but the type is wrong, redirect with the correct type.
|
||||||
$isDifferentFromRequested = ((string) $fallbackId !== (string) $lesson_id) || ($fallbackType !== $requestedType);
|
$resolved = $this->findCourseContentById($course, $requestedId);
|
||||||
|
if ($resolved && $resolved['type'] !== $requestedType) {
|
||||||
if ($isDifferentFromRequested) {
|
|
||||||
try {
|
|
||||||
$this->coursePlay->getWatchingLesson((string) $fallbackId, $fallbackType);
|
|
||||||
|
|
||||||
return redirect()->route('course.player', [
|
return redirect()->route('course.player', [
|
||||||
'type' => $fallbackType,
|
'type' => $resolved['type'],
|
||||||
'watch_history' => $watch_history->id,
|
'watch_history' => $watchHistoryId,
|
||||||
'lesson_id' => $fallbackId,
|
'lesson_id' => $resolved['id'],
|
||||||
]);
|
]);
|
||||||
} catch (ModelNotFoundException $fallbackException) {
|
|
||||||
// Fall through to generic error below.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('category.courses', ['category' => 'all'])->with('error', 'The requested content could not be found.');
|
// 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', 'Der angeforderte Inhalt konnte nicht gefunden werden.');
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
return redirect()->route('category.courses', ['category' => 'all'])->with('error', $th->getMessage());
|
return redirect()->route('category.courses', ['category' => 'all'])->with('error', $th->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,11 +26,19 @@ class CheckEnroll
|
|||||||
}
|
}
|
||||||
|
|
||||||
$watchHistory = $request->route('watch_history');
|
$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');
|
return back()->with('error', 'Invalid watch history');
|
||||||
}
|
}
|
||||||
|
|
||||||
$course = Course::find($watchHistory->course_id);
|
$course = Course::find($watchHistory->course_id);
|
||||||
|
if (!$course) {
|
||||||
|
return back()->with('error', 'Invalid course');
|
||||||
|
}
|
||||||
|
|
||||||
if ($user->role == 'instructor' && $user->instructor_id == $course->instructor_id) {
|
if ($user->role == 'instructor' && $user->instructor_id == $course->instructor_id) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|||||||
@ -12,12 +12,12 @@ use Illuminate\Support\Facades\Auth;
|
|||||||
|
|
||||||
class CoursePlayerService
|
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();
|
$user = Auth::user();
|
||||||
|
|
||||||
if ($watching_type === 'lesson') {
|
if ($watching_type === 'lesson') {
|
||||||
return SectionLesson::with([
|
$query = SectionLesson::with([
|
||||||
'resources',
|
'resources',
|
||||||
'forums' => function ($query) {
|
'forums' => function ($query) {
|
||||||
$query->with([
|
$query->with([
|
||||||
@ -27,15 +27,27 @@ class CoursePlayerService
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
])->findOrFail($lesson_id);
|
]);
|
||||||
|
|
||||||
|
if ($course_id) {
|
||||||
|
$query->where('course_id', $course_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SectionQuiz::with([
|
return $query->findOrFail($lesson_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = SectionQuiz::with([
|
||||||
'quiz_questions',
|
'quiz_questions',
|
||||||
'quiz_submissions' => function ($query) use ($user) {
|
'quiz_submissions' => function ($query) use ($user) {
|
||||||
$query->where('user_id', $user->id);
|
$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
|
function getWatchHistory(string $course_id, ?string $user_id): ?WatchHistory
|
||||||
|
|||||||
@ -289,21 +289,50 @@ class CourseSectionService extends MediaService
|
|||||||
|
|
||||||
public function initWatchHistory(string $course_id, string $watching_type, string $user_id): ?WatchHistory
|
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)
|
$history = WatchHistory::where('course_id', $course_id)
|
||||||
->where('user_id', $user_id)
|
->where('user_id', $user_id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($lesson->count() >= 0 && !$history) {
|
if ($history) {
|
||||||
$lesson = $lesson->orderBy('sort', 'asc')->first();
|
return $history;
|
||||||
|
|
||||||
$coursePlay = new CoursePlayerService();
|
|
||||||
$course = Course::where('id', $course_id)->with('sections')->first();
|
|
||||||
|
|
||||||
return $coursePlay->watchHistory($course, $lesson->id, $watching_type, $user_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user