Documentation Index
Fetch the complete documentation index at: https://mintlify.com/exelearning/mod_exelearning/llms.txt
Use this file to discover all available pages before exploring further.
mod_exelearning maps each gradable iDevice in a published package to a distinct Moodle grade item, enabling per-exercise visibility in the gradebook and flexible aggregation across multiple attempts. The grading model is configurable per instance, and the entire lifecycle — from package upload through user submission to completion — is driven by stable identifiers extracted from content.xml rather than by page order or position in the activity.
Two grading models
Grading has exactly two mutually exclusive presentations, selected per instance by thegrademodel field. The legacy “both” mode was removed in DEC-0008.
| Model | Constant (value) | Gradebook columns | Overall column (itemnumber=0)? |
|---|---|---|---|
| PER-ITEM (default) | EXELEARNING_GRADEMODEL_PERITEM (1) | One column per gradable iDevice (itemnumber 1..100) | No |
| OVERALL | EXELEARNING_GRADEMODEL_OVERALL (0) | A single aggregated column | Yes |
moodle/grade:viewhidden and was reported as a confusing extra grade, so DEC-0038 removed it.
Switching an existing instance between the two models deletes and recreates the columns and re-publishes from exelearning_attempt via exelearning_update_grades() → grade_recalculator::recalculate_for_users().
itemnumber semantics
itemnumber is the routing axis shared by exelearning_grade_item and exelearning_attempt:
0= the overall aggregated grade (exists only in OVERALL mode).1..100= one gradable iDevice each (PER-ITEM mode).
itemnumber is declared in a component mapping. mod_exelearning implements core_grades\local\gradeitem\itemnumber_mapping in classes/grades/gradeitems.php:52, with MAX_ITEMNUMBER = 100. The mapping is 0 => 'overall', 1..100 => 'idevice1'..'idevice100'.
objectid-stable routing
Grade items are keyed by the package’s stableobjectid (the <odeIdeviceId> from content.xml), not by the page-local index, which can collide across pages. The exelearning_grade_item table stores objectid char(191) with a UNIQUE (exelearningid, objectid) index, and grade_sync::sync() looks up existing rows by objectid, assigning a new monotonic itemnumber only when an objectid is first seen (DEC-0017).
Soft-delete on re-upload
An iDevice that disappears in a re-upload has its row marked
deleted=1, preserving grade history, and its gradebook column is removed. A re-appearing iDevice keeps its original itemnumber.Content-hash staleness
Each row stores
contenthash (sha1 of the iDevice content block, DEC-0021). An in-place options/scoring edit keeps the objectid but changes the hash, flagging it as changed so the teacher can be warned that existing grades may be stale. Existing attempts and grades are not recomputed.Server-only routing
track::ingest() accepts only objectids already registered for this instance and ignores unknown ones. It never creates grade items from client data.Attempt aggregation
Each user submission is one row per gradable item inexelearning_attempt, keeping full attempt history (DEC-0007). The gradebook grade for an item is aggregated across that user’s attempts by the per-instance grademethod:
| Method | Constant (value) | Behaviour |
|---|---|---|
| highest (default) | attempts::GRADE_HIGHEST (0) | max() of scaled scores |
| average | attempts::GRADE_AVERAGE (1) | mean of all attempts |
| first | attempts::GRADE_FIRST (2) | first attempt only |
| last | attempts::GRADE_LAST (3) | most recent attempt only |
| lowest | attempts::GRADE_LOWEST (4) | min() of scaled scores |
attempts::aggregate_scaled() (attempts.php:279–311), which reads each attempt’s scaledscore (a 0..1 value stored as rawscore/maxscore) and returns a single scaled score, or null when there are no attempts.
Grade recalculation
exelearning_recalculate_user_grades() delegates to grade_recalculator::recalculate_user() (DEC-0054), which:
- Calls
attempts::aggregate_scaled()for each registered grade item. - Multiplies the scaled score by the instance
grademax(rawgrade = scaled × grademax). - Calls
grade_update()per item, skippingitemnumber=0in PER-ITEM mode anditemnumber>0in OVERALL mode.
exelearning_update_grades() fans this out across every user with attempts and short-circuits when grading is disabled.
gradepass and completion
Completion by grade (gradepass, completionpassgrade) is Moodle-native SCORM-style completion. The teacher points completiongradeitemnumber at a per-iDevice item (workshop model) or uses OVERALL mode to complete on passing the activity as a whole. track::ingest() recalculates completion after every grading write.
gradepass must lie in [grademin, grademax] (when non-zero) and grademin must not exceed grademax, otherwise “require passing grade” completion is unreachable (err_grademinmax, err_gradepassrange).
Completion by status (completionstatusrequired, DEC-0052) is a custom completion rule that marks the activity complete when the user’s attempt reaches a required status. The column was added to the exelearning table in upgrade stage 17 (2026061202):
| Value | Meaning |
|---|---|
NULL | Rule disabled |
1 (EXELEARNING_COMPLETIONSTATUS_PASSED) | Passed |
2 (EXELEARNING_COMPLETIONSTATUS_COMPLETED) | Completed |
3 (EXELEARNING_COMPLETIONSTATUS_ANY) | Passed or completed |
custom_completion::get_state() returns COMPLETION_COMPLETE when a matching row exists in exelearning_attempt, else COMPLETION_INCOMPLETE. The rule survives backup/restore because completionstatusrequired is included in the backed-up instance fields.
When grading is disabled
Whengradeenabled=0 (the master grading switch), the module form greys out all grade and attempt fields. The module then:
- Creates no grade items —
grade_sync::sync()removes all grade items viagrade_item_manager::remove_all(). - Returns early from
exelearning_update_grades()— no gradebook writes. - Preserves attempt history in
exelearning_attempt— the rows are kept for potential re-enabling.
FEATURE_GRADE_HAS_GRADE is static — exelearning_supports() returns true unconditionally regardless of gradeenabled, so Moodle classifies the activity type as gradable even when a given instance is not (tracked in DEC-0047).
Worked example
An.elpx package with 3 gradable iDevices produces:
- PER-ITEM (default) → 3 gradebook columns,
itemnumber1, 2, 3 — one per iDevice, no overall column. - OVERALL → 1 aggregated column,
itemnumber=0— the three per-iDevice scores are server-recomputed into a single overall (DEC-0018) and the per-iDevice rows are kept only for the attempts report.
exelearning_attempt via exelearning_update_grades() → grade_recalculator::recalculate_for_users().
Admin settings reference
All grading and attempt fields frommod_form.php:78–227, with defaults from db/install.xml:
| Setting | Field | Options / range | Default |
|---|---|---|---|
| Graded? (master switch) | gradeenabled | on / off | on (1) |
| Gradebook columns model | grademodel | PER-ITEM / OVERALL | PER-ITEM (1) |
| Maximum grade (per item) | grademax | float | 100 |
| Minimum grade | grademin | float | 0 |
| Grade to pass | gradepass | float in [grademin, grademax], 0 = none | 0 |
| Grade display type | gradedisplaytype | DEFAULT / REAL / PERCENTAGE / LETTER / REAL_PERCENTAGE | DEFAULT (0) |
| Grade category | gradecat | course grade categories | course top category (0) |
| Maximum attempts | maxattempt | int, 0 = unlimited | 0 |
| Attempt aggregation | grademethod | highest / average / first / last / lowest | highest (0) |
| Attempt review | reviewmode | never / always / after completion | always (1) |