Files
saqel/backend/app/Services/PublishedContentService.php
T

75 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
use App\Core\Database;
/** Resolves only approved assets belonging to a currently published bundle. */
final class PublishedContentService
{
private const STORAGE_ROOT = __DIR__ . '/../../storage/curriculum';
public static function findPublishedAsset(string $assetUuid): ?array
{
if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $assetUuid)) {
return null;
}
return Database::selectOne(
"SELECT a.uuid, a.asset_type, a.storage_driver, a.storage_key, a.mime_type,
a.byte_size, a.sha256, cl.uuid AS curriculum_lesson_uuid,
cl.grade_key, cl.subject_key, cl.semester_key, cl.unit_key,
cl.lesson_key, pb.bundle_version
FROM content_assets a
JOIN publication_bundle_assets pba ON pba.content_asset_id = a.id
JOIN publication_bundles pb ON pb.id = pba.publication_bundle_id
JOIN curriculum_lessons cl ON cl.id = pb.curriculum_lesson_id
WHERE a.uuid = ?
AND a.review_status = 'approved'
AND a.rights_status = 'cleared'
AND pb.status = 'published'
AND cl.source_status = 'approved'
ORDER BY pb.published_at DESC, pb.id DESC
LIMIT 1",
[$assetUuid]
);
}
public static function readLocalAsset(array $asset): ?string
{
if (($asset['storage_driver'] ?? '') !== 'local') {
return null;
}
$root = realpath(self::STORAGE_ROOT);
if (!$root) {
return null;
}
$storageKey = ltrim((string)($asset['storage_key'] ?? ''), '/');
if ($storageKey === '') {
return null;
}
// Published storage keys must resolve to their exact file inside the
// curriculum root. Do not fall back to another similarly named file.
$real = realpath($root . '/' . $storageKey);
$prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
return $real !== false && str_starts_with($real, $prefix) && is_file($real) ? $real : null;
}
public static function studentMayRead(int $studentId, array $asset): bool
{
if ($studentId <= 0 || empty($asset['grade_key'])) return false;
$student = Database::selectOne(
"SELECT s.grade_level FROM students s
JOIN auth_identities ai ON ai.id=s.identity_id AND ai.status='active'
WHERE s.id=? LIMIT 1",
[$studentId]
);
if (!$student || empty($student['grade_level'])) return false;
return StudentAccessControlService::normalizeGrade((string)$student['grade_level'])
=== StudentAccessControlService::normalizeGrade((string)$asset['grade_key']);
}
}