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