215 lines
7.9 KiB
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);
|
|
}
|