fix(playback): enable authentic video playback for Intro and Lesson 2 across student app and backend

This commit is contained in:
Hamza-Ayed
2026-09-09 01:23:51 +03:00
parent 0a2ca71cb4
commit a8e895d5da
7 changed files with 179 additions and 60 deletions
+85 -19
View File
@@ -77,17 +77,21 @@ class VideoController
}
} else {
$subjectName = trim((string)($request->getBody()['subject'] ?? $_POST['subject'] ?? ''));
$gradeLevel = trim((string)($request->getBody()['grade_level'] ?? $_POST['grade_level'] ?? ''));
$rawGrade = trim((string)($request->getBody()['grade_level'] ?? $_POST['grade_level'] ?? ''));
$gradeLevel = !empty($rawGrade) ? \App\Services\StudentAccessControlService::normalizeGrade($rawGrade) : 'grade_10';
// The production schema uses subjects.name (not name_ar/name_en).
// Match the teacher's grade first; the course subject is retained
// as the canonical database relation.
$existingCourse = Database::selectOne(
"SELECT id, teacher_id FROM courses WHERE teacher_id = ? AND grade_level = ? LIMIT 1",
[$request->user_id, $gradeLevel]
"SELECT id, teacher_id, grade_level FROM courses WHERE teacher_id = ? AND (grade_level = ? OR grade_level = ?) LIMIT 1",
[$request->user_id, $gradeLevel, $rawGrade]
);
if ($existingCourse) {
$courseId = (int)$existingCourse['id'];
$course = $existingCourse;
if (!empty($gradeLevel) && ($existingCourse['grade_level'] ?? '') !== $gradeLevel) {
Database::query("UPDATE courses SET grade_level = ? WHERE id = ?", [$gradeLevel, $courseId]);
}
} else {
$subject = Database::selectOne(
"SELECT id FROM subjects WHERE name = ? ORDER BY id LIMIT 1",
@@ -521,35 +525,97 @@ class VideoController
$curriculumKey = trim((string)($request->getQuery('curriculum_key') ?? ''));
$curriculumKeyNoExt = preg_replace('/\.md$/i', '', $curriculumKey);
$title = trim((string)($request->getQuery('title') ?? ''));
$rawId = $curriculumKey !== '' ? $curriculumKey : ($request->getParam('id') ?? '');
$lesson = null;
$candidates = [];
// 1. Match by curriculum_key if provided
if ($curriculumKey !== '') {
$lesson = Database::selectOne(
"SELECT * FROM lessons WHERE curriculum_key = ? OR curriculum_key = ? OR local_path LIKE ? OR markdown_content LIKE ? LIMIT 1",
[$curriculumKey, $curriculumKeyNoExt, '%' . $curriculumKeyNoExt . '%', '%' . $curriculumKeyNoExt . '%']
$baseKey = basename($curriculumKeyNoExt);
$matched = Database::select(
"SELECT * FROM lessons
WHERE curriculum_key = ? OR curriculum_key = ?
OR curriculum_key LIKE ? OR local_path LIKE ? OR markdown_content LIKE ?
ORDER BY (hls_url IS NOT NULL AND hls_url != '') DESC, id DESC LIMIT 5",
[$curriculumKey, $curriculumKeyNoExt, '%' . $baseKey . '%', '%' . $curriculumKeyNoExt . '%', '%' . $curriculumKeyNoExt . '%']
);
} elseif (is_numeric($rawId) && (int)$rawId > 0) {
$lesson = Database::selectOne("SELECT * FROM lessons WHERE id = ? LIMIT 1", [(int)$rawId]);
} elseif (!empty($rawId)) {
if (!empty($matched)) {
$candidates = array_merge($candidates, $matched);
}
}
// 2. Match by title if provided
if (!empty($title)) {
$cleanTitle = trim(preg_replace('/^(الدرس\s*\d+:\s*|معملُ\s*[^:]+:\s*|مقدمة\s*[^:]+:\s*)/u', '', $title));
$matchedTitle = Database::select(
"SELECT * FROM lessons
WHERE title = ? OR title LIKE ? OR title LIKE ?
ORDER BY (hls_url IS NOT NULL AND hls_url != '') DESC, id DESC LIMIT 5",
[$title, "%{$title}%", "%{$cleanTitle}%"]
);
if (!empty($matchedTitle)) {
$candidates = array_merge($candidates, $matchedTitle);
}
}
// 3. Match by numeric ID
if (is_numeric($rawId) && (int)$rawId > 0) {
$matchedId = Database::select("SELECT * FROM lessons WHERE id = ? LIMIT 1", [(int)$rawId]);
if (!empty($matchedId)) {
$candidates = array_merge($candidates, $matchedId);
}
}
// 4. Manifest slug lookup
if (empty($candidates) && !empty($rawId)) {
$rawIdNoExt = preg_replace('/\.md$/i', '', $rawId);
$lesson = Database::selectOne("SELECT * FROM lessons WHERE curriculum_key = ? OR curriculum_key = ? OR title LIKE ? OR local_path LIKE ? OR markdown_content LIKE ? LIMIT 1", [$rawId, $rawIdNoExt, "%{$rawId}%", "%{$rawIdNoExt}%", "%{$rawIdNoExt}%"]);
// Flutter curriculum lessons use the manifest slug (e.g. u1_l1_*),
// while the database stores the canonical lesson title.
if (!$lesson) {
$matchedRaw = Database::select(
"SELECT * FROM lessons
WHERE curriculum_key = ? OR curriculum_key = ? OR title LIKE ? OR local_path LIKE ? OR markdown_content LIKE ?
ORDER BY (hls_url IS NOT NULL AND hls_url != '') DESC, id DESC LIMIT 5",
[$rawId, $rawIdNoExt, "%{$rawId}%", "%{$rawIdNoExt}%", "%{$rawIdNoExt}%"]
);
if (!empty($matchedRaw)) {
$candidates = array_merge($candidates, $matchedRaw);
}
if (empty($candidates)) {
$manifestLesson = CurriculumService::findLessonById($rawId);
if ($manifestLesson && !empty($manifestLesson['title'])) {
$lesson = Database::selectOne(
"SELECT * FROM lessons WHERE title = ? OR markdown_content LIKE ? LIMIT 1",
[$manifestLesson['title'], '%' . ($manifestLesson['file'] ?? '') . '%']
$matchedManifest = Database::select(
"SELECT * FROM lessons
WHERE title = ? OR title LIKE ? OR markdown_content LIKE ?
ORDER BY (hls_url IS NOT NULL AND hls_url != '') DESC, id DESC LIMIT 5",
[$manifestLesson['title'], '%' . $manifestLesson['title'] . '%', '%' . ($manifestLesson['file'] ?? '') . '%']
);
if (!empty($matchedManifest)) {
$candidates = array_merge($candidates, $matchedManifest);
}
}
}
}
// Pick best candidate: prioritize one with valid hls_url
if (!empty($candidates)) {
foreach ($candidates as $cand) {
if (!empty($cand['hls_url'])) {
$lesson = $cand;
break;
}
}
if (!$lesson) {
$lesson = $candidates[0];
}
}
if (!$lesson) {
// Fallback to latest available lesson in DB
$lesson = Database::selectOne("SELECT * FROM lessons ORDER BY id DESC LIMIT 1");
// Fallback to latest available lesson in DB with a video
$lesson = Database::selectOne(
"SELECT * FROM lessons
WHERE hls_url IS NOT NULL AND hls_url != ''
ORDER BY id DESC LIMIT 1"
);
}
if (!$lesson) {
@@ -564,7 +630,7 @@ class VideoController
// Strict Academic Access Control & Institutional Free / CliQ Paid Validation
$course = Database::selectOne("SELECT * FROM courses WHERE id = ? LIMIT 1", [$lesson['course_id']]);
$targetGrade = $course['grade_level'] ?? 'grade_10';
$targetGrade = \App\Services\StudentAccessControlService::normalizeGrade($course['grade_level'] ?? 'grade_10');
$studentId = $request->user_id ? (int)$request->user_id : null;
$nationalId = $request->getHeader('x-national-id') ?: ($request->getQuery('national_id') ?? null);