d9bd5e0815
ops/provision-certs.php (idempotent): lock each customcert behind AND-completion of the course 4 quizzes, emailstudents=1, SMTP config from env (noreplyaddress = iCloud-owned From, 550 trap documented). Host cron */2 on Tower runs admin/cli/cron.php; proof: gate-check (no premature issue) + override-issue-revert cycle on freertos/userid 3.
106 lines
4.4 KiB
PHP
106 lines
4.4 KiB
PHP
<?php
|
|
// Idempotent provisioning of certificate issuing (SP4 Task 1).
|
|
// For every real course (id > 1):
|
|
// - locks the customcert activity behind AND-completion of the course's
|
|
// 4 quizzes (course_modules.availability), closing the "any enrolled
|
|
// user can download the certificate" hole;
|
|
// - sets customcert.emailstudents = 1 so the issue task emails the PDF.
|
|
// Then configures Moodle SMTP from env (secrets never live in this repo).
|
|
//
|
|
// ⚠️ SMTP_FROM (-> noreplyaddress) MUST be the iCloud-owned account
|
|
// address (SMTP_USER). iCloud rejects any other From with a 550 — the
|
|
// same trap as the lelectronrare.fr contact form.
|
|
//
|
|
// Run: docker cp ops/provision-certs.php moodle:/tmp/ &&
|
|
// docker exec -e SMTP_HOST=... -e SMTP_PORT=... -e SMTP_USER=... \
|
|
// -e SMTP_PASS=... -e SMTP_FROM=... \
|
|
// moodle php /tmp/provision-certs.php
|
|
define('CLI_SCRIPT', true);
|
|
require('/var/www/html/config.php');
|
|
require_once($CFG->dirroot.'/course/lib.php'); // rebuild_course_cache()
|
|
|
|
$certmoduleid = $DB->get_field('modules', 'id', ['name' => 'customcert'], MUST_EXIST);
|
|
$quizmoduleid = $DB->get_field('modules', 'id', ['name' => 'quiz'], MUST_EXIST);
|
|
|
|
foreach ($DB->get_records_select('course', 'id > 1', null, 'id ASC') as $course) {
|
|
// The course's customcert course-module (exactly one per course).
|
|
$certcms = $DB->get_records('course_modules',
|
|
['course' => $course->id, 'module' => $certmoduleid], 'id ASC');
|
|
if (count($certcms) !== 1) {
|
|
fwrite(STDERR, "skip {$course->shortname}: " . count($certcms)
|
|
. " customcert cms (expected 1)\n");
|
|
continue;
|
|
}
|
|
$certcm = reset($certcms);
|
|
|
|
// The 4 quiz cmids, in module order ("Quiz N : ..." => name order,
|
|
// same convention as export-quiz-bank.php).
|
|
$quizcms = $DB->get_records_sql("
|
|
SELECT cm.id AS cmid, q.name
|
|
FROM {course_modules} cm
|
|
JOIN {quiz} q ON q.id = cm.instance
|
|
WHERE cm.course = :courseid AND cm.module = :mod
|
|
ORDER BY q.name ASC",
|
|
['courseid' => $course->id, 'mod' => $quizmoduleid]);
|
|
if (count($quizcms) !== 4) {
|
|
fwrite(STDERR, "skip {$course->shortname}: " . count($quizcms)
|
|
. " quizzes (expected 4)\n");
|
|
continue;
|
|
}
|
|
|
|
// availability = AND of "quiz marked complete" (e:1 = COMPLETE).
|
|
$conditions = [];
|
|
$showc = [];
|
|
foreach ($quizcms as $qcm) {
|
|
$conditions[] = ['type' => 'completion', 'cm' => (int)$qcm->cmid, 'e' => 1];
|
|
$showc[] = true;
|
|
}
|
|
$availability = json_encode(
|
|
['op' => '&', 'c' => $conditions, 'showc' => $showc],
|
|
JSON_UNESCAPED_SLASHES);
|
|
|
|
if ((string)$certcm->availability === $availability) {
|
|
echo "{$course->shortname}: availability unchanged (cm {$certcm->id})\n";
|
|
} else {
|
|
$DB->set_field('course_modules', 'availability', $availability,
|
|
['id' => $certcm->id]);
|
|
rebuild_course_cache($course->id, true);
|
|
echo "{$course->shortname}: availability set on cm {$certcm->id} = "
|
|
. $availability . "\n";
|
|
}
|
|
|
|
// Email the student the PDF once issued.
|
|
if ((int)$DB->get_field('customcert', 'emailstudents',
|
|
['id' => $certcm->instance]) !== 1) {
|
|
$DB->set_field('customcert', 'emailstudents', 1, ['id' => $certcm->instance]);
|
|
echo "{$course->shortname}: emailstudents set to 1\n";
|
|
} else {
|
|
echo "{$course->shortname}: emailstudents already 1\n";
|
|
}
|
|
}
|
|
|
|
// SMTP outgoing mail config from env (values from ES mail-api.env at
|
|
// provisioning time; stored in Moodle config, never in git).
|
|
$smtphost = getenv('SMTP_HOST');
|
|
$smtpport = getenv('SMTP_PORT');
|
|
$smtpuser = getenv('SMTP_USER');
|
|
$smtppass = getenv('SMTP_PASS');
|
|
$smtpfrom = getenv('SMTP_FROM');
|
|
if (!$smtphost || !$smtpport || !$smtpuser || !$smtppass || !$smtpfrom) {
|
|
fwrite(STDERR, "SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS/SMTP_FROM required\n");
|
|
exit(1);
|
|
}
|
|
if (strcasecmp($smtpfrom, $smtpuser) !== 0) {
|
|
fwrite(STDERR, "WARNING: SMTP_FROM != SMTP_USER — iCloud 550-rejects "
|
|
. "From addresses the account does not own\n");
|
|
}
|
|
set_config('smtphosts', $smtphost . ':' . $smtpport);
|
|
set_config('smtpsecure', 'tls');
|
|
set_config('smtpuser', $smtpuser);
|
|
set_config('smtppass', $smtppass);
|
|
set_config('noreplyaddress', $smtpfrom);
|
|
set_config('allowedemaildomains', '');
|
|
echo "smtp: smtphosts={$smtphost}:{$smtpport} smtpsecure=tls "
|
|
. "smtpuser={$smtpuser} noreplyaddress={$smtpfrom} (smtppass set)\n";
|
|
echo "OK\n";
|