Files
formations-app/ops/provision-certs.php
T
electron-rare d9bd5e0815 feat: certificate issuing provisioning
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.
2026-06-11 01:36:36 +02:00

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