lms/app/Http/Controllers/Course/PlayerController.php
Ahmed Darrazi 1b8be73219
All checks were successful
Build & Push Docker Image / docker (push) Successful in 1m48s
bugfix
2025-12-19 15:37:20 +01:00

309 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Course;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Course\WatchHistory;
use App\Services\Course\CoursePlayerService;
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,
protected CourseSectionService $sectionService,
protected CourseReviewService $reviewService,
protected ZoomLiveService $zoomLiveService,
// protected AssignmentSubmissionService $submissionService,
) {}
public function index(Request $request)
{
$courses = $this->courseService->getCourses($request->all(), null, true);
return Inertia::render('courses/index', compact('courses'));
}
public function intWatchHistory(Request $request)
{
$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,
'lesson_id' => $watchHistory->current_watching_id,
]);
}
public function course_player(Request $request, string $type, WatchHistory $watch_history, string $lesson_id)
{
try {
$user = Auth::user();
$watching_id = $lesson_id ?: $watch_history->current_watching_id;
$watching_type = in_array($type, ['lesson', 'quiz'], true) ? $type : ($watch_history->current_watching_type ?? 'lesson');
$course = $this->courseService->getUserCourseById($watch_history->course_id, $user);
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 doesnt 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);
$zoomConfig = $this->zoomLiveService->zoomConfig;
$totalContent = 0;
foreach ($course->sections as $courseSection) {
$totalContent += count($courseSection->section_lessons) + count($courseSection->section_quizzes);
}
$watchHistory = $this->coursePlay->watchHistory($course, $watching_id, $watching_type, $user->id);
$section = $course->sections->firstWhere('id', $watchHistory->current_section_id);
// $submissions = null;
// if ($assignment) {
// $submissions = $this->submissionService->getSubmissions($assignment, $request->all());
// }
return Inertia::render('course-player/index', [
'type' => $watching_type,
'course' => $course,
'section' => $section,
'reviews' => $reviews,
'watching' => $watching,
'totalContent' => $totalContent,
'watchHistory' => $watchHistory,
'userReview' => $userReview,
'totalReviews' => $totalReviews,
'zoomConfig' => $zoomConfig,
]);
} catch (ModelNotFoundException $th) {
$user = Auth::user();
$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;
// 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', 'Der angeforderte Inhalt konnte nicht gefunden werden.');
} catch (\Throwable $th) {
return redirect()->route('category.courses', ['category' => 'all'])->with('error', $th->getMessage());
}
}
public function finish_course(WatchHistory $watch_history)
{
$completedItems = json_decode($watch_history->completed_watching, true) ?: [];
$lastItem = [
'id' => $watch_history->current_watching_id,
'type' => $watch_history->current_watching_type,
];
// Check if lastItem already exists in completedItems
$itemExists = false;
foreach ($completedItems as $item) {
if ((string)$item['id'] === (string)$lastItem['id'] && $item['type'] === $lastItem['type']) {
$itemExists = true;
break;
}
}
// Add lastItem to completedItems if it doesn't exist
if (!$itemExists) {
$completedItems[] = $lastItem;
}
// Clean up duplicates and ensure consistent data types
$completedItems = $this->cleanupCompletedItems($completedItems);
$watch_history->completed_watching = json_encode($completedItems);
$watch_history->completion_date = now();
$watch_history->save();
return back()->with('success', 'Kurs erfolgreich abgeschlossen.');
}
/**
* Clean up completed items to remove duplicates and ensure consistent data types
*/
private function cleanupCompletedItems(array $completedItems): array
{
$cleaned = [];
$seen = [];
foreach ($completedItems as $item) {
// Ensure consistent data types (string for ID)
$normalizedItem = [
'id' => (string)$item['id'],
'type' => $item['type']
];
// Create unique key for duplicate checking
$key = $normalizedItem['id'] . '|' . $normalizedItem['type'];
// Only add if not already seen
if (!isset($seen[$key])) {
$seen[$key] = true;
$cleaned[] = $normalizedItem;
}
}
return $cleaned;
}
}