Automatic enrolment key creator
local_enrolkeycreator
Local plugin that listens for the core enrol_instance_created event and, when enabled by the site administrator, automatically sets a randomly generated enrolment key on newly created self-enrolment instances that have none, while still allowing teachers to remove the key later (unlike core's enrol_self | requirepassword).
The plugin is very small: one event observer, one admin checkbox, a null privacy provider, a language file and Behat tests. It has no user-facing pages, no request parameters, no forms, no AJAX or web services, no SQL strings and no outbound HTTP.
The observer only runs on a core event, which means a user who could already create an enrolment instance has passed core's capability checks. It reads and updates the enrol record with $DB array conditions. It generates the key with core's \core\authentication\password::generate(20), the same call core's enrol_self makes, and it copies core's enrol_plugin::update_instance() side effects (it updates timemodified and fires enrol_instance_updated). The plugin stores no personal data, so the null privacy provider is correct.
The only finding is a redundant database lookup, rated info. It has no security or functional impact.
local_enrolkeycreator is a single-purpose plugin of about 130 lines of PHP. When an admin turns it on, it gives each new self-enrolment instance a random enrolment key if the instance was created without one. Teachers can still remove the key later, which core's requirepassword setting does not allow.
The security posture is clean. The plugin has no web entry points. It never reads user input, writes no raw SQL, uses no superglobals and makes no network calls. All logic sits in one event observer. It uses the $DB API with array conditions and core's password::generate() to make the key, so the key follows the site password policy in the same way as core's enrol_self default instance. Updating the record directly and then firing enrol_instance_updated is a reasonable copy of enrol_plugin::update_instance(), which I checked against core.
There is nothing that needs action. One info-level note covers a redundant course lookup in the observer. The plugin also ships Behat coverage for every combination of its setting and core's setting.
Findings
The observer loads the full course record with $DB->get_record() but only uses it to check that the course exists. The $course variable is never used after that check. The enrol row was just created for that course and the event fires with a course context, so the extra query almost never adds anything. It is one extra lookup per new enrolment instance, which matters a little during bulk course creation or restores.
Informational. This has no security or correctness impact. The only cost is one extra query on a rare event.
enrol_instance_created() runs every time any enrolment instance is created on the site, including when courses are created, restored or uploaded. It exits early for non-self instances, when requirepassword is on, or when a key is already set. After that it writes the generated key.
$courseid = $enrol->courseid;
$course = $DB->get_record('course', ['id' => $courseid]);
if (!$course) {
return true;
}
Remove the lookup. If the existence check should stay as a safeguard, use a cheaper query:
if (!$DB->record_exists('course', ['id' => $enrol->courseid])) {
return true;
}
Behaviour note for administrators: the observer listens to every enrol_instance_created event. Besides interactive course creation, that includes course restore, course upload, and a teacher adding a new self-enrolment method while deliberately leaving the key blank. In all of these cases, if the plugin is enabled and the instance has no key, a random key is set and the user is not notified. This is the plugin's intended design, and the key can be removed afterwards, but sites that create open self-enrolment instances on purpose through upload or restore should be aware of it.
Version targeting: version.php requires 2026100500 and declares supported = [503, 503]. This matches the Moodle 5.3 core under review ($version = 2026100500.00, branch 503). The plugin uses \core\authentication\password, which exists in this core tree.