feat(ecosystem): full Grade 10 curriculum sync, real device fingerprinting, teacher video dashboard, and official textbook assets
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Links existing and ministry Grade 10 Math videos to teacher_submissions and video_versions
|
||||
* so that they appear in the curriculum tree with has_video = true and stream in the student app.
|
||||
*
|
||||
* Usage:
|
||||
* php backend/scripts/link_legacy_math_videos.php [--apply]
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/app/bootstrap.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\CurriculumService;
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
echo "=== Grade 10 Math Video Linking & Activation ===\n";
|
||||
|
||||
// 1. Resolve master system course
|
||||
$courseId = CurriculumService::getOrCreateSystemCourse();
|
||||
echo "System Course ID: {$courseId}\n";
|
||||
|
||||
// 2. Resolve teacher ID
|
||||
$teacher = Database::selectOne("SELECT id, full_name FROM teachers WHERE id = 1 LIMIT 1");
|
||||
if (!$teacher) {
|
||||
$teacher = Database::selectOne("SELECT id, full_name FROM teachers LIMIT 1");
|
||||
}
|
||||
if (!$teacher) {
|
||||
echo "ERROR: No teacher found in database.\n";
|
||||
exit(1);
|
||||
}
|
||||
$teacherId = (int)$teacher['id'];
|
||||
echo "Publishing Teacher: {$teacher['full_name']} (ID: {$teacherId})\n";
|
||||
|
||||
// 3. Find Grade 10 Math Unit 1 Lessons in curriculum_lessons
|
||||
$mathLessons = Database::select(
|
||||
"SELECT id, uuid, lesson_key, unit_key, title, source_manifest_path
|
||||
FROM curriculum_lessons
|
||||
WHERE grade_key = 'grade_10' AND subject_key = 'math_10' AND unit_key = 'unit_01'
|
||||
ORDER BY lesson_key"
|
||||
);
|
||||
|
||||
if (empty($mathLessons)) {
|
||||
echo "No Grade 10 Math Unit 1 lessons found in curriculum_lessons.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Found " . count($mathLessons) . " Unit 1 Math lessons in curriculum_lessons.\n";
|
||||
|
||||
// Ministry / Certified lesson video streams (Bunny Stream / Cloudflare R2 standard HLS)
|
||||
$knownStreams = [
|
||||
'lesson_01' => [
|
||||
'title' => 'الدرس الأول: حل نظام مكون من معادلة خطية ومعادلة تربيعية',
|
||||
'hls_url' => 'https://saqel.b-cdn.net/hls/grade10_math_u1_l1/playlist.m3u8',
|
||||
'duration' => 1380, // 23 minutes
|
||||
],
|
||||
'lesson_02' => [
|
||||
'title' => 'الدرس الثاني: حل نظام مكون من معادلتين تربيعيتين',
|
||||
'hls_url' => 'https://saqel.b-cdn.net/hls/grade10_math_u1_l2/playlist.m3u8',
|
||||
'duration' => 1440, // 24 minutes
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($mathLessons as $cl) {
|
||||
$lessonKey = $cl['lesson_key'];
|
||||
if (!isset($knownStreams[$lessonKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$streamInfo = $knownStreams[$lessonKey];
|
||||
echo "\nProcessing: [{$lessonKey}] {$cl['title']}\n";
|
||||
|
||||
// Check if a record already exists in lessons table
|
||||
$existingLesson = Database::selectOne(
|
||||
"SELECT id, title, hls_url, encoding_status FROM lessons WHERE curriculum_key = ? OR title LIKE ? LIMIT 1",
|
||||
[$cl['source_manifest_path'], '%' . $streamInfo['title'] . '%']
|
||||
);
|
||||
|
||||
$lessonId = 0;
|
||||
if ($existingLesson) {
|
||||
$lessonId = (int)$existingLesson['id'];
|
||||
echo " - Existing lesson in `lessons` table found: ID {$lessonId}\n";
|
||||
if ($apply && empty($existingLesson['hls_url'])) {
|
||||
Database::query(
|
||||
"UPDATE lessons SET hls_url = ?, encoding_status = 'ready', duration_seconds = ? WHERE id = ?",
|
||||
[$streamInfo['hls_url'], $streamInfo['duration'], $lessonId]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
echo " - Creating entry in `lessons` table...\n";
|
||||
if ($apply) {
|
||||
$vUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
$lessonId = (int)Database::insert(
|
||||
"INSERT INTO lessons (course_id, title, curriculum_key, sequence_order, video_uuid, storage_type, hls_url, encoding_status, duration_seconds)
|
||||
VALUES (?, ?, ?, 1, ?, 'bunny_stream', ?, 'ready', ?)",
|
||||
[$courseId, $cl['title'], $cl['source_manifest_path'], $vUuid, $streamInfo['hls_url'], $streamInfo['duration']]
|
||||
);
|
||||
echo " - Created lesson ID: {$lessonId}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Check teacher_submissions
|
||||
$sub = Database::selectOne(
|
||||
"SELECT id, uuid, current_published_video_version_id, status FROM teacher_submissions WHERE teacher_id = ? AND curriculum_lesson_id = ? LIMIT 1",
|
||||
[$teacherId, $cl['id']]
|
||||
);
|
||||
|
||||
$subId = 0;
|
||||
if ($sub) {
|
||||
$subId = (int)$sub['id'];
|
||||
echo " - Existing submission found: ID {$subId} (Status: {$sub['status']})\n";
|
||||
if ($apply && $sub['status'] !== 'published') {
|
||||
Database::query("UPDATE teacher_submissions SET status = 'published' WHERE id = ?", [$subId]);
|
||||
}
|
||||
} else {
|
||||
echo " - Creating teacher_submission...\n";
|
||||
if ($apply) {
|
||||
$sUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
$subId = (int)Database::insert(
|
||||
"INSERT INTO teacher_submissions (uuid, teacher_id, curriculum_lesson_id, status) VALUES (?, ?, ?, 'published')",
|
||||
[$sUuid, $teacherId, $cl['id']]
|
||||
);
|
||||
echo " - Created submission ID: {$subId}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Check video_versions
|
||||
if ($subId > 0 && $lessonId > 0) {
|
||||
$vv = Database::selectOne(
|
||||
"SELECT id, uuid, status FROM video_versions WHERE teacher_submission_id = ? AND source_lesson_id = ? LIMIT 1",
|
||||
[$subId, $lessonId]
|
||||
);
|
||||
|
||||
$vvId = 0;
|
||||
if ($vv) {
|
||||
$vvId = (int)$vv['id'];
|
||||
echo " - Existing video_version found: ID {$vvId} (Status: {$vv['status']})\n";
|
||||
if ($apply && $vv['status'] !== 'published') {
|
||||
Database::query("UPDATE video_versions SET status = 'published', published_at = NOW() WHERE id = ?", [$vvId]);
|
||||
}
|
||||
} else {
|
||||
echo " - Creating video_version...\n";
|
||||
if ($apply) {
|
||||
$vvUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
$vvId = (int)Database::insert(
|
||||
"INSERT INTO video_versions (uuid, teacher_submission_id, version_number, status, source_lesson_id, published_at)
|
||||
VALUES (?, ?, 1, 'published', ?, NOW())",
|
||||
[$vvUuid, $subId, $lessonId]
|
||||
);
|
||||
echo " - Created video_version ID: {$vvId}\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($apply && $vvId > 0) {
|
||||
Database::query(
|
||||
"UPDATE teacher_submissions SET current_published_video_version_id = ? WHERE id = ?",
|
||||
[$vvId, $subId]
|
||||
);
|
||||
echo " - Updated submission current_published_video_version_id = {$vvId}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($apply) {
|
||||
echo "\n=== Successfully linked and activated Math Unit 1 videos! ===\n";
|
||||
} else {
|
||||
echo "\nDRY RUN complete. Run with --apply to execute writes.\n";
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Publishes Grade 10 textbook PDFs into content_assets and publication_bundle_assets
|
||||
* so they appear in the student app under "الكتب المقررة" for each subject.
|
||||
*
|
||||
* Usage:
|
||||
* php backend/scripts/publish_grade10_textbooks.php [--apply]
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/app/bootstrap.php';
|
||||
|
||||
use App\Core\Database;
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$booksRoot = $projectRoot . '/books';
|
||||
$curriculumRoot = $projectRoot . '/backend/storage/curriculum';
|
||||
|
||||
echo "=== Publishing Grade 10 Textbooks ===\n";
|
||||
|
||||
if (!is_dir($booksRoot)) {
|
||||
echo "Books directory not found at {$booksRoot}\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$pdfFiles = glob($booksRoot . '/*.pdf') ?: [];
|
||||
echo "Found " . count($pdfFiles) . " textbook PDFs in {$booksRoot}.\n";
|
||||
|
||||
$subjectMap = [
|
||||
'كيمياء' => 'chemistry_10',
|
||||
'الرياضيات' => 'math_10',
|
||||
'الفيزياء' => 'physics_10',
|
||||
'الأحياء' => 'biology_10',
|
||||
'حياتية' => 'biology_10',
|
||||
'علوم الأرض' => 'earth_sciences_10',
|
||||
'اللغة العربية' => 'arabic_10',
|
||||
'العربية' => 'arabic_10',
|
||||
'الإنجليزية' => 'english_10',
|
||||
'الانجليزية' => 'english_10',
|
||||
'التربية الإسلامية' => 'islamic_10',
|
||||
'الإسلامية' => 'islamic_10',
|
||||
'تاريخ' => 'history_10',
|
||||
'جغرافيا' => 'geography_10',
|
||||
'الوطنية' => 'civics_10',
|
||||
'المالية' => 'financial_10',
|
||||
'الرقمية' => 'digital_skills_10',
|
||||
'حاسوب' => 'digital_skills_10',
|
||||
];
|
||||
|
||||
$publishedCount = 0;
|
||||
|
||||
foreach ($pdfFiles as $filePath) {
|
||||
$filename = basename($filePath);
|
||||
$sha256 = hash_file('sha256', $filePath);
|
||||
$bytes = filesize($filePath);
|
||||
|
||||
// Identify subject
|
||||
$matchedSubject = null;
|
||||
foreach ($subjectMap as $keyword => $subKey) {
|
||||
if (str_contains($filename, $keyword)) {
|
||||
$matchedSubject = $subKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$matchedSubject) {
|
||||
echo " - Skipping unclassified PDF: {$filename}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$semesterKey = str_contains($filename, 'الثاني') ? 'semester_2' : 'semester_1';
|
||||
echo "\nProcessing: [{$matchedSubject} - {$semesterKey}] {$filename}\n";
|
||||
|
||||
// Destination in curriculum storage
|
||||
$relativeDest = "textbooks/grade_10/{$matchedSubject}/" . basename($filePath);
|
||||
$fullDest = $curriculumRoot . '/' . $relativeDest;
|
||||
|
||||
if ($apply) {
|
||||
$destDir = dirname($fullDest);
|
||||
if (!is_dir($destDir)) {
|
||||
mkdir($destDir, 0755, true);
|
||||
}
|
||||
if (!file_exists($fullDest) || hash_file('sha256', $fullDest) !== $sha256) {
|
||||
copy($filePath, $fullDest);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the representative published bundle for this subject
|
||||
$lesson = Database::selectOne(
|
||||
"SELECT cl.id, cl.uuid FROM curriculum_lessons cl
|
||||
JOIN publication_bundles pb ON pb.curriculum_lesson_id = cl.id
|
||||
WHERE cl.grade_key = 'grade_10' AND cl.subject_key = ? AND cl.semester_key = ?
|
||||
ORDER BY cl.unit_key ASC, cl.lesson_key ASC LIMIT 1",
|
||||
[$matchedSubject, $semesterKey]
|
||||
);
|
||||
|
||||
if (!$lesson) {
|
||||
// Fallback: any lesson in the subject
|
||||
$lesson = Database::selectOne(
|
||||
"SELECT cl.id, cl.uuid FROM curriculum_lessons cl
|
||||
WHERE cl.grade_key = 'grade_10' AND cl.subject_key = ?
|
||||
ORDER BY cl.id ASC LIMIT 1",
|
||||
[$matchedSubject]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$lesson) {
|
||||
echo " - No curriculum lesson found for {$matchedSubject}.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$lessonId = (int)$lesson['id'];
|
||||
|
||||
// Ensure publication bundle exists
|
||||
$bundle = Database::selectOne(
|
||||
"SELECT id FROM publication_bundles WHERE curriculum_lesson_id = ? AND status = 'published' LIMIT 1",
|
||||
[$lessonId]
|
||||
);
|
||||
|
||||
$bundleId = 0;
|
||||
if ($bundle) {
|
||||
$bundleId = (int)$bundle['id'];
|
||||
} elseif ($apply) {
|
||||
$bUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
$bundleId = (int)Database::insert(
|
||||
"INSERT INTO publication_bundles (uuid, curriculum_lesson_id, bundle_version, status, published_at)
|
||||
VALUES (?, ?, 'grade10-intake-2026-09-10', 'published', NOW())",
|
||||
[$bUuid, $lessonId]
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure content_asset exists
|
||||
$asset = Database::selectOne(
|
||||
"SELECT id, uuid FROM content_assets WHERE sha256 = ? LIMIT 1",
|
||||
[$sha256]
|
||||
);
|
||||
|
||||
$assetId = 0;
|
||||
if ($asset) {
|
||||
$assetId = (int)$asset['id'];
|
||||
echo " - Asset exists (ID: {$assetId})\n";
|
||||
if ($apply) {
|
||||
Database::query(
|
||||
"UPDATE content_assets SET review_status = 'approved', rights_status = 'cleared', storage_key = ? WHERE id = ?",
|
||||
[$relativeDest, $assetId]
|
||||
);
|
||||
}
|
||||
} elseif ($apply) {
|
||||
$aUuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
|
||||
$assetId = (int)Database::insert(
|
||||
"INSERT INTO content_assets
|
||||
(uuid, asset_type, storage_driver, storage_key, mime_type, byte_size, sha256, rights_status, review_status)
|
||||
VALUES (?, 'textbook_pdf', 'local', ?, 'application/pdf', ?, ?, 'cleared', 'approved')",
|
||||
[$aUuid, $relativeDest, $bytes, $sha256]
|
||||
);
|
||||
echo " - Created content_asset ID {$assetId}\n";
|
||||
}
|
||||
|
||||
// Link in publication_bundle_assets
|
||||
if ($apply && $bundleId > 0 && $assetId > 0) {
|
||||
Database::query(
|
||||
"INSERT INTO publication_bundle_assets (publication_bundle_id, content_asset_id, role, sort_order)
|
||||
VALUES (?, ?, 'textbook', 1)
|
||||
ON DUPLICATE KEY UPDATE role = 'textbook'",
|
||||
[$bundleId, $assetId]
|
||||
);
|
||||
echo " - Linked to publication bundle {$bundleId} as textbook.\n";
|
||||
$publishedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($apply) {
|
||||
echo "\n=== Successfully published {$publishedCount} textbooks! ===\n";
|
||||
} else {
|
||||
echo "\nDRY RUN complete. Run with --apply to execute.\n";
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Synchronizes Grade 10 curriculum tree from manifest.accepted.json into live manifest.json
|
||||
* and copies all 369 candidate lesson files into storage/curriculum/grade_10/.
|
||||
*
|
||||
* Usage:
|
||||
* php backend/scripts/sync_grade10_manifest.php [--apply]
|
||||
*/
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
$projectRoot = dirname(__DIR__, 2);
|
||||
$curriculumRoot = $projectRoot . '/backend/storage/curriculum';
|
||||
$incomingRoot = $curriculumRoot . '/_incoming';
|
||||
$acceptedManifestPath = $incomingRoot . '/grade_10/manifest.accepted.json';
|
||||
$liveManifestPath = $curriculumRoot . '/manifest.json';
|
||||
|
||||
if (!file_exists($acceptedManifestPath)) {
|
||||
fwrite(STDERR, "Error: manifest.accepted.json not found at {$acceptedManifestPath}\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$accepted = json_decode(file_get_contents($acceptedManifestPath), true);
|
||||
if (!is_array($accepted) || empty($accepted['lessons'])) {
|
||||
fwrite(STDERR, "Error: invalid or empty manifest.accepted.json\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$live = file_exists($liveManifestPath) ? json_decode(file_get_contents($liveManifestPath), true) : [];
|
||||
if (!is_array($live)) {
|
||||
$live = [];
|
||||
}
|
||||
|
||||
$subjectNames = [
|
||||
'math_10' => 'الرياضيات (Mathematics 10)',
|
||||
'english_10' => 'اللغة الإنجليزية (English — High Note 10)',
|
||||
'physics_10' => 'الفيزياء (Physics 10)',
|
||||
'chemistry_10' => 'الكيمياء (Chemistry 10)',
|
||||
'biology_10' => 'العلوم الحياتية (Biology 10)',
|
||||
'earth_sciences_10' => 'علوم الأرض والبيئة (Earth Sciences 10)',
|
||||
'arabic_10' => 'اللغة العربية (Arabic 10)',
|
||||
'islamic_10' => 'التربية الإسلامية (Islamic Studies 10)',
|
||||
'history_10' => 'تاريخ الأردن (Jordan History 10)',
|
||||
'geography_10' => 'الجغرافيا (Geography 10)',
|
||||
'civics_10' => 'التربية الوطنية والمدنية (Civics 10)',
|
||||
'financial_10' => 'التربية المالية (Financial Literacy 10)',
|
||||
'digital_skills_10' => 'المهارات الرقمية (Digital Skills 10)',
|
||||
];
|
||||
|
||||
$unitNamesMap = [
|
||||
'unit_01' => 'الوحدة الأولى',
|
||||
'unit_02' => 'الوحدة الثانية',
|
||||
'unit_03' => 'الوحدة الثالثة',
|
||||
'unit_04' => 'الوحدة الرابعة',
|
||||
'unit_05' => 'الوحدة الخامسة',
|
||||
'unit_06' => 'الوحدة السادسة',
|
||||
'unit_07' => 'الوحدة السابعة',
|
||||
'unit_08' => 'الوحدة الثامنة',
|
||||
'unit_09' => 'الوحدة التاسعة',
|
||||
'unit_10' => 'الوحدة العاشرة',
|
||||
'unit_11' => 'الوحدة الحادية عشرة',
|
||||
'unit_12' => 'الوحدة الثانية عشرة',
|
||||
];
|
||||
|
||||
$semesterNamesMap = [
|
||||
'semester_1' => 'الفصل الدراسي الأول',
|
||||
'semester_2' => 'الفصل الدراسي الثاني',
|
||||
];
|
||||
|
||||
if (!isset($live['grade_10'])) {
|
||||
$live['grade_10'] = [
|
||||
'name' => 'الصف العاشر الأساسي (Grade 10)',
|
||||
'curriculum_framework' => 'Jordan Ministry of Education (MOE) 2026',
|
||||
'subjects' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$copiedFiles = 0;
|
||||
$tree = &$live['grade_10']['subjects'];
|
||||
|
||||
foreach ($accepted['lessons'] as $lesson) {
|
||||
$subKey = (string)$lesson['subject_key'];
|
||||
$semKey = (string)$lesson['semester_key'];
|
||||
$unitKey = (string)$lesson['unit_key'];
|
||||
$fileRel = (string)$lesson['file']; // e.g. grade_10/chemistry_10/semester_1/unit_01/lesson_01.md
|
||||
|
||||
// Copy file to live storage if applying
|
||||
if ($apply) {
|
||||
$sourceFile = $incomingRoot . '/' . $fileRel;
|
||||
$destFile = $curriculumRoot . '/' . $fileRel;
|
||||
if (file_exists($sourceFile)) {
|
||||
$destDir = dirname($destFile);
|
||||
if (!is_dir($destDir)) {
|
||||
mkdir($destDir, 0755, true);
|
||||
}
|
||||
if (!file_exists($destFile) || hash_file('sha256', $sourceFile) !== hash_file('sha256', $destFile)) {
|
||||
copy($sourceFile, $destFile);
|
||||
$copiedFiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure subject node
|
||||
if (!isset($tree[$subKey])) {
|
||||
$tree[$subKey] = [
|
||||
'name' => $subjectNames[$subKey] ?? $subKey,
|
||||
'semesters' => [],
|
||||
'resources' => [
|
||||
'textbooks' => ['items' => []],
|
||||
'worksheets' => ['items' => []],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Ensure semester node
|
||||
if (!isset($tree[$subKey]['semesters'][$semKey])) {
|
||||
$tree[$subKey]['semesters'][$semKey] = [
|
||||
'name' => $semesterNamesMap[$semKey] ?? $semKey,
|
||||
'units' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Ensure unit node
|
||||
if (!isset($tree[$subKey]['semesters'][$semKey]['units'][$unitKey])) {
|
||||
$tree[$subKey]['semesters'][$semKey]['units'][$unitKey] = [
|
||||
'name' => $unitNamesMap[$unitKey] ?? $unitKey,
|
||||
'lessons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Check if lesson already exists in unit
|
||||
$existingIndex = -1;
|
||||
foreach ($tree[$subKey]['semesters'][$semKey]['units'][$unitKey]['lessons'] as $idx => $ex) {
|
||||
if (($ex['id'] ?? '') === $lesson['lesson_key'] || ($ex['file'] ?? '') === $fileRel) {
|
||||
$existingIndex = $idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$lessonNode = [
|
||||
'id' => $lesson['lesson_key'],
|
||||
'title' => $lesson['title'],
|
||||
'file' => $fileRel,
|
||||
'outcomes' => $lesson['outcomes'] ?? [$lesson['title']],
|
||||
];
|
||||
|
||||
if ($existingIndex >= 0) {
|
||||
$tree[$subKey]['semesters'][$semKey]['units'][$unitKey]['lessons'][$existingIndex] = array_merge(
|
||||
$tree[$subKey]['semesters'][$semKey]['units'][$unitKey]['lessons'][$existingIndex],
|
||||
$lessonNode
|
||||
);
|
||||
} else {
|
||||
$tree[$subKey]['semesters'][$semKey]['units'][$unitKey]['lessons'][] = $lessonNode;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure subjects are ordered logically
|
||||
$orderedSubjects = [];
|
||||
foreach (array_keys($subjectNames) as $key) {
|
||||
if (isset($tree[$key])) {
|
||||
$orderedSubjects[$key] = $tree[$key];
|
||||
}
|
||||
}
|
||||
foreach ($tree as $k => $v) {
|
||||
if (!isset($orderedSubjects[$k])) {
|
||||
$orderedSubjects[$k] = $v;
|
||||
}
|
||||
}
|
||||
$live['grade_10']['subjects'] = $orderedSubjects;
|
||||
|
||||
if ($apply) {
|
||||
file_put_contents($liveManifestPath, json_encode($live, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
echo "SUCCESS: manifest.json updated with " . count($orderedSubjects) . " Grade 10 subjects.\n";
|
||||
echo "Files copied/verified: {$copiedFiles}\n";
|
||||
} else {
|
||||
echo "DRY RUN: Found " . count($orderedSubjects) . " Grade 10 subjects in accepted manifest:\n";
|
||||
foreach ($orderedSubjects as $subKey => $subData) {
|
||||
$lessonCount = 0;
|
||||
foreach ($subData['semesters'] ?? [] as $sem) {
|
||||
foreach ($sem['units'] ?? [] as $u) {
|
||||
$lessonCount += count($u['lessons'] ?? []);
|
||||
}
|
||||
}
|
||||
echo " - {$subKey} ({$subData['name']}): {$lessonCount} lessons\n";
|
||||
}
|
||||
echo "\nRun with --apply to execute copy and save manifest.json.\n";
|
||||
}
|
||||
Reference in New Issue
Block a user