Problem
quizaccess_failgrade decides whether to block a new attempt using only two signals: the
number of previous attempts and the user's current grade in the gradebook. It has no notion
of "training cycles."
This came up in practice from a site using
local_recompletion to reset ~70
courses for annual compliance training. Depending on each course's recompletion settings
(whether "Quiz attempts" is set to delete/keep, and whether "Delete grade data" is enabled),
a user who already passed in a previous cycle can be permanently blocked from a new attempt
after their completion is reset, because their old attempt/grade is still there and the
plugin has no way to know a reset happened in between.
The most commonly suggested workaround (local_recompletion's "Delete grade data" option)
has a real downside: it removes the old grade from the normal gradebook view (recoverable
only via the seldom-used "Grade history" report), which isn't great for compliance use cases
where you actually want last cycle's pass visibly on record.
Proposed fix (in progress)
See branch recompletion-compat
(based on top of the Moodle 5.x support work in moodle5-support, not yet merged).
Rather than requiring any grade/attempt data to be deleted, the plugin now detects resets
directly and adjusts its own interpretation, with zero data loss:
- A new
quizaccess_failgrade_reset table (courseid, nullable userid, timereset).
- Two event observers:
\core\event\course_reset_ended — Moodle's own native "Reset course" feature (course-wide,
so userid is left null).
\local_recompletion\event\completion_reset — fired once per user by that plugin's
scheduled task. Soft/optional dependency: registered by class name, never fires if
local_recompletion isn't installed.
is_finished() now ignores any attempt/grade older than the most recent matching reset
(course-wide or for that specific user), instead of blocking on it.
- Full privacy provider added (the new table stores
userid for the per-user path, so the
plugin could no longer honestly declare itself a null_provider).
Status
Work in progress, no release planned yet. Still open:
- Needs to actually run green in CI (not yet pushed as a PR).
- The
local_recompletion observer path has no automated test coverage, since that plugin
isn't part of this repo's CI environment — it delegates to the same code path the
course_reset_ended test does exercise, so risk is limited but not zero.
- Only covers these two specific reset sources; any other custom/proprietary reset tooling
that doesn't fire one of these two events remains unaddressed.
🤖 Generated with Claude Code
Problem
quizaccess_failgradedecides whether to block a new attempt using only two signals: thenumber of previous attempts and the user's current grade in the gradebook. It has no notion
of "training cycles."
This came up in practice from a site using
local_recompletion to reset ~70
courses for annual compliance training. Depending on each course's recompletion settings
(whether "Quiz attempts" is set to delete/keep, and whether "Delete grade data" is enabled),
a user who already passed in a previous cycle can be permanently blocked from a new attempt
after their completion is reset, because their old attempt/grade is still there and the
plugin has no way to know a reset happened in between.
The most commonly suggested workaround (
local_recompletion's "Delete grade data" option)has a real downside: it removes the old grade from the normal gradebook view (recoverable
only via the seldom-used "Grade history" report), which isn't great for compliance use cases
where you actually want last cycle's pass visibly on record.
Proposed fix (in progress)
See branch
recompletion-compat(based on top of the Moodle 5.x support work in
moodle5-support, not yet merged).Rather than requiring any grade/attempt data to be deleted, the plugin now detects resets
directly and adjusts its own interpretation, with zero data loss:
quizaccess_failgrade_resettable (courseid, nullableuserid,timereset).\core\event\course_reset_ended— Moodle's own native "Reset course" feature (course-wide,so
useridis left null).\local_recompletion\event\completion_reset— fired once per user by that plugin'sscheduled task. Soft/optional dependency: registered by class name, never fires if
local_recompletionisn't installed.is_finished()now ignores any attempt/grade older than the most recent matching reset(course-wide or for that specific user), instead of blocking on it.
useridfor the per-user path, so theplugin could no longer honestly declare itself a
null_provider).Status
Work in progress, no release planned yet. Still open:
local_recompletionobserver path has no automated test coverage, since that pluginisn't part of this repo's CI environment — it delegates to the same code path the
course_reset_endedtest does exercise, so risk is limited but not zero.that doesn't fire one of these two events remains unaddressed.
🤖 Generated with Claude Code