Files
saqel/backend/scripts/publish_grade10_textbooks.php

180 lines
6.2 KiB
PHP

<?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";
}