Files
saqel/backend/scripts/publish_grade10_bundle.php

215 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
/**
* Publication manager for Grade 10 curriculum bundles and assets.
*
* Transitions lessons and their assets from `draft`/`unverified` to `published`/`approved`.
*
* Safety rules:
* - Default mode is a read-only dry run. It writes nothing unless `--apply` is present.
* - All database writes happen inside a single atomic transaction.
* - Only assets belonging to the selected curriculum lessons are updated.
*
* Usage:
* php backend/scripts/publish_grade10_bundle.php --status
* php backend/scripts/publish_grade10_bundle.php --pilot
* php backend/scripts/publish_grade10_bundle.php --pilot --apply
* php backend/scripts/publish_grade10_bundle.php --subject=math_10 --unit=unit_01 --apply
* php backend/scripts/publish_grade10_bundle.php --all --apply
* php backend/scripts/publish_grade10_bundle.php --pilot --revert --apply
*/
$projectRoot = dirname(__DIR__, 2);
require_once dirname(__DIR__) . '/app/bootstrap.php';
use App\Core\Database;
$apply = in_array('--apply', $argv, true);
$statusOnly = in_array('--status', $argv, true);
$isPilot = in_array('--pilot', $argv, true);
$isAll = in_array('--all', $argv, true);
$isRevert = in_array('--revert', $argv, true);
$subjectFilter = null;
$unitFilter = null;
foreach ($argv as $arg) {
if (str_starts_with($arg, '--subject=')) {
$subjectFilter = substr($arg, strlen('--subject='));
}
if (str_starts_with($arg, '--unit=')) {
$unitFilter = substr($arg, strlen('--unit='));
}
}
// 1. Status mode
if ($statusOnly) {
echo "=== Saqel Grade 10 Publication Status ===\n";
$lessonStats = Database::select(
"SELECT source_status, COUNT(*) as count FROM curriculum_lessons WHERE grade_key = 'grade_10' GROUP BY source_status"
);
echo "\nCurriculum Lessons (source_status):\n";
foreach ($lessonStats as $s) {
echo sprintf(" - %-15s: %d\n", $s['source_status'], $s['count']);
}
$bundleStats = Database::select(
"SELECT pb.status, COUNT(*) as count
FROM publication_bundles pb
JOIN curriculum_lessons cl ON cl.id = pb.curriculum_lesson_id
WHERE cl.grade_key = 'grade_10'
GROUP BY pb.status"
);
echo "\nPublication Bundles (status):\n";
foreach ($bundleStats as $s) {
echo sprintf(" - %-15s: %d\n", $s['status'], $s['count']);
}
$assetStats = Database::select(
"SELECT a.review_status, a.rights_status, COUNT(*) as count
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 cl.grade_key = 'grade_10'
GROUP BY a.review_status, a.rights_status"
);
echo "\nContent Assets (review_status / rights_status):\n";
foreach ($assetStats as $s) {
echo sprintf(" - %-12s / %-15s: %d\n", $s['review_status'], $s['rights_status'], $s['count']);
}
exit(0);
}
// 2. Resolve target lessons
$where = ["grade_key = 'grade_10'"];
$params = [];
if ($isPilot) {
// 12 key pilot lessons representing Math, Physics, Arabic, and Islamic
$pilotPaths = [
// Math 10: Semester 1, Unit 1 (Equations)
'grade_10/math_10/semester_1/unit_01/lesson_01.md',
'grade_10/math_10/semester_1/unit_01/lesson_02.md',
'grade_10/math_10/semester_1/unit_01/lesson_03.md',
'grade_10/math_10/semester_1/unit_01/intro_and_project.md',
// Physics 10: Semester 1, Units 1 & 2
'grade_10/physics_10/semester_1/unit_01/lesson_01.md',
'grade_10/physics_10/semester_1/unit_01/lesson_02.md',
'grade_10/physics_10/semester_1/unit_02/lesson_01.md',
'grade_10/physics_10/semester_1/unit_02/lesson_02.md',
// Arabic 10: Semester 1, Unit 1
'grade_10/arabic_10/semester_1/unit_01/lesson_01.md',
'grade_10/arabic_10/semester_1/unit_01/lesson_02.md',
// Islamic 10: Semester 1, Unit 1
'grade_10/islamic_10/semester_1/unit_01/lesson_01.md',
'grade_10/islamic_10/semester_1/unit_01/lesson_02.md',
];
$placeholders = implode(',', array_fill(0, count($pilotPaths), '?'));
$where[] = "source_manifest_path IN ($placeholders)";
$params = array_merge($params, $pilotPaths);
} elseif ($isAll) {
// All Grade 10 lessons
} else {
if ($subjectFilter !== null) {
$where[] = "subject_key = ?";
$params[] = $subjectFilter;
}
if ($unitFilter !== null) {
$where[] = "unit_key = ?";
$params[] = $unitFilter;
}
if ($subjectFilter === null && $unitFilter === null) {
fwrite(STDERR, "Error: Specify --pilot, --all, or filter with --subject=... [--unit=...]\n");
fwrite(STDERR, "Run with --help or --status to view available options.\n");
exit(1);
}
}
$sqlWhere = implode(' AND ', $where);
$targetLessons = Database::select(
"SELECT id, uuid, subject_key, semester_key, unit_key, lesson_key, title, source_manifest_path, source_status
FROM curriculum_lessons
WHERE {$sqlWhere}
ORDER BY subject_key, semester_key, unit_key, lesson_key",
$params
);
if (empty($targetLessons)) {
echo "No matching curriculum lessons found for the specified criteria.\n";
exit(0);
}
$lessonIds = array_column($targetLessons, 'id');
$idList = implode(',', array_map('intval', $lessonIds));
// 3. Plan summary
$targetAction = $isRevert ? 'REVERT TO DRAFT' : 'PUBLISH / APPROVE';
echo sprintf("=== Grade 10 Publication Plan (%s) ===\n", $apply ? 'APPLY' : 'DRY-RUN');
echo sprintf("Action: %s\n", $targetAction);
echo sprintf("Matched Lessons: %d\n", count($targetLessons));
echo "\nTarget Lessons:\n";
foreach ($targetLessons as $idx => $l) {
echo sprintf(" %2d. [%-12s] %-10s / %-10s : %s (Current: %s)\n",
$idx + 1,
$l['subject_key'],
$l['unit_key'],
$l['lesson_key'],
$l['title'],
$l['source_status']
);
}
if (!$apply) {
echo "\n[Dry-run completed. To execute, append --apply to your command.]\n";
exit(0);
}
// 4. Apply changes inside a transaction
$pdo = Database::getConnection();
$pdo->beginTransaction();
try {
if ($isRevert) {
// Revert to draft
Database::query(
"UPDATE curriculum_lessons SET source_status = 'unverified' WHERE id IN ({$idList})"
);
Database::query(
"UPDATE publication_bundles SET status = 'draft', published_at = NULL WHERE curriculum_lesson_id IN ({$idList})"
);
Database::query(
"UPDATE 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
SET a.review_status = 'draft', a.rights_status = 'review_required'
WHERE pb.curriculum_lesson_id IN ({$idList})"
);
} else {
// Publish and approve
Database::query(
"UPDATE curriculum_lessons SET source_status = 'approved', reviewed_at = CURRENT_TIMESTAMP WHERE id IN ({$idList})"
);
Database::query(
"UPDATE publication_bundles SET status = 'published', published_at = CURRENT_TIMESTAMP WHERE curriculum_lesson_id IN ({$idList})"
);
Database::query(
"UPDATE 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
SET a.review_status = 'approved', a.rights_status = 'cleared'
WHERE pb.curriculum_lesson_id IN ({$idList})"
);
}
$pdo->commit();
echo "\n=== SUCCESS: " . ($isRevert ? "Reverted" : "Published") . " " . count($targetLessons) . " lessons successfully. ===\n";
} catch (\Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
fwrite(STDERR, "\nFAILED: Publication transaction rolled back: " . $e->getMessage() . "\n");
exit(1);
}