xAPI ingestion layer (xAPI-primary) alongside SCORM#85
Merged
Conversation
Consume the xAPI statements emitted by packages exported with the merged eXeLearning PR #1867 (exe_xapi.js), grading through the existing pipeline without touching the productive SCORM path. - js/xapi_listener.js: inline IIFE listener (single source of truth, Vitest tested) that validates event.origin, de-dups by statement.id and forwards each exe-xapi-statement postMessage to xapi_track.php. - xapi_track.php: sesskey/capability-authenticated endpoint mirroring track.php. - classes/local/xapi/statement_normalizer.php: pure DEC-0063 validation + mapping (verb whitelist, scaled in [0,1], raw in [min,max], UUID id, null only inside extensions, permissive version, objectid from idevice-id extension or object.id suffix). - classes/local/xapi/ingestor.php: reuses track::apply_item_scores / attempts:: / grade_update; ignores the actor (grade -> $USER), rejects unknown objectids, honours gradeenabled, idempotent by statement.id. - xAPI-primary: when a package bundles exe_xapi.js the SCORM shim stays alive but inert (js/scorm_tracker.js disableTracking) so it cannot double-grade; legacy packages keep SCORM grading unchanged. - Overall taken from the package passed/failed/completed statement (the producer's weighted finalScore), validated server-side; answered statements carry no weight (refines DEC-0063 §2, see DEC-0064). - config_injector pins window.exeXapi.parentOrigin/actor (RIE-013). - New exelearning_tracking_events table (statementid UNIQUE) for audit/idempotency; privacy provider + lang strings updated. - Tests: PHPUnit statement_normalizer + ingestor; Vitest xapi_listener + scorm_tracker disableTracking.
- DEC-0064: xAPI-primary coexistence + inert SCORM stub; overall from the package statement (refines DEC-0063 §2, grounded in the verified answered-has-no-weight finding); always-on; events deferred; plain xapi_track.php / inline-IIFE listener rationale. Moves DEC-0032/DEC-0063 toward Accepted. Cites merged contract e3b1bd13 + upstream security additions (PII anonymisation on '*', </script> escaping). - FTE-011: note #1867 merged at e3b1bd13, contract frozen. - TAREA-015: status Done. - docs/xapi-integration-plan.md + tracking-architecture.md: status implemented. - Regenerated research indices.
Add a focused side-by-side comparison of the two grade channels as implemented (differences + where each one wins), in English (docs/tracking-architecture.md) and Spanish (research FTE-009, the canonical standards-comparison source).
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
Review of the xAPI ingestion layer surfaced several robustness gaps; this fixes the contained ones and pins the edge cases with tests and docs. - Sanitise + bound the attempt-grouping registration to char(40) / PARAM_ALPHANUMEXT in both xapi_track.php (POST body) and the normaliser (the context.registration fallback) so a crafted or over-long value cannot overflow exelearning_attempt.sessiontoken / _tracking_events.registration. - Read result.success from its correct xAPI location (not result.score.success). - Reject the nil UUID (require a real RFC version+variant) so a constant statement.id cannot pin idempotency and silently drop later grades. - Narrow ingestor::record_event to swallow only the genuine UNIQUE(statementid) race and rethrow any other audit-write failure. - js/xapi_listener.js now inspects the POST result and retries transient non-2xx / network failures with bounded backoff (mirrors the SCORM dirty-resend; a 409 attempt-cap rejection is final), so a grade-bearing statement is not silently lost. Tests: Vitest listener resend cases; PHPUnit normaliser (registration sanitise/bound, non-string drop, nil-UUID, result.success) and ingestor (PERITEM per-item publish, long-registration no-overflow, lifecycle audited, answered-only overall-row edge). Docs: edge-cases section in tracking-architecture.md + cross-reference in xapi-integration-plan.md.
Acts on the PR review recommendations. - Feature flag: admin setting "Use xAPI grading when the package supports it" (exelearning/xapiprimaryenabled, default on) + helper exelearning_xapi_primary_enabled(). When off, view.php keeps the SCORM shim live and skips the xAPI listener, and xapi_track.php accept-and-ignores, so a site can fall back to SCORM grading without a code change. Strings added in all five languages. - Docs: make explicit that this is NOT cmi5 and NOT an external-LRS integration, that xapi_track.php is a custom grading endpoint (not a full core_xapi integration; the events/analytics handler stays a deferred follow-up), and that SCORM 1.2 remains the compatibility path. - Monitoring: document how to detect a lost terminal statement (answered rows without a passed/failed/completed for the same registration) from the exelearning_tracking_events audit log. - Manual QA: add docs/xapi-qa-checklist.md (legacy SCORM, xAPI multi-iDevice, tab-close before terminal, transient-retry, max attempts, preview, grading off, PERITEM/OVERALL, kill switch, idempotency, registration hardening, origin). Test: lib_helpers_test covers the flag default-on and the off/on toggle.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an xAPI compatibility layer to
mod_exelearning, in addition to the existingSCORM 1.2 bridge. Packages exported by the merged eXeLearning PR #1867 (
exe_xapi.js,verified at commit
e3b1bd13) emit xAPI statements bypostMessage; this PR consumes themand grades through the same unit-tested pipeline the SCORM path uses
(
track::apply_item_scores/attempts::*/grade_update) — no parallel grade model.This implements the design already recorded in the research trail: DEC-0032 (dual
ingestion), DEC-0063 (validation/version rules) and DEC-0064 (this implementation),
i.e. task TAREA-015, which was gated on #1867 being merged.
Design decisions
libs/xapi/exe_xapi.js, grading flowsthrough xAPI and the SCORM shim is kept alive but inert (
window.APIanswers sopipwerks/iDevices still run and emit statements, but never POSTs). Legacy packages without
the emitter keep SCORM grading unchanged. No package ever double-grades.
gamification.scorm.sendScoreNew(), which drives bothgamification.track('answered')(xAPI) and the pipwerks SCORM call — so xAPI captures exactly what SCORM would.
ignored and the grade is attributed to
$USER;result.score.scaledmust be in[0,1],rawin[min,max]; the verb must be whitelisted;statement.idmust be a UUID; anulloutside
extensionsis rejected; the version is accepted permissively (1.0.xand2.0.0). Anobject.idthat does not resolve to a registered iDevice of this instance isrejected.
answeredstatements carry no weight,so the authoritative weighted overall is the package
passed/failed/completedfinalScore,taken and validated/clamped server-side (refines DEC-0063 §2; rationale in DEC-0064).
statement.idvia a newexelearning_tracking_eventstable.gradeenabledremains the only grading switch.core_xapianalytics handler/events(deferred to a follow-up).
What's included
Client
js/xapi_listener.js— inline IIFE listener (single source of truth, Vitest-tested):validates
event.origin(rejects'*'/mismatch, RIE-013), de-dups bystatement.id,forwards to
xapi_track.php.js/scorm_tracker.js— opt-indisableTrackinginert mode (legacy path byte-for-byteunchanged).
classes/local/xapi/config_injector.php— pinswindow.exeXapi.parentOrigin/actor.view.php— channel detection + wiring (shares one page-load token as the xAPI registration).Server
xapi_track.php— sesskey/capability endpoint mirroringtrack.php.classes/local/xapi/statement_normalizer.php— pure validation + mapping.classes/local/xapi/ingestor.php— orchestration reusing the existing pipeline.db/install.xml+db/upgrade.php(stage2026061800) —exelearning_tracking_events.classes/privacy/provider.php+lang/*— declare/export/delete the new table.Tests
tests/local/xapi/statement_normalizer_test.php,tests/local/xapi/ingestor_test.php(answered→item parity, package→overall, unknownobjectid rejected, actor ignored→$USER, idempotency, maxattempt, gradeenabled-off no-op,
preview).
tests/js/xapi_listener.test.js(origin/dedup/forward) + ascorm_trackerdisableTrackingcase.Docs (Spanish research)
research/decisiones/adr/DEC-0064-implementacion-ingesta-xapi.md(+ FTE-011/TAREA-015updates, diary entry);
docs/xapi-integration-plan.md+docs/tracking-architecture.mdflipped to implemented.
Verification
phpcs --standard=moodleclean on all changed PHP.tests/local/xapi/*) runs in CI (needs a Moodle tree).Compatibility
SCORM 1.2 remains the documented compatibility path (DEC-0003) for legacy packages. No
breaking changes to the existing grade model, schema or APIs.
Update — review round
Acting on the review recommendations:
exelearning/xapiprimaryenabled, default on;exelearning_xapi_primary_enabled()). When off,view.phpkeeps the SCORM shim live and skips the listener andxapi_track.phpaccept-and-ignores, so a site reverts to SCORM grading with no code change.xapi_track.phpis a custom grading endpoint, not a full Moodlecore_xapiintegration. Acore_xapihandler for events/analytics (not grading) stays a deferred follow-up (tracked separately). SCORM 1.2 remains the compatibility path.char(40);result.successread from its correct xAPI location; nil-UUID rejected;record_eventrethrows non-duplicate write failures; the listener now inspects the POST result and retries transient non-2xx with bounded backoff (a409cap rejection is final).exelearning_tracking_eventsaudit log — seedocs/tracking-architecture.md.docs/xapi-qa-checklist.md.Moodle Playground Preview
The changes in this pull request can be previewed and tested using a Moodle Playground instance.
ℹ️ The eXeLearning editor is fetched from the shared release and unpacked into the plugin when the playground boots, so the first load may take a few extra seconds. ELPX upload, viewer and preview work normally.