Files
coursehub/api/library.php
T
Harwinder Sekhon deda7c3c18 Add quiz support: parse *_quiz_solved.txt files, take and grade quizzes
Scanner indexes quiz files found next to videos ([&] questions, "- "
options, "-- Correct" markers; _notsolved twins ignored). Quizzes appear
in the folder content list interleaved with videos by filename order.
New api/quiz.php serves questions without answers and grades attempts
server-side; per-user best/last score and attempts stored in
quiz_progress. Folder/course progress now aggregates videos + quizzes
with partial credit for partial scores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:24:37 -04:00

136 lines
4.9 KiB
PHP

<?php
// GET api/library.php?view=home -> courses + continue-watching
// GET api/library.php?view=folder&id=N -> folder contents + progress
// GET api/library.php?view=video&id=N -> video + playlist + resume info
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
header('Content-Type: application/json');
$userId = requireLoginApi();
function folderSummary(array $f, int $userId): array
{
return [
'id' => (int)$f['id'],
'name' => $f['name'],
'progress' => folderProgress($userId, $f['path']),
];
}
function videoSummary(array $v): array
{
$duration = $v['duration_seconds'] ?? $v['watched_duration'] ?? null;
$position = (float)($v['position_seconds'] ?? 0);
return [
'id' => (int)$v['id'],
'name' => $v['name'],
'filename' => $v['filename'],
'duration' => $duration !== null ? (float)$duration : null,
'position' => $position,
'completed' => (bool)($v['completed'] ?? false),
'percent' => ($duration ?? 0) > 0 ? min(100, (int)round(100 * $position / $duration)) : 0,
];
}
function quizSummary(array $q): array
{
return [
'id' => (int)$q['id'],
'name' => $q['name'],
'filename' => $q['filename'],
'questions' => (int)$q['num_questions'],
'attempts' => (int)($q['attempts'] ?? 0),
'best_score' => (int)($q['best_score'] ?? 0),
'completed' => (bool)($q['completed'] ?? false),
];
}
function crumbList(array $folder): array
{
return array_map(
fn($c) => ['id' => (int)$c['id'], 'name' => $c['name']],
folderBreadcrumbs($folder)
);
}
$view = $_GET['view'] ?? 'home';
switch ($view) {
case 'home':
echo json_encode([
'courses' => array_map(fn($c) => folderSummary($c, $userId), childFolders(null)),
'continue' => array_map(fn($r) => [
'id' => (int)$r['id'],
'name' => $r['name'],
'folder_path' => $r['folder_path'],
'position' => (float)$r['position_seconds'],
'duration' => $r['duration_seconds'] !== null ? (float)$r['duration_seconds'] : null,
], continueWatching($userId)),
]);
break;
case 'folder':
$folder = getFolder((int)($_GET['id'] ?? 0));
if (!$folder) {
http_response_code(404);
echo json_encode(['error' => 'Folder not found']);
break;
}
echo json_encode([
'folder' => ['id' => (int)$folder['id'], 'name' => $folder['name']],
'breadcrumbs' => crumbList($folder),
'progress' => folderProgress($userId, $folder['path']),
'subfolders' => array_map(fn($f) => folderSummary($f, $userId),
childFolders((int)$folder['id'])),
'videos' => array_map('videoSummary',
folderVideos((int)$folder['id'], $userId)),
'quizzes' => array_map('quizSummary',
folderQuizzes((int)$folder['id'], $userId)),
]);
break;
case 'video':
$video = getVideo((int)($_GET['id'] ?? 0));
if (!$video) {
http_response_code(404);
echo json_encode(['error' => 'Video not found']);
break;
}
$folder = getFolder((int)$video['folder_id']);
$playlist = folderVideos((int)$folder['id'], $userId);
$stmt = db()->prepare(
'SELECT position_seconds, completed FROM video_progress
WHERE user_id = ? AND video_id = ?'
);
$stmt->execute([$userId, (int)$video['id']]);
$saved = $stmt->fetch() ?: null;
$resumeAt = ($saved && !$saved['completed'] && $saved['position_seconds'] > 5)
? (float)$saved['position_seconds'] : 0;
$prevId = $nextId = null;
foreach ($playlist as $i => $p) {
if ((int)$p['id'] === (int)$video['id']) {
$prevId = isset($playlist[$i - 1]) ? (int)$playlist[$i - 1]['id'] : null;
$nextId = isset($playlist[$i + 1]) ? (int)$playlist[$i + 1]['id'] : null;
break;
}
}
echo json_encode([
'video' => ['id' => (int)$video['id'], 'name' => $video['name']],
'folder' => ['id' => (int)$folder['id'], 'name' => $folder['name']],
'breadcrumbs' => crumbList($folder),
'playlist' => array_map('videoSummary', $playlist),
'resume_at' => $resumeAt,
'prev_id' => $prevId,
'next_id' => $nextId,
'stream_url' => 'stream.php?id=' . (int)$video['id'],
]);
break;
default:
http_response_code(400);
echo json_encode(['error' => 'Unknown view']);
}